@iotready/nextjs-components-library 1.0.0-preview4 → 1.0.0-preview41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/assets/translations/en.json +92 -0
  2. package/assets/translations/index.d.ts +95 -0
  3. package/assets/translations/index.js +4 -0
  4. package/assets/translations/it.json +92 -0
  5. package/assets/translations/scripts/export.js +74 -0
  6. package/assets/translations/scripts/import.js +66 -0
  7. package/components/accounts/AccountMenu.d.ts +2 -1
  8. package/components/accounts/AccountMenu.js +2 -2
  9. package/components/accounts/AccountProfile.d.ts +2 -1
  10. package/components/accounts/AccountProfile.js +15 -15
  11. package/components/charts/TrendChart.d.ts +28 -6
  12. package/components/charts/TrendChart.js +555 -149
  13. package/components/groups/GroupUpdate.d.ts +9 -10
  14. package/components/groups/GroupUpdate.js +21 -38
  15. package/components/groups/GroupsDevices.d.ts +22 -17
  16. package/components/groups/GroupsDevices.js +165 -110
  17. package/components/groups/Map.d.ts +5 -9
  18. package/components/groups/Map.js +2 -2
  19. package/components/settings/DynamicMenu.d.ts +1 -0
  20. package/components/settings/DynamicMenu.js +2 -1
  21. package/components/users/UserUpdate.d.ts +2 -1
  22. package/components/users/UserUpdate.js +2 -2
  23. package/components/users/UsersDataGrid.d.ts +11 -3
  24. package/components/users/UsersDataGrid.js +49 -32
  25. package/package.json +8 -4
  26. package/server-actions/annotations.d.ts +4 -0
  27. package/server-actions/annotations.js +12 -0
  28. package/server-actions/groups.d.ts +16 -16
  29. package/server-actions/groups.js +157 -72
  30. package/server-actions/index.d.ts +1 -0
  31. package/server-actions/index.js +1 -0
  32. package/server-actions/influx.d.ts +17 -13
  33. package/server-actions/influx.js +207 -98
  34. package/server-actions/trackle.d.ts +10 -4
  35. package/server-actions/trackle.js +59 -38
  36. package/server-actions/types.d.ts +16 -0
  37. package/server-actions/types.js +6 -0
  38. package/types/device.d.ts +19 -0
  39. package/types/device.js +1 -0
  40. package/types/index.d.ts +1 -0
  41. package/types/index.js +1 -0
  42. package/types/user.d.ts +2 -0
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useState, useEffect, useRef } from 'react';
4
4
  import { Line } from 'react-chartjs-2';
5
- import { Chart as ChartJS, Colors, Title, LinearScale, Legend, Tooltip, TimeScale, PointElement, LineElement } from 'chart.js';
5
+ import { Chart as ChartJS, Colors, Title, LinearScale, Legend, Tooltip, TimeScale, PointElement, LineElement, Interaction } from 'chart.js';
6
6
  import annotationPlugin from 'chartjs-plugin-annotation';
7
7
  import 'chartjs-adapter-moment';
8
8
  import { ToggleButtonGroup, ToggleButton, Box, Button, Typography } from "@mui/material";
@@ -20,24 +20,173 @@ import { AdapterMoment } from '@mui/x-date-pickers/AdapterMoment';
20
20
  import { ThemeProvider } from '@mui/material/styles';
21
21
  import TimelineIcon from '@mui/icons-material/Timeline';
22
22
  import MuiTooltip from '@mui/material/Tooltip';
