definma-ui/src/app/sample/sample.component.ts

342 lines
12 KiB
TypeScript
Raw Normal View History

import _ from 'lodash';
import {
AfterContentChecked,
Component,
OnInit,
ViewChild
} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {AutocompleteService} from '../services/autocomplete.service';
import {ApiService} from '../services/api.service';
import {MaterialModel} from '../models/material.model';
import {SampleModel} from '../models/sample.model';
import {NgForm, Validators} from '@angular/forms';
import {ValidationService} from '../services/validation.service';
import {TemplateModel} from '../models/template.model';
import {MeasurementModel} from '../models/measurement.model';
// TODO: tests
// TODO: confirmation for new group/supplier
// TODO: DPT preview
// TODO: work on better recognition for file input
@Component({
selector: 'app-sample',
templateUrl: './sample.component.html',
styleUrls: ['./sample.component.scss']
})
export class SampleComponent implements OnInit, AfterContentChecked {
@ViewChild('sampleForm') sampleForm: NgForm;
new; // true if new sample should be created
newMaterial = false; // true if new material should be created
materials: MaterialModel[] = []; // all materials
suppliers: string[] = []; // all suppliers
groups: string[] = []; // all groups
conditionTemplates: TemplateModel[]; // all conditions
condition: TemplateModel | null = null; // selected condition
materialNames = []; // names of all materials
material = new MaterialModel(); // object of current selected material
sample = new SampleModel();
customFields: [string, string][] = [['', '']];
availableCustomFields: string[] = [];
responseData: SampleModel; // gets filled with response data after saving the sample
measurementTemplates: TemplateModel[];
loading = 0; // number of currently loading instances
checkFormAfterInit = false;
constructor(
private router: Router,
private route: ActivatedRoute,
private api: ApiService,
private validation: ValidationService,
public autocomplete: AutocompleteService
) { }
ngOnInit(): void {
this.new = this.router.url === '/samples/new';
this.loading = 6;
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);
this.loading--;
});
this.api.get<string[]>('/material/suppliers', (data: any) => {
this.suppliers = data;
this.loading--;
});
this.api.get<string[]>('/material/groups', (data: any) => {
this.groups = data;
this.loading--;
});
this.api.get<TemplateModel[]>('/template/conditions', data => {
this.conditionTemplates = data.map(e => new TemplateModel().deserialize(e));
this.loading--;
});
this.api.get<TemplateModel[]>('/template/measurements', data => {
this.measurementTemplates = data.map(e => new TemplateModel().deserialize(e));
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.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() {
// attach validators to dynamic condition fields when all values are available and template was fully created
if (this.condition && this.condition.hasOwnProperty('parameters') && this.condition.parameters.length > 0 && this.condition.parameters[0].hasOwnProperty('range') && this.sampleForm && this.sampleForm.form.get('conditionParameter0')) {
for (const i in this.condition.parameters) {
if (this.condition.parameters[i]) {
this.attachValidator('conditionParameter' + i, this.condition.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);
for (const i in template.parameters) {
if (template.parameters[i]) {
this.attachValidator('measurementParameter' + mIndex + '-' + i, template.parameters[i].range, false);
}
}
});
if (this.checkFormAfterInit) {
this.checkFormAfterInit = false;
this.initialValidate();
}
}
}
initialValidate() {
console.log('initVal');
Object.keys(this.sampleForm.form.controls).forEach(field => {
this.sampleForm.form.get(field).updateValueAndValidity();
});
}
attachValidator(name: string, range: {[prop: string]: any}, required: boolean) {
if (this.sampleForm.form.get(name)) {
const validators = [];
if (required) {
validators.push(Validators.required);
}
if (range.hasOwnProperty('values')) {
validators.push(this.validation.generate('stringOf', [range.values]));
}
else if (range.hasOwnProperty('min') && range.hasOwnProperty('max')) {
validators.push(this.validation.generate('minMax', [range.min, range.max]));
}
else if (range.hasOwnProperty('min')) {
validators.push(this.validation.generate('min', [range.min]));
}
else if (range.hasOwnProperty('max')) {
validators.push(this.validation.generate('max', [range.max]));
}
this.sampleForm.form.get(name).setValidators(validators);
}
}
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.api.post<MaterialModel>('/material/new', this.material.sendFormat(), data => {
this.materials.push(data); // add material to data
this.material = data;
this.sample.material_id = data._id; // add new material id to sample data
resolve();
});
}
else {
resolve();
}
}).then(() => { // save sample
this.sample.notes.custom_fields = {};
this.customFields.forEach(element => {
if (element[0] !== '') {
this.sample.notes.custom_fields[element[0]] = element[1];
}
});
new Promise<SampleModel>(resolve => {
if (this.new) {
this.api.post<SampleModel>('/sample/new', this.sample.sendFormat(), resolve);
}
else {
this.api.put<SampleModel>('/sample/' + this.sample._id, this.sample.sendFormat(), resolve);
}
}).then( data => {
this.responseData = new SampleModel().deserialize(data);
this.material = this.materials.find(e => e._id === this.responseData.material_id);
this.sample.measurements.forEach(measurement => {
if (Object.keys(measurement.values).map(e => measurement.values[e]).join('') !== '') {
Object.keys(measurement.values).forEach(key => {
measurement.values[key] = measurement.values[key] === '' ? null : measurement.values[key];
});
if (measurement._id === null) { // new measurement
measurement.sample_id = data._id;
this.api.post<MeasurementModel>('/measurement/new', measurement.sendFormat());
}
else { // update measurement
this.api.put<MeasurementModel>('/measurement/' + measurement._id, measurement.sendFormat(['sample_id', 'measurement_template']));
}
}
else if (measurement._id !== null) { // existing measurement was left empty to delete
this.api.delete('/measurement/' + measurement._id);
}
});
});
});
}
findMaterial(name) {
const res = this.materials.find(e => e.name === name); // search for match
if (res) {
this.material = _.cloneDeep(res);
this.sample.material_id = this.material._id;
}
else {
this.sample.material_id = null;
}
this.setNewMaterial();
}
preventSubmit(event) {
if (event.key === 'Enter') {
event.preventDefault();
}
}
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 {
this.newMaterial = value;
}
if (this.newMaterial) {
this.sampleForm.form.get('materialname').setValidators([Validators.required]);
}
else {
this.sampleForm.form.get('materialname').setValidators([Validators.required, this.validation.generate('stringOf', [this.materialNames])]);
}
this.sampleForm.form.get('materialname').updateValueAndValidity();
}
handleMaterialNumbers() {
const fieldNo = this.material.numbers.length;
let filledFields = 0;
this.material.numbers.forEach(mNumber => {
if (mNumber.color !== '') {
filledFields ++;
}
});
// append new field
if (filledFields === fieldNo) {
this.material.addNumber();
}
// 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();
}
}
selectCondition(id) {
this.condition = this.conditionTemplates.find(e => e._id === id);
console.log(this.condition);
console.log(this.sample);
if ('condition_template' in this.sample.condition) {
this.sample.condition.condition_template = id;
}
}
getMeasurementTemplate(id): TemplateModel {
return this.measurementTemplates && id ? this.measurementTemplates.find(e => e._id === id) : new TemplateModel();
}
addMeasurement() {
this.sample.measurements.push(new MeasurementModel(this.measurementTemplates[0]._id));
}
removeMeasurement(index) {
if (this.sample.measurements[index]._id !== null) {
this.api.delete('/measurement/' + this.sample.measurements[index]._id);
}
this.sample.measurements.splice(index, 1);
}
fileToArray(event, mIndex, parameter) {
const fileReader = new FileReader();
fileReader.onload = () => {
this.sample.measurements[mIndex].values[parameter] = fileReader.result.toString().split('\r\n').map(e => e.split(','));
};
fileReader.readAsText(event.target.files[0]);
}
toggleCondition() {
if (this.condition) {
this.condition = null;
}
else {
this.sample.condition = {condition_template: null};
this.selectCondition(this.conditionTemplates[0]._id);
}
}
adjustCustomFields(value, index) {
this.customFields[index][0] = value;
const fieldNo = this.customFields.length;
let filledFields = 0;
this.customFields.forEach(field => {
if (field[0] !== '') {
filledFields ++;
}
});
// append new field
if (filledFields === fieldNo) {
this.customFields.push(['', '']);
}
// remove if two end fields are empty
if (fieldNo > 1 && this.customFields[fieldNo - 1][0] === '' && this.customFields[fieldNo - 2][0] === '') {
this.customFields.pop();
}
}
uniqueCfValues(index) { // returns all names until index for unique check
return this.customFields.slice(0, index).map(e => e[0]);
}
}
// 1. ngAfterViewInit wird ja jedes mal nach einem ngOnChanges aufgerufen, also zB wenn sich dein ngFor aufbaut. Du könntest also in der Methode prüfen, ob die Daten schon da sind und dann dementsprechend handeln. Das wäre die Eleganteste Variante
// 2. Der state "dirty" soll eigentlich anzeigen, wenn ein Form-Field vom User geändert wurde; damit missbrauchst du es hier etwas
// 3. Die Dirty-Variante: Pack in deine ngFor ein {{ onFirstLoad(data) }} rein, das einfach ausgeführt wird. müsstest dann natürlich abfangen, dass das nicht nach jedem view-cycle neu getriggert wird. Schön ist das nicht, aber besser als mit Timeouts^^