Merge pull request #42 in ~VLE2FE/definma-ui from f/dashboard to master

* commit 'bfe3788c377f25e239416b6f41057517c59cdf68':
  Apply sample page fix
This commit is contained in:
Kai S. K. Engelbart 2020-12-10 13:17:56 +01:00
commit b54e167ee7
3 changed files with 214 additions and 74 deletions

View File

@ -1,3 +1,23 @@
<app-login *ngIf="!login.isLoggedIn"></app-login> <div *ngIf="!login.isLoggedIn">
<app-login></app-login>
<img src="/assets/imgs/key-visual.png" alt="" class="key-visual">
</div>
<img src="/assets/imgs/key-visual.png" alt="" class="key-visual">
<div *ngIf="login.isLoggedIn">
<rb-form-multi-select name="groupSelect" idField="id" [items]="keys" [(ngModel)]="isActiveKey" label="Groups"
class="selection" (ngModelChange)="updateGroups($event)">
<span *rbFormMultiSelectOption="let key">{{key.id}}</span>
</rb-form-multi-select>
<!--
<rb-icon-button icon="forward-right" mode="primary" (click)="updateGroups()">
Apply groups
</rb-icon-button>
-->
<div id="divChart">
<canvas id="myChart">
</canvas>
</div>
</div>

View File