23
+ import EditNoteIcon from '@mui/icons-material/EditNote';
24
+ // import AspectRatioIcon from '@mui/icons-material/AspectRatio';
25
+ import { FilterTagMode } from '../../server-actions/types';
26
+ import { getRelativePosition } from 'chart.js/helpers';
27
+ import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
28
+ import KeyboardArrowLeftIcon from '@mui/icons-material/KeyboardArrowLeft';
29
+ function findIndexForX(arr, t) {
30
+ if (!arr || arr.length === 0)
31
+ return -1;
32
+ if (t <= arr[0].x)
33
+ return 0;
34
+ let lo = 0;
35
+ let hi = arr.length - 1;
36
+ let res = -1;
37
+ while (lo <= hi) {
38
+ const mid = Math.floor((lo + hi) / 2);
39
+ const midX = arr[mid]?.x;
40
+ if (midX === undefined)
41
+ return -1;
42
+ if (midX === t)
43
+ return mid;
44
+ if (midX < t) {
45
+ res = mid;
46
+ lo = mid + 1;
47
+ }
48
+ else
49
+ hi = mid - 1;
50
+ }
51
+ return res;
52
+ }
53
+ // @ts-ignore
54
+ Tooltip.positioners.myCustomPositioner = function (elements, eventPosition) {
55
+ const chart = this.chart;
56
+ const activePos = chart.__activePos;
57
+ if (!activePos || !activePos.closestPoint) {
58
+ return { x: eventPosition.x, y: eventPosition.y };
59
+ }
60
+ const closest = activePos.closestPoint.element;
61
+ const pixelX = activePos.closestPoint.pixelX;
62
+ const pixelY = closest?.y ?? eventPosition.y;
63
+ return { x: pixelX, y: pixelY };
64
+ };
65
+ // Definisci la modalità solo se Interaction è disponibile
66
+ if (Interaction && Interaction.modes) {
67
+ // @ts-ignore
68
+ Interaction.modes.myCustomMode = function (chart, e, options, useFinalPosition) {
69
+ const pos = getRelativePosition(e, chart);
70
+ const xScale = chart.scales?.x;
71
+ const hoveredX = xScale && typeof xScale.getValueForPixel === "function"
72
+ ? xScale.getValueForPixel(pos.x)
73
+ : null;
74
+ const items = [];
75
+ let closestPoint = null;
76
+ let closestDistance = Infinity;
77
+ for (let datasetIndex = 0; datasetIndex < chart.data.datasets.length; datasetIndex++) {
78
+ const meta = chart.getDatasetMeta(datasetIndex);
79
+ if (!meta || !meta.data || meta.data.length === 0)
80
+ continue;
81
+ let found = null;
82
+ let pointPixelX = null;
83
+ for (let i = 0; i < meta.data.length; i++) {
84
+ const el = meta.data[i];
85
+ if (el && typeof el.inXRange === "function" && el.inXRange(pos.x, useFinalPosition)) {
86
+ found = { element: el, datasetIndex, index: i };
87
+ pointPixelX = el.x;
88
+ break;
89
+ }
90
+ }
91
+ if (!found && hoveredX != null) {
92
+ const dataArr = chart.data.datasets[datasetIndex]?.data || [];
93
+ const idx = findIndexForX(dataArr, hoveredX);
94
+ if (idx >= 0 && meta.data[idx]) {
95
+ found = { element: meta.data[idx], datasetIndex, index: idx };
96
+ const valX = dataArr[idx].x;
97
+ pointPixelX = xScale.getPixelForValue(valX);
98
+ }
99
+ }
100
+ if (found && pointPixelX != null) {
101
+ items.push(found);
102
+ const xMin = xScale?.left ?? 0;
103
+ const xMax = xScale?.right ?? chart.width;
104
+ console.log({ pointPixelX, xMin, xMax });
105
+ if (pointPixelX < xMin || pointPixelX > xMax) {
106
+ continue; // ignora questo punto
107
+ }
108
+ // Aggiorna il closestPoint globale per la barra verticale
109
+ const distance = Math.abs(pointPixelX - pos.x);
110
+ if (distance < closestDistance) {
111
+ closestDistance = distance;
112
+ closestPoint = { ...found, pixelX: pointPixelX };
113
+ }
114
+ }
115
+ }
116
+ chart.__activePos = hoveredX != null
117
+ ? {
118
+ value: hoveredX,
119
+ px: closestPoint ? closestPoint.pixelX : pos.x,
120
+ hasPoint: !!closestPoint
121
+ }
122
+ : null;
123
+ return items;
124
+ };
125
+ }
126
+ function getValueAtX(datasetData, t) {
127
+ if (!datasetData || datasetData.length === 0)
128
+ return null;
129
+ // se t è prima del primo punto -> primo valore
130
+ if (t <= datasetData[0].x)
131
+ return datasetData[0].y;
132
+ // binary search per ultimo index con x <= t
133
+ let lo = 0;
134
+ let hi = datasetData.length - 1;
135
+ let res = -1;
136
+ while (lo <= hi) {
137
+ const mid = (lo + hi) >> 1;
138
+ const midX = datasetData[mid].x;
139
+ if (midX <= t) {
140
+ res = mid;
141
+ lo = mid + 1;
142
+ }
143
+ else
144
+ hi = mid - 1;
145
+ }
146
+ return res >= 0 ? datasetData[res].y : null;
147
+ }
23
148
  const lineOptions = {
24
149
  parsing: false,
25
150
  normalized: true,
26
- spanGaps: false, // enable for all datasets
151
+ spanGaps: true, // enable for all datasets
27
152
  // showLine: false, // disable for all datasets
28
153
  animation: false,
29
154
  responsive: true,
30
155
  maintainAspectRatio: false,
31
156
  interaction: {
32
157
  intersect: false,
33
- mode: 'nearest',
158
+ mode: 'myCustomMode',
34
159
  axis: 'x'
35
160
  },
36
161
  plugins: {
37
162
  tooltip: {
163
+ position: 'myCustomPositioner',
38
164
  callbacks: {
39
165
  label: (context) => {
40
- return `${context.dataset.label}: ${context.parsed.y.toFixed(2)} ${context.dataset.unit}`;
166
+ const ds = context.dataset || {};
167
+ const parsed = context.parsed || {};
168
+ const unit = ds.unit ?? '';
169
+ const chart = context.chart;
170
+ // 1) se parsed.y è disponibile usalo (gestisci stepped senza toFixed)
171
+ if (parsed.y !== null && parsed.y !== undefined) {
172
+ // se la serie è stepped vogliamo mostrare il valore grezzo (es. 1/0)
173
+ if (ds.stepped)
174
+ return `${ds.label}: ${parsed.y} ${unit}`;
175
+ // numero normale -> 3 decimali
176
+ return `${ds.label}: ${Number(parsed.y).toFixed(3)} ${unit}`;
177
+ }
178
+ // 2) fallback: usa parsed.x o context.parsed.x per calcolare il valore via getValueAtX
179
+ const xVal = parsed.x ?? (context.parsed && context.parsed.x) ?? null;
180
+ if (xVal != null && Array.isArray(ds.data)) {
181
+ const val = getValueAtX(ds.data, xVal);
182
+ if (val !== null && val !== undefined) {
183
+ if (ds.stepped)
184
+ return `${ds.label}: ${val} ${unit}`;
185
+ return `${ds.label}: ${Number(val).toFixed(3)} ${unit}`;
186
+ }
187
+ }
188
+ // 3) fallback finale
189
+ return `${ds.label}: - ${unit}`;
41
190
  }
42
191
  },
43
192
  },
@@ -64,7 +213,19 @@ const lineOptions = {
64
213
  day: 'DD MMM YY',
65
214
  hour: 'DD MMM HH:mm',
66
215
  minute: 'HH:mm'
67
- }
216
+ },
217
+ y1: {
218
+ type: 'linear',
219
+ position: 'left',
220
+ title: { display: false, text: 'Primary Axis' },
221
+ display: false
222
+ },
223
+ y2: {
224
+ type: 'linear',
225
+ position: 'right',
226
+ title: { display: false, text: 'Secondary Axis' },
227
+ display: false
228
+ },
68
229
  },
69
230
  title: {
70
231
  display: false,
@@ -76,15 +237,6 @@ const lineOptions = {
76
237
  drawOnChartArea: false,
77
238
  }
78
239
  },
79
- y: {
80
- ticks: {
81
- callback: function (value) {
82
- if (Math.floor(value) === value) {
83
- return value;
84
- }
85
- }
86
- }
87
- },
88
240
  },
89
241
  };
90
242
  const chartConfigByPeriod = {
@@ -121,15 +273,6 @@ const chartConfigByPeriod = {
121
273
  scaleUnit: 'year',
122
274
  }
123
275
  };
