@iotready/nextjs-components-library 1.0.0-preview3 → 1.0.0-preview31

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.
@@ -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,60 +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
- // Create rows from timestampMap
166
- Object.values(timestampMap).forEach((entry) => {
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[]
167
315
  const row = [entry.timestamp];
168
- measures.forEach(measure => {
169
- // Push the corresponding value or an empty string if undefined
170
- 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
+ : '');
171
320
  });
172
- // Check if the row contains only empty values (besides the timestamp)
173
- const hasNonEmptyValues = row.slice(1).some(value => value !== null && value !== '' && value !== undefined);
174
- // If the row has at least one non-empty value or just the timestamp, add it to csvData
175
- if (hasNonEmptyValues || row.length === 1) {
176
- 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);
177
325
  }
178
326
  });
179
- // Join rows into a CSV string
180
- return csvData.map(row => row.join(',')).join('\n');
327
+ // Unisci in stringa
328
+ return csvRows
329
+ .map(r => r.join(','))
330
+ .join('\n');
181
331
  }
182
332
  // eslint-disable-next-line no-unused-vars
183
- const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, handleGetInfluxData, handleGetFirstTimestamp, theme, ...props }) => {
333
+ const TrendChart = ({ filter, measures1, annotationsDataFn, measures2, enableDatePicker, handleGetInfluxData, handleGetDwSlotsCB, handleExportDataCB, theme, initialTimeStart, initialTimeEnd }) => {
184
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);
185
338
  const [dataMeasures, setDataMeasures] = useState(null);
186
339
  const [chartPeriod, setChartPeriod] = useState('1D');
187
340
  const [chartPeriodConfig, setChartPeriodConfig] = useState(chartConfigByPeriod['1D']);
188
341
  const [chartLoading, setChartLoading] = useState(false);
189
- const [timeStart, setTimeStart] = useState(moment().subtract(1, 'day').unix());
190
- const [timeEnd, setTimeEnd] = useState(moment().unix());
191
- const [firstTimestamp, setFirstTimestamp] = useState();
192
- const [datePickerUsed, setDatePickerUsed] = useState(false);
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());
193
348
  const [loadingButton, setLoadingButton] = useState(false);
194
349
  const csvLinkRef = useRef(null);
195
350
  const [csvData, setCsvData] = useState('');
196
- 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] : [];
197
356
  const [options, setOptions] = useState({
198
357
  ...lineOptions,
199
358
  plugins: {
@@ -217,13 +376,19 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
217
376
  }
218
377
  });
219
378
  const [zoomed, setZoomed] = useState(false);
