implemented sample references
This commit is contained in:
		@@ -1,2 +1,4 @@
 | 
			
		||||
pushstate: enabled
 | 
			
		||||
force_https: true
 | 
			
		||||
root: UI
 | 
			
		||||
location_include: custom-header.conf
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ import { ObjectPipe } from './object.pipe';
 | 
			
		||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
 | 
			
		||||
import { DocumentationComponent } from './documentation/documentation.component';
 | 
			
		||||
import { ImgMagnifierComponent } from './img-magnifier/img-magnifier.component';
 | 
			
		||||
import { ExistsPipe } from './exists.pipe';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
@@ -32,7 +33,8 @@ import { ImgMagnifierComponent } from './img-magnifier/img-magnifier.component';
 | 
			
		||||
    ErrorComponent,
 | 
			
		||||
    ObjectPipe,
 | 
			
		||||
    DocumentationComponent,
 | 
			
		||||
    ImgMagnifierComponent
 | 
			
		||||
    ImgMagnifierComponent,
 | 
			
		||||
    ExistsPipe
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    LocalStorageModule.forRoot({
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								src/app/exists.pipe.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/app/exists.pipe.spec.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
import { ExistsPipe } from './exists.pipe';
 | 
			
		||||
 | 
			
		||||
describe('ExistsPipe', () => {
 | 
			
		||||
  it('create an instance', () => {
 | 
			
		||||
    const pipe = new ExistsPipe();
 | 
			
		||||
    expect(pipe).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										14
									
								
								src/app/exists.pipe.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/app/exists.pipe.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
import { Pipe, PipeTransform } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
@Pipe({
 | 
			
		||||
  name: 'exists',
 | 
			
		||||
  pure: true
 | 
			
		||||
})
 | 
			
		||||
export class ExistsPipe implements PipeTransform {
 | 
			
		||||
 | 
			
		||||
  transform(value: unknown, key?): unknown {
 | 
			
		||||
    // console.log(new Date().getTime());
 | 
			
		||||
    return value || value === 0 ? (key ? value[key] : value) : '';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -7,21 +7,10 @@ export class MaterialModel extends BaseModel {
 | 
			
		||||
  name = '';
 | 
			
		||||
  supplier = '';
 | 
			
		||||
  group = '';
 | 
			
		||||
  mineral = 0;
 | 
			
		||||
  glass_fiber = 0;
 | 
			
		||||
  carbon_fiber = 0;
 | 
			
		||||
  private numberTemplate = {color: '', number: ''};
 | 
			
		||||
  numbers: {color: string, number: string}[] = [_.cloneDeep(this.numberTemplate)];
 | 
			
		||||
  properties: {material_template: string, [prop: string]: string} = {material_template: null};
 | 
			
		||||
  numbers: string[] = [''];
 | 
			
		||||
 | 
			
		||||
  sendFormat() {
 | 
			
		||||
    return _.pick(this, ['name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addNumber() {
 | 
			
		||||
    this.numbers.push(_.cloneDeep(this.numberTemplate));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  popNumber() {
 | 
			
		||||
    this.numbers.pop();
 | 
			
		||||
    return _.pick(this, ['name', 'supplier', 'group', 'numbers', 'properties']);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,13 @@
 | 
			
		||||
import { Pipe, PipeTransform } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
@Pipe({
 | 
			
		||||
  name: 'object'
 | 
			
		||||
  name: 'object',
 | 
			
		||||
  pure: true
 | 
			
		||||
})
 | 
			
		||||
export class ObjectPipe implements PipeTransform {
 | 
			
		||||
 | 
			
		||||
  transform(value: object): string {
 | 
			
		||||
    return value ? Object.entries(value).map(e => e.join(': ')).join(', ') : '';
 | 
			
		||||
    return value ? JSON.stringify(value) : '';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
<script src="rb-table.component.ts"></script>
 | 
			
		||||
<table>
 | 
			
		||||
  <ng-content></ng-content>
 | 
			
		||||
</table>
 | 
			
		||||
<div class="table-wrapper">
 | 
			
		||||
  <table>
 | 
			
		||||
    <ng-content></ng-content>
 | 
			
		||||
  </table>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,16 @@
 | 
			
		||||
@import "~@inst-iot/bosch-angular-ui-components/styles/variables/colors";
 | 
			
		||||
 | 
			
		||||
.table-wrapper {
 | 
			
		||||
  overflow-x: auto;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
 | 
			
		||||
  &, & > table {  // scrollbar at the top
 | 
			
		||||
    transform:rotateX(180deg);
 | 
			
		||||
    -ms-transform:rotateX(180deg); /* IE 9 */
 | 
			
		||||
    -webkit-transform:rotateX(180deg); /* Safari and Chrome */
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  border-collapse: collapse;
 | 
			
		||||
@@ -9,6 +20,10 @@ table {
 | 
			
		||||
 | 
			
		||||
    ::ng-deep td, ::ng-deep th {
 | 
			
		||||
      padding: 8px 5px;
 | 
			
		||||
      text-overflow: ellipsis;
 | 
			
		||||
      white-space: nowrap;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
      max-width: 200px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ::ng-deep th {
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
        <ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
 | 
			
		||||
        <ng-template rbFormValidationMessage="failure">Unknown material, add properties for new material</ng-template>
 | 
			
		||||
      </rb-form-input>
 | 
			
		||||
      <button class="rb-btn rb-secondary" type="button" (click)="setNewMaterial(true)"><span class="rb-ic rb-ic-add"></span> New material</button>
 | 
			
		||||
      <button class="rb-btn rb-secondary" type="button" (click)="setNewMaterial(!newMaterial)"><span class="rb-ic rb-ic-add"></span> New material</button>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="material shaded-container" *ngIf="newMaterial" [@inOut]>
 | 
			
		||||
@@ -21,30 +21,16 @@
 | 
			
		||||
      <rb-form-input name="group" label="group" [rbFormInputAutocomplete]="autocomplete.bind(this, groups)" [rbDebounceTime]="0" [rbInitialOpen]="true" appValidate="string" required [(ngModel)]="material.group" #groupInput="ngModel">
 | 
			
		||||
        <ng-template rbFormValidationMessage="failure">{{groupInput.errors.failure}}</ng-template>
 | 
			
		||||
      </rb-form-input>
 | 
			
		||||
      <rb-form-input name="mineral" label="mineral" type="number" required rbNumberConverter rbMin="0" rbMax="100" [(ngModel)]="material.mineral" ngModel>
 | 
			
		||||
        <ng-template rbFormValidationMessage="required">Invalid value</ng-template>
 | 
			
		||||
        <ng-template rbFormValidationMessage="rbMin">Minimum value is 0</ng-template>
 | 
			
		||||
        <ng-template rbFormValidationMessage="rbMax">Maximum value is 100</ng-template>
 | 
			
		||||
      </rb-form-input>
 | 
			
		||||
      <rb-form-input name="glass_fiber" label="glass_fiber" type="number" required rbNumberConverter rbMin="0" rbMax="100" [(ngModel)]="material.glass_fiber" ngModel>
 | 
			
		||||
        <ng-template rbFormValidationMessage="required">Invalid value</ng-template>
 | 
			
		||||
        <ng-template rbFormValidationMessage="rbMin">Minimum value is 0</ng-template>
 | 
			
		||||
        <ng-template rbFormValidationMessage="rbMax">Maximum value is 100</ng-template>
 | 
			
		||||
      </rb-form-input>
 | 
			
		||||
      <rb-form-input name="carbon_fiber" label="carbon_fiber" type="number" required rbNumberConverter rbMin="0" rbMax="100" [(ngModel)]="material.carbon_fiber" ngModel>
 | 
			
		||||
        <ng-template rbFormValidationMessage="required">Invalid value</ng-template>
 | 
			
		||||
        <ng-template rbFormValidationMessage="rbMin">Minimum value is 0</ng-template>
 | 
			
		||||
        <ng-template rbFormValidationMessage="rbMax">Maximum value is 100</ng-template>
 | 
			
		||||
      </rb-form-input>
 | 
			
		||||
 | 
			
		||||
      <div class="material-numbers">
 | 
			
		||||
        <div *ngFor="let number of material.numbers; index as i" class="two-col" [@inOut]>
 | 
			
		||||
          <rb-form-input label="color" appValidate="string" [required]="i < material.numbers.length - 1" (keyup)="handleMaterialNumbers()" [(ngModel)]="number.color" ngModel [ngModelOptions]="{standalone: true}">
 | 
			
		||||
          </rb-form-input>
 | 
			
		||||
          <rb-form-input label="material number" appValidate="string" [(ngModel)]="number.number" ngModel [ngModelOptions]="{standalone: true}">
 | 
			
		||||
          </rb-form-input>
 | 
			
		||||
        </div>
 | 
			
		||||
        <rb-form-input *ngFor="let ignore of [].constructor(material.numbers.length); index as i" label="material number" appValidate="string" [name]="'material.number-' + i" (keyup)="handleMaterialNumbers()" [(ngModel)]="material.numbers[i]" ngModel></rb-form-input>
 | 
			
		||||
      </div>
 | 
			
		||||
      <rb-form-select name="conditionSelect" label="Condition" (ngModelChange)="selectMaterialTemplate($event)" [ngModel]="material.properties.material_template">
 | 
			
		||||
        <option *ngFor="let m of materialTemplates" [value]="m._id">{{m.name}}</option>
 | 
			
		||||
      </rb-form-select>
 | 
			
		||||
      <rb-form-input *ngFor="let parameter of materialTemplate.parameters; index as i" [name]="'materialParameter' + i" [label]="parameter.name" appValidate="string" required [(ngModel)]="material.properties[parameter.name]" #parameterInput="ngModel">
 | 
			
		||||
        <ng-template rbFormValidationMessage="failure">{{parameterInput.errors.failure}}</ng-template>
 | 
			
		||||
        <ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
 | 
			
		||||
      </rb-form-input>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
     
 | 
			
		||||
@@ -54,7 +40,7 @@
 | 
			
		||||
        <ng-template rbFormValidationMessage="failure">{{typeInput.errors.failure}}</ng-template>
 | 
			
		||||
        <ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
 | 
			
		||||
      </rb-form-input>
 | 
			
		||||
      <rb-form-input name="color" label="color" [rbFormInputAutocomplete]="autocomplete.bind(this, getColors(material))" [rbDebounceTime]="0" [rbInitialOpen]="true" appValidate="stringOf" [appValidateArgs]="[getColors(material)]" required [(ngModel)]="sample.color" #colorInput="ngModel">
 | 
			
		||||
      <rb-form-input name="color" label="color" appValidate="string" required [(ngModel)]="sample.color" #colorInput="ngModel">
 | 
			
		||||
        <ng-template rbFormValidationMessage="failure">{{colorInput.errors.failure}}</ng-template>
 | 
			
		||||
        <ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
 | 
			
		||||
      </rb-form-input>
 | 
			
		||||
@@ -68,6 +54,17 @@
 | 
			
		||||
    <rb-form-input name="comment" label="comment" appValidate="stringLength" [appValidateArgs]="[512]" [(ngModel)]="sample.notes.comment" #commentInput="ngModel">
 | 
			
		||||
      <ng-template rbFormValidationMessage="failure">{{commentInput.errors.failure}}</ng-template>
 | 
			
		||||
    </rb-form-input>
 | 
			
		||||
    <h5>Sample references</h5>
 | 
			
		||||
    <div *ngFor="let reference of sampleReferences; index as i" class="two-col" [@inOut]>
 | 
			
		||||
      <div>
 | 
			
		||||
        <rb-form-input [name]="'sr-id' + i" label="sample number" [rbFormInputAutocomplete]="sampleReferenceListBind()" [rbDebounceTime]="300" appValidate="stringOf" [appValidateArgs]="[sampleReferenceAutocomplete[i]]" (ngModelChange)="checkSampleReference($event, i)" [ngModel]="reference[0]" #idInput="ngModel">
 | 
			
		||||
          <ng-template rbFormValidationMessage="failure">Unknown sample number</ng-template>
 | 
			
		||||
        </rb-form-input>
 | 
			
		||||
      </div>
 | 
			
		||||
      <rb-form-input [name]="'sr-relation' + i" label="relation" appValidate="string" [required]="reference[0] !== ''" [(ngModel)]="reference[1]">
 | 
			
		||||
        <ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
 | 
			
		||||
      </rb-form-input>
 | 
			
		||||
    </div>
 | 
			
		||||
    <h5>Additional properties</h5>
 | 
			
		||||
    <div *ngFor="let field of customFields; index as i" class="two-col" [@inOut]>
 | 
			
		||||
      <div>
 | 
			
		||||
@@ -79,7 +76,6 @@
 | 
			
		||||
        <ng-template rbFormValidationMessage="required">Cannot be empty</ng-template>
 | 
			
		||||
      </rb-form-input>
 | 
			
		||||
    </div>
 | 
			
		||||
<!--    TODO: Sample reference-->
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
   
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ import {TemplateModel} from '../models/template.model';
 | 
			
		||||
import {MeasurementModel} from '../models/measurement.model';
 | 
			
		||||
import { ChartOptions } from 'chart.js';
 | 
			
		||||
import {animate, style, transition, trigger} from '@angular/animations';
 | 
			
		||||
import {Observable} from 'rxjs';
 | 
			
		||||
 | 
			
		||||
// TODO: tests
 | 
			
		||||
// TODO: confirmation for new group/supplier
 | 
			
		||||
@@ -24,6 +25,9 @@ import {animate, style, transition, trigger} from '@angular/animations';
 | 
			
		||||
// TODO: multiple spectra
 | 
			
		||||
// TODO: multiple samples for base data, extend multiple measurements, conditions
 | 
			
		||||
 | 
			
		||||
// TODO: material properties, color (in material and sample (not required))
 | 
			
		||||
 | 
			
		||||
// TODO: API $in Regex
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-sample',
 | 
			
		||||
@@ -55,11 +59,17 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
			
		||||
  groups: string[] = [];  // all groups
 | 
			
		||||
  conditionTemplates: TemplateModel[];  // all conditions
 | 
			
		||||
  condition: TemplateModel | null = null;  // selected condition
 | 
			
		||||
  materialTemplates: TemplateModel[];  // all material templates
 | 
			
		||||
  materialTemplate: TemplateModel | null = null;  // selected material template
 | 
			
		||||
  materialNames = [];  // names of all materials
 | 
			
		||||
  material = new MaterialModel();  // object of current selected material
 | 
			
		||||
  sample = new SampleModel();
 | 
			
		||||
  customFields: [string, string][] = [['', '']];
 | 
			
		||||
  sampleReferences: [string, string, string][] = [['', '', '']];
 | 
			
		||||
  sampleReferenceFinds: {_id: string, number: string}[] = [];  // raw sample reference data from db
 | 
			
		||||
  currentSRIndex = 0;  // index of last entered sample reference
 | 
			
		||||
  availableCustomFields: string[] = [];
 | 
			
		||||
  sampleReferenceAutocomplete: string[][] = [[]];
 | 
			
		||||
  responseData: SampleModel;  // gets filled with response data after saving the sample
 | 
			
		||||
  measurementTemplates: TemplateModel[];
 | 
			
		||||
  loading = 0;  // number of currently loading instances
 | 
			
		||||
@@ -96,7 +106,7 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.new = this.router.url === '/samples/new';
 | 
			
		||||
    this.loading = 6;
 | 
			
		||||
    this.loading = 7;
 | 
			
		||||
    this.api.get<MaterialModel[]>('/materials?status=all', (data: any) => {
 | 
			
		||||
      this.materials = data.map(e => new MaterialModel().deserialize(e));
 | 
			
		||||
      this.materialNames = data.map(e => e.name);
 | 
			
		||||
@@ -114,41 +124,63 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
			
		||||
      this.conditionTemplates = data.map(e => new TemplateModel().deserialize(e));
 | 
			
		||||
      this.loading--;
 | 
			
		||||
    });
 | 
			
		||||
    this.api.get<TemplateModel[]>('/template/materials', data => {
 | 
			
		||||
      this.materialTemplates = data.map(e => new TemplateModel().deserialize(e));
 | 
			
		||||
      this.selectMaterialTemplate(this.materialTemplates[0]._id);
 | 
			
		||||
      this.loading--;
 | 
			
		||||
    });
 | 
			
		||||
    this.api.get<TemplateModel[]>('/template/measurements', data => {
 | 
			
		||||
      this.measurementTemplates = data.map(e => new TemplateModel().deserialize(e));
 | 
			
		||||
      if (!this.new) {
 | 
			
		||||
        this.loading++;
 | 
			
		||||
        this.api.get<SampleModel>('/sample/' + this.route.snapshot.paramMap.get('id'), sData => {
 | 
			
		||||
          this.sample.deserialize(sData);
 | 
			
		||||
          this.charts = [];
 | 
			
		||||
          const spectrumTemplate = this.measurementTemplates.find(e => e.name === 'spectrum')._id;
 | 
			
		||||
          let spectrumCounter = 0;
 | 
			
		||||
          this.sample.measurements.forEach((measurement, i) => {
 | 
			
		||||
            this.charts.push(_.cloneDeep(this.chartInit));
 | 
			
		||||
            if (measurement.measurement_template === spectrumTemplate) {
 | 
			
		||||
              setTimeout(() => {
 | 
			
		||||
                this.generateChart(measurement.values.dpt, i);
 | 
			
		||||
                console.log(this.charts);
 | 
			
		||||
              }, spectrumCounter * 20);
 | 
			
		||||
              spectrumCounter ++;
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
          this.material = sData.material;
 | 
			
		||||
          this.customFields = this.sample.notes.custom_fields && this.sample.notes.custom_fields !== {} ? Object.keys(this.sample.notes.custom_fields).map(e => [e, this.sample.notes.custom_fields[e]]) : [['', '']];
 | 
			
		||||
          if (this.sample.notes.sample_references.length) {
 | 
			
		||||
            this.sampleReferences = [];
 | 
			
		||||
            this.sampleReferenceAutocomplete = [];
 | 
			
		||||
            let loadCounter = this.sample.notes.sample_references.length;
 | 
			
		||||
            this.sample.notes.sample_references.forEach(reference => {
 | 
			
		||||
              this.api.get<SampleModel>('/sample/' + reference.sample_id, srData => {
 | 
			
		||||
                this.sampleReferences.push([srData.number, reference.relation, reference.sample_id]);
 | 
			
		||||
                this.sampleReferenceAutocomplete.push([srData.number]);
 | 
			
		||||
                if (!--loadCounter) {
 | 
			
		||||
                  this.sampleReferences.push(['', '', '']);
 | 
			
		||||
                  this.sampleReferenceAutocomplete.push([]);
 | 
			
		||||
                  console.log(this.sampleReferences);
 | 
			
		||||
                  console.log(this.sampleReferenceAutocomplete);
 | 
			
		||||
                }
 | 
			
		||||
              });
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
          if ('condition_template' in this.sample.condition) {
 | 
			
		||||
            this.selectCondition(this.sample.condition.condition_template);
 | 
			
		||||
          }
 | 
			
		||||
          console.log('data loaded');
 | 
			
		||||
          this.loading--;
 | 
			
		||||
          this.checkFormAfterInit = true;
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
      this.loading--;
 | 
			
		||||
    });
 | 
			
		||||
    this.api.get<TemplateModel[]>('/sample/notes/fields', data => {
 | 
			
		||||
      this.availableCustomFields = data.map(e => e.name);
 | 
			
		||||
      this.loading--;
 | 
			
		||||
    });
 | 
			
		||||
    if (!this.new) {
 | 
			
		||||
      this.loading++;
 | 
			
		||||
      this.api.get<SampleModel>('/sample/' + this.route.snapshot.paramMap.get('id'), sData => {
 | 
			
		||||
        this.sample.deserialize(sData);
 | 
			
		||||
        this.charts = [];
 | 
			
		||||
        const spectrumTemplate = this.measurementTemplates.find(e => e.name === 'spectrum')._id;
 | 
			
		||||
        let spectrumCounter = 0;
 | 
			
		||||
        this.sample.measurements.forEach((measurement, i) => {
 | 
			
		||||
          this.charts.push(_.cloneDeep(this.chartInit));
 | 
			
		||||
          if (measurement.measurement_template === spectrumTemplate) {
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
              this.generateChart(measurement.values.dpt, i);
 | 
			
		||||
              console.log(this.charts);
 | 
			
		||||
            }, spectrumCounter * 20);
 | 
			
		||||
            spectrumCounter ++;
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
        this.material = sData.material;
 | 
			
		||||
        this.customFields = this.sample.notes.custom_fields && this.sample.notes.custom_fields !== {} ? Object.keys(this.sample.notes.custom_fields).map(e => [e, this.sample.notes.custom_fields[e]]) : [['', '']];
 | 
			
		||||
        if ('condition_template' in this.sample.condition) {
 | 
			
		||||
          this.selectCondition(this.sample.condition.condition_template);
 | 
			
		||||
        }
 | 
			
		||||
        console.log('data loaded');
 | 
			
		||||
        this.loading--;
 | 
			
		||||
        this.checkFormAfterInit = true;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngAfterContentChecked() {
 | 
			
		||||
@@ -161,6 +193,15 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // attach validators to dynamic material fields when all values are available and template was fully created
 | 
			
		||||
    if (this.materialTemplate && this.materialTemplate.hasOwnProperty('parameters') && this.materialTemplate.parameters.length > 0 && this.materialTemplate.parameters[0].hasOwnProperty('range') && this.sampleForm && this.sampleForm.form.get('materialParameter0')) {
 | 
			
		||||
      for (const i in this.materialTemplate.parameters) {
 | 
			
		||||
        if (this.materialTemplate.parameters[i]) {
 | 
			
		||||
          this.attachValidator('materialParameter' + i, this.materialTemplate.parameters[i].range, true);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this.sampleForm && this.sampleForm.form.get('measurementParameter0-0')) {
 | 
			
		||||
      this.sample.measurements.forEach((measurement, mIndex) => {
 | 
			
		||||
        const template = this.getMeasurementTemplate(measurement.measurement_template);
 | 
			
		||||
@@ -209,11 +250,7 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
			
		||||
  saveSample() {
 | 
			
		||||
    new Promise<void>(resolve => {
 | 
			
		||||
      if (this.newMaterial) {  // save material first if new one exists
 | 
			
		||||
        for (const i in this.material.numbers) {  // remove empty numbers fields
 | 
			
		||||
          if (this.material.numbers[i].color === '') {
 | 
			
		||||
            this.material.numbers.splice(i as any as number, 1);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        this.material.numbers = this.material.numbers.filter(e => e !== '');
 | 
			
		||||
        this.api.post<MaterialModel>('/material/new', this.material.sendFormat(), data => {
 | 
			
		||||
          this.materials.push(data);  // add material to data
 | 
			
		||||
          this.material = data;
 | 
			
		||||
@@ -231,6 +268,7 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
			
		||||
          this.sample.notes.custom_fields[element[0]] = element[1];
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      this.sample.notes.sample_references = this.sampleReferences.filter(e => e[0] && e[1] && e[2]).map(e => ({sample_id: e[2], relation: e[1]}));
 | 
			
		||||
      new Promise<SampleModel>(resolve => {
 | 
			
		||||
        if (this.new) {
 | 
			
		||||
          this.api.post<SampleModel>('/sample/new', this.sample.sendFormat(), resolve);
 | 
			
		||||
@@ -269,6 +307,9 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
			
		||||
      this.sample.material_id = this.material._id;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      if (this.sample.material_id !== null) {  // reset previous match
 | 
			
		||||
        this.material = new MaterialModel();
 | 
			
		||||
      }
 | 
			
		||||
      this.sample.material_id = null;
 | 
			
		||||
    }
 | 
			
		||||
    this.setNewMaterial();
 | 
			
		||||
@@ -280,16 +321,12 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getColors(material) {
 | 
			
		||||
    return material ? material.numbers.map(e => e.color) : [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // TODO: rework later
 | 
			
		||||
  setNewMaterial(value = null) {
 | 
			
		||||
    if (value === null) {
 | 
			
		||||
      this.newMaterial = !this.sample.material_id;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
    else if (value || (!value && this.sample.material_id !== null )) {  // set to false only if material already exists
 | 
			
		||||
      this.newMaterial = value;
 | 
			
		||||
    }
 | 
			
		||||
    if (this.newMaterial) {
 | 
			
		||||
@@ -303,19 +340,14 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
			
		||||
 | 
			
		||||
  handleMaterialNumbers() {
 | 
			
		||||
    const fieldNo = this.material.numbers.length;
 | 
			
		||||
    let filledFields = 0;
 | 
			
		||||
    this.material.numbers.forEach(mNumber => {
 | 
			
		||||
      if (mNumber.color !== '') {
 | 
			
		||||
        filledFields ++;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    const filledFields = this.material.numbers.filter(e => e !== '').length;
 | 
			
		||||
    // append new field
 | 
			
		||||
    if (filledFields === fieldNo) {
 | 
			
		||||
      this.material.addNumber();
 | 
			
		||||
      this.material.numbers.push('');
 | 
			
		||||
    }
 | 
			
		||||
    // remove if two end fields are empty
 | 
			
		||||
    if (fieldNo > 1 && this.material.numbers[fieldNo - 1].color === '' && this.material.numbers[fieldNo - 2].color === '') {
 | 
			
		||||
      this.material.popNumber();
 | 
			
		||||
    if (fieldNo > 1 && this.material.numbers[fieldNo - 1] === '' && this.material.numbers[fieldNo - 2] === '') {
 | 
			
		||||
      this.material.numbers.pop();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -328,6 +360,13 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  selectMaterialTemplate(id) {
 | 
			
		||||
    this.materialTemplate = this.materialTemplates.find(e => e._id === id);
 | 
			
		||||
    if ('material_template' in this.material.properties) {
 | 
			
		||||
      this.material.properties.material_template = id;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getMeasurementTemplate(id): TemplateModel {
 | 
			
		||||
    return this.measurementTemplates && id ? this.measurementTemplates.find(e => e._id === id) : new TemplateModel();
 | 
			
		||||
  }
 | 
			
		||||
@@ -391,6 +430,58 @@ export class SampleComponent implements OnInit, AfterContentChecked {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  checkSampleReference(value, index) {
 | 
			
		||||
    if (value) {
 | 
			
		||||
      this.sampleReferences[index][0] = value;
 | 
			
		||||
    }
 | 
			
		||||
    this.currentSRIndex = index;
 | 
			
		||||
    const fieldNo = this.sampleReferences.length;
 | 
			
		||||
    let filledFields = 0;
 | 
			
		||||
    this.sampleReferences.forEach(field => {
 | 
			
		||||
      if (field[0] !== '') {
 | 
			
		||||
        filledFields ++;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    // append new field
 | 
			
		||||
    if (filledFields === fieldNo) {
 | 
			
		||||
      this.sampleReferences.push(['', '', '']);
 | 
			
		||||
      this.sampleReferenceAutocomplete.push([]);
 | 
			
		||||
    }
 | 
			
		||||
    // remove if two end fields are empty
 | 
			
		||||
    if (fieldNo > 1 && this.sampleReferences[fieldNo - 1][0] === '' && this.sampleReferences[fieldNo - 2][0] === '') {
 | 
			
		||||
      this.sampleReferences.pop();
 | 
			
		||||
      this.sampleReferenceAutocomplete.pop();
 | 
			
		||||
    }
 | 
			
		||||
    this.sampleReferenceIdFind(value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  sampleReferenceList(value) {
 | 
			
		||||
    return new Observable(observer => {
 | 
			
		||||
      this.api.get<{_id: string, number: string}[]>('/samples?status=all&page-size=25&sort=number-asc&fields[]=number&fields[]=_id&filters[]=%7B%22mode%22%3A%22stringin%22%2C%22field%22%3A%22number%22%2C%22values%22%3A%5B%22' + value + '%22%5D%7D', data => {
 | 
			
		||||
        console.log(data);
 | 
			
		||||
        this.sampleReferenceAutocomplete[this.currentSRIndex] = data.map(e => e.number);
 | 
			
		||||
        this.sampleReferenceFinds = data;
 | 
			
		||||
        observer.next(data.map(e => e.number));
 | 
			
		||||
        observer.complete();
 | 
			
		||||
        this.sampleReferenceIdFind(value);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  sampleReferenceIdFind(value) {
 | 
			
		||||
    const idFind = this.sampleReferenceFinds.find(e => e.number === value);
 | 
			
		||||
    if (idFind) {
 | 
			
		||||
      this.sampleReferences[this.currentSRIndex][2] = idFind._id;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      this.sampleReferences[this.currentSRIndex][2] = '';
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  sampleReferenceListBind() {
 | 
			
		||||
    return this.sampleReferenceList.bind(this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uniqueCfValues(index) {  // returns all names until index for unique check
 | 
			
		||||
    return this.customFields.slice(0, index).map(e => e[0]);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<rb-accordion>
 | 
			
		||||
  <rb-accordion-title [open]="true"><span class="rb-ic rb-ic-filter"></span>  Filter</rb-accordion-title>
 | 
			
		||||
  <rb-accordion-title [open]="false"><span class="rb-ic rb-ic-filter"></span>  Filter</rb-accordion-title>
 | 
			
		||||
  <rb-accordion-body>
 | 
			
		||||
    <form class="filters">
 | 
			
		||||
      <div class="status-selection">
 | 
			
		||||
@@ -28,36 +28,37 @@
 | 
			
		||||
        <option value="500">500</option>
 | 
			
		||||
      </rb-form-select>
 | 
			
		||||
 | 
			
		||||
      <rb-form-multi-select name="fieldSelect" idField="id" [items]="keys" [(ngModel)]="activeKeys" label="Fields" class="selection" (ngModelChange)="loadSamples()">
 | 
			
		||||
      <rb-form-multi-select name="fieldSelect" idField="id" [items]="keys" [(ngModel)]="isActiveKey" label="Fields" class="selection" (ngModelChange)="loadSamples({}, $event)">
 | 
			
		||||
        <span *rbFormMultiSelectOption="let item" class="load-first-page">{{item.label}}</span>
 | 
			
		||||
      </rb-form-multi-select>
 | 
			
		||||
 | 
			
		||||
      <div class="fieldfilters">
 | 
			
		||||
        <div *ngFor="let filter of filters.filters">
 | 
			
		||||
          <rb-form-checkbox [name]="'filteractive-' + filter.field" [(ngModel)]="filter.active" (ngModelChange)="loadSamples({firstPage: true})"></rb-form-checkbox>
 | 
			
		||||
          <rb-form-select [name]="'filtermode-' + filter.field" class="filtermode" [(ngModel)]="filter.mode" (ngModelChange)="updateFilterFields(filter.field)">
 | 
			
		||||
            <option value="eq">=</option>
 | 
			
		||||
            <option value="ne">≠</option>
 | 
			
		||||
            <option value="lt"><</option>
 | 
			
		||||
            <option value="lte">≤</option>
 | 
			
		||||
            <option value="gt">></option>
 | 
			
		||||
            <option value="gte">≥</option>
 | 
			
		||||
            <option value="in">∈</option>
 | 
			
		||||
            <option value="nin">∉</option>
 | 
			
		||||
          </rb-form-select>
 | 
			
		||||
          <div class="filter-inputs">
 | 
			
		||||
            <ng-container *ngFor="let ignore of [].constructor(filter.values.length); index as i">
 | 
			
		||||
              <rb-form-date-input *ngIf="filter.field === 'added'; else noDate" [name]="'filter-' + filter.field + i" [label]="filter.label" [(ngModel)]="filter.values[i]" (ngModelChange)="updateFilterFields(filter.field)"></rb-form-date-input>
 | 
			
		||||
              <ng-template #noDate>
 | 
			
		||||
                <rb-form-input *ngIf="!filter.autocomplete.length" [name]="'filter-' + filter.field + i" [label]="filter.label" [(ngModel)]="filter.values[i]" (ngModelChange)="updateFilterFields(filter.field)"></rb-form-input>
 | 
			
		||||
                <rb-form-input *ngIf="filter.autocomplete.length" [name]="'filter-' + filter.field + i" [label]="filter.label" [rbFormInputAutocomplete]="autocomplete.bind(this, filter.autocomplete)" [rbDebounceTime]="0" (keydown)="preventDefault($event, 'Enter')" [(ngModel)]="filter.values[i]" (ngModelChange)="updateFilterFields(filter.field)" ngModel></rb-form-input>
 | 
			
		||||
              </ng-template>
 | 
			
		||||
            </ng-container>
 | 
			
		||||
          </div>
 | 
			
		||||
          <ng-container *ngIf="isActiveKey[filter.field]">
 | 
			
		||||
            <rb-form-checkbox [name]="'filteractive-' + filter.field" [(ngModel)]="filter.active" (ngModelChange)="loadSamples({firstPage: true})"></rb-form-checkbox>
 | 
			
		||||
            <rb-form-select [name]="'filtermode-' + filter.field" class="filtermode" [(ngModel)]="filter.mode" (ngModelChange)="updateFilterFields(filter.field)">
 | 
			
		||||
              <option value="eq" title="field is equal to value">=</option>
 | 
			
		||||
              <option value="ne" title="field is not equal to value">≠</option>
 | 
			
		||||
              <option value="lt" title="field is lower than value"><</option>
 | 
			
		||||
              <option value="lte" title="field is lower than or equal to value">≤</option>
 | 
			
		||||
              <option value="gt" title="field is greater than value">></option>
 | 
			
		||||
              <option value="gte" title="field is greater than or equal to value">≥</option>
 | 
			
		||||
              <option value="stringin" title="field contains value">⊇</option>
 | 
			
		||||
              <option value="in" title="field is one of the values">∈</option>
 | 
			
		||||
              <option value="nin" title="field is not one of the values">∉</option>
 | 
			
		||||
            </rb-form-select>
 | 
			
		||||
            <div class="filter-inputs">
 | 
			
		||||
              <ng-container *ngFor="let ignore of [].constructor(filter.values.length); index as i">
 | 
			
		||||
                <rb-form-date-input *ngIf="filter.field === 'added'; else noDate" [name]="'filter-' + filter.field + i" [label]="filter.label" [(ngModel)]="filter.values[i]" (ngModelChange)="updateFilterFields(filter.field)"></rb-form-date-input>
 | 
			
		||||
                <ng-template #noDate>
 | 
			
		||||
                  <rb-form-input *ngIf="!filter.autocomplete.length" [name]="'filter-' + filter.field + i" [label]="filter.label" [(ngModel)]="filter.values[i]" (ngModelChange)="updateFilterFields(filter.field)"></rb-form-input>
 | 
			
		||||
                  <rb-form-input *ngIf="filter.autocomplete.length" [name]="'filter-' + filter.field + i" [label]="filter.label" [rbFormInputAutocomplete]="autocomplete.bind(this, filter.autocomplete)" [rbDebounceTime]="0" (keydown)="preventDefault($event, 'Enter')" [(ngModel)]="filter.values[i]" (ngModelChange)="updateFilterFields(filter.field)" ngModel></rb-form-input>
 | 
			
		||||
                </ng-template>
 | 
			
		||||
              </ng-container>
 | 
			
		||||
            </div>
 | 
			
		||||
          </ng-container>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
<!--      <button class="rb-btn rb-secondary" (click)="loadSamples()">Apply filters</button>-->
 | 
			
		||||
    </form>
 | 
			
		||||
  </rb-accordion-body>
 | 
			
		||||
</rb-accordion>
 | 
			
		||||
@@ -83,7 +84,7 @@
 | 
			
		||||
 | 
			
		||||
<rb-table>
 | 
			
		||||
  <tr>
 | 
			
		||||
    <th *ngFor="let key of activeKeysArray()">
 | 
			
		||||
    <th *ngFor="let key of activeKeys">
 | 
			
		||||
      <div class="sort-header">
 | 
			
		||||
        <span>{{key.label}}</span>
 | 
			
		||||
        <span class="rb-ic rb-ic-up sort-arr-up" (click)="setSort(key.id + '-' + 'desc')"><span *ngIf="filters.sort === key.id + '-' + 'desc'"></span></span>
 | 
			
		||||
@@ -94,19 +95,18 @@
 | 
			
		||||
  </tr>
 | 
			
		||||
 | 
			
		||||
  <tr *ngFor="let sample of samples">
 | 
			
		||||
    <td *ngIf="activeKeys['number']">{{sample.number}}</td>
 | 
			
		||||
    <td *ngIf="activeKeys['material.number']">{{sample.material_number}}</td>
 | 
			
		||||
    <td *ngIf="activeKeys['material.name']">{{materials[sample.material_id].name}}</td>
 | 
			
		||||
    <td *ngIf="activeKeys['material.supplier']">{{materials[sample.material_id].supplier}}</td>
 | 
			
		||||
    <td *ngIf="activeKeys['material.group']">{{materials[sample.material_id].group}}</td>
 | 
			
		||||
    <td *ngIf="activeKeys['material.glass_fiber']">{{materials[sample.material_id].glass_fiber}}</td>
 | 
			
		||||
    <td *ngIf="activeKeys['material.carbon_fiber']">{{materials[sample.material_id].carbon_fiber}}</td>
 | 
			
		||||
    <td *ngIf="activeKeys['material.mineral']">{{materials[sample.material_id].mineral}}</td>
 | 
			
		||||
    <td *ngIf="activeKeys['type']">{{sample.type}}</td>
 | 
			
		||||
    <td *ngIf="activeKeys['color']">{{sample.color}}</td>
 | 
			
		||||
    <td *ngIf="activeKeys['batch']">{{sample.batch}}</td>
 | 
			
		||||
    <td *ngIf="activeKeys['added']">{{sample.added | date:'dd/MM/yy'}}</td>
 | 
			
		||||
    <td *ngFor="let key of activeMeasurementKeys()">{{sample[key[1]] ? sample[key[1]][key[2]] : ''}}</td>
 | 
			
		||||
    <td *ngIf="isActiveKey['number']">{{sample.number}}</td>
 | 
			
		||||
    <td *ngIf="isActiveKey['material.numbers']">{{materials[sample.material_id].numbers}}</td>
 | 
			
		||||
    <td *ngIf="isActiveKey['material.name']">{{materials[sample.material_id].name}}</td>
 | 
			
		||||
    <td *ngIf="isActiveKey['material.supplier']">{{materials[sample.material_id].supplier}}</td>
 | 
			
		||||
    <td *ngIf="isActiveKey['material.group']">{{materials[sample.material_id].group}}</td>
 | 
			
		||||
    <td *ngFor="let key of activeTemplateKeys.material">{{materials[sample.material_id].properties[key[2]] | exists}}</td>
 | 
			
		||||
    <td *ngIf="isActiveKey['type']">{{sample.type}}</td>
 | 
			
		||||
    <td *ngIf="isActiveKey['color']">{{sample.color}}</td>
 | 
			
		||||
    <td *ngIf="isActiveKey['batch']">{{sample.batch}}</td>
 | 
			
		||||
    <td *ngIf="isActiveKey['notes']">{{sample.notes | object}}</td>
 | 
			
		||||
    <td *ngFor="let key of activeTemplateKeys.measurements">{{sample[key[1]] | exists: key[2]}}</td>
 | 
			
		||||
    <td *ngIf="isActiveKey['added']">{{sample.added | date:'dd/MM/yy'}}</td>
 | 
			
		||||
    <td><a [routerLink]="'/samples/edit/' + sample._id"><span class="rb-ic rb-ic-edit"></span></a></td>
 | 
			
		||||
  </tr>
 | 
			
		||||
</rb-table>
 | 
			
		||||
@@ -120,9 +120,9 @@
 | 
			
		||||
    </button>
 | 
			
		||||
    <rb-form-input label="page" (change)="loadPage({toPage: $event.target.value - page})" [ngModel]="page"></rb-form-input>
 | 
			
		||||
    <span>
 | 
			
		||||
      of {{pages()}} ({{totalSamples}} samples)
 | 
			
		||||
      of {{pages}} ({{totalSamples}} samples)
 | 
			
		||||
    </span>
 | 
			
		||||
    <button class="rb-btn rb-link" type="button" (click)="loadPage(1)" [disabled]="page >= pages()">
 | 
			
		||||
    <button class="rb-btn rb-link" type="button" (click)="loadPage(1)" [disabled]="page >= pages">
 | 
			
		||||
      <span class="rb-ic  rb-ic-forward-right"></span>
 | 
			
		||||
    </button>
 | 
			
		||||
  </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,10 @@
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
rb-table {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.rb-ic.rb-ic-edit {
 | 
			
		||||
  font-size: 1.1rem;
 | 
			
		||||
  color: $color-gray-silver-sand;
 | 
			
		||||
@@ -156,11 +160,13 @@
 | 
			
		||||
  & > div {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-columns: auto auto 1fr;
 | 
			
		||||
    float: left;
 | 
			
		||||
    margin-right: 30px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.filtermode {
 | 
			
		||||
  max-width: 100px;
 | 
			
		||||
  max-width: 80px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
textarea.linkmodal {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,12 +4,16 @@ import {AutocompleteService} from '../services/autocomplete.service';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
interface LoadSamplesOptions {
 | 
			
		||||
  toPage?: number;
 | 
			
		||||
  event?: Event;
 | 
			
		||||
  firstPage?: boolean;
 | 
			
		||||
}
 | 
			
		||||
interface KeyInterface {
 | 
			
		||||
  id: string;
 | 
			
		||||
  label: string;
 | 
			
		||||
  active: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-samples',
 | 
			
		||||
@@ -17,6 +21,8 @@ interface LoadSamplesOptions {
 | 
			
		||||
  styleUrls: ['./samples.component.scss']
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// TODO: manage branches, introduce versioning, only upload ui from master
 | 
			
		||||
// TODO: check if custom-header.conf works, add headers from helmet https://docs.cloudfoundry.org/buildpacks/staticfile/index.html
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export class SamplesComponent implements OnInit {
 | 
			
		||||
@@ -24,7 +30,6 @@ export class SamplesComponent implements OnInit {
 | 
			
		||||
  @ViewChild('pageSizeSelection') pageSizeSelection: ElementRef<HTMLElement>;
 | 
			
		||||
  @ViewChild('linkarea') linkarea: ElementRef<HTMLTextAreaElement>;
 | 
			
		||||
 | 
			
		||||
  customFields = [''];
 | 
			
		||||
  downloadCsv = false;
 | 
			
		||||
  materials = {};
 | 
			
		||||
  samples = [];
 | 
			
		||||
@@ -47,40 +52,29 @@ export class SamplesComponent implements OnInit {
 | 
			
		||||
      {field: 'type', label: 'Type', active: false, autocomplete: [], mode: 'eq', values: ['']},
 | 
			
		||||
      {field: 'color', label: 'Color', active: false, autocomplete: [], mode: 'eq', values: ['']},
 | 
			
		||||
      {field: 'batch', label: 'Batch', active: false, autocomplete: [], mode: 'eq', values: ['']},
 | 
			
		||||
      {field: 'notes', label: 'Notes', active: false, autocomplete: [], mode: 'eq', values: ['']},
 | 
			
		||||
      {field: 'added', label: 'Added', active: false, autocomplete: [], mode: 'eq', values: [new Date()]}
 | 
			
		||||
    ]
 | 
			
		||||
  };
 | 
			
		||||
  page = 1;
 | 
			
		||||
  pages = 1;
 | 
			
		||||
  loadSamplesQueue = [];  // arguments of queued up loadSamples() calls
 | 
			
		||||
  apiKey = '';
 | 
			
		||||
  activeKeys =  {
 | 
			
		||||
    number: true,
 | 
			
		||||
    'material.number': true,
 | 
			
		||||
    'material.name': true,
 | 
			
		||||
    'material.supplier': true,
 | 
			
		||||
    'material.group': false,
 | 
			
		||||
    'material.glass_fiber': false,
 | 
			
		||||
    'material.carbon_fiber': false,
 | 
			
		||||
    'material.mineral': false,
 | 
			
		||||
    type: true,
 | 
			
		||||
    color: true,
 | 
			
		||||
    batch: true,
 | 
			
		||||
    added: true,
 | 
			
		||||
  };
 | 
			
		||||
  keys = [
 | 
			
		||||
    {id: 'number', label: 'Number'},
 | 
			
		||||
    {id: 'material.number', label: 'Material number'},
 | 
			
		||||
    {id: 'material.name', label: 'Material name'},
 | 
			
		||||
    {id: 'material.supplier', label: 'Supplier'},
 | 
			
		||||
    {id: 'material.group', label: 'Material'},
 | 
			
		||||
    {id: 'material.glass_fiber', label: 'GF'},
 | 
			
		||||
    {id: 'material.carbon_fiber', label: 'CF'},
 | 
			
		||||
    {id: 'material.mineral', label: 'M'},
 | 
			
		||||
    {id: 'type', label: 'Type'},
 | 
			
		||||
    {id: 'color', label: 'Color'},
 | 
			
		||||
    {id: 'batch', label: 'Batch'},
 | 
			
		||||
    {id: 'added', label: 'Added'}
 | 
			
		||||
  keys: KeyInterface[] = [
 | 
			
		||||
    {id: 'number', label: 'Number', active: true},
 | 
			
		||||
    {id: 'material.numbers', label: 'Material numbers', active: true},
 | 
			
		||||
    {id: 'material.name', label: 'Material name', active: true},
 | 
			
		||||
    {id: 'material.supplier', label: 'Supplier', active: true},
 | 
			
		||||
    {id: 'material.group', label: 'Material', active: false},
 | 
			
		||||
    {id: 'type', label: 'Type', active: true},
 | 
			
		||||
    {id: 'color', label: 'Color', active: true},
 | 
			
		||||
    {id: 'batch', label: 'Batch', active: true},
 | 
			
		||||
    {id: 'notes', label: 'Notes', active: false},
 | 
			
		||||
    {id: 'added', label: 'Added', active: true}
 | 
			
		||||
  ];
 | 
			
		||||
  isActiveKey: {[key: string]: boolean} = {};
 | 
			
		||||
  activeKeys: KeyInterface[] = [];
 | 
			
		||||
  activeTemplateKeys = {material: [], measurements: []};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
@@ -90,6 +84,7 @@ export class SamplesComponent implements OnInit {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.calcFieldSelectKeys();
 | 
			
		||||
    this.api.get('/materials?status=all', (mData: any) => {
 | 
			
		||||
      this.materials = {};
 | 
			
		||||
      mData.forEach(material => {
 | 
			
		||||
@@ -108,21 +103,35 @@ export class SamplesComponent implements OnInit {
 | 
			
		||||
    this.api.get<string[]>('/material/groups', (data: any) => {
 | 
			
		||||
      this.filters.filters.find(e => e.field === 'material.group').autocomplete = data;
 | 
			
		||||
    });
 | 
			
		||||
    this.api.get('/template/measurements', (data: {name: string, parameters: {name: string, range: object}[]}[]) => {
 | 
			
		||||
      const measurementKeys = [];
 | 
			
		||||
    this.loadTemplateKeys('materials', 'type');
 | 
			
		||||
    this.loadTemplateKeys('measurements', 'added');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  loadTemplateKeys(collection, insertBefore) {
 | 
			
		||||
    this.api.get('/template/' + collection, (data: {name: string, parameters: {name: string, range: object}[]}[]) => {
 | 
			
		||||
      const templateKeys = [];
 | 
			
		||||
      data.forEach(item => {
 | 
			
		||||
        item.parameters.forEach(parameter => {
 | 
			
		||||
          this.activeKeys[`measurements.${item.name}.${encodeURIComponent(parameter.name)}`] = false;
 | 
			
		||||
          measurementKeys.push({id: `measurements.${item.name}.${encodeURIComponent(parameter.name)}`, label: `${this.ucFirst(item.name)} ${this.ucFirst(parameter.name)}`});
 | 
			
		||||
          this.filters.filters.push({field: `measurements.${item.name}.${parameter.name}`, label: `${this.ucFirst(item.name)} ${this.ucFirst(parameter.name)}`, active: false, autocomplete: [], mode: 'eq', values: ['']});
 | 
			
		||||
          templateKeys.push({id: `${collection === 'materials' ? 'material' : collection}.${collection === 'materials' ? 'properties' : item.name}.${encodeURIComponent(parameter.name)}`, label: `${this.ucFirst(item.name)} ${this.ucFirst(parameter.name)}`, active: false});
 | 
			
		||||
          this.filters.filters.push({field: `${collection === 'materials' ? 'material' : collection}.${collection === 'materials' ? 'properties' : item.name}.${parameter.name}`, label: `${this.ucFirst(item.name)} ${this.ucFirst(parameter.name)}`, active: false, autocomplete: [], mode: 'eq', values: ['']});
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
      console.log(this.filters.filters);
 | 
			
		||||
      this.keys = [...this.keys, ...measurementKeys];
 | 
			
		||||
      this.keys.splice(this.keys.findIndex(e => e.id === insertBefore), 0, ...templateKeys);
 | 
			
		||||
      this.keys = [...this.keys];  // complete overwrite array to invoke update in rb-multiselect
 | 
			
		||||
      this.updateActiveKeys();
 | 
			
		||||
      this.calcFieldSelectKeys();
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  loadSamples(options: LoadSamplesOptions = {}) {  // set toPage to null to reload first page, queues calls
 | 
			
		||||
  loadSamples(options: LoadSamplesOptions = {}, event = null) {  // set toPage to null to reload first page, queues calls
 | 
			
		||||
    if (event) {  // adjust active keys
 | 
			
		||||
      this.keys.forEach(key => {
 | 
			
		||||
        if (event.hasOwnProperty(key.id)) {
 | 
			
		||||
          key.active = event[key.id];
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      this.updateActiveKeys();
 | 
			
		||||
    }
 | 
			
		||||
    this.loadSamplesQueue.push(options);
 | 
			
		||||
    if (this.loadSamplesQueue.length <= 1) {  // nothing queued up
 | 
			
		||||
      this.sampleLoader(this.loadSamplesQueue[0]);
 | 
			
		||||
@@ -131,13 +140,11 @@ export class SamplesComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  private sampleLoader(options: LoadSamplesOptions) {  // actual loading of the sample, do not call directly
 | 
			
		||||
    this.api.get(this.sampleUrl({paging: true, pagingOptions: options}), (sData, ignore, headers) => {
 | 
			
		||||
      if (!options.toPage) {
 | 
			
		||||
      if (!options.toPage && headers['x-total-items']) {
 | 
			
		||||
        this.totalSamples = headers['x-total-items'];
 | 
			
		||||
      }
 | 
			
		||||
      this.pages = Math.ceil(this.totalSamples / this.filters.pageSize);
 | 
			
		||||
      this.samples = sData as any;
 | 
			
		||||
      this.samples.forEach(sample => {
 | 
			
		||||
        sample.material_number = sample.color === '' ? '' : this.materials[sample.material_id].numbers.find(e => sample.color === e.color).number;
 | 
			
		||||
      });
 | 
			
		||||
      this.loadSamplesQueue.shift();
 | 
			
		||||
      if (this.loadSamplesQueue.length > 0) {  // execute next queue item
 | 
			
		||||
        this.sampleLoader(this.loadSamplesQueue[0]);
 | 
			
		||||
@@ -146,7 +153,7 @@ export class SamplesComponent implements OnInit {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  sampleUrl(options: {paging?: boolean, pagingOptions?: {firstPage?: boolean, toPage?: number, event?: Event}, csv?: boolean, export?: boolean, host?: boolean}) {  // return url to fetch samples
 | 
			
		||||
    const additionalTableKeys = ['material_id', '_id', 'color'];  // keys which should always be added if export = false
 | 
			
		||||
    const additionalTableKeys = ['material_id', '_id'];  // keys which should always be added if export = false
 | 
			
		||||
    const query: string[] = [];
 | 
			
		||||
    query.push('status=' + (this.filters.status.new && this.filters.status.validated ? 'all' : (this.filters.status.new ? 'new' : 'validated')));
 | 
			
		||||
    if (options.paging) {
 | 
			
		||||
@@ -170,23 +177,22 @@ export class SamplesComponent implements OnInit {
 | 
			
		||||
    if (options.export) {
 | 
			
		||||
      query.push('key=' + this.apiKey);
 | 
			
		||||
    }
 | 
			
		||||
    Object.keys(this.activeKeys).forEach(key => {
 | 
			
		||||
      if (this.activeKeys[key] && (options.export || (!options.export && key.indexOf('material') < 0))) {  // do not load material properties for table
 | 
			
		||||
        query.push('fields[]=' + key);
 | 
			
		||||
    this.keys.forEach(key => {
 | 
			
		||||
      if (key.active && (options.export || (!options.export && key.id.indexOf('material') < 0))) {  // do not load material properties for table
 | 
			
		||||
        query.push('fields[]=' + key.id);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    console.log(this.filters.filters);
 | 
			
		||||
 | 
			
		||||
    query.push(..._.cloneDeep(this.filters.filters)
 | 
			
		||||
      .map(e => {
 | 
			
		||||
        e.values = e.values.filter(el => el !== '');
 | 
			
		||||
        if (e.field === 'added') {
 | 
			
		||||
          console.log(e.values);
 | 
			
		||||
        e.values = e.values.filter(el => el !== '');  // do not include empty values
 | 
			
		||||
        if (e.field === 'added') {  // correct timezone
 | 
			
		||||
          e.values = e.values.map(el => new Date(new Date(el).getTime() - new Date(el).getTimezoneOffset() * 60000).toISOString());
 | 
			
		||||
        }
 | 
			
		||||
        return e;
 | 
			
		||||
      })
 | 
			
		||||
      .filter(e => e.active && e.values[0] !== '')
 | 
			
		||||
      .filter(e => e.active && e.values.length > 0)
 | 
			
		||||
      .map(e => 'filters[]=' + encodeURIComponent(JSON.stringify(_.pick(e, ['mode', 'field', 'values']))))
 | 
			
		||||
    );
 | 
			
		||||
    console.log(this.filters);
 | 
			
		||||
@@ -230,21 +236,23 @@ export class SamplesComponent implements OnInit {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pages() {
 | 
			
		||||
    return Math.ceil(this.totalSamples / this.filters.pageSize);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setSort(string) {
 | 
			
		||||
    this.filters.sort = string;
 | 
			
		||||
    this.loadSamples({firstPage: true});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  activeKeysArray() {  // array with all activeKeys names
 | 
			
		||||
    return Object.keys(this.activeKeys).filter(e => this.activeKeys[e] === true).map(e => this.keys.find(el => el.id === e));
 | 
			
		||||
  updateActiveKeys() {  // array with all activeKeys
 | 
			
		||||
    this.activeKeys = this.keys.filter(e => e.active);
 | 
			
		||||
    this.activeTemplateKeys.material = this.keys.filter(e => e.id.indexOf('material.properties.') >= 0 && e.active).map(e => e.id.split('.').map(el => decodeURIComponent(el)));
 | 
			
		||||
    this.activeTemplateKeys.measurements = this.keys.filter(e => e.id.indexOf('measurements.') >= 0 && e.active).map(e => e.id.split('.').map(el => decodeURIComponent(el)));
 | 
			
		||||
    console.log(this.activeTemplateKeys);
 | 
			
		||||
    console.log(this.keys);  // TODO: glass fiber filter not working
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  activeMeasurementKeys() {
 | 
			
		||||
    return Object.keys(this.activeKeys).filter(e => e.indexOf('measurements.') >= 0 && this.activeKeys[e]).map(e => e.split('.').map(el => decodeURIComponent(el)));
 | 
			
		||||
  calcFieldSelectKeys() {
 | 
			
		||||
    this.keys.forEach(key => {
 | 
			
		||||
      this.isActiveKey[key.id] = key.active;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  preventDefault(event, key = 'all') {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,8 @@ export class ApiService {
 | 
			
		||||
  constructor(
 | 
			
		||||
    private http: HttpClient,
 | 
			
		||||
    private storage: LocalStorageService,
 | 
			
		||||
    private modalService: ModalService
 | 
			
		||||
    private modalService: ModalService,
 | 
			
		||||
    private window: Window
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  get hostName() {
 | 
			
		||||
@@ -49,6 +50,9 @@ export class ApiService {
 | 
			
		||||
      else {
 | 
			
		||||
        const modalRef = this.modalService.openComponent(ErrorComponent);
 | 
			
		||||
        modalRef.instance.message = 'Network request failed!';
 | 
			
		||||
        modalRef.result.then(() => {
 | 
			
		||||
          this.window.location.reload();
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user