|
|
@@ -1,4 +1,4 @@
|
|
|
-import {Component, Input, OnChanges, SimpleChanges} from '@angular/core';
|
|
|
+import {Component, HostListener, Input, OnChanges, SimpleChanges} from '@angular/core';
|
|
|
import {User} from '../../../auth/models/user';
|
|
|
import {Drivers} from '../../../shared/api/endpoints/models/drivers';
|
|
|
import {GeneralInfo} from '../../../shared/api/endpoints/models/general-info';
|
|
|
@@ -12,7 +12,13 @@ import {ManagementService} from '../../../shared/api/endpoints/services/manageme
|
|
|
import {ToastService} from '../../../shared/services/toast.service';
|
|
|
import {AuthService} from '../../../auth/services/auth.service';
|
|
|
import {Config} from '../../../shared/api/endpoints/models/config';
|
|
|
-import {HttpClient} from '@angular/common/http';
|
|
|
+import {HttpClient, HttpResponse} from '@angular/common/http';
|
|
|
+import {GraphLoader} from '../../../shared/graph-loading/graphloader';
|
|
|
+import {ObservationService} from '../../../shared/api/endpoints/services/observation.service';
|
|
|
+import {catchError, map} from 'rxjs/operators';
|
|
|
+import {forkJoin, of} from 'rxjs';
|
|
|
+import * as moment from 'moment-timezone';
|
|
|
+import type {View} from 'vega';
|
|
|
|
|
|
@Component({
|
|
|
selector: 'app-custom-dashboard',
|
|
|
@@ -25,8 +31,11 @@ export class CustomDashboardComponent implements OnChanges {
|
|
|
Array<{ drivers?: Drivers; generalInfo?: GeneralInfo; holder?: any; lastpos?: Lastpos; sensors?: Array<Sensor>; unit?: Unit }>;
|
|
|
|
|
|
cstmUnits: Array<{ drivers?: Drivers; generalInfo?: GeneralInfo; holder?: any; lastpos?: Lastpos; sensors?: Array<Sensor>; unit?: Unit }>;
|
|
|
+ config : Config;
|
|
|
+ graphViews: Record<number, View> = {};
|
|
|
|
|
|
constructor(
|
|
|
+ private observationService: ObservationService,
|
|
|
private dataService: DataService,
|
|
|
private sensorService: SensorsService,
|
|
|
private confirmationService: ConfirmationService,
|
|
|
@@ -41,55 +50,151 @@ export class CustomDashboardComponent implements OnChanges {
|
|
|
ngOnChanges(changes: SimpleChanges): void {
|
|
|
if (changes.units && changes.units.currentValue) {
|
|
|
console.log('New units data arrived in child.');
|
|
|
- // Once data is here, process
|
|
|
this.createGraphs();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Get all units and theirs sensors from backend
|
|
|
- */
|
|
|
- createGraphs() {
|
|
|
- this.http.get<Config>('assets/example.json').subscribe(config => {
|
|
|
+ async loadAllGraphs(range: Date[]) {
|
|
|
+ for (const graph of this.config.graphs) {
|
|
|
+ await this.loadSingleGraph(graph, range);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // 1. Store and sort the raw master data
|
|
|
- this.units.forEach(u => u.sensors?.sort((a, b) => (a.sensorId ?? 0) - (b.sensorId ?? 0)));
|
|
|
+ private async loadSingleGraph(graph: any, range: Date[]) {
|
|
|
+ const containerId = `#graph-${graph.graphId}`;
|
|
|
+ const element = document.querySelector(containerId) as HTMLElement;
|
|
|
|
|
|
- // 2. Extract configuration selections
|
|
|
- const {units: unitSelections, globalSensors} = config.preferences.selections;
|
|
|
+ if (!element) return;
|
|
|
|
|
|
- // 3. Perform the filtering logic
|
|
|
- this.cstmUnits = this.units.map(masterUnit => {
|
|
|
+ // 2. Setup ResizeObserver to wait for a valid width
|
|
|
+ const resizeObserver = new ResizeObserver((entries) => {
|
|
|
+ for (const entry of entries) {
|
|
|
+ const width = entry.contentRect.width;
|
|
|
|
|
|
- // Find specific config for this unit
|
|
|
- const selection = unitSelections.find(u => u.unitId === masterUnit.unit?.unitId);
|
|
|
+ // Only proceed if width is greater than 0 (Tab is now visible)
|
|
|
+ if (width > 0) {
|
|
|
+ console.log(`Tab activated. Valid width detected for ${containerId}: ${width}px`);
|
|
|
|
|
|
- const filteredSensors = (masterUnit.sensors || []).filter(sensor => {
|
|
|
- const sId = sensor.sensorId;
|
|
|
- if (sId === undefined) return false;
|
|
|
+ // Set the size
|
|
|
+ GraphLoader.setSize(width - 50, 300);
|
|
|
|
|
|
- const isGlobal = globalSensors.includes(sId);
|
|
|
- let isLocallySelected = false;
|
|
|
+ // Fetch data and draw
|
|
|
+ this.fetchDataAndDraw(graph, range, containerId);
|
|
|
|
|
|
- if (selection) {
|
|
|
- isLocallySelected = selection.sensors === 'ALL' ||
|
|
|
- (Array.isArray(selection.sensors) && selection.sensors.includes(sId));
|
|
|
- }
|
|
|
+ // Stop observing once we have successfully rendered
|
|
|
+ resizeObserver.disconnect();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
- return isGlobal || isLocallySelected;
|
|
|
- });
|
|
|
+ resizeObserver.observe(element);
|
|
|
+ }
|
|
|
|
|
|
- // Return the unit structure with the new filtered sensor array
|
|
|
- return {
|
|
|
- ...masterUnit,
|
|
|
- sensors: filteredSensors
|
|
|
- };
|
|
|
- })
|
|
|
- // 4. Final step: Only keep units that have at least one valid sensor
|
|
|
- .filter(u => u.sensors.length > 0);
|
|
|
+ /**
|
|
|
+ * Helper method to handle the actual data request and graph rendering
|
|
|
+ */
|
|
|
+ private fetchDataAndDraw(graph: any, range: Date[], containerId: string) {
|
|
|
+ const groupId = graph.graphId;
|
|
|
+ const sensorIds = [];
|
|
|
+ const sensors = [];
|
|
|
+ const requests = []; // Array to hold all observable requests
|
|
|
+
|
|
|
+ this.graphViews[groupId] = null;
|
|
|
+ console.log(groupId);
|
|
|
+
|
|
|
+ for (const source of graph.sources) {
|
|
|
+ // 1. Find the sensor metadata
|
|
|
+ const unit = this.units.find(u => u.unit.unitId === graph.sources[0].unitId);
|
|
|
+ const sensor = unit?.sensors?.find(s => s.sensorId === graph.sources[0].sensorIds[0]);
|
|
|
+
|
|
|
+ if (!sensor) {
|
|
|
+ console.error(`Metadata for sensor ${graph.sources[0].sensorIds[0]} not found!`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const unitId = unit.unit.unitId;
|
|
|
+ const sensorId = sensor.sensorId;
|
|
|
+
|
|
|
+ sensorIds.push(sensorId);
|
|
|
+ sensors.push(sensor);
|
|
|
+
|
|
|
+ // 4. Create the request for this specific source/sensor
|
|
|
+ const request = this.observationService.getObservation$Response({
|
|
|
+ unit_id: unitId,
|
|
|
+ sensor_id: sensorId,
|
|
|
+ from: moment(range[0]).format('YYYY-MM-DD HH:mm:ss'),
|
|
|
+ to: moment(range[1]).format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ }).pipe(
|
|
|
+ map(response => {
|
|
|
+ if (response.status === 200) return response.body;
|
|
|
+ if (response.status === 204) this.toastService.showWarningNoData();
|
|
|
+ return null;
|
|
|
+ }),
|
|
|
+ catchError(err => {
|
|
|
+ this.toastService.showError(err.error?.message || 'Error loading data');
|
|
|
+ return of(null);
|
|
|
+ })
|
|
|
+ );
|
|
|
+
|
|
|
+ requests.push(request);
|
|
|
+ }
|
|
|
|
|
|
- console.log('Filtered cstmUnits:', this.cstmUnits);
|
|
|
+ // Execute all requests and render
|
|
|
+ if (requests.length > 0) {
|
|
|
+ forkJoin(requests).subscribe(async (results: any[][]) => {
|
|
|
+ const allObservations = results.map(res => res ? res : []);
|
|
|
+
|
|
|
+ // TODO here i want to load a custom graph with multiple datasets
|
|
|
+ // - each dataset could have different type (line or bar)
|
|
|
+ // - sensorIds, sensors - information about sensors
|
|
|
+ // - allObservations - the recieved data that will be displayed
|
|
|
+
|
|
|
+ if (allObservations.length > 0) {
|
|
|
+ this.graphViews[groupId] = await GraphLoader.getGraph(sensorIds, allObservations, sensors, containerId, false);
|
|
|
+ } else {
|
|
|
+ this.graphViews[groupId] = await GraphLoader.getGraph(null, null, null, containerId, null);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get all units and theirs sensors from backend
|
|
|
+ */
|
|
|
+ createGraphs() {
|
|
|
+ this.http.get<Config>('assets/example.json').subscribe(config => {
|
|
|
+ console.log(config);
|
|
|
+ this.config = config;
|
|
|
+
|
|
|
+ // Once data is here, process
|
|
|
+ setTimeout(() => {
|
|
|
+ const range = [moment().subtract(1, 'day').toDate(), moment().toDate()];
|
|
|
+ this.loadAllGraphs(range);
|
|
|
+ }, 0);
|
|
|
}, err => this.toastService.showError(err.error.message));
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Fires on every resize event
|
|
|
+ */
|
|
|
+ @HostListener('window:resize', ['$event'])
|
|
|
+ public onWindowResize(event: UIEvent): void {
|
|
|
+ this.onResize();
|
|
|
+ }
|
|
|
+
|
|
|
+ private onResize() {
|
|
|
+ const itemCount = Object.keys(this.graphViews).length;
|
|
|
+ // console.log('Number of entries:', itemCount);
|
|
|
+
|
|
|
+ Object.entries(this.graphViews).forEach(([key, view]) => {
|
|
|
+ if (view) {
|
|
|
+ console.log('#graph-' + key);
|
|
|
+ const containerId = `#graph-${key}`;
|
|
|
+ const box = document.querySelector(containerId) as HTMLElement;
|
|
|
+ const newWidth = box.getBoundingClientRect().width - 50;
|
|
|
+ GraphLoader.ResizeGraph(view, newWidth);
|
|
|
+ }
|
|
|
+ })
|
|
|
}
|
|
|
+
|
|
|
+}
|