379
+ const prevMeasures = useRef();
220
380
  const resetChart = () => {
221
381
  setOptions({
222
382
  ...options,
223
383
  scales: {
224
384
  ...options.scales,
225
- y: {
226
- ...options.scales.y,
385
+ y1: {
386
+ ...options.scales.y1,
387
+ min: 0,
388
+ max: 1
389
+ },
390
+ y2: {
391
+ ...options.scales.y2,
227
392
  min: 0,
228
393
  max: 1
229
394
  }
@@ -240,114 +405,294 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
240
405
  setSpanGapsOption(spanG);
241
406
  };
242
407
  const handleChange = (event, newPeriod) => {
243
- setZoomed(false);
244
- setDatePickerUsed(false);
245
- setCsvData('');
246
- if (newPeriod === "ALL") {
247
- setTimeStart(firstTimestamp - 86400);
248
- setTimeEnd(moment().unix());
249
- setChartPeriod(newPeriod);
408
+ if (newPeriod == null) {
250
409
  return;
251
410
  }
252
- const periodConfig = chartConfigByPeriod[newPeriod];
253
- if (periodConfig) {
411
+ if (chartPeriod === newPeriod) {
412
+ setChartLoading(true);
413
+ setZoomed(false);
414
+ setDatePickerUsed(false);
415
+ setCsvData('');
416
+ const periodConfig = chartConfigByPeriod[chartPeriod];
254
417
  setTimeStart(Math.round(moment().subtract(periodConfig.from).valueOf() / 1000));
255
418
  setTimeEnd(Math.round(moment().valueOf() / 1000));
256
- setChartPeriod(newPeriod);
419
+ setChartPeriod(chartPeriod);
257
420
  setChartPeriodConfig(periodConfig);
258
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
+ }
259
441
  };
260
442
  const handleExportData = async () => {
261
443
  setLoadingButton(true);
262
- const intervalInSeconds = timeEnd - timeStart;
263
- const data = await Promise.all(measures.map(async (measure) => {
264
- const polltime = getPollTime(intervalInSeconds, measure.polltime);
265
- return await handleGetInfluxData(measure.name, timeStart, timeEnd, deviceId, polltime, true);
266
- }));
267
- const csvData = getCsvData(data, measures);
268
- setCsvData(csvData);
269
- 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
+ }
270
452
  };
271
453
  useEffect(() => {
272
454
  if (csvData.length > 0) {
273
455
  csvLinkRef.current?.link.click();
274
456
  }
275
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
+ }
276
480
  const loadDatasets = async (chartPeriod) => {
277
- setChartLoading(true);
278
- const intervalInSeconds = timeEnd - timeStart;
279
- // Inizializza un array di promesse per ottenere i dati per ciascuna misura
280
- const datasetsPromises = measures.map(async (measure) => {
281
- const polltime = getPollTime(intervalInSeconds, measure.polltime);
282
- const influxData = await handleGetInfluxData(measure.name, timeStart, timeEnd, deviceId, polltime, false);
283
- 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
+ }
284
525
  return {
285
- label: measure.name,
286
- data: points,
526
+ label: measure.description || measure.name,
527
+ data: combined,
287
528
  unit: measure.unit,
288
- // borderColor: `hsl(${index * 50}, 70%, 50%)`, // Colore unico per ogni dataset
289
529
  borderWidth: 2,
290
530
  pointRadius: 1,
291
531
  pointHoverRadius: 5,
292
532
  pointHoverBackgroundColor: 'rgba(52, 125, 236, 0.5)',
293
- // spanGaps: false,
294
- fill: false
533
+ change: !measure.polltime,
534
+ stepped: measure.stepped,
535
+ yAxisID: measure.source === 'measures1' ? 'y1' : 'y2'
295
536
  };
296
537
  });
297
- // Risolvi tutte le promesse per popolare i dataset
298
538
  const datasets = await Promise.all(datasetsPromises);
299
- let min = null;
300
- let max = null;
301
- let values = [];
539
+ let min1 = null;
540
+ let max1 = null;
541
+ let min2 = null;
542
+ let max2 = null;
543
+ let time;
544
+ let minTime = null;
302
545
  datasets.forEach(dataset => {
303
- values = [...values, ...dataset.data.map((point) => point.y).filter((data) => data !== null)];
546
+ const values = dataset.data.map((point) => point.y).filter((data) => data !== null);
547
+ if (chartPeriod === "ALL" && !datePickerUsed && !zoomed && values.length) {
548
+ time = dataset.data.filter((data) => data.y !== null).map((data) => data.x)[0];
549
+ }
304
550
  const datasetMin = Math.min(...values);
305
551
  const datasetMax = Math.max(...values);
306
- if (min === null || datasetMin < min)
307
- min = datasetMin;
308
- if (max === null || datasetMax > max)
309
- max = datasetMax;
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;
310
566
  });
311
- let paddedMin = null;
312
- let paddedMax = null;
313
- if (min !== null && max !== null) {
314
- const diff = Math.round((max - min) / 3) || 1;
315
- paddedMin = min - diff;
316
- 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
+ }, {});
317
635
  }
318
- setDataMeasures([...datasets]);
636
+ // Combina statiche + dinamiche
637
+ const annotations = {
638
+ // ...staticAnnotations,
639
+ ...dynamicAnnotations
640
+ };
641
+ // 👇 Configurazione completa
642
+ setDataMeasures(datasets);
643
+ setTimeStartPicker(minTime || timeStart);
319
644
  setOptions({
320
645
  ...options,
321
646
  scales: {
322
647
  ...options.scales,
323
- y: {
324
- ...options.scales.y,
325
- min: paddedMin,
326
- 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"
327
661
  },
328
662
  x: {
329
663
  ...options.scales.x,
330
- min: moment.unix(timeStart).toString(),
331
- max: moment.unix(timeEnd).toString(),
332
- time: {
333
- ...options.scales.x.time,
334
- unit: chartPeriod.scaleUnit
335
- }
664
+ min: moment.unix(minTime || timeStart).toString(),
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
336
676
  }
337
677
  }
338
678
  });
339
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
+ });
340
694
  useEffect(() => {
341
- const fetchFirstTimestamp = async () => {
342
- const response = await handleGetFirstTimestamp(deviceId);
343
- if (response) {
344
- setFirstTimestamp(response);
345
- }
346
- };
347
- fetchFirstTimestamp();
348
- }, []);
349
- 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;
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;
398
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,34 +771,57 @@ 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(timeStart * 1000),
407
- // format="DD/MM/YY hh:mm A"
408
- onChange: (newValue) => {
409
- setTimeStart((moment(newValue).unix()));
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);
776
+ setDatePickerUsed(true);
777
+ setZoomed(false);
778
+ setPickerTimeStart(moment(newValue).unix());
779
+ setTimeStart(moment(newValue).unix());
410
780
  }, maxDateTime: moment(timeEnd * 1000), slotProps: {
411
781
  textField: { size: 'small', sx: { width: { sm: 210 } } }
412
- } }), " \u00A0\u00A0 ", _jsx(Box, { children: "\u2013" }), " \u00A0\u00A0", _jsx(DateTimePicker, { value: moment(timeEnd * 1000),
413
- // format="DD/MM/YY hh:mm A"
414
- onChange: (newValue) => {
415
- setTimeEnd((moment(newValue).unix()));
782
+ } }), " \u00A0\u00A0 ", _jsx(Box, { children: "\u2013" }), " \u00A0\u00A0", _jsx(DateTimePicker, { value: moment(timeEnd * 1000), onChange: (newValue) => {
783
+ setChartLoading(true);
784
+ setDatePickerUsed(true);
785
+ setZoomed(false);
786
+ setPickerTimeEnd(moment(newValue).unix());
787
+ setTimeEnd(moment(newValue).unix());
416
788
  }, minDateTime: moment(timeStart * 1000), slotProps: {
417
789
  textField: { size: 'small', sx: { width: { sm: 210 } } }
418
790
  } })] }) });
