Merge pull request #42 in ~VLE2FE/definma-ui from f/dashboard to master
* commit 'bfe3788c377f25e239416b6f41057517c59cdf68': Apply sample page fix
This commit is contained in:
		| @@ -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> | ||||
|   | ||||
| @@ -1,5 +1,15 @@ | ||||
| import { Component, OnInit } from '@angular/core'; | ||||
| 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({ | ||||
|   selector: 'app-home', | ||||
| @@ -8,11 +18,121 @@ import {LoginService} from '../services/login.service'; | ||||
| }) | ||||
| export class HomeComponent implements OnInit { | ||||
|  | ||||
|   keys: KeyInterface[] = []; | ||||
|   isActiveKey: { [key: string]: boolean } = {};  // object to check if key is currently active | ||||
|   myChart: Chart; | ||||
|  | ||||
|   constructor( | ||||
|     public login: LoginService | ||||
|     public login: LoginService, | ||||
|     public api: ApiService | ||||
|   ) { } | ||||
|  | ||||
|   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 | ||||
|               } | ||||
|             }] | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,23 +1,22 @@ | ||||
| import {Component, ElementRef, isDevMode, OnInit, TemplateRef, ViewChild} from '@angular/core'; | ||||
| import {ApiService} from '../services/api.service'; | ||||
| import {AutocompleteService} from '../services/autocomplete.service'; | ||||
| 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 omit from 'lodash/omit'; | ||||
| 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'; | ||||
| import {LocalStorageService} from 'angular-2-local-storage'; | ||||
| import {Router} from '@angular/router'; | ||||
|  | ||||
|  | ||||
| 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'; | ||||
| import { LocalStorageService } from 'angular-2-local-storage'; | ||||
| import { Router } from '@angular/router'; | ||||
|  | ||||
| interface LoadSamplesOptions { | ||||
|   toPage?: number; | ||||
|   event?: Event; | ||||
|   firstPage?: boolean; | ||||
| } | ||||
|  | ||||
| interface KeyInterface { | ||||
|   id: string; | ||||
|   label: string; | ||||
| @@ -43,46 +42,46 @@ export class SamplesComponent implements OnInit { | ||||
|   totalSamples = 0;  // total number of samples | ||||
|   csvUrl = '';  // store url separate so it only has to be generated when clicking the download button | ||||
|   filters = { | ||||
|     status: {new: true, validated: true, deleted: false}, | ||||
|     status: { new: true, validated: true, deleted: false }, | ||||
|     pageSize: 25, | ||||
|     toPage: 0, | ||||
|     sort: 'added-asc', | ||||
|     no: {condition: false, measurements: false}, | ||||
|     no: { condition: false, measurements: false }, | ||||
|     filters: [ | ||||
|       {field: 'number', label: 'Number', 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.group', label: 'Material', 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.mineral', label: 'M', 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: 'batch', label: 'Batch', 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: 'number', label: 'Number', 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.group', label: 'Material', 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.mineral', label: 'M', 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: 'batch', label: 'Batch', 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: [''] } | ||||
|     ] | ||||
|   }; | ||||
|   page = 1;   // current page | ||||
|   pages = 1;  // total number of pages | ||||
|   loadSamplesQueue = [];  // arguments of queued up loadSamples() calls | ||||
|   keys: KeyInterface[] = [ | ||||
|     {id: 'number', label: 'Number', active: true, sortable: true}, | ||||
|     {id: 'material.numbers', label: 'Material numbers', active: false, sortable: false}, | ||||
|     {id: 'material.name', label: 'Product name', active: true, sortable: true}, | ||||
|     {id: 'material.supplier', label: 'Supplier', active: false, sortable: true}, | ||||
|     {id: 'material.group', label: 'Material', active: true, sortable: true}, | ||||
|     {id: 'type', label: 'Type', active: true, sortable: true}, | ||||
|     {id: 'color', label: 'Color', active: false, sortable: true}, | ||||
|     {id: 'batch', label: 'Batch', active: true, sortable: true}, | ||||
|     {id: 'notes.comment', label: 'Comment', active: false, sortable: false}, | ||||
|     {id: 'notes', label: 'Notes', active: false, sortable: false}, | ||||
|     {id: 'status', label: 'Status', active: false, sortable: true}, | ||||
|     {id: 'added', label: 'Added', active: true, sortable: true} | ||||
|     { id: 'number', label: 'Number', active: true, sortable: true }, | ||||
|     { id: 'material.numbers', label: 'Material numbers', active: false, sortable: false }, | ||||
|     { id: 'material.name', label: 'Product name', active: true, sortable: true }, | ||||
|     { id: 'material.supplier', label: 'Supplier', active: false, sortable: true }, | ||||
|     { id: 'material.group', label: 'Material', active: true, sortable: true }, | ||||
|     { id: 'type', label: 'Type', active: true, sortable: true }, | ||||
|     { id: 'color', label: 'Color', active: false, sortable: true }, | ||||
|     { id: 'batch', label: 'Batch', active: true, sortable: true }, | ||||
|     { id: 'notes.comment', label: 'Comment', active: false, sortable: false }, | ||||
|     { id: 'notes', label: 'Notes', active: false, sortable: false }, | ||||
|     { id: 'status', label: 'Status', active: false, 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 | ||||
|   activeTemplateKeys = {material: [], condition: [], measurements: []}; | ||||
|   activeTemplateKeys = { material: [], condition: [], measurements: [] }; | ||||
|   sampleDetailsSample: any = null;  // sample for the sample details dialog | ||||
|   sampleSelect = 0;  // modes: 0 - no selection, 1 - sample edit selection, 2 - validation selection | ||||
|   loading = 0;  // number of loading instances | ||||
| @@ -168,6 +167,7 @@ export class SamplesComponent implements OnInit { | ||||
|   // set toPage to null to reload first page, queues calls | ||||
|   loadSamples(options: LoadSamplesOptions = {}, event = null) { | ||||
|     if (event) {  // adjust active keys | ||||
|       console.log(event); | ||||
|       this.keys.forEach(key => { | ||||
|         if (event.hasOwnProperty(key.id)) { | ||||
|           key.active = event[key.id]; | ||||
| @@ -190,9 +190,9 @@ export class SamplesComponent implements OnInit { | ||||
|   } | ||||
|  | ||||
|   private sampleLoader(options: LoadSamplesOptions) {  // actual loading of the sample, do not call directly | ||||
|     this.loading ++; | ||||
|     this.api.get(this.sampleUrl({paging: true, pagingOptions: options}), (sData, err, headers) => { | ||||
|       this.loading --; | ||||
|     this.loading++; | ||||
|     this.api.get(this.sampleUrl({ paging: true, pagingOptions: options }), (sData, err, headers) => { | ||||
|       this.loading--; | ||||
|       if (err) {  // remove stored options on error to avoid loop | ||||
|         this.storage.remove('samplesPreferences'); | ||||
|         this.api.requestError(err); | ||||
| @@ -272,11 +272,11 @@ export class SamplesComponent implements OnInit { | ||||
|       .map(e => 'filters[]=' + encodeURIComponent(JSON.stringify(pick(e, ['mode', 'field', 'values'])))) | ||||
|     ); | ||||
|     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) { | ||||
|       query.push('filters[]=' + | ||||
|         encodeURIComponent( JSON.stringify( {mode: 'eq', field: 'measurements', values: [null]}))); | ||||
|         encodeURIComponent(JSON.stringify({ mode: 'eq', field: 'measurements', values: [null] }))); | ||||
|     } | ||||
|     if (!options.export) { | ||||
|       additionalTableKeys.forEach(key => { | ||||
| @@ -310,7 +310,7 @@ export class SamplesComponent implements OnInit { | ||||
|     } | ||||
|     this.page += delta; | ||||
|     this.storage.set('currentPage', this.page); | ||||
|     this.loadSamples({toPage: delta}); | ||||
|     this.loadSamples({ toPage: delta }); | ||||
|   } | ||||
|  | ||||
|   storePreferences() { | ||||
| @@ -327,11 +327,11 @@ export class SamplesComponent implements OnInit { | ||||
|   loadPreferences() { | ||||
|     const store: any = this.storage.get('samplesPreferences'); | ||||
|     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 => { | ||||
|         const filterIndex = this.filters.filters.findIndex(e => e.field === filter.field); | ||||
|         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 => { | ||||
| @@ -357,7 +357,7 @@ export class SamplesComponent implements OnInit { | ||||
|  | ||||
|   setSort(string) { | ||||
|     this.filters.sort = string; | ||||
|     this.loadSamples({firstPage: true}); | ||||
|     this.loadSamples({ firstPage: true }); | ||||
|   } | ||||
|  | ||||
|   updateActiveKeys() {  // array with all activeKeys | ||||
| @@ -415,7 +415,7 @@ export class SamplesComponent implements OnInit { | ||||
|         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) + ' ' + e[0], value: e[1]}))); | ||||
|             .map(e => ({ name: this.ucFirst(name) + ' ' + e[0], value: e[1] }))); | ||||
|       }); | ||||
|       new Promise(resolve => { | ||||
|         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.api.get<SampleModel>('/sample/' + reference.sample_id, rData => { | ||||
|               reference.number = rData.number; | ||||
|               loadingCounter --; | ||||
|               loadingCounter--; | ||||
|               if (!loadingCounter) { | ||||
|                 resolve(); | ||||
|               } | ||||
| @@ -434,7 +434,7 @@ export class SamplesComponent implements OnInit { | ||||
|           resolve(); | ||||
|         } | ||||
|       }).then(() => { | ||||
|         this.modalService.open(modal).then(() => {}); | ||||
|         this.modalService.open(modal).then(() => { }); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user