bug button, data service, user level adjustments, multiple sample support for edit dialog, validation functionality
This commit is contained in:
@ -1,9 +1,15 @@
|
||||
<script src="samples.component.ts"></script>
|
||||
<div class="header-addnew">
|
||||
<h2>Samples</h2>
|
||||
<a routerLink="/samples/new">
|
||||
<rb-icon-button icon="add" mode="primary">New sample</rb-icon-button>
|
||||
<a routerLink="/samples/new" *ngIf="login.isLevel.write">
|
||||
<rb-icon-button icon="add" mode="primary" class="space-left">New sample</rb-icon-button>
|
||||
</a>
|
||||
<rb-icon-button *ngIf="validation" mode="secondary" icon="close" (click)="validation = false" class="validation-close"
|
||||
iconOnly></rb-icon-button>
|
||||
<rb-icon-button *ngIf="login.isLevel.dev" [icon]="validation? 'checkmark' : 'clear-all'"
|
||||
mode="secondary" (click)="validate()">
|
||||
Validate
|
||||
</rb-icon-button>
|
||||
</div>
|
||||
|
||||
<rb-accordion>
|
||||
@ -86,13 +92,15 @@
|
||||
|
||||
<ng-container *ngTemplateOutlet="paging"></ng-container>
|
||||
|
||||
<div class="download">
|
||||
<rb-icon-button icon="download" mode="secondary" [rbModal]="linkModal">JSON download link</rb-icon-button>
|
||||
<div class="download space-below" *ngIf="login.isLevel.dev">
|
||||
<rb-icon-button class="space-right" icon="download" mode="secondary" [rbModal]="linkModal">
|
||||
JSON download link
|
||||
</rb-icon-button>
|
||||
<ng-template #linkModal>
|
||||
<label for="jsonUrl">URL for JSON download</label>
|
||||
<textarea class="linkmodal" id="jsonUrl" #linkarea [value]="sampleUrl({export: true, host: true})"
|
||||
(keydown)="preventDefault($event)"></textarea>
|
||||
<rb-form-checkbox name="download-csv" [(ngModel)]="downloadCsv">
|
||||
<rb-form-checkbox class="space-below" name="download-csv" [(ngModel)]="downloadCsv">
|
||||
add spectra
|
||||
</rb-form-checkbox>
|
||||
<rb-icon-button icon="clipboard" mode="secondary" (click)="clipboard()">Copy to clipboard</rb-icon-button>
|
||||
@ -104,8 +112,11 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<rb-table>
|
||||
<rb-table class="samples-table">
|
||||
<tr>
|
||||
<th *ngIf="validation">
|
||||
<rb-form-checkbox name="validate-all" (change)="selectAll($event)">all</rb-form-checkbox>
|
||||
</th>
|
||||
<th *ngFor="let key of activeKeys">
|
||||
<div class="sort-header">
|
||||
<span>{{key.label}}</span>
|
||||
@ -119,17 +130,21 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
</th>
|
||||
<th></th>
|
||||
<th *ngIf="login.isLevel.write"></th>
|
||||
</tr>
|
||||
|
||||
<tr *ngFor="let sample of samples">
|
||||
<tr *ngFor="let sample of samples; index as i" class="clickable" (click)="sampleDetails(sample._id, sampleModal)">
|
||||
<td *ngIf="validation">
|
||||
<rb-form-checkbox [name]="'validate-' + i" (click)="stopPropagation($event)" [(ngModel)]="sample.validate">
|
||||
</rb-form-checkbox>
|
||||
</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 *ngIf="isActiveKey['material.numbers']">{{d.id.materials[sample.material_id].numbers}}</td>
|
||||
<td *ngIf="isActiveKey['material.name']">{{d.id.materials[sample.material_id].name}}</td>
|
||||
<td *ngIf="isActiveKey['material.supplier']">{{d.id.materials[sample.material_id].supplier}}</td>
|
||||
<td *ngIf="isActiveKey['material.group']">{{d.id.materials[sample.material_id].group}}</td>
|
||||
<td *ngFor="let key of activeTemplateKeys.material">
|
||||
{{materials[sample.material_id].properties[key[2]] | exists}}
|
||||
{{d.id.materials[sample.material_id].properties[key[2]] | exists}}
|
||||
</td>
|
||||
<td *ngIf="isActiveKey['type']">{{sample.type}}</td>
|
||||
<td *ngIf="isActiveKey['color']">{{sample.color}}</td>
|
||||
@ -137,7 +152,12 @@
|
||||
<td *ngIf="isActiveKey['notes']">{{sample.notes | object: ['_id', 'sample_references']}}</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>
|
||||
<td *ngIf="login.isLevel.write">
|
||||
<a [routerLink]="'/samples/edit/' + sample._id"
|
||||
*ngIf="login.isLevel.dev || (login.isLevel.write && sample.user_id === login.userId)">
|
||||
<span class="rb-ic rb-ic-edit"></span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</rb-table>
|
||||
|
||||
@ -159,3 +179,35 @@
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #sampleModal>
|
||||
<rb-loading-spinner *ngIf="sampleDetailsSample === null; else sampleDetailsTemplate"></rb-loading-spinner>
|
||||
<ng-template #sampleDetailsTemplate>
|
||||
<h3>{{sampleDetailsSample.number}}</h3>
|
||||
<rb-table class="sample-details-table">
|
||||
<tr><th>Material</th><td>{{sampleDetailsSample.material.name}}</td></tr>
|
||||
<tr><th>Supplier</th><td>{{sampleDetailsSample.material.supplier}}</td></tr>
|
||||
<tr><th>Group</th><td>{{sampleDetailsSample.material.group}}</td></tr>
|
||||
<tr><th>Type</th><td>{{sampleDetailsSample.type}}</td></tr>
|
||||
<tr><th>color</th><td>{{sampleDetailsSample.color}}</td></tr>
|
||||
<tr><th>Batch</th><td>{{sampleDetailsSample.batch}}</td></tr>
|
||||
<tr><th>Comment</th><td>{{sampleDetailsSample.notes.comment | exists}}</td></tr>
|
||||
<tr *ngFor="let customField of sampleDetailsSample.notes.custom_fields_entries">
|
||||
<th>{{customField[0]}}</th>
|
||||
<td>{{customField[0]}}</td>
|
||||
</tr>
|
||||
<tr *ngFor="let reference of sampleDetailsSample.notes.sample_references">
|
||||
<th>{{reference.relation}}</th>
|
||||
<td>
|
||||
<button class="rb-btn rb-link" (click)="sampleDetails(reference.sample_id, sampleModal)">
|
||||
{{reference.number}}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngFor="let measurement of sampleDetailsSample.measurement_entries">
|
||||
<th>{{measurement.name}}</th>
|
||||
<td>{{measurement.value}}</td>
|
||||
</tr>
|
||||
<tr><th>User</th><td>{{sampleDetailsSample.user}}</td></tr>
|
||||
</rb-table>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
@ -180,3 +180,33 @@ textarea.linkmodal {
|
||||
display: inline-block;
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.sample-details-table {
|
||||
|
||||
::ng-deep .table-wrapper {
|
||||
max-height: 80vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
td {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
.validation-close {
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
.samples-table tr.clickable {
|
||||
background: none;
|
||||
transition: background-color 0.5s;
|
||||
|
||||
&:hover {
|
||||
background: $color-gray-mercury;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .samples-table rb-form-checkbox .input-wrapper {
|
||||
padding-top: 0;
|
||||
margin-top: -4.5px;
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
import {Component, ElementRef, isDevMode, OnInit, ViewChild} from '@angular/core';
|
||||
import {Component, ElementRef, isDevMode, OnInit, TemplateRef, ViewChild} from '@angular/core';
|
||||
import {ApiService} from '../services/api.service';
|
||||
import {AutocompleteService} from '../services/autocomplete.service';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import pick from 'lodash/pick';
|
||||
import {SampleModel} from '../models/sample.model';
|
||||
import {LoginService} from '../services/login.service';
|
||||
import {ModalService} from '@inst-iot/bosch-angular-ui-components';
|
||||
import {DataService} from '../services/data.service';
|
||||
|
||||
|
||||
interface LoadSamplesOptions {
|
||||
@ -32,7 +35,6 @@ export class SamplesComponent implements OnInit {
|
||||
@ViewChild('linkarea') linkarea: ElementRef<HTMLTextAreaElement>;
|
||||
|
||||
downloadCsv = false;
|
||||
materials = {};
|
||||
samples: SampleModel[] = [];
|
||||
totalSamples = 0; // total number of samples
|
||||
csvUrl = ''; // store url separate so it only has to be generated when clicking the download button
|
||||
@ -60,7 +62,6 @@ export class SamplesComponent implements OnInit {
|
||||
page = 1;
|
||||
pages = 1;
|
||||
loadSamplesQueue = []; // arguments of queued up loadSamples() calls
|
||||
apiKey = '';
|
||||
keys: KeyInterface[] = [
|
||||
{id: 'number', label: 'Number', active: true, sortable: true},
|
||||
{id: 'material.numbers', label: 'Material numbers', active: true, sortable: false},
|
||||
@ -76,55 +77,55 @@ export class SamplesComponent implements OnInit {
|
||||
isActiveKey: {[key: string]: boolean} = {};
|
||||
activeKeys: KeyInterface[] = [];
|
||||
activeTemplateKeys = {material: [], measurements: []};
|
||||
sampleDetailsSample: any = null;
|
||||
validation = false; // true to activate validation mode
|
||||
|
||||
|
||||
constructor(
|
||||
private api: ApiService,
|
||||
public autocomplete: AutocompleteService
|
||||
public autocomplete: AutocompleteService,
|
||||
public login: LoginService,
|
||||
private modalService: ModalService,
|
||||
public d: DataService
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.calcFieldSelectKeys();
|
||||
this.api.get('/materials?status=all', (mData: any) => {
|
||||
this.materials = {};
|
||||
mData.forEach(material => {
|
||||
this.materials[material._id] = material;
|
||||
});
|
||||
this.filters.filters.find(e => e.field === 'material.name').autocomplete = mData.map(e => e.name);
|
||||
this.d.load('materials', () => {
|
||||
this.filters.filters.find(e => e.field === 'material.name').autocomplete = this.d.arr.materials.map(e => e.name);
|
||||
this.filters.filters.find(e => e.field === 'color').autocomplete =
|
||||
[...new Set(mData.reduce((s, e) => {s.push(...e.numbers.map(el => el.color)); return s; }, []))];
|
||||
[...new Set(this.d.arr.materials.reduce((s, e) => {s.push(...e.numbers.map(el => el.color)); return s; }, []))];
|
||||
this.loadSamples();
|
||||
});
|
||||
this.api.get('/user/key', (data: {key: string}) => {
|
||||
this.apiKey = data.key;
|
||||
this.d.load('materialSuppliers', () => {
|
||||
this.filters.filters.find(e => e.field === 'material.supplier').autocomplete = this.d.arr.materialSuppliers;
|
||||
});
|
||||
this.api.get<string[]>('/material/suppliers', (data: any) => {
|
||||
this.filters.filters.find(e => e.field === 'material.supplier').autocomplete = data;
|
||||
this.d.load('materialGroups', () => {
|
||||
console.log(this.d.arr.materialGroups);
|
||||
this.filters.filters.find(e => e.field === 'material.group').autocomplete = this.d.arr.materialGroups;
|
||||
});
|
||||
this.api.get<string[]>('/material/groups', (data: any) => {
|
||||
this.filters.filters.find(e => e.field === 'material.group').autocomplete = data;
|
||||
});
|
||||
this.loadTemplateKeys('materials', 'type');
|
||||
this.loadTemplateKeys('measurements', 'added');
|
||||
this.d.load('userKey');
|
||||
this.loadTemplateKeys('material', 'type');
|
||||
this.loadTemplateKeys('measurement', 'added');
|
||||
}
|
||||
|
||||
loadTemplateKeys(collection, insertBefore) {
|
||||
this.api.get('/template/' + collection, (data: {name: string, parameters: {name: string, range: object}[]}[]) => {
|
||||
this.d.load(collection + 'Templates', () => {
|
||||
const templateKeys = [];
|
||||
data.forEach(item => {
|
||||
this.d.arr[collection + 'Templates'].forEach(item => {
|
||||
item.parameters.forEach(parameter => {
|
||||
const parameterName = encodeURIComponent(parameter.name);
|
||||
// exclude spectrum
|
||||
// exclude spectrum and duplicates
|
||||
if (parameter.name !== 'dpt' && !templateKeys.find(e => new RegExp('.' + parameterName + '$').test(e.id))) {
|
||||
templateKeys.push({
|
||||
id: `${collection === 'materials' ? 'material.properties' : collection + '.' + item.name}.${parameterName}`,
|
||||
id: `${collection === 'material' ? 'material.properties' : collection + 's.' + item.name}.${parameterName}`,
|
||||
label: `${this.ucFirst(item.name)} ${this.ucFirst(parameter.name)}`,
|
||||
active: false,
|
||||
sortable: true
|
||||
});
|
||||
this.filters.filters.push({
|
||||
field: `${collection === 'materials' ? 'material.properties' : collection + '.' + item.name}.${parameterName}`,
|
||||
field: `${collection === 'material' ? 'material.properties' : collection + 's.' + item.name}.${parameterName}`,
|
||||
label: `${this.ucFirst(item.name)} ${this.ucFirst(parameter.name)}`,
|
||||
active: false,
|
||||
autocomplete: [],
|
||||
@ -181,7 +182,7 @@ export class SamplesComponent implements OnInit {
|
||||
export?: boolean,
|
||||
host?: boolean
|
||||
}) { // return url to fetch samples
|
||||
const additionalTableKeys = ['material_id', '_id']; // keys which should always be added if export = false
|
||||
const additionalTableKeys = ['material_id', '_id', 'user_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'))
|
||||
@ -205,7 +206,7 @@ export class SamplesComponent implements OnInit {
|
||||
query.push('csv=true');
|
||||
}
|
||||
if (options.export) {
|
||||
query.push('key=' + this.apiKey);
|
||||
query.push('key=' + this.d.d.userKey.key);
|
||||
}
|
||||
this.keys.forEach(key => {
|
||||
// do not load material properties for table
|
||||
@ -278,12 +279,75 @@ export class SamplesComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: add measurements when ressource service is done
|
||||
sampleDetails(id: string, modal: TemplateRef<any>) {
|
||||
this.sampleDetailsSample = null;
|
||||
this.api.get<SampleModel>('/sample/' + id, data => {
|
||||
this.sampleDetailsSample = new SampleModel().deserialize(data);
|
||||
if (data.notes.custom_fields) { // convert custom_fields for more optimized display
|
||||
this.sampleDetailsSample.notes.custom_fields_entries = Object.entries(this.sampleDetailsSample.notes.custom_fields);
|
||||
}
|
||||
else {
|
||||
this.sampleDetailsSample.custom_fields_entries = [];
|
||||
}
|
||||
this.sampleDetailsSample.measurement_entries = [];
|
||||
this.sampleDetailsSample.measurements.forEach(measurement => { // convert measurements for more optimized display without dpt
|
||||
const name = this.d.id.measurementTemplates[measurement.measurement_template].name;
|
||||
this.sampleDetailsSample.measurement_entries.push(...Object.entries(measurement.values).filter(e => e[0] !== 'dpt')
|
||||
.map(e => ({name: this.ucFirst(name) + ' ' + this.ucFirst(e[0]), value: e[1]})));
|
||||
});
|
||||
new Promise(resolve => {
|
||||
if (data.notes.sample_references.length) { // load referenced samples if available
|
||||
let loadingCounter = data.notes.sample_references.length;
|
||||
this.sampleDetailsSample.notes.sample_references.forEach(reference => {
|
||||
this.api.get<SampleModel>('/sample/' + reference.sample_id, rData => {
|
||||
reference.number = rData.number;
|
||||
loadingCounter --;
|
||||
if (!loadingCounter) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
resolve();
|
||||
}
|
||||
}).then(() => {
|
||||
this.modalService.open(modal).then(() => {});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
validate() {
|
||||
if (this.validation) {
|
||||
this.samples.forEach(sample => {
|
||||
if (sample.validate) {
|
||||
this.api.put('/sample/validate/' + sample._id);
|
||||
}
|
||||
});
|
||||
this.validation = false;
|
||||
}
|
||||
else {
|
||||
this.validation = true;
|
||||
}
|
||||
}
|
||||
|
||||
selectAll(event) {
|
||||
this.samples.forEach(sample => {
|
||||
sample.validate = event.target.checked;
|
||||
});
|
||||
}
|
||||
|
||||
preventDefault(event, key = 'all') {
|
||||
if (key === 'all' || event.key === key) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
stopPropagation(event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
clipboard() {
|
||||
this.linkarea.nativeElement.select();
|
||||
this.linkarea.nativeElement.setSelectionRange(0, 99999);
|
||||
|
Reference in New Issue
Block a user