419
- 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: "Export" })] }), _jsx(CSVLink
420
808
  //@ts-ignore
421
809
  , {
422
810
  //@ts-ignore
423
- 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: () => {
424
- if (datePickerUsed) {
425
- setZoomed(false);
426
- }
427
- else if (chartPeriod === "ALL") {
428
- setZoomed(false);
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);
813
+ setZoomed(false);
814
+ if (chartPeriod === "ALL") {
815
+ setDatePickerUsed(false);
816
+ setTimeStart(1577854800);
429
817
  setTimeEnd(moment().unix());
430
- setTimeStart(firstTimestamp - 86400);
818
+ }
819
+ else if (datePickerUsed) {
820
+ setTimeStart(pickerTimeStart);
821
+ setTimeEnd(pickerTimeEnd);
431
822
  }
432
823
  else {
433
- setZoomed(false);
824
+ setDatePickerUsed(false);
434
825
  setChartPeriodConfig(chartConfigByPeriod[chartPeriod]);
435
826
  setTimeStart(chartPeriodConfig.from?.days
436
827
  ? moment()
@@ -439,12 +830,31 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
439
830
  : moment().subtract(1, 'day').unix());
440
831
  setTimeEnd(moment().unix());
441
832
  }
442
- }, children: [_jsx(ZoomOut, { fontSize: 'small' }), _jsx(Box, { sx: { display: { xs: 'none', xl: '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: chartPeriod, exclusive: true, onChange: handleChange, size: "small",
443
- // sx={{ boxShadow: 1 }}
444
- 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' ?
445
- _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: "Connect point values", 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, {}) }) }) }), annotationsData !== null && (_jsx(MuiTooltip, { placement: "top", arrow: true, title: "Show annotations", 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)) ?
446
855
  (_jsx(Line, { options: options, data: {
447
- datasets: dataMeasures || [{ data: [] }]
856
+ // datasets: dataMeasures || [{ data: [] }]
857
+ datasets: dataMeasures.map(d => ({ ...d, showLine: spanGapsOption || !d.change })) || [{ data: [] }]
448
858
  } })) : _jsxs(Box, { sx: {
449
859
  display: 'flex',
450
860
  flexDirection: 'column',
@@ -458,7 +868,7 @@ const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, ha
458
868
  alignItems: 'center',
459
869
  justifyContent: 'center',
460
870
  textAlign: 'center',
461
- height: '100%'
871
+ height: 'calc(100% - 50px)'
462
872
  }, children: _jsx(CircularProgress, {}) })) })] }));
463
873
  };
464
874
  export default TrendChart;