@ -1,5 +1,15 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import {LoginService} from '../services/login.service'; import {LoginService} from '../services/login.service';
import { ApiService } from '../services/api.service';
import {Chart} from 'chart.js';
interface KeyInterface {
id: string;
count: number;
active: boolean;
}
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
@ -8,11 +18,121 @@ import {LoginService} from '../services/login.service';
}) })
export class HomeComponent implements OnInit { export class HomeComponent implements OnInit {
keys: KeyInterface[] = [];
isActiveKey: { [key: string]: boolean } = {}; // object to check if key is currently active
myChart: Chart;
constructor( constructor(
public login: LoginService public login: LoginService,
public api: ApiService
) { } ) { }
ngOnInit() { ngOnInit() {
// fetch all available groups
this.fetchData('/material/groups', data => this.createGroup(data));
//this.initChart();
console.log(this.login.username);
} }
// api access with callback
async fetchData(URL: string, processor: any) {
this.api.get(URL, (sData, err, headers) => {
processor(sData);
});
}
// Fill interface with data
createGroup(data: any) {
let temp: KeyInterface[] = [];
for (var i = 0; i < data.length; i++) {
temp.push({ id: data[i], count: 0, active: false });
}
this.keys = temp; // invoke update in rb-multiselect
this.calcFieldSelectKeys();
// fetch all samples populated with according group
this.fetchData('/samples?status%5B%5D=validated&status=new&page-size=10&sort=_id-asc&fields%5B%5D=material.group', data => this.countSamples(data));
}
// loop through samples and count
countSamples(data: any) {
for (var i = 0; i < data.length; i++) {
this.keys.forEach(key => {
if (key.id === data[i].material.group) {
key.count += 1;
}
});
}
this.initChart();
}
// preset select
calcFieldSelectKeys() {
this.keys.forEach(key => {
this.isActiveKey[key.id] = key.active;
});
}
// update keys based on select
updateGroups(event: any) {
this.keys.forEach(key => {
if (event.hasOwnProperty(key.id)) {
key.active = event[key.id];
}
});
this.myChart.destroy();
this.initChart();
}
// get data for graph based on active keys
async getData() {
let nameList: string[] = [];
let dataList: number[] = [];
this.keys.forEach(key => {
if (key.active) {
nameList.push(key.id);
dataList.push(key.count);
}
})
return { names: nameList, data: dataList };
}
// draw graph
async initChart() {
const data = await this.getData();
const canvas = document.getElementById('myChart') as HTMLCanvasElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const ctx = canvas.getContext('2d');
var img = new Image(width, height);
img.src = "/assets/imgs/supergraphic.svg";
img.onload = () => {
const fillPattern = ctx.createPattern(img, 'no-repeat');
this.myChart = new Chart("myChart", {
type: 'line',
data: {
labels: data.names,
datasets: [{
label: 'Number of samples per group',
data: data.data,
backgroundColor: fillPattern,
borderWidth: 2
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
}
}
} }

View File

@ -1,23 +1,22 @@
import {Component, ElementRef, isDevMode, OnInit, TemplateRef, ViewChild} from '@angular/core'; import { Component, ElementRef, isDevMode, OnInit, TemplateRef, ViewChild } from '@angular/core';
import {ApiService} from '../services/api.service'; import { ApiService } from '../services/api.service';
import {AutocompleteService} from '../services/autocomplete.service'; import { AutocompleteService } from '../services/autocomplete.service';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import pick from 'lodash/pick'; import pick from 'lodash/pick';
import omit from 'lodash/omit'; import omit from 'lodash/omit';
import {SampleModel} from '../models/sample.model'; import { SampleModel } from '../models/sample.model';
import {LoginService} from '../services/login.service'; import { LoginService } from '../services/login.service';
import {ModalService} from '@inst-iot/bosch-angular-ui-components'; import { ModalService } from '@inst-iot/bosch-angular-ui-components';
import {DataService} from '../services/data.service'; import { DataService } from '../services/data.service';
import {LocalStorageService} from 'angular-2-local-storage'; import { LocalStorageService } from 'angular-2-local-storage';
import {Router} from '@angular/router'; import { Router } from '@angular/router';
interface LoadSamplesOptions { interface LoadSamplesOptions {
toPage?: number; toPage?: number;
event?: Event; event?: Event;
firstPage?: boolean; firstPage?: boolean;
} }
interface KeyInterface { interface KeyInterface {
id: string; id: string;
label: string; label: string;
@ -43,46 +42,46 @@ export class SamplesComponent implements OnInit {
totalSamples = 0; // total number of samples totalSamples = 0; // total number of samples
csvUrl = ''; // store url separate so it only has to be generated when clicking the download button csvUrl = ''; // store url separate so it only has to be generated when clicking the download button
filters = { filters = {
status: {new: true, validated: true, deleted: false}, status: { new: true, validated: true, deleted: false },
pageSize: 25, pageSize: 25,
toPage: 0, toPage: 0,
sort: 'added-asc', sort: 'added-asc',
no: {condition: false, measurements: false}, no: { condition: false, measurements: false },
filters: [ filters: [
{field: 'number', label: 'Number', active: false, autocomplete: [], mode: 'eq', values: ['']}, { field: 'number', label: 'Number', active: false, autocomplete: [], mode: 'eq', values: [''] },
{field: 'material.name', label: 'Product name', active: false, autocomplete: [], mode: 'eq', values: ['']}, { field: 'material.name', label: 'Product name', active: false, autocomplete: [], mode: 'eq', values: [''] },
{field: 'material.supplier', label: 'Supplier', active: false, autocomplete: [], mode: 'eq', values: ['']}, { field: 'material.supplier', label: 'Supplier', active: false, autocomplete: [], mode: 'eq', values: [''] },
{field: 'material.group', label: 'Material', active: false, autocomplete: [], mode: 'eq', values: ['']}, { field: 'material.group', label: 'Material', active: false, autocomplete: [], mode: 'eq', values: [''] },
{field: 'material.glass_fiber', label: 'GF', active: false, autocomplete: [], mode: 'eq', values: ['']}, { field: 'material.glass_fiber', label: 'GF', active: false, autocomplete: [], mode: 'eq', values: [''] },
{field: 'material.carbon_fiber', label: 'CF', active: false, autocomplete: [], mode: 'eq', values: ['']}, { field: 'material.carbon_fiber', label: 'CF', active: false, autocomplete: [], mode: 'eq', values: [''] },
{field: 'material.mineral', label: 'M', active: false, autocomplete: [], mode: 'eq', values: ['']}, { field: 'material.mineral', label: 'M', active: false, autocomplete: [], mode: 'eq', values: [''] },
{field: 'type', label: 'Type', active: false, autocomplete: [], mode: 'eq', values: ['']}, { field: 'type', label: 'Type', active: false, autocomplete: [], mode: 'eq', values: [''] },
{field: 'color', label: 'Color', 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: 'batch', label: 'Batch', active: false, autocomplete: [], mode: 'eq', values: [''] },
{field: 'notes.comment', label: 'Comment', active: false, autocomplete: [], mode: 'eq', values: ['']}, { field: 'notes.comment', label: 'Comment', active: false, autocomplete: [], mode: 'eq', values: [''] },
{field: 'added', label: 'Added', active: false, autocomplete: [], mode: 'eq', values: ['']} { field: 'added', label: 'Added', active: false, autocomplete: [], mode: 'eq', values: [''] }
] ]
}; };
page = 1; // current page page = 1; // current page
pages = 1; // total number of pages pages = 1; // total number of pages
loadSamplesQueue = []; // arguments of queued up loadSamples() calls loadSamplesQueue = []; // arguments of queued up loadSamples() calls
keys: KeyInterface[] = [ keys: KeyInterface[] = [
{id: 'number', label: 'Number', active: true, sortable: true}, { id: 'number', label: 'Number', active: true, sortable: true },
{id: 'material.numbers', label: 'Material numbers', active: false, sortable: false}, { id: 'material.numbers', label: 'Material numbers', active: false, sortable: false },
{id: 'material.name', label: 'Product name', active: true, sortable: true}, { id: 'material.name', label: 'Product name', active: true, sortable: true },
{id: 'material.supplier', label: 'Supplier', active: false, sortable: true}, { id: 'material.supplier', label: 'Supplier', active: false, sortable: true },
{id: 'material.group', label: 'Material', active: true, sortable: true}, { id: 'material.group', label: 'Material', active: true, sortable: true },
{id: 'type', label: 'Type', active: true, sortable: true}, { id: 'type', label: 'Type', active: true, sortable: true },
{id: 'color', label: 'Color', active: false, sortable: true}, { id: 'color', label: 'Color', active: false, sortable: true },
{id: 'batch', label: 'Batch', active: true, sortable: true}, { id: 'batch', label: 'Batch', active: true, sortable: true },
{id: 'notes.comment', label: 'Comment', active: false, sortable: false}, { id: 'notes.comment', label: 'Comment', active: false, sortable: false },
{id: 'notes', label: 'Notes', active: false, sortable: false}, { id: 'notes', label: 'Notes', active: false, sortable: false },
{id: 'status', label: 'Status', active: false, sortable: true}, { id: 'status', label: 'Status', active: false, sortable: true },
{id: 'added', label: 'Added', active: true, sortable: true} { id: 'added', label: 'Added', active: true, sortable: true }
]; ];
isActiveKey: {[key: string]: boolean} = {}; // object to check if key is currently active isActiveKey: { [key: string]: boolean } = {}; // object to check if key is currently active
activeKeys: KeyInterface[] = []; // list of active keys activeKeys: KeyInterface[] = []; // list of active keys
activeTemplateKeys = {material: [], condition: [], measurements: []}; activeTemplateKeys = { material: [], condition: [], measurements: [] };
sampleDetailsSample: any = null; // sample for the sample details dialog sampleDetailsSample: any = null; // sample for the sample details dialog
sampleSelect = 0; // modes: 0 - no selection, 1 - sample edit selection, 2 - validation selection sampleSelect = 0; // modes: 0 - no selection, 1 - sample edit selection, 2 - validation selection
loading = 0; // number of loading instances loading = 0; // number of loading instances
@ -105,7 +104,7 @@ export class SamplesComponent implements OnInit {
const onLoad = () => { const onLoad = () => {
if ((--this.loading) <= 0) { if ((--this.loading) <= 0) {
this.loadSamples(); this.loadSamples();
} }
}; };
this.calcFieldSelectKeys(); this.calcFieldSelectKeys();
@ -164,10 +163,11 @@ export class SamplesComponent implements OnInit {
f(); f();
}); });
} }
// set toPage to null to reload first page, queues calls // set toPage to null to reload first page, queues calls
loadSamples(options: LoadSamplesOptions = {}, event = null) { loadSamples(options: LoadSamplesOptions = {}, event = null) {
if (event) { // adjust active keys if (event) { // adjust active keys
console.log(event);
this.keys.forEach(key => { this.keys.forEach(key => {
if (event.hasOwnProperty(key.id)) { if (event.hasOwnProperty(key.id)) {
key.active = event[key.id]; key.active = event[key.id];
@ -179,9 +179,9 @@ export class SamplesComponent implements OnInit {
} }
this.updateActiveKeys(); this.updateActiveKeys();
} }
if(options.firstPage){ if(options.firstPage){
this.storage.set('currentPage', 1); this.storage.set('currentPage', 1);
} }
this.loadSamplesQueue.push(options); this.loadSamplesQueue.push(options);
if (this.loadSamplesQueue.length <= 1) { // nothing queued up if (this.loadSamplesQueue.length <= 1) { // nothing queued up
this.sampleLoader(this.loadSamplesQueue[0]); this.sampleLoader(this.loadSamplesQueue[0]);
@ -190,9 +190,9 @@ export class SamplesComponent implements OnInit {
} }
private sampleLoader(options: LoadSamplesOptions) { // actual loading of the sample, do not call directly private sampleLoader(options: LoadSamplesOptions) { // actual loading of the sample, do not call directly
this.loading ++; this.loading++;
this.api.get(this.sampleUrl({paging: true, pagingOptions: options}), (sData, err, headers) => { this.api.get(this.sampleUrl({ paging: true, pagingOptions: options }), (sData, err, headers) => {
this.loading --; this.loading--;
if (err) { // remove stored options on error to avoid loop if (err) { // remove stored options on error to avoid loop
this.storage.remove('samplesPreferences'); this.storage.remove('samplesPreferences');
this.api.requestError(err); this.api.requestError(err);
@ -209,9 +209,9 @@ export class SamplesComponent implements OnInit {
} }
} }
}); });
if(this.storage.get('currentPage') !== this.page){ if(this.storage.get('currentPage') !== this.page){
this.loadPage(Number(this.storage.get('currentPage')) - this.page); this.loadPage(Number(this.storage.get('currentPage')) - this.page);
} }
} }
sampleUrl(options: { sampleUrl(options: {
@ -272,17 +272,17 @@ export class SamplesComponent implements OnInit {
.map(e => 'filters[]=' + encodeURIComponent(JSON.stringify(pick(e, ['mode', 'field', 'values'])))) .map(e => 'filters[]=' + encodeURIComponent(JSON.stringify(pick(e, ['mode', 'field', 'values']))))
); );
if (this.filters.no.condition) { if (this.filters.no.condition) {
query.push('filters[]=' + encodeURIComponent( JSON.stringify({mode: 'eq', field: 'condition', values: [{}]}))); query.push('filters[]=' + encodeURIComponent(JSON.stringify({ mode: 'eq', field: 'condition', values: [{}] })));
} }
if (this.filters.no.measurements) { if (this.filters.no.measurements) {
query.push('filters[]=' + query.push('filters[]=' +
encodeURIComponent( JSON.stringify( {mode: 'eq', field: 'measurements', values: [null]}))); encodeURIComponent(JSON.stringify({ mode: 'eq', field: 'measurements', values: [null] })));
} }
if (!options.export) { if (!options.export) {
additionalTableKeys.forEach(key => { additionalTableKeys.forEach(key => {
if (query.indexOf('fields[]=' + key) < 0) { // add key if not already added if (query.indexOf('fields[]=' + key) < 0) { // add key if not already added
query.push('fields[]=' + key); query.push('fields[]=' + key);
} }
}); });
} }
else { // export options else { // export options
@ -307,10 +307,10 @@ export class SamplesComponent implements OnInit {
loadPage(delta) { loadPage(delta) {
if (!/[0-9]+/.test(delta) || this.page + delta < 1) { // invalid delta if (!/[0-9]+/.test(delta) || this.page + delta < 1) { // invalid delta
return; return;
} }
this.page += delta; this.page += delta;
this.storage.set('currentPage', this.page); this.storage.set('currentPage', this.page);
this.loadSamples({toPage: delta}); this.loadSamples({ toPage: delta });
} }
storePreferences() { storePreferences() {
@ -327,11 +327,11 @@ export class SamplesComponent implements OnInit {
loadPreferences() { loadPreferences() {
const store: any = this.storage.get('samplesPreferences'); const store: any = this.storage.get('samplesPreferences');
if (store) { if (store) {
this.filters = {...this.filters, ...pick(store.filters, ['status', 'pageSize', 'toPage', 'sort'])}; this.filters = { ...this.filters, ...pick(store.filters, ['status', 'pageSize', 'toPage', 'sort']) };
store.filters.filters.forEach(filter => { store.filters.filters.forEach(filter => {
const filterIndex = this.filters.filters.findIndex(e => e.field === filter.field); const filterIndex = this.filters.filters.findIndex(e => e.field === filter.field);
if (filterIndex >= 0) { if (filterIndex >= 0) {
this.filters.filters[filterIndex] = {...this.filters.filters[filterIndex], ...filter}; this.filters.filters[filterIndex] = { ...this.filters.filters[filterIndex], ...filter };
} }
}); });
store.keys.forEach(key => { store.keys.forEach(key => {
@ -357,7 +357,7 @@ export class SamplesComponent implements OnInit {
setSort(string) { setSort(string) {
this.filters.sort = string; this.filters.sort = string;
this.loadSamples({firstPage: true}); this.loadSamples({ firstPage: true });
} }
updateActiveKeys() { // array with all activeKeys updateActiveKeys() { // array with all activeKeys
@ -399,12 +399,12 @@ export class SamplesComponent implements OnInit {
if (Object.keys(data.condition).length) { // convert condition if (Object.keys(data.condition).length) { // convert condition
this.sampleDetailsSample.condition_entries = this.sampleDetailsSample.condition_entries =
Object.entries(omit(this.sampleDetailsSample.condition, ['condition_template'])) Object.entries(omit(this.sampleDetailsSample.condition, ['condition_template']))
.map(e => { .map(e => {
e[0] = `${this.ucFirst( e[0] = `${this.ucFirst(
this.d.id.conditionTemplates[this.sampleDetailsSample.condition.condition_template].name this.d.id.conditionTemplates[this.sampleDetailsSample.condition.condition_template].name
)} ${e[0]}`; )} ${e[0]}`;
return e; return e;
}); });
} }
else { else {
this.sampleDetailsSample.condition_entries = []; this.sampleDetailsSample.condition_entries = [];
@ -415,7 +415,7 @@ export class SamplesComponent implements OnInit {
const name = this.d.id.measurementTemplates[measurement.measurement_template].name; const name = this.d.id.measurementTemplates[measurement.measurement_template].name;
this.sampleDetailsSample.measurement_entries this.sampleDetailsSample.measurement_entries
.push(...Object.entries(measurement.values).filter(e => e[0] !== 'dpt') .push(...Object.entries(measurement.values).filter(e => e[0] !== 'dpt')
.map(e => ({name: this.ucFirst(name) + ' ' + e[0], value: e[1]}))); .map(e => ({ name: this.ucFirst(name) + ' ' + e[0], value: e[1] })));
}); });
new Promise(resolve => { new Promise(resolve => {
if (data.notes.sample_references.length) { // load referenced samples if available if (data.notes.sample_references.length) { // load referenced samples if available
@ -423,7 +423,7 @@ export class SamplesComponent implements OnInit {
this.sampleDetailsSample.notes.sample_references.forEach(reference => { this.sampleDetailsSample.notes.sample_references.forEach(reference => {
this.api.get<SampleModel>('/sample/' + reference.sample_id, rData => { this.api.get<SampleModel>('/sample/' + reference.sample_id, rData => {
reference.number = rData.number; reference.number = rData.number;
loadingCounter --; loadingCounter--;
if (!loadingCounter) { if (!loadingCounter) {
resolve(); resolve();
} }
@ -434,7 +434,7 @@ export class SamplesComponent implements OnInit {
resolve(); resolve();
} }
}).then(() => { }).then(() => {
this.modalService.open(modal).then(() => {}); this.modalService.open(modal).then(() => { });
}); });
}); });
} }