124
- function GetPoints(data) {
125
- const points = data.results[0].series[0].values.map((row) => {
126
- return {
127
- x: moment.unix(row[0]),
128
- y: row[1]
129
- };
130
- });
131
- return points;
132
- }
133
276
  function getPollTime(intervalInSeconds, pollTime) {
134
277
  const CalculatedPollTime = Math.round(intervalInSeconds / 2880);
135
278
  if (CalculatedPollTime <= pollTime) {
@@ -140,65 +283,76 @@ function getPollTime(intervalInSeconds, pollTime) {
140
283
  }
141
284
  }
142
285
  function getCsvData(data, measures) {
143
- // Initialize the header with timestamp and measure names
144
- const headers = ["timestamp", ...measures.map(measure => measure.name)];
145
- const csvData = [];
146
- // Add the header to the csvData
147
- csvData.push(headers);
148
- // Collect all timestamps and values for each measure
286
+ // Intestazioni CSV
287
+ const headers = [
288
+ 'timestamp',
289
+ ...measures.map(m => m.description || m.name)
290
+ ];
291
+ const csvRows = [headers];
292
+ // Mappa temporale: ISO string → { timestamp, [measureName]: value, … }
149
293
  const timestampMap = {};
150
- // Populate timestampMap with data for each timestamp
151
- data.forEach((obj) => {
152
- obj.results[0].series.forEach((series) => {
153
- series.values.forEach((value) => {
154
- const timestamp = moment.unix(value[0]).toISOString();
155
- if (!timestampMap[timestamp]) {
156
- timestampMap[timestamp] = { timestamp: timestamp };
157
- }
158
- const measureName = series.name; // Assuming the series name corresponds to the measure name
159
- if (measures.some(measure => measure.name === measureName)) {
160
- timestampMap[timestamp][measureName] = value[1]; // Assign the value to the measure
161
- }
162
- });
294
+ // Ogni serie corrisponde a measures[index]
295
+ data.forEach((series, index) => {
296
+ const measure = measures[index];
297
+ if (!measure)
298
+ return; // difesa su mismatch lunghezze
299
+ series.forEach(point => {
300
+ // usa moment per coerenza con prima versione
301
+ const ts = moment(point.x).toISOString();
302
+ if (!timestampMap[ts]) {
303
+ timestampMap[ts] = { timestamp: ts };
304
+ }
305
+ timestampMap[ts][measure.name] = point.y;
163
306
  });
164
307
  });
165
- // Sort timestamps in ascending order
166
- const sortedTimestamps = Object.keys(timestampMap).sort((a, b) => new Date(a).getTime() - new Date(b).getTime());
167
- // Create rows from sorted timestampMap
168
- sortedTimestamps.forEach(timestamp => {
169
- const entry = timestampMap[timestamp];
308
+ // Ordina i timestamp
309
+ const sortedTs = Object.keys(timestampMap)
310
+ .sort((a, b) => new Date(a).getTime() - new Date(b).getTime());
311
+ // Costruisci le righe CSV
312
+ sortedTs.forEach(ts => {
313
+ const entry = timestampMap[ts];
314
+ // prima colonna timestamp, poi i valori nelle colonne in ordine measures[]
170
315
  const row = [entry.timestamp];
171
- measures.forEach(measure => {
172
- // Push the corresponding value or an empty string if undefined
173
- row.push(entry[measure.name] !== undefined ? entry[measure.name] : "");
316
+ measures.forEach(m => {
317
+ row.push(entry[m.name] !== undefined
318
+ ? entry[m.name]
319
+ : '');
174
320
  });
175
- // Check if the row contains only empty values (besides the timestamp)
176
- const hasNonEmptyValues = row.slice(1).some(value => value !== null && value !== '' && value !== undefined);
177
- // If the row has at least one non-empty value, add it to csvData
178
- if (hasNonEmptyValues || row.length === 1) {
179
- csvData.push(row);
321
+ // optional: filtra le righe che non hanno alcun valore utile
322
+ const hasValue = row.slice(1).some(v => v !== '' && v !== null);
323
+ if (hasValue) {
324
+ csvRows.push(row);
180
325
  }
181
326
  });
182
- // Join rows into a CSV string
183
- return csvData.map(row => row.join(',')).join('\n');
327
+ // Unisci in stringa
328
+ return csvRows
329
+ .map(r => r.join(','))
330
+ .join('\n');
184
331
  }
185
332
  // eslint-disable-next-line no-unused-vars
186
- const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, handleGetInfluxData, theme, ...props }) => {
333
+ const TrendChart = ({ filter, measures1, annotationsDataFn, measures2, enableDatePicker, handleGetInfluxData, handleGetDwSlotsCB, handleExportDataCB, theme, initialTimeStart, initialTimeEnd, t }) => {
187
334
  const [chartJsLoaded, setChartJsLoaded] = useState(false);
335
+ // Dichiarazione di annotationsData come funzione che ritorna una Promise<any>
336
+ const [annotationsData, setAnnotationsData] = useState(null);
337
+ const [annotationsEnabled, setAnnotationsEnabled] = useState(true);
188
338
  const [dataMeasures, setDataMeasures] = useState(null);
189
339
  const [chartPeriod, setChartPeriod] = useState('1D');
190
340
  const [chartPeriodConfig, setChartPeriodConfig] = useState(chartConfigByPeriod['1D']);
191
341
  const [chartLoading, setChartLoading] = useState(false);
192
- const [timeStartPicker, setTimeStartPicker] = useState(moment().subtract(1, 'day').unix());
193
- const [timeStart, setTimeStart] = useState(moment().subtract(1, 'day').unix());
194
- const [timeEnd, setTimeEnd] = useState(moment().unix());
195
- const [datePickerUsed, setDatePickerUsed] = useState(false);
196
- const [pickerTimeStart, setPickerTimeStart] = useState(moment().subtract(1, 'day').unix());
197
- const [pickerTimeEnd, setPickerTimeEnd] = useState(moment().unix());
342
+ const [timeStartPicker, setTimeStartPicker] = useState(initialTimeStart || moment().subtract(1, 'day').unix());
343
+ const [timeStart, setTimeStart] = useState(initialTimeStart || moment().subtract(1, 'day').unix());
344
+ const [timeEnd, setTimeEnd] = useState(initialTimeEnd || moment().unix());
345
+ const [datePickerUsed, setDatePickerUsed] = useState(initialTimeStart || initialTimeEnd ? true : false);
346
+ const [pickerTimeStart, setPickerTimeStart] = useState(initialTimeStart || moment().subtract(1, 'day').unix());
347
+ const [pickerTimeEnd, setPickerTimeEnd] = useState(initialTimeEnd || moment().unix());
198
348
  const [loadingButton, setLoadingButton] = useState(false);
199
349
  const csvLinkRef = useRef(null);
200
350
  const [csvData, setCsvData] = useState('');
201
- const [spanGapsOption, setSpanGapsOption] = useState(false);
351
+ const [spanGapsOption, setSpanGapsOption] = useState(true);
352
+ const enableExportData = handleExportDataCB ? true : false;
353
+ const measures = measures1 && measures2 ? [...measures1, ...measures2] :
354
+ measures1 ? [...measures1] :
355
+ measures2 ? [...measures2] : [];
202
356
  const [options, setOptions] = useState({
203
357
  ...lineOptions,
204
358
  plugins: {
@@ -222,13 +376,19 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
222
376
  }
223
377
  });
224
378
  const [zoomed, setZoomed] = useState(false);
379
+ const prevMeasures = useRef();
225
380
  const resetChart = () => {
226
381
  setOptions({
227
382
  ...options,
228
383
  scales: {
229
384
  ...options.scales,
230
- y: {
231
- ...options.scales.y,
385
+ y1: {
386
+ ...options.scales.y1,
387
+ min: 0,
388
+ max: 1
389
+ },
390
+ y2: {
391
+ ...options.scales.y2,
232
392
  min: 0,
233
393
  max: 1
234
394
  }
@@ -245,61 +405,141 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
245
405
  setSpanGapsOption(spanG);
246
406
  };
247
407
  const handleChange = (event, newPeriod) => {
248
- setZoomed(false);
249
- setDatePickerUsed(false);
250
- setCsvData('');
251
- if (newPeriod === "ALL") {
252
- setTimeStart(1577854800);
253
- setTimeEnd(moment().unix());
254
- setChartPeriod(newPeriod);
408
+ if (newPeriod == null) {
255
409
  return;
256
410
  }
257
- const periodConfig = chartConfigByPeriod[newPeriod];
258
- if (periodConfig) {
411
+ if (chartPeriod === newPeriod) {
412
+ setChartLoading(true);
413
+ setZoomed(false);
414
+ setDatePickerUsed(false);
415
+ setCsvData('');
416
+ const periodConfig = chartConfigByPeriod[chartPeriod];
259
417
  setTimeStart(Math.round(moment().subtract(periodConfig.from).valueOf() / 1000));
260
418
  setTimeEnd(Math.round(moment().valueOf() / 1000));
261
- setChartPeriod(newPeriod);
419
+ setChartPeriod(chartPeriod);
262
420
  setChartPeriodConfig(periodConfig);
263
421
  }
422
+ if (newPeriod && newPeriod !== chartPeriod) {
423
+ setChartLoading(true);
424
+ setZoomed(false);
425
+ setDatePickerUsed(false);
426
+ setCsvData('');
427
+ if (newPeriod === "ALL") {
428
+ setTimeStart(1577854800);
429
+ setTimeEnd(moment().unix());
430
+ setChartPeriod(newPeriod);
431
+ return;
432
+ }
433
+ const periodConfig = chartConfigByPeriod[newPeriod];
434
+ if (periodConfig) {
435
+ setTimeStart(Math.round(moment().subtract(periodConfig.from).valueOf() / 1000));
436
+ setTimeEnd(Math.round(moment().valueOf() / 1000));
437
+ setChartPeriod(newPeriod);
438
+ setChartPeriodConfig(periodConfig);
439
+ }
440
+ }
264
441
  };
265
442
  const handleExportData = async () => {
266
443
  setLoadingButton(true);
267
- const data = await Promise.all(measures.map(async (measure) => {
268
- return await handleGetInfluxData(measure.name, timeStart, timeEnd, deviceId, "0s", true);
269
- }));
270
- const csvData = getCsvData(data, measures);
271
- setCsvData(csvData);
272
- setLoadingButton(false);
444
+ if (handleExportDataCB) {
445
+ const data = await Promise.all(measures.map(async (measure) => {
446
+ return await handleExportDataCB(measure.name, timeStart, timeEnd, filter);
447
+ }));
448
+ const csvData = getCsvData(data, measures);
449
+ setCsvData(csvData);
450
+ setLoadingButton(false);
451
+ }
273
452
  };
274
453
  useEffect(() => {
275
454
  if (csvData.length > 0) {
276
455
  csvLinkRef.current?.link.click();
277
456
  }
278
457
  }, [csvData]);
458
+ useEffect(() => {
459
+ if (annotationsDataFn && annotationsEnabled) {
460
+ (async () => {
461
+ const resp = await annotationsDataFn();
462
+ setAnnotationsData(resp);
463
+ })();
464
+ }
465
+ }, [annotationsDataFn, annotationsEnabled]);
466
+ function getDwSlotsFromValues(dwValues) {
467
+ const slots = [];
468
+ let start = null;
469
+ for (const point of dwValues) {
470
+ if (point.y === 1 && start === null) {
471
+ start = point.x;
472
+ }
473
+ if (point.y === 0 && start !== null) {
474
+ slots.push({ start, end: point.x });
475
+ start = null;
476
+ }
477
+ }
478
+ return slots;
479
+ }
279
480
  const loadDatasets = async (chartPeriod) => {
280
- setChartLoading(true);
281
- const intervalInSeconds = timeEnd - timeStart;
282
- // Inizializza un array di promesse per ottenere i dati per ciascuna misura
283
- const datasetsPromises = measures.map(async (measure) => {
284
- const polltime = measure.polltime ? getPollTime(intervalInSeconds, measure.polltime) : "0s";
285
- const influxData = await handleGetInfluxData(measure.name, timeStart, timeEnd, deviceId, polltime, !!!measure.polltime);
286
- const points = GetPoints(influxData);
481
+ let intervalInSeconds = chartPeriod === "ALL" && !datePickerUsed && !zoomed ? 31536000 : timeEnd - timeStart;
482
+ const rawQuery = intervalInSeconds < 86400;
483
+ let tempSlots = [];
484
+ if (handleGetDwSlotsCB) {
485
+ const dwValues = await handleGetDwSlotsCB(timeStart, timeEnd, filter);
486
+ tempSlots = getDwSlotsFromValues(dwValues);
487
+ }
488
+ // Combine measures and track their source
489
+ const allMeasures = [
490
+ ...(measures1?.map(m => ({ ...m, source: 'measures1' })) || []),
491
+ ...(measures2?.map(m => ({ ...m, source: 'measures2' })) || [])
492
+ ];
493
+ const datasetsPromises = allMeasures.map(async (measure) => {
494
+ const polltime = getPollTime(intervalInSeconds, measure.polltime || 30);
495
+ let dwPoints = [];
496
+ let dwSlots = [];
497
+ if (measure.dwPolltime) {
498
+ const dwPolltime = getPollTime(intervalInSeconds, measure.dwPolltime);
499
+ // Query DW solo tag=100
500
+ const rawDwPoints = await handleGetInfluxData(measure.name, timeStart, timeEnd, filter, dwPolltime, false, "previous", 100, FilterTagMode.DW);
501
+ dwSlots = tempSlots;
502
+ if (dwSlots.length > 0) {
503
+ dwPoints = rawDwPoints.filter((p) => dwSlots.some(slot => p.x >= slot.start && p.x <= slot.end));
504
+ }
505
+ else {
506
+ dwPoints = rawDwPoints;
507
+ }
508
+ }
509
+ const stdPoints = await handleGetInfluxData(measure.name, timeStart, timeEnd, filter, polltime, !measure.polltime && rawQuery, !measure.polltime ? "none" : "null", 100, FilterTagMode.Exclude);
510
+ const filtered = dwSlots.length
511
+ ? stdPoints.filter((p) => !dwSlots.some(slot => p.x >= slot.start && p.x <= slot.end))
512
+ : stdPoints;
513
+ let combined;
514
+ // 6) Unisco e ordino
515
+ if (dwPoints.length > 2 && dwSlots.length > 0) {
516
+ combined = [...dwPoints, ...filtered]
517
+ .sort((a, b) => a.x - b.x);
518
+ }
519
+ else if (dwPoints.length > 2 && dwSlots.length <= 0) {
520
+ combined = [...dwPoints];
521
+ }
522
+ else {
523
+ combined = [...filtered];
524
+ }
287
525
  return {
288
- label: measure.name,
289
- data: points,
526
+ label: measure.description || measure.name,
527
+ data: combined,
290
528
  unit: measure.unit,
291
529
  borderWidth: 2,
292
530
  pointRadius: 1,
293
531
  pointHoverRadius: 5,
294
532
  pointHoverBackgroundColor: 'rgba(52, 125, 236, 0.5)',
295
- // showLine: measure.polltime ? true : false, //<- set this
296
- fill: false
533
+ change: !measure.polltime,
534
+ stepped: measure.stepped,
535
+ yAxisID: measure.source === 'measures1' ? 'y1' : 'y2'
297
536
  };
298
537
  });
299
- // Risolvi tutte le promesse per popolare i dataset
300
538
  const datasets = await Promise.all(datasetsPromises);
301
- let min = null;
302
- let max = null;
539
+ let min1 = null;
540
+ let max1 = null;
541
+ let min2 = null;
542
+ let max2 = null;
303
543
  let time;
304
544
  let minTime = null;
305
545
  datasets.forEach(dataset => {
@@ -309,45 +549,150 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
309
549
  }
310
550
  const datasetMin = Math.min(...values);
311
551
  const datasetMax = Math.max(...values);
312
- if (min === null || datasetMin < min)
313
- min = datasetMin;
314
- if (max === null || datasetMax > max)
315
- max = datasetMax;
316
- if (time && (minTime === null || time.unix() < minTime))
317
- minTime = time.unix() - 86400;
552
+ if (dataset.yAxisID === 'y1') {
553
+ if (min1 === null || datasetMin < min1)
554
+ min1 = datasetMin;
555
+ if (max1 === null || datasetMax > max1)
556
+ max1 = datasetMax;
557
+ }
558
+ else {
559
+ if (min2 === null || datasetMin < min2)
560
+ min2 = datasetMin;
561
+ if (max2 === null || datasetMax > max2)
562
+ max2 = datasetMax;
563
+ }
564
+ if (time && (minTime === null || time < minTime))
565
+ minTime = Math.floor(time / 1000) - 86400;
318
566
  });
319
- let paddedMin = null;
320
- let paddedMax = null;
321
- if (min !== null && max !== null) {
322
- const diff = Math.round((max - min) / 3) || 1;
323
- paddedMin = min - diff;
324
- paddedMax = max + diff;
567
+ const getPaddedMinMax = (min, max) => {
568
+ if (min === null || max === null)
569
+ return { paddedMin: null, paddedMax: null };
570
+ const diff = ((max - min) * 0.2) < 0.1 ? 0.1 : (max - min) * 0.2;
571
+ return {
572
+ paddedMin: Math.floor((min - diff) * 10) / 10,
573
+ paddedMax: Math.ceil((max + diff) * 10) / 10
574
+ };
575
+ };
576
+ const { paddedMin: paddedMin1, paddedMax: paddedMax1 } = getPaddedMinMax(min1, max1);
577
+ const { paddedMin: paddedMin2, paddedMax: paddedMax2 } = getPaddedMinMax(min2, max2);
578
+ // Handle undefined/null annotationsData
579
+ let dynamicAnnotations = {};
580
+ if (annotationsData && Array.isArray(annotationsData) && annotationsData.length > 0 && annotationsEnabled) {
581
+ dynamicAnnotations = annotationsData.reduce((acc, [timestamp, label], index) => {
582
+ const yVal = paddedMin1 !== null && paddedMin1 !== undefined
583
+ ? paddedMin1 + 0.01 * (paddedMax1 - paddedMin1)
584
+ : paddedMin2 !== null && paddedMin2 !== undefined
585
+ ? paddedMin2 + 0.01 * (paddedMax2 - paddedMin2)
586
+ : 0;
587
+ acc[`line${index}`] = {
588
+ type: 'line',
589
+ xMin: timestamp,
590
+ xMax: timestamp,
591
+ borderColor: 'rgba(255, 225, 0, 0.8)',
592
+ borderWidth: 2,
593
+ drawTime: 'afterDatasetsDraw',
594
+ label: {
595
+ content: label,
596
+ enabled: false
597
+ }
598
+ };
599
+ acc[`triangle${index}`] = {
600
+ type: 'point',
601
+ xValue: timestamp,
602
+ yValue: yVal,
603
+ backgroundColor: 'rgba(255, 225, 0, 0.8)',
604
+ pointStyle: 'triangle',
605
+ radius: 6,
606
+ rotation: 0,
607
+ tooltip: {
608
+ enabled: true,
609
+ callbacks: {
610
+ label: () => label
611
+ }
612
+ }
613
+ };
614
+ acc[`label${index}`] = {
615
+ type: 'label',
616
+ xValue: timestamp,
617
+ yValue: yVal + 0.1 * (((paddedMax1 ?? paddedMax2 ?? 0) - (paddedMax1 ?? paddedMax2 ?? 0))),
618
+ xAdjust: 0,
619
+ yAdjust: -20,
620
+ backgroundColor: 'rgba(245,245,245)',
621
+ borderColor: 'rgba(255, 225, 0, 0.8)',
622
+ borderWidth: 1,
623
+ content: [label],
624
+ textAlign: 'start',
625
+ font: {
626
+ size: 10,
627
+ weight: 'bold'
628
+ },
629
+ callout: {
630
+ display: false,
631
+ }
632
+ };
633
+ return acc;
634
+ }, {});
325
635
  }
326
- setDataMeasures([...datasets]);
636
+ // Combina statiche + dinamiche
637
+ const annotations = {
638
+ // ...staticAnnotations,
639
+ ...dynamicAnnotations
640
+ };
641
+ // 👇 Configurazione completa
642
+ setDataMeasures(datasets);
327
643
  setTimeStartPicker(minTime || timeStart);
328
644
  setOptions({
329
645
  ...options,
330
646
  scales: {
331
647
  ...options.scales,
332
- y: {
333
- ...options.scales.y,
334
- min: paddedMin,
335
- max: paddedMax,
648
+ y1: {
649
+ ...options.scales.y1,
650
+ min: paddedMin1,
651
+ max: paddedMax1,
652
+ position: "left",
653
+ display: "auto"
654
+ },
655
+ y2: {
656
+ ...options.scales.y2,
657
+ min: paddedMin2,
658
+ max: paddedMax2,
659
+ position: "right",
660
+ display: "auto"
336
661
  },
337
662
  x: {
338
663
  ...options.scales.x,
339
664
  min: moment.unix(minTime || timeStart).toString(),
340
- max: moment.unix(timeEnd).toString(),
341
- time: {
342
- ...options.scales.x.time,
343
- unit: chartPeriod.scaleUnit
344
- }
665
+ max: moment.unix(timeEnd).toString()
666
+ }
667
+ },
668
+ plugins: {
669
+ ...options.plugins,
670
+ annotation: {
671
+ interaction: {
672
+ mode: 'nearest',
673
+ intersect: true
674
+ },
675
+ annotations
345
676
  }
346
677
  }
347
678
  });
348
679
  };
680
+ const shiftChart = ((timeToShift) => {
681
+ setChartLoading(true);
682
+ const timeInRange = timeEnd - timeStart;
683
+ const percentageToShift = 0.1; // 10%
684
+ const shiftAmount = Math.floor(timeInRange * percentageToShift);
685
+ if (timeToShift === 'timeStart') {
686
+ setTimeStart(prev => prev - shiftAmount);
687
+ setTimeEnd(prev => prev - shiftAmount);
688
+ }
689
+ else {
690
+ setTimeEnd(prev => prev + shiftAmount);
691
+ setTimeStart(prev => prev + shiftAmount);
692
+ }
693
+ });
349
694
  useEffect(() => {
350
- const timeDifference = Math.abs(moment(timeStart).valueOf() - moment(timeEnd).valueOf()); // Convert milliseconds to seconds
695
+ const timeDifference = Math.abs(moment(timeEnd).valueOf() - moment(timeStart).valueOf()); // Convert milliseconds to seconds
351
696
  let newChartPeriod = '1D'; // Default to 1 day
352
697
  if (timeDifference < 86400) { // Less than 1 day
353
698
  newChartPeriod = '1H'; // Set to 1 day
@@ -355,47 +700,70 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
355
700
  else if (timeDifference < 604800) { // Less than 1 week
356
701
  newChartPeriod = '1D'; // Set to 1 day
357
702
  }
358
- else if (timeDifference < 2629800) { // Less than 1 month
703
+ else if (timeDifference < 2678400) { // Less than 1 month
359
704
  newChartPeriod = '1W'; // Set to 1 week
360
705
  }
361
- else if (timeDifference < 7889400) { // Less than 3 months
706
+ else if (timeDifference < 8035200) { // Less than 3 months
362
707
  newChartPeriod = '1M'; // Set to 1 month
363
708
  }
364
- else if (timeDifference < 15778440) { // Less than 6 months
709
+ else if (timeDifference < 16070400) { // Less than 6 months
365
710
  newChartPeriod = '3M'; // Set to 3 months
366
711
  }
367
- else if (timeDifference < 31556926) { // Less than 1 year
712
+ else if (timeDifference < 31536000) { // Less than 1 year
368
713
  newChartPeriod = '6M'; // Set to 6 months
369
714
  }
370
715
  else {
371
716
  newChartPeriod = '1Y'; // Set to 1 year
372
717
  }
373
718
  setChartPeriodConfig(chartConfigByPeriod[newChartPeriod]);
719
+ // check prev measures value in order to show the loader
720
+ // hide the loader if measure is the same (for interval get measure value)
721
+ const prevMeasuresValue = prevMeasures.current || [];
722
+ //@ts-ignore
723
+ prevMeasures.current = measures;
724
+ if (!prevMeasuresValue || prevMeasuresValue.length !== measures?.length || (prevMeasuresValue[0] && prevMeasuresValue[0].name !== measures[0].name)) {
725
+ setChartLoading(true);
726
+ }
374
727
  loadDatasets(chartPeriod).then(() => {
375
728
  setChartLoading(false);
376
729
  });
377
- }, [timeEnd, timeStart]);
730
+ }, [measures1, timeEnd, timeStart, measures2, annotationsEnabled, annotationsData]);
378
731
  useEffect(() => {
379
732
  const loadZoomPlugin = async () => {
380
733
  const zoomPlugin = (await import('chartjs-plugin-zoom')).default;
381
734
  ChartJS.register(Colors, Legend, Title, Tooltip, PointElement, LineElement, LinearScale, TimeScale, annotationPlugin, zoomPlugin, {
382
735
  id: 'uniqueid5',
383
736
  afterDraw: function (chart) {
384
- if (chart.tooltip?._active && chart.tooltip?._active.length) {
385
- const activePoint = chart.tooltip._active[0];
386
- const ctx = chart.ctx;
387
- const x = activePoint.element.x;
388
- const topY = chart.scales.y.top;
389
- const bottomY = chart.scales.y.bottom;
390
- ctx.save();
391
- ctx.beginPath();
392
- ctx.moveTo(x, topY);
393
- ctx.lineTo(x, bottomY);
394
- ctx.lineWidth = 1;
395
- ctx.strokeStyle = '#722257';
396
- ctx.stroke();
397
- ctx.restore();
737
+ if (!chart.__activePos?.hasPoint)
738
+ return; // disegna solo sui punti
739
+ const ctx = chart.ctx;
740
+ const x = chart.__activePos.px; // pixel esatto del punto attivo
741
+ let topY1 = 0;
742
+ let topY2 = 0;
743
+ let bottomY1 = 0;
744
+ let bottomY2 = 0;
745
+ if (chart.scales.y1?.top) {
746
+ topY1 = chart.scales.y1.top;
747
+ }
748
+ if (chart.scales.y2?.top) {
749
+ topY2 = chart.scales.y2.top;
398
750
  }
751
+ if (chart.scales.y1?.bottom) {
752
+ bottomY1 = chart.scales.y1.bottom;
753
+ }
754
+ if (chart.scales.y2?.bottom) {
755
+ bottomY2 = chart.scales.y2.bottom;
756
+ }
757
+ const topY = Math.max(topY1, topY2);
758
+ const bottomY = Math.min(bottomY1, bottomY2);
759
+ ctx.save();
760
+ ctx.beginPath();
761
+ ctx.moveTo(x, topY);
762
+ ctx.lineTo(x, bottomY);
763
+ ctx.lineWidth = 1;
764
+ ctx.strokeStyle = '#722257';
765
+ ctx.stroke();
766
+ ctx.restore();
399
767
  }
400
768
  });
401
769
  setChartJsLoaded(true);
@@ -403,7 +771,8 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
403
771
  loadZoomPlugin();
404
772
  resetChart();
405
773
  }, []);
406
- const datePicker = _jsx(Box, { sx: { display: 'flex', alignItems: 'center', mr: { xs: 0, lg: 2 } }, children: _jsxs(LocalizationProvider, { dateAdapter: AdapterMoment, adapterLocale: "it", children: [_jsx(DateTimePicker, { value: moment(timeStartPicker * 1000), onChange: (newValue) => {
774
+ const datePicker = _jsx(Box, { sx: { display: 'flex', alignItems: 'center' }, children: _jsxs(LocalizationProvider, { dateAdapter: AdapterMoment, adapterLocale: "it", children: [_jsx(DateTimePicker, { value: moment(timeStartPicker * 1000), onChange: (newValue) => {
775
+ setChartLoading(true);
407
776
  setDatePickerUsed(true);
408
777
  setZoomed(false);
409
778
  setPickerTimeStart(moment(newValue).unix());
@@ -411,6 +780,7 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
411
780
  }, maxDateTime: moment(timeEnd * 1000), slotProps: {
412
781
  textField: { size: 'small', sx: { width: { sm: 210 } } }
413
782
  } }), " \u00A0\u00A0 ", _jsx(Box, { children: "\u2013" }), " \u00A0\u00A0", _jsx(DateTimePicker, { value: moment(timeEnd * 1000), onChange: (newValue) => {
783
+ setChartLoading(true);
414
784
  setDatePickerUsed(true);
415
785
  setZoomed(false);
416
786
  setPickerTimeEnd(moment(newValue).unix());
@@ -418,11 +788,28 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
418
788
  }, minDateTime: moment(timeStart * 1000), slotProps: {
419
789
  textField: { size: 'small', sx: { width: { sm: 210 } } }
420
790
  } })] }) });
421
- return (_jsxs(ThemeProvider, { theme: theme, children: [enableDatePicker && _jsx(Box, { sx: { display: { xs: 'flex', lg: 'none', justifyContent: 'flex-end' }, mb: 2 }, children: datePicker }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center' }, children: [!enableExportData && measures && measures.length === 1 ? _jsx(Typography, { variant: 'body1', sx: { mr: 2 }, children: measures[0].name }) : '', enableExportData && (_jsxs(_Fragment, { children: [_jsxs(LoadingButton, { sx: { minWidth: '40px !important', mr: 1, px: 1 }, loading: loadingButton, type: "submit", variant: "text", color: "primary", onClick: handleExportData, disabled: !dataMeasures || !dataMeasures.length, size: 'small', children: [_jsx(LoginIcon, { fontSize: 'small', style: { transform: "rotate(90deg)" } }), _jsx(Box, { sx: { display: { xs: 'none', xl: 'block' }, ml: { md: 1 } }, children: "Export" })] }), _jsx(CSVLink
791
+ return (_jsxs(ThemeProvider, { theme: theme, children: [_jsxs(Box, { sx: { display: { xs: 'flex', lg: 'none', justifyContent: 'center' }, alignItems: 'center', mb: 3, mt: 1 }, children: [_jsx(Button, { sx: {
792
+ borderWidth: 1,
793
+ borderStyle: 'solid',
794
+ borderColor: '#c4c4c4',
795
+ color: 'text.primary',
796
+ ":hover": { backgroundColor: 'action.hover' },
797
+ mr: enableDatePicker ? 1 : 0,
798
+ p: 0.85
799
+ }, onClick: () => shiftChart('timeStart'), children: _jsx(KeyboardArrowLeftIcon, {}) }), enableDatePicker && _jsx(Box, { sx: { display: { xs: 'flex', lg: 'none', justifyContent: 'flex-end' } }, children: datePicker }), _jsx(Button, { sx: {
800
+ borderWidth: 1,
801
+ borderStyle: 'solid',
802
+ borderColor: '#c4c4c4',
803
+ color: 'text.primary',
804
+ ":hover": { backgroundColor: 'action.hover' },
805
+ ml: enableDatePicker ? 1 : 0,
806
+ p: 0.85
807
+ }, onClick: () => shiftChart('timeEnd'), children: _jsx(KeyboardArrowRightIcon, {}) })] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center' }, children: [!enableExportData && measures && measures.length === 1 ? _jsx(Typography, { variant: 'body1', sx: { mr: 2 }, children: measures[0].description || measures[0].name }) : '', enableExportData && (_jsxs(_Fragment, { children: [_jsxs(LoadingButton, { sx: { minWidth: '40px !important', mr: 1, px: 1 }, loading: loadingButton, type: "submit", variant: "text", color: "primary", onClick: handleExportData, disabled: !dataMeasures || !dataMeasures.length, size: 'small', children: [_jsx(LoginIcon, { fontSize: 'small', style: { transform: "rotate(90deg)" } }), _jsx(Box, { sx: { display: { xs: 'none', xl: 'block' }, ml: { md: 1 } }, children: t('library.trendChart.export') })] }), _jsx(CSVLink
422
808
  //@ts-ignore
423
809
  , {
424
810
  //@ts-ignore
425
- ref: csvLinkRef, data: csvData, filename: `export_${moment().toISOString()}.csv`, separator: ';' })] })), zoomed && (_jsxs(Button, { sx: { minWidth: '40px !important', boxShadow: 1, px: 1 }, variant: "contained", color: "secondary", size: 'small', onClick: () => {
811
+ ref: csvLinkRef, data: csvData, filename: `export_${moment().toISOString()}.csv`, separator: ';' })] })), zoomed && measures.length > 0 && (_jsx(Button, { sx: { minWidth: '40px !important', boxShadow: 1, p: 1, mr: 1 }, variant: "contained", color: "primary", size: 'small', onClick: () => {
812
+ setChartLoading(true);
426
813
  setZoomed(false);
427
814
  if (chartPeriod === "ALL") {
428
815
  setDatePickerUsed(false);
@@ -443,12 +830,31 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
443
830
  : moment().subtract(1, 'day').unix());
444
831
  setTimeEnd(moment().unix());
445
832
  }
446
- }, children: [_jsx(ZoomOut, { fontSize: 'small' }), _jsx(Box, { sx: { display: { xs: 'none', lg: 'block' }, ml: { md: 1 } }, children: "Reset" })] }))] }), _jsxs(Box, { sx: { display: 'flex', justifyContent: 'flex-end' }, children: [enableDatePicker && _jsx(Box, { sx: { display: { xs: 'none', lg: 'flex' } }, children: datePicker }), _jsxs(ToggleButtonGroup, { color: "primary", value: !datePickerUsed && !zoomed ? chartPeriod : null, exclusive: true, onChange: handleChange, size: "small",
447
- // sx={{ boxShadow: 1 }}
448
- disabled: chartLoading, children: [_jsx(ToggleButton, { value: "1D", sx: { px: 1 }, children: "1d" }), _jsx(ToggleButton, { value: "1W", sx: { px: 1 }, children: "1w" }), _jsx(ToggleButton, { value: "1M", sx: { px: 1 }, children: "1M" }), _jsx(ToggleButton, { value: "3M", sx: { px: 1 }, children: "3M" }), _jsx(ToggleButton, { value: "6M", sx: { px: 1 }, children: "6M" }), _jsx(ToggleButton, { value: "1Y", sx: { px: 1 }, children: "1Y" }), _jsx(ToggleButton, { value: "ALL", sx: { px: 1 }, children: "ALL" })] }), _jsx(MuiTooltip, { placement: "top", arrow: true, title: "Connect point values", children: _jsx(ToggleButton, { value: "check", color: "primary", size: "small", selected: spanGapsOption, disabled: chartLoading, onChange: () => handleSpanGaps(!spanGapsOption), sx: { ml: 2 }, children: _jsx(TimelineIcon, {}) }) })] })] }), _jsx(Box, { component: 'div', className: "chart-container", sx: { height: { xs: enableDatePicker && props.height ? `calc(${props.height} - 50px)` : props.height, lg: props.height }, minHeight: 300, mt: 2 }, children: chartJsLoaded && !chartLoading && typeof window !== 'undefined' ?
449
- _jsx(_Fragment, { children: dataMeasures && dataMeasures[0]?.data?.length ?
833
+ }, children: _jsx(ZoomOut, { fontSize: 'small' }) }))] }), _jsxs(Box, { sx: { display: 'flex', justifyContent: 'flex-end' }, children: [_jsxs(Box, { sx: { display: { xs: 'none', lg: 'flex' }, alignItems: 'center', mr: 1 }, children: [_jsx(Button, { sx: {
834
+ borderWidth: 1,
835
+ borderStyle: 'solid',
836
+ borderColor: '#c4c4c4',
837
+ color: 'text.primary',
838
+ ":hover": { backgroundColor: 'action.hover' },
839
+ mr: enableDatePicker ? 1 : 0,
840
+ p: 0.85
841
+ }, onClick: () => shiftChart('timeStart'), children: _jsx(KeyboardArrowLeftIcon, {}) }), enableDatePicker && _jsx(Box, { sx: { display: { xs: 'none', lg: 'flex' } }, children: datePicker }), _jsx(Button, { sx: {
842
+ borderWidth: 1,
843
+ borderStyle: 'solid',
844
+ borderColor: '#c4c4c4',
845
+ color: 'text.primary',
846
+ ":hover": { backgroundColor: 'action.hover' },
847
+ ml: 1,
848
+ p: 0.85
849
+ }, onClick: () => shiftChart('timeEnd'), children: _jsx(KeyboardArrowRightIcon, {}) })] }), _jsxs(ToggleButtonGroup, { color: "primary", value: !datePickerUsed && !zoomed ? chartPeriod : null, exclusive: true, onChange: handleChange, size: "small", sx: {
850
+ '& .MuiToggleButton-root': {
851
+ color: 'text.primary', fontSize: '0.95rem', fontWeight: 'normal', paddingTop: '6px', paddingBottom: '6px'
852
+ }
853
+ }, disabled: chartLoading, children: [_jsx(ToggleButton, { value: "1D", sx: { px: 1 }, children: "1d" }), _jsx(ToggleButton, { value: "1W", sx: { px: 1 }, children: "1w" }), _jsx(ToggleButton, { value: "1M", sx: { px: 1 }, children: "1M" }), _jsx(ToggleButton, { value: "3M", sx: { px: 1 }, children: "3M" }), _jsx(ToggleButton, { value: "6M", sx: { px: 1 }, children: "6M" }), _jsx(ToggleButton, { value: "1Y", sx: { px: 1 }, children: "1Y" }), _jsx(ToggleButton, { value: "ALL", sx: { px: 1 }, children: "ALL" })] }), _jsx(MuiTooltip, { placement: "top", arrow: true, title: t('library.trendChart.connectPointValues'), children: _jsx("span", { children: _jsx(ToggleButton, { value: "check", color: "primary", size: "small", selected: spanGapsOption, disabled: chartLoading, onChange: () => handleSpanGaps(!spanGapsOption), sx: { ml: 1 }, children: _jsx(TimelineIcon, {}) }) }) }), annotationsDataFn && annotationsData !== null && (_jsx(MuiTooltip, { placement: "top", arrow: true, title: t('library.trendChart.showAnnotations'), children: _jsx("span", { children: _jsx(ToggleButton, { value: "check", color: "primary", size: "small", selected: annotationsEnabled, disabled: chartLoading, onChange: () => setAnnotationsEnabled(!annotationsEnabled), sx: { ml: 1 }, children: _jsx(EditNoteIcon, {}) }) }) }))] })] }), _jsx(Box, { component: 'div', className: "chart-container", sx: { mt: 2, height: '100%' }, children: chartJsLoaded && !chartLoading && typeof window !== 'undefined' ?
854
+ _jsx(_Fragment, { children: dataMeasures && (dataMeasures.length > 1 || (dataMeasures.length === 1 && dataMeasures[0].data?.length)) ?
450
855
  (_jsx(Line, { options: options, data: {
451
- datasets: dataMeasures || [{ data: [] }]
856
+ // datasets: dataMeasures || [{ data: [] }]
857
+ datasets: dataMeasures.map(d => ({ ...d, showLine: spanGapsOption || !d.change })) || [{ data: [] }]
452
858
  } })) : _jsxs(Box, { sx: {
453
859
  display: 'flex',
454
860
  flexDirection: 'column',
@@ -456,13 +862,13 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
456
862
  justifyContent: 'center',
457
863
  textAlign: 'center',
458
864
  height: '100%',
459
- }, children: [_jsx(SearchOffOutlinedIcon, { sx: { fontSize: 50, color: 'grey.500', mb: 2 } }), _jsx(Typography, { variant: "body1", color: "textSecondary", align: "center", children: "No data measure" })] }) }) : (_jsx(Box, { sx: {
865
+ }, children: [_jsx(SearchOffOutlinedIcon, { sx: { fontSize: 50, color: 'grey.500', mb: 2 } }), _jsx(Typography, { variant: "body1", color: "textSecondary", align: "center", children: t('library.trendChart.noDataMeasure') })] }) }) : (_jsx(Box, { sx: {
460
866
  display: 'flex',
461
867
  flexDirection: 'column',
462
868
  alignItems: 'center',
463
869
  justifyContent: 'center',
464
870
  textAlign: 'center',
465
- height: '100%'
871
+ height: 'calc(100% - 50px)'
466
872
  }, children: _jsx(CircularProgress, {}) })) })] }));
467
873
  };
468
874
  export default TrendChart;