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
- New material
+ 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
-
-
+
+ {{m.name}}
+
+
+ {{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