From 93878e4e0a8f509277248ceba7c695c96c2b72b2 Mon Sep 17 00:00:00 2001 From: VLE2FE Date: Wed, 22 Jul 2020 10:45:34 +0200 Subject: [PATCH] implemented sample references --- manifest.yml | 3 +- src/Staticfile | 2 + src/app/app.module.ts | 4 +- src/app/exists.pipe.spec.ts | 8 + src/app/exists.pipe.ts | 14 ++ src/app/models/material.model.ts | 17 +- src/app/object.pipe.ts | 5 +- .../rb-table/rb-table/rb-table.component.html | 8 +- .../rb-table/rb-table/rb-table.component.scss | 15 ++ src/app/sample/sample.component.html | 46 ++--- src/app/sample/sample.component.ts | 185 +++++++++++++----- src/app/samples/samples.component.html | 80 ++++---- src/app/samples/samples.component.scss | 8 +- src/app/samples/samples.component.ts | 122 ++++++------ src/app/services/api.service.ts | 6 +- 15 files changed, 331 insertions(+), 192 deletions(-) create mode 100644 src/app/exists.pipe.spec.ts create mode 100644 src/app/exists.pipe.ts diff --git a/manifest.yml b/manifest.yml index 852c5cd..19a5de6 100644 --- a/manifest.yml +++ b/manifest.yml @@ -2,7 +2,8 @@ applications: - name: definma path: dist/UI - buildpack: staticfile_buildpack + buildpacks: + - staticfile_buildpack memory: 128M instances: 1 stack: cflinuxfs3 diff --git a/src/Staticfile b/src/Staticfile index 03b776c..1b9f882 100644 --- a/src/Staticfile +++ b/src/Staticfile @@ -1,2 +1,4 @@ pushstate: enabled force_https: true +root: UI +location_include: custom-header.conf diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 9e1268c..4fd6a1c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -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({ diff --git a/src/app/exists.pipe.spec.ts b/src/app/exists.pipe.spec.ts new file mode 100644 index 0000000..110c53c --- /dev/null +++ b/src/app/exists.pipe.spec.ts @@ -0,0 +1,8 @@ +import { ExistsPipe } from './exists.pipe'; + +describe('ExistsPipe', () => { + it('create an instance', () => { + const pipe = new ExistsPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/src/app/exists.pipe.ts b/src/app/exists.pipe.ts new file mode 100644 index 0000000..2fefbd7 --- /dev/null +++ b/src/app/exists.pipe.ts @@ -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) : ''; + } + +} diff --git a/src/app/models/material.model.ts b/src/app/models/material.model.ts index e0e8821..30e37f6 100644 --- a/src/app/models/material.model.ts +++ b/src/app/models/material.model.ts @@ -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']); } } diff --git a/src/app/object.pipe.ts b/src/app/object.pipe.ts index cf7ced5..7dc3e50 100644 --- a/src/app/object.pipe.ts +++ b/src/app/object.pipe.ts @@ -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) : ''; } } diff --git a/src/app/rb-table/rb-table/rb-table.component.html b/src/app/rb-table/rb-table/rb-table.component.html index 49cef91..5e3c6a4 100644 --- a/src/app/rb-table/rb-table/rb-table.component.html +++ b/src/app/rb-table/rb-table/rb-table.component.html @@ -1,4 +1,6 @@ - - -
+
+ + +
+
diff --git a/src/app/rb-table/rb-table/rb-table.component.scss b/src/app/rb-table/rb-table/rb-table.component.scss index 80a2419..c1b1e0a 100644 --- a/src/app/rb-table/rb-table/rb-table.component.scss +++ b/src/app/rb-table/rb-table/rb-table.component.scss @@ -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 { diff --git a/src/app/sample/sample.component.html b/src/app/sample/sample.component.html index 138e0e9..7401416 100644 --- a/src/app/sample/sample.component.html +++ b/src/app/sample/sample.component.html @@ -10,7 +10,7 @@ Cannot be empty Unknown material, add properties for new material - +
@@ -21,30 +21,16 @@ {{groupInput.errors.failure}} - - Invalid value - Minimum value is 0 - Maximum value is 100 - - - Invalid value - Minimum value is 0 - Maximum value is 100 - - - Invalid value - Minimum value is 0 - Maximum value is 100 - -
-
- - - - -
+
+ + + + + {{parameterInput.errors.failure}} + Cannot be empty +
  @@ -54,7 +40,7 @@ {{typeInput.errors.failure}} Cannot be empty - + {{colorInput.errors.failure}} Cannot be empty @@ -68,6 +54,17 @@ {{commentInput.errors.failure}} +
Sample references
+
+
+ + Unknown sample number + +
+ + Cannot be empty + +
Additional properties
@@ -79,7 +76,6 @@ Cannot be empty
-
  diff --git a/src/app/sample/sample.component.ts b/src/app/sample/sample.component.ts index ba13d26..5f68668 100644 --- a/src/app/sample/sample.component.ts +++ b/src/app/sample/sample.component.ts @@ -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('/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('/template/materials', data => { + this.materialTemplates = data.map(e => new TemplateModel().deserialize(e)); + this.selectMaterialTemplate(this.materialTemplates[0]._id); + this.loading--; + }); this.api.get('/template/measurements', data => { this.measurementTemplates = data.map(e => new TemplateModel().deserialize(e)); + if (!this.new) { + this.loading++; + this.api.get('/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('/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('/sample/notes/fields', data => { this.availableCustomFields = data.map(e => e.name); this.loading--; }); - if (!this.new) { - this.loading++; - this.api.get('/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(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('/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(resolve => { if (this.new) { this.api.post('/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]); } diff --git a/src/app/samples/samples.component.html b/src/app/samples/samples.component.html index 9aae131..1e453bb 100644 --- a/src/app/samples/samples.component.html +++ b/src/app/samples/samples.component.html @@ -6,7 +6,7 @@ -   Filter +   Filter
@@ -28,36 +28,37 @@ - + {{item.label}}
- - - - - - - - - - - -
- - - - - - - -
+ + + + + + + + + + + + + +
+ + + + + + + +
+
- - @@ -83,7 +84,7 @@ - +
{{key.label}} @@ -94,19 +95,18 @@ - {{sample.number}} - {{sample.material_number}} - {{materials[sample.material_id].name}} - {{materials[sample.material_id].supplier}} - {{materials[sample.material_id].group}} - {{materials[sample.material_id].glass_fiber}} - {{materials[sample.material_id].carbon_fiber}} - {{materials[sample.material_id].mineral}} - {{sample.type}} - {{sample.color}} - {{sample.batch}} - {{sample.added | date:'dd/MM/yy'}} - {{sample[key[1]] ? sample[key[1]][key[2]] : ''}} + {{sample.number}} + {{materials[sample.material_id].numbers}} + {{materials[sample.material_id].name}} + {{materials[sample.material_id].supplier}} + {{materials[sample.material_id].group}} + {{materials[sample.material_id].properties[key[2]] | exists}} + {{sample.type}} + {{sample.color}} + {{sample.batch}} + {{sample.notes | object}} + {{sample[key[1]] | exists: key[2]}} + {{sample.added | date:'dd/MM/yy'}} @@ -120,9 +120,9 @@ - of {{pages()}} ({{totalSamples}} samples) + of {{pages}} ({{totalSamples}} samples) -
diff --git a/src/app/samples/samples.component.scss b/src/app/samples/samples.component.scss index 9b4a2f6..55d78fd 100644 --- a/src/app/samples/samples.component.scss +++ b/src/app/samples/samples.component.scss @@ -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 { diff --git a/src/app/samples/samples.component.ts b/src/app/samples/samples.component.ts index 8e51b5a..0aa5c89 100644 --- a/src/app/samples/samples.component.ts +++ b/src/app/samples/samples.component.ts @@ -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; @ViewChild('linkarea') linkarea: ElementRef; - 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('/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') { diff --git a/src/app/services/api.service.ts b/src/app/services/api.service.ts index 0924640..60780d9 100644 --- a/src/app/services/api.service.ts +++ b/src/app/services/api.service.ts @@ -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(); + }); } }); }