@iotready/nextjs-components-library 1.0.0-preview1
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.
- package/components/accounts/AccountMenu.d.ts +9 -0
- package/components/accounts/AccountMenu.js +37 -0
- package/components/accounts/AccountProfile.d.ts +15 -0
- package/components/accounts/AccountProfile.js +153 -0
- package/components/accounts/index.d.ts +2 -0
- package/components/accounts/index.js +2 -0
- package/components/charts/TrendChart.d.ts +17 -0
- package/components/charts/TrendChart.js +454 -0
- package/components/charts/index.d.ts +1 -0
- package/components/charts/index.js +1 -0
- package/components/groups/GroupUpdate.d.ts +24 -0
- package/components/groups/GroupUpdate.js +134 -0
- package/components/groups/GroupsDevices.d.ts +37 -0
- package/components/groups/GroupsDevices.js +299 -0
- package/components/groups/Map.d.ts +14 -0
- package/components/groups/Map.js +17 -0
- package/components/groups/index.d.ts +3 -0
- package/components/groups/index.js +3 -0
- package/components/index.d.ts +5 -0
- package/components/index.js +5 -0
- package/components/settings/DynamicMenu.d.ts +17 -0
- package/components/settings/DynamicMenu.js +42 -0
- package/components/settings/index.d.ts +1 -0
- package/components/settings/index.js +1 -0
- package/components/users/UserUpdate.d.ts +11 -0
- package/components/users/UserUpdate.js +26 -0
- package/components/users/UsersDataGrid.d.ts +23 -0
- package/components/users/UsersDataGrid.js +76 -0
- package/components/users/index.d.ts +2 -0
- package/components/users/index.js +2 -0
- package/index.d.ts +3 -0
- package/index.js +4 -0
- package/package.json +45 -0
- package/server-actions/groups.d.ts +22 -0
- package/server-actions/groups.js +109 -0
- package/server-actions/index.d.ts +4 -0
- package/server-actions/index.js +5 -0
- package/server-actions/influx.d.ts +13 -0
- package/server-actions/influx.js +145 -0
- package/server-actions/logto.d.ts +39 -0
- package/server-actions/logto.js +194 -0
- package/server-actions/trackle.d.ts +35 -0
- package/server-actions/trackle.js +158 -0
- package/types/index.d.ts +1 -0
- package/types/index.js +1 -0
- package/types/user.d.ts +12 -0
- package/types/user.js +1 -0
@@ -0,0 +1,454 @@
|
|
1
|
+
"use client";
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
3
|
+
import { useState, useEffect, useRef } from 'react';
|
4
|
+
import { Line } from 'react-chartjs-2';
|
5
|
+
import { Chart as ChartJS, Colors, Title, LinearScale, Legend, Tooltip, TimeScale, PointElement, LineElement } from 'chart.js';
|
6
|
+
import annotationPlugin from 'chartjs-plugin-annotation';
|
7
|
+
import 'chartjs-adapter-moment';
|
8
|
+
import { ToggleButtonGroup, ToggleButton, Box, Button, Typography } from "@mui/material";
|
9
|
+
import moment from 'moment';
|
10
|
+
import { LoadingButton } from '@mui/lab';
|
11
|
+
import { CSVLink } from "react-csv";
|
12
|
+
import CircularProgress from '@mui/material/CircularProgress';
|
13
|
+
import ZoomOut from '@mui/icons-material/ZoomOut';
|
14
|
+
import LoginIcon from '@mui/icons-material/Login';
|
15
|
+
import SearchOffOutlinedIcon from '@mui/icons-material/SearchOffOutlined';
|
16
|
+
import { LocalizationProvider, DateTimePicker } from "@mui/x-date-pickers";
|
17
|
+
import 'moment/locale/it';
|
18
|
+
// import 'moment/locale/en-gb'; // TODO set locale based on browser
|
19
|
+
import { AdapterMoment } from '@mui/x-date-pickers/AdapterMoment';
|
20
|
+
import { ThemeProvider } from '@mui/material/styles';
|
21
|
+
const lineOptions = {
|
22
|
+
parsing: false,
|
23
|
+
normalized: true,
|
24
|
+
spanGaps: true, // enable for all datasets
|
25
|
+
// showLine: false, // disable for all datasets
|
26
|
+
animation: false,
|
27
|
+
responsive: true,
|
28
|
+
maintainAspectRatio: false,
|
29
|
+
interaction: {
|
30
|
+
intersect: false,
|
31
|
+
mode: 'nearest',
|
32
|
+
axis: 'x'
|
33
|
+
},
|
34
|
+
plugins: {
|
35
|
+
tooltip: {
|
36
|
+
callbacks: {
|
37
|
+
label: (context) => {
|
38
|
+
return `${context.dataset.label}: ${context.parsed.y.toFixed(2)} ${context.dataset.unit}`;
|
39
|
+
}
|
40
|
+
},
|
41
|
+
},
|
42
|
+
zoom: {
|
43
|
+
zoom: {
|
44
|
+
drag: {
|
45
|
+
enabled: true,
|
46
|
+
},
|
47
|
+
pinch: {
|
48
|
+
enabled: true
|
49
|
+
},
|
50
|
+
mode: 'x',
|
51
|
+
},
|
52
|
+
},
|
53
|
+
},
|
54
|
+
scales: {
|
55
|
+
x: {
|
56
|
+
type: 'time',
|
57
|
+
time: {
|
58
|
+
unit: "hour",
|
59
|
+
displayFormats: {
|
60
|
+
month: 'MMM YY',
|
61
|
+
week: 'DD MMM YY',
|
62
|
+
day: 'DD MMM YY',
|
63
|
+
hour: 'DD MMM HH:mm',
|
64
|
+
minute: 'HH:mm'
|
65
|
+
}
|
66
|
+
},
|
67
|
+
title: {
|
68
|
+
display: false,
|
69
|
+
},
|
70
|
+
ticks: {
|
71
|
+
stepSize: 1,
|
72
|
+
},
|
73
|
+
grid: {
|
74
|
+
drawOnChartArea: false,
|
75
|
+
}
|
76
|
+
},
|
77
|
+
y: {
|
78
|
+
ticks: {
|
79
|
+
callback: function (value) {
|
80
|
+
if (Math.floor(value) === value) {
|
81
|
+
return value;
|
82
|
+
}
|
83
|
+
}
|
84
|
+
}
|
85
|
+
},
|
86
|
+
},
|
87
|
+
};
|
88
|
+
const chartConfigByPeriod = {
|
89
|
+
'1H': {
|
90
|
+
from: { days: 1 / 24 },
|
91
|
+
scaleUnit: 'minute',
|
92
|
+
},
|
93
|
+
'1D': {
|
94
|
+
from: { days: 1 },
|
95
|
+
scaleUnit: 'hour',
|
96
|
+
},
|
97
|
+
'1W': {
|
98
|
+
from: { days: 7 },
|
99
|
+
scaleUnit: 'day',
|
100
|
+
},
|
101
|
+
'1M': {
|
102
|
+
from: { days: 30 },
|
103
|
+
scaleUnit: 'week',
|
104
|
+
},
|
105
|
+
'3M': {
|
106
|
+
from: { days: 90 },
|
107
|
+
scaleUnit: 'month',
|
108
|
+
},
|
109
|
+
'6M': {
|
110
|
+
from: { days: 180 },
|
111
|
+
scaleUnit: 'month',
|
112
|
+
},
|
113
|
+
'1Y': {
|
114
|
+
from: { days: 365 },
|
115
|
+
scaleUnit: 'year',
|
116
|
+
},
|
117
|
+
'ALL': {
|
118
|
+
from: { days: 365 }, // not used
|
119
|
+
scaleUnit: 'year',
|
120
|
+
}
|
121
|
+
};
|
122
|
+
function GetPoints(data) {
|
123
|
+
const points = data.results[0].series[0].values.map((row) => {
|
124
|
+
return {
|
125
|
+
x: moment.unix(row[0]),
|
126
|
+
y: row[1]
|
127
|
+
};
|
128
|
+
});
|
129
|
+
return points;
|
130
|
+
}
|
131
|
+
function getPollTime(intervalInSeconds, pollTime) {
|
132
|
+
const CalculatedPollTime = Math.round(intervalInSeconds / 2880);
|
133
|
+
if (CalculatedPollTime <= pollTime) {
|
134
|
+
return pollTime + "s";
|
135
|
+
}
|
136
|
+
else {
|
137
|
+
return CalculatedPollTime + "s";
|
138
|
+
}
|
139
|
+
}
|
140
|
+
function getCsvData(data, measures) {
|
141
|
+
// Initialize the header with timestamp and measure names
|
142
|
+
const headers = ["timestamp", ...measures.map(measure => measure.name)];
|
143
|
+
const csvData = [];
|
144
|
+
// Add the header to the csvData
|
145
|
+
csvData.push(headers);
|
146
|
+
// Collect all timestamps and values for each measure
|
147
|
+
const timestampMap = {};
|
148
|
+
// Populate timestampMap with data for each timestamp
|
149
|
+
data.forEach((obj) => {
|
150
|
+
obj.results[0].series.forEach((series) => {
|
151
|
+
series.values.forEach((value) => {
|
152
|
+
const timestamp = moment.unix(value[0]).toISOString();
|
153
|
+
if (!timestampMap[timestamp]) {
|
154
|
+
timestampMap[timestamp] = { timestamp: timestamp };
|
155
|
+
}
|
156
|
+
const measureName = series.name; // Assuming the series name corresponds to the measure name
|
157
|
+
if (measures.some(measure => measure.name === measureName)) {
|
158
|
+
timestampMap[timestamp][measureName] = value[1]; // Assign the value to the measure
|
159
|
+
}
|
160
|
+
});
|
161
|
+
});
|
162
|
+
});
|
163
|
+
// Create rows from timestampMap
|
164
|
+
Object.values(timestampMap).forEach((entry) => {
|
165
|
+
const row = [entry.timestamp];
|
166
|
+
measures.forEach(measure => {
|
167
|
+
// Push the corresponding value or an empty string if undefined
|
168
|
+
row.push(entry[measure.name] !== undefined ? entry[measure.name] : "");
|
169
|
+
});
|
170
|
+
// Check if the row contains only empty values (besides the timestamp)
|
171
|
+
const hasNonEmptyValues = row.slice(1).some(value => value !== null && value !== '' && value !== undefined);
|
172
|
+
// If the row has at least one non-empty value or just the timestamp, add it to csvData
|
173
|
+
if (hasNonEmptyValues || row.length === 1) {
|
174
|
+
csvData.push(row);
|
175
|
+
}
|
176
|
+
});
|
177
|
+
// Join rows into a CSV string
|
178
|
+
return csvData.map(row => row.join(',')).join('\n');
|
179
|
+
}
|
180
|
+
// eslint-disable-next-line no-unused-vars
|
181
|
+
const TrendChart = ({ deviceId, measures, enableExportData, enableDatePicker, handleGetInfluxData, handleGetFirstTimestamp, theme, ...props }) => {
|
182
|
+
const [chartJsLoaded, setChartJsLoaded] = useState(false);
|
183
|
+
const [dataMeasures, setDataMeasures] = useState(null);
|
184
|
+
const [chartPeriod, setChartPeriod] = useState('1D');
|
185
|
+
const [chartPeriodConfig, setChartPeriodConfig] = useState(chartConfigByPeriod['1D']);
|
186
|
+
const [chartLoading, setChartLoading] = useState(false);
|
187
|
+
const [timeStart, setTimeStart] = useState(moment().subtract(1, 'day').unix());
|
188
|
+
const [timeEnd, setTimeEnd] = useState(moment().unix());
|
189
|
+
const [firstTimestamp, setFirstTimestamp] = useState();
|
190
|
+
const [datePickerUsed, setDatePickerUsed] = useState(false);
|
191
|
+
const [loadingButton, setLoadingButton] = useState(false);
|
192
|
+
const csvLinkRef = useRef(null);
|
193
|
+
const [csvData, setCsvData] = useState('');
|
194
|
+
const [options, setOptions] = useState({
|
195
|
+
...lineOptions,
|
196
|
+
plugins: {
|
197
|
+
...lineOptions.plugins,
|
198
|
+
legend: {
|
199
|
+
display: enableExportData ? true : false,
|
200
|
+
position: 'bottom', // Legenda sotto il grafico
|
201
|
+
},
|
202
|
+
zoom: {
|
203
|
+
// @ts-ignore
|
204
|
+
zoom: {
|
205
|
+
...lineOptions.plugins.zoom.zoom,
|
206
|
+
onZoom: async (chart) => {
|
207
|
+
setChartLoading(true);
|
208
|
+
setZoomed(true);
|
209
|
+
setTimeStart(Math.round(moment(chart.chart.scales.x.min).valueOf() / 1000));
|
210
|
+
setTimeEnd(Math.round(moment(chart.chart.scales.x.max).valueOf() / 1000));
|
211
|
+
}
|
212
|
+
}
|
213
|
+
}
|
214
|
+
}
|
215
|
+
});
|
216
|
+
const [zoomed, setZoomed] = useState(false);
|
217
|
+
const resetChart = () => {
|
218
|
+
setOptions({
|
219
|
+
...options,
|
220
|
+
scales: {
|
221
|
+
...options.scales,
|
222
|
+
y: {
|
223
|
+
...options.scales.y,
|
224
|
+
min: 0,
|
225
|
+
max: 1
|
226
|
+
}
|
227
|
+
}
|
228
|
+
});
|
229
|
+
setDataMeasures([...[]]);
|
230
|
+
setCsvData("");
|
231
|
+
};
|
232
|
+
const handleChange = (event, newPeriod) => {
|
233
|
+
setZoomed(false);
|
234
|
+
setDatePickerUsed(false);
|
235
|
+
setCsvData('');
|
236
|
+
if (newPeriod === "ALL") {
|
237
|
+
setTimeStart(firstTimestamp - 86400);
|
238
|
+
setTimeEnd(moment().unix());
|
239
|
+
setChartPeriod(newPeriod);
|
240
|
+
return;
|
241
|
+
}
|
242
|
+
const periodConfig = chartConfigByPeriod[newPeriod];
|
243
|
+
if (periodConfig) {
|
244
|
+
setTimeStart(Math.round(moment().subtract(periodConfig.from).valueOf() / 1000));
|
245
|
+
setTimeEnd(Math.round(moment().valueOf() / 1000));
|
246
|
+
setChartPeriod(newPeriod);
|
247
|
+
setChartPeriodConfig(periodConfig);
|
248
|
+
}
|
249
|
+
};
|
250
|
+
const handleExportData = async () => {
|
251
|
+
setLoadingButton(true);
|
252
|
+
const intervalInSeconds = timeEnd - timeStart;
|
253
|
+
const data = await Promise.all(measures.map(async (measure) => {
|
254
|
+
const polltime = getPollTime(intervalInSeconds, measure.polltime);
|
255
|
+
return await handleGetInfluxData(measure.name, timeStart, timeEnd, deviceId, polltime, true);
|
256
|
+
}));
|
257
|
+
const csvData = getCsvData(data, measures);
|
258
|
+
setCsvData(csvData);
|
259
|
+
setLoadingButton(false);
|
260
|
+
};
|
261
|
+
useEffect(() => {
|
262
|
+
if (csvData.length > 0) {
|
263
|
+
csvLinkRef.current?.link.click();
|
264
|
+
}
|
265
|
+
}, [csvData]);
|
266
|
+
const loadDatasets = async (chartPeriod) => {
|
267
|
+
setChartLoading(true);
|
268
|
+
const intervalInSeconds = timeEnd - timeStart;
|
269
|
+
// Inizializza un array di promesse per ottenere i dati per ciascuna misura
|
270
|
+
const datasetsPromises = measures.map(async (measure) => {
|
271
|
+
const polltime = getPollTime(intervalInSeconds, measure.polltime);
|
272
|
+
const influxData = await handleGetInfluxData(measure.name, timeStart, timeEnd, deviceId, polltime, false);
|
273
|
+
const points = GetPoints(influxData);
|
274
|
+
return {
|
275
|
+
label: measure.name,
|
276
|
+
data: points,
|
277
|
+
unit: measure.unit,
|
278
|
+
// borderColor: `hsl(${index * 50}, 70%, 50%)`, // Colore unico per ogni dataset
|
279
|
+
borderWidth: 2,
|
280
|
+
pointRadius: 1,
|
281
|
+
pointHoverRadius: 5,
|
282
|
+
pointHoverBackgroundColor: 'rgba(52, 125, 236, 0.5)',
|
283
|
+
spanGaps: false,
|
284
|
+
fill: false
|
285
|
+
};
|
286
|
+
});
|
287
|
+
// Risolvi tutte le promesse per popolare i dataset
|
288
|
+
const datasets = await Promise.all(datasetsPromises);
|
289
|
+
let min = null;
|
290
|
+
let max = null;
|
291
|
+
let values = [];
|
292
|
+
datasets.forEach(dataset => {
|
293
|
+
values = [...values, ...dataset.data.map((point) => point.y).filter((data) => data !== null)];
|
294
|
+
const datasetMin = Math.min(...values);
|
295
|
+
const datasetMax = Math.max(...values);
|
296
|
+
if (min === null || datasetMin < min)
|
297
|
+
min = datasetMin;
|
298
|
+
if (max === null || datasetMax > max)
|
299
|
+
max = datasetMax;
|
300
|
+
});
|
301
|
+
let paddedMin = null;
|
302
|
+
let paddedMax = null;
|
303
|
+
if (min !== null && max !== null) {
|
304
|
+
const diff = Math.round((max - min) / 3) || 1;
|
305
|
+
paddedMin = min - diff;
|
306
|
+
paddedMax = max + diff;
|
307
|
+
}
|
308
|
+
setDataMeasures([...datasets]);
|
309
|
+
setOptions({
|
310
|
+
...options,
|
311
|
+
scales: {
|
312
|
+
...options.scales,
|
313
|
+
y: {
|
314
|
+
...options.scales.y,
|
315
|
+
min: paddedMin,
|
316
|
+
max: paddedMax,
|
317
|
+
},
|
318
|
+
x: {
|
319
|
+
...options.scales.x,
|
320
|
+
min: moment.unix(timeStart).toString(),
|
321
|
+
max: moment.unix(timeEnd).toString(),
|
322
|
+
time: {
|
323
|
+
...options.scales.x.time,
|
324
|
+
unit: chartPeriod.scaleUnit
|
325
|
+
}
|
326
|
+
}
|
327
|
+
}
|
328
|
+
});
|
329
|
+
};
|
330
|
+
useEffect(() => {
|
331
|
+
const fetchFirstTimestamp = async () => {
|
332
|
+
const response = await handleGetFirstTimestamp(deviceId);
|
333
|
+
if (response) {
|
334
|
+
setFirstTimestamp(response);
|
335
|
+
}
|
336
|
+
};
|
337
|
+
fetchFirstTimestamp();
|
338
|
+
}, []);
|
339
|
+
useEffect(() => {
|
340
|
+
const timeDifference = Math.abs(moment(timeStart).valueOf() - moment(timeEnd).valueOf()); // Convert milliseconds to seconds
|
341
|
+
let newChartPeriod = '1D'; // Default to 1 day
|
342
|
+
if (timeDifference < 86400) { // Less than 1 day
|
343
|
+
newChartPeriod = '1H'; // Set to 1 day
|
344
|
+
}
|
345
|
+
else if (timeDifference < 604800) { // Less than 1 week
|
346
|
+
newChartPeriod = '1D'; // Set to 1 day
|
347
|
+
}
|
348
|
+
else if (timeDifference < 2629800) { // Less than 1 month
|
349
|
+
newChartPeriod = '1W'; // Set to 1 week
|
350
|
+
}
|
351
|
+
else if (timeDifference < 7889400) { // Less than 3 months
|
352
|
+
newChartPeriod = '1M'; // Set to 1 month
|
353
|
+
}
|
354
|
+
else if (timeDifference < 15778440) { // Less than 6 months
|
355
|
+
newChartPeriod = '3M'; // Set to 3 months
|
356
|
+
}
|
357
|
+
else if (timeDifference < 31556926) { // Less than 1 year
|
358
|
+
newChartPeriod = '6M'; // Set to 6 months
|
359
|
+
}
|
360
|
+
else {
|
361
|
+
newChartPeriod = '1Y'; // Set to 1 year
|
362
|
+
}
|
363
|
+
setChartPeriodConfig(chartConfigByPeriod[newChartPeriod]);
|
364
|
+
loadDatasets(chartPeriod).then(() => {
|
365
|
+
setChartLoading(false);
|
366
|
+
});
|
367
|
+
}, [timeEnd, timeStart]);
|
368
|
+
useEffect(() => {
|
369
|
+
const loadZoomPlugin = async () => {
|
370
|
+
const zoomPlugin = (await import('chartjs-plugin-zoom')).default;
|
371
|
+
ChartJS.register(Colors, Legend, Title, Tooltip, PointElement, LineElement, LinearScale, TimeScale, annotationPlugin, zoomPlugin, {
|
372
|
+
id: 'uniqueid5',
|
373
|
+
afterDraw: function (chart) {
|
374
|
+
if (chart.tooltip?._active && chart.tooltip?._active.length) {
|
375
|
+
const activePoint = chart.tooltip._active[0];
|
376
|
+
const ctx = chart.ctx;
|
377
|
+
const x = activePoint.element.x;
|
378
|
+
const topY = chart.scales.y.top;
|
379
|
+
const bottomY = chart.scales.y.bottom;
|
380
|
+
ctx.save();
|
381
|
+
ctx.beginPath();
|
382
|
+
ctx.moveTo(x, topY);
|
383
|
+
ctx.lineTo(x, bottomY);
|
384
|
+
ctx.lineWidth = 1;
|
385
|
+
ctx.strokeStyle = '#722257';
|
386
|
+
ctx.stroke();
|
387
|
+
ctx.restore();
|
388
|
+
}
|
389
|
+
}
|
390
|
+
});
|
391
|
+
setChartJsLoaded(true);
|
392
|
+
};
|
393
|
+
loadZoomPlugin();
|
394
|
+
resetChart();
|
395
|
+
}, []);
|
396
|
+
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),
|
397
|
+
// format="DD/MM/YY hh:mm A"
|
398
|
+
onChange: (newValue) => {
|
399
|
+
setTimeStart((moment(newValue).unix()));
|
400
|
+
}, maxDate: moment(timeEnd * 1000), slotProps: {
|
401
|
+
textField: { size: 'small', sx: { width: { sm: 210 } } }
|
402
|
+
} }), " \u00A0\u00A0 ", _jsx(Box, { children: "\u2013" }), " \u00A0\u00A0", _jsx(DateTimePicker, { value: moment(timeEnd * 1000),
|
403
|
+
// format="DD/MM/YY hh:mm A"
|
404
|
+
onChange: (newValue) => {
|
405
|
+
setTimeEnd((moment(newValue).unix()));
|
406
|
+
}, minDate: moment(timeStart * 1000), slotProps: {
|
407
|
+
textField: { size: 'small', sx: { width: { sm: 210 } } }
|
408
|
+
} })] }) });
|
409
|
+
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, { children: [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
|
410
|
+
//@ts-ignore
|
411
|
+
, {
|
412
|
+
//@ts-ignore
|
413
|
+
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: () => {
|
414
|
+
if (datePickerUsed) {
|
415
|
+
setZoomed(false);
|
416
|
+
}
|
417
|
+
else if (chartPeriod === "ALL") {
|
418
|
+
setZoomed(false);
|
419
|
+
setTimeEnd(moment().unix());
|
420
|
+
setTimeStart(firstTimestamp - 86400);
|
421
|
+
}
|
422
|
+
else {
|
423
|
+
setZoomed(false);
|
424
|
+
setChartPeriodConfig(chartConfigByPeriod[chartPeriod]);
|
425
|
+
setTimeStart(chartPeriodConfig.from?.days
|
426
|
+
? moment()
|
427
|
+
.subtract(chartConfigByPeriod[chartPeriod].from.days, 'days')
|
428
|
+
.unix()
|
429
|
+
: moment().subtract(1, 'day').unix());
|
430
|
+
setTimeEnd(moment().unix());
|
431
|
+
}
|
432
|
+
}, 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",
|
433
|
+
// sx={{ boxShadow: 1 }}
|
434
|
+
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(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' ?
|
435
|
+
_jsx(_Fragment, { children: dataMeasures && dataMeasures[0]?.data?.length ?
|
436
|
+
(_jsx(Line, { options: options, data: {
|
437
|
+
datasets: dataMeasures || [{ data: [] }]
|
438
|
+
} })) : _jsxs(Box, { sx: {
|
439
|
+
display: 'flex',
|
440
|
+
flexDirection: 'column',
|
441
|
+
alignItems: 'center',
|
442
|
+
justifyContent: 'center',
|
443
|
+
textAlign: 'center',
|
444
|
+
height: '100%',
|
445
|
+
}, 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: {
|
446
|
+
display: 'flex',
|
447
|
+
flexDirection: 'column',
|
448
|
+
alignItems: 'center',
|
449
|
+
justifyContent: 'center',
|
450
|
+
textAlign: 'center',
|
451
|
+
height: '100%'
|
452
|
+
}, children: _jsx(CircularProgress, {}) })) })] }));
|
453
|
+
};
|
454
|
+
export default TrendChart;
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default as TrendChart } from "./TrendChart";
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default as TrendChart } from "./TrendChart";
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { UserType } from '../../types';
|
2
|
+
declare const GroupUpdate: ({ userInfo, groupInfo, usersGroup, usersList, devicesList, handleAddUserToGroup, handleRemoveUserFromGroup, handleGetGroups, handleUpdateGroup, handleUpdateDevice, handleDeleteGroup, container, containerProps, afterUpdateCallback, afterRemoveCallback, confirmMui }: {
|
3
|
+
userInfo: UserType;
|
4
|
+
groupInfo: any;
|
5
|
+
usersGroup: any[];
|
6
|
+
usersList: any[];
|
7
|
+
devicesList: {
|
8
|
+
id: string;
|
9
|
+
state: any;
|
10
|
+
managers: any[];
|
11
|
+
}[];
|
12
|
+
handleAddUserToGroup: (groupID: string, userName: string, userID: string) => Promise<any>;
|
13
|
+
handleRemoveUserFromGroup: (groupID: string, userID: string) => Promise<any>;
|
14
|
+
handleUpdateDevice: (id: string, body: any, user: UserType) => Promise<any>;
|
15
|
+
handleGetGroups: (productID: number, userInfo?: UserType) => Promise<any>;
|
16
|
+
handleUpdateGroup: (id: string, group: any) => Promise<void>;
|
17
|
+
handleDeleteGroup: (id: string) => Promise<void>;
|
18
|
+
container?: "Box" | "Card";
|
19
|
+
containerProps?: any;
|
20
|
+
afterUpdateCallback?: (groupInfo: any) => Promise<void>;
|
21
|
+
afterRemoveCallback?: () => Promise<void>;
|
22
|
+
confirmMui?: (options?: any) => Promise<void>;
|
23
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
24
|
+
export default GroupUpdate;
|
@@ -0,0 +1,134 @@
|
|
1
|
+
"use client";
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
3
|
+
import { Box, Typography, Card, CardContent, TableBody } from '@mui/material';
|
4
|
+
import Grid from '@mui/material/Grid2';
|
5
|
+
import { useState } from 'react';
|
6
|
+
import { FormControl, TextField, Autocomplete, Alert, Table, TableRow, TableCell } from '@mui/material';
|
7
|
+
import AddIcon from '@mui/icons-material/Add';
|
8
|
+
import CloseIcon from '@mui/icons-material/Close';
|
9
|
+
import DeleteIcon from '@mui/icons-material/Delete';
|
10
|
+
import { LoadingButton } from '@mui/lab';
|
11
|
+
const GroupUpdate = ({ userInfo, groupInfo, usersGroup, usersList, devicesList, handleAddUserToGroup, handleRemoveUserFromGroup, handleGetGroups, handleUpdateGroup, handleUpdateDevice, handleDeleteGroup, container = 'Box', containerProps = {}, afterUpdateCallback, afterRemoveCallback, confirmMui }) => {
|
12
|
+
const [group, setGroup] = useState(groupInfo);
|
13
|
+
const [loadingUpdateButton, setLoadingUpdateButton] = useState(false);
|
14
|
+
const [loadingRemoveUserButton, setLoadingRemoveUserButton] = useState(false);
|
15
|
+
const [loadingAddUserButton, setLoadingAddUserButton] = useState(false);
|
16
|
+
const [loadingDeleteGroupButton, setLoadingDeleteGroupButton] = useState(false);
|
17
|
+
const [selectedUser, setSelectedUser] = useState(null);
|
18
|
+
const [error, setError] = useState(null);
|
19
|
+
const [success, setSuccess] = useState(null);
|
20
|
+
function handleChange(event) {
|
21
|
+
setGroup({ ...group, [event.target.name]: event.target.value });
|
22
|
+
}
|
23
|
+
const removeUserFromCurrentGroup = async (userId) => {
|
24
|
+
setLoadingRemoveUserButton(true);
|
25
|
+
try {
|
26
|
+
const response = await handleRemoveUserFromGroup(group.id, userId);
|
27
|
+
if (response) {
|
28
|
+
if (devicesList.length > 0) {
|
29
|
+
// should check if device is present in other groups the user is member of
|
30
|
+
const groupsData = await handleGetGroups(1008, { role: 'support', uid: userId });
|
31
|
+
const userGroupIds = groupsData.map((group) => group.id);
|
32
|
+
// for all devices in the group set the selectedUser as manager
|
33
|
+
for (const device of devicesList) {
|
34
|
+
// check device is in a group of user is member of
|
35
|
+
const found = device.state.groups.some((gID) => userGroupIds.includes(gID));
|
36
|
+
if (!found) {
|
37
|
+
// remove user from managers
|
38
|
+
const managers = device.managers && device.managers.filter(man => man !== userId) || [];
|
39
|
+
await handleUpdateDevice(device.id, {
|
40
|
+
managers
|
41
|
+
}, userInfo);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
if (afterUpdateCallback) {
|
46
|
+
await afterUpdateCallback(group);
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
catch (err) {
|
51
|
+
console.log(err);
|
52
|
+
}
|
53
|
+
finally {
|
54
|
+
setLoadingRemoveUserButton(false);
|
55
|
+
}
|
56
|
+
};
|
57
|
+
const addUserToCurrentGroup = async () => {
|
58
|
+
setLoadingAddUserButton(true);
|
59
|
+
if (group && selectedUser) {
|
60
|
+
try {
|
61
|
+
const response = await handleAddUserToGroup(group.id, selectedUser.label, selectedUser.id);
|
62
|
+
if (response) {
|
63
|
+
if (devicesList.length > 0) {
|
64
|
+
for (const device of devicesList) {
|
65
|
+
// for all devices in the group set the selectedUser as manager
|
66
|
+
await handleUpdateDevice(device.id, {
|
67
|
+
managers: [
|
68
|
+
...(device?.managers || []),
|
69
|
+
selectedUser.id
|
70
|
+
]
|
71
|
+
}, userInfo);
|
72
|
+
}
|
73
|
+
}
|
74
|
+
if (afterUpdateCallback) {
|
75
|
+
await afterUpdateCallback(group);
|
76
|
+
}
|
77
|
+
setSelectedUser(null);
|
78
|
+
}
|
79
|
+
}
|
80
|
+
catch (err) {
|
81
|
+
console.log(err);
|
82
|
+
}
|
83
|
+
finally {
|
84
|
+
setLoadingAddUserButton(false);
|
85
|
+
}
|
86
|
+
}
|
87
|
+
else {
|
88
|
+
console.log("No user");
|
89
|
+
}
|
90
|
+
};
|
91
|
+
const updateGroup = async () => {
|
92
|
+
setLoadingUpdateButton(true);
|
93
|
+
try {
|
94
|
+
await handleUpdateGroup(group.id, group);
|
95
|
+
if (afterUpdateCallback) {
|
96
|
+
afterUpdateCallback(group);
|
97
|
+
}
|
98
|
+
}
|
99
|
+
catch (err) {
|
100
|
+
console.error(err);
|
101
|
+
}
|
102
|
+
setLoadingUpdateButton(false);
|
103
|
+
};
|
104
|
+
const deleteGroup = async () => {
|
105
|
+
try {
|
106
|
+
if (confirmMui) {
|
107
|
+
await confirmMui({ description: "Are you sure you want to delete the group?", confirmationText: "Yes, remove it", confirmationButtonProps: { variant: 'contained', color: 'error' } });
|
108
|
+
}
|
109
|
+
else {
|
110
|
+
confirm("Are you sure you want to delete the group?");
|
111
|
+
}
|
112
|
+
setLoadingDeleteGroupButton(true);
|
113
|
+
await handleDeleteGroup(group.id);
|
114
|
+
if (afterRemoveCallback) {
|
115
|
+
afterRemoveCallback();
|
116
|
+
}
|
117
|
+
}
|
118
|
+
catch (err) {
|
119
|
+
console.error(err);
|
120
|
+
}
|
121
|
+
finally {
|
122
|
+
setLoadingDeleteGroupButton(false);
|
123
|
+
}
|
124
|
+
};
|
125
|
+
const renderGroupUpdate = () => {
|
126
|
+
return (_jsxs(Box, { component: "div", children: [_jsx(Grid, { container: true, spacing: 2, children: _jsx(Grid, { size: { xs: 12, md: 8 }, children: _jsxs(Box, { component: "div", children: [_jsx(FormControl, { fullWidth: true, children: _jsx(TextField, { sx: { flexGrow: 1 }, label: "Name", value: group.name, name: "name", onChange: handleChange }) }), _jsx(FormControl, { fullWidth: true, sx: { mt: 2 }, children: _jsx(TextField, { sx: { flexGrow: 1 }, label: "Description", value: group.description, name: "description", multiline: true, rows: 4, onChange: handleChange }) }), _jsx(LoadingButton, { variant: "contained", color: "primary", loading: loadingUpdateButton, sx: { mt: 2 }, onClick: () => updateGroup(), children: "Update" })] }) }) }), _jsx(Grid, { container: true, spacing: 2, sx: { mt: 4 }, children: _jsxs(Grid, { size: { xs: 12, md: 8 }, children: [_jsx(Typography, { variant: 'body1', sx: { fontWeight: 'bold' }, children: "Add members" }), _jsx(Box, { component: "div", sx: { mt: 2 }, children: usersGroup && usersGroup.length > 0 ?
|
127
|
+
_jsx(Table, { size: "small", children: _jsx(TableBody, { children: usersGroup.map((ug) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx(Typography, { variant: "body1", children: ug.user.fullName }) }), _jsx(TableCell, { align: 'right', children: _jsx(LoadingButton, { size: "small", color: "error", loading: loadingRemoveUserButton, onClick: () => removeUserFromCurrentGroup(ug.user.userId), children: _jsx(CloseIcon, { sx: { m: 0, p: 0 }, fontSize: "small" }) }) })] }, ug.id))) }) })
|
128
|
+
: _jsx(Box, { component: 'div', sx: { mt: 2 }, children: "No members found" }) }), _jsxs(Box, { component: "div", sx: { mt: 4, display: 'flex', alignItems: 'center' }, children: [_jsx(Autocomplete, { fullWidth: true, disablePortal: true, options: usersList || [], value: selectedUser, size: 'small', onChange: (event, newValue) => {
|
129
|
+
setSelectedUser(newValue);
|
130
|
+
}, renderInput: (params) => _jsx(TextField, { ...params, label: "Select user" }) }), _jsxs(LoadingButton, { variant: "contained", color: "success", loading: loadingAddUserButton, sx: { ml: 2 }, disabled: !selectedUser, onClick: () => addUserToCurrentGroup(), children: [_jsx(AddIcon, { sx: { mr: 1 } }), " Add"] })] }), _jsxs(Box, { component: "div", sx: { mt: 4 }, children: [_jsx(Typography, { variant: 'body1', sx: { fontWeight: 'bold' }, children: "Delete group" }), _jsx(Alert, { severity: "error", sx: { mt: 2 }, action: _jsxs(LoadingButton, { variant: "contained", disabled: devicesList.length > 0, color: "error", loading: loadingDeleteGroupButton, onClick: () => deleteGroup(), size: 'small', children: [_jsx(DeleteIcon, { fontSize: "small", sx: { mr: 1 } }), " Delete"] }), children: devicesList.length === 0 ? 'This action cannot be undone' : 'You must manually remove devices from group before deletion' })] })] }) })] }));
|
131
|
+
};
|
132
|
+
return (container === "Card" ? (_jsx(Card, { ...containerProps, children: _jsx(CardContent, { children: renderGroupUpdate() }) })) : (_jsx(Box, { ...containerProps, children: renderGroupUpdate() })));
|
133
|
+
};
|
134
|
+
export default GroupUpdate;
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import { UserType } from '../../types/user';
|
2
|
+
import { Position } from "./Map";
|
3
|
+
import { Theme } from "@emotion/react";
|
4
|
+
declare const GroupsDevices: ({ userInfo, handleGetUsersList, handleAddUserToGroup, handleRemoveUserFromGroup, handleGetGroups, handleGetUsersGroup, handleGetDevices, handleUpdateDevice, handleGetPositions, handleAddDevicesToGroup, handleRemoveDevicesFromGroup, handleCreateGroup, handleDeleteGroup, handleUpdateGroup, group, columnsArray, containerDataGrid, props, propsDatagrid, loadingComponent, containerProps, enableMaps, mapsHeight, mapsClickCallback, theme, confirmMui }: {
|
5
|
+
userInfo: UserType;
|
6
|
+
handleGetUsersList: ({ page, pageSize }: {
|
7
|
+
page: number;
|
8
|
+
pageSize: number;
|
9
|
+
}) => Promise<{
|
10
|
+
users: UserType[];
|
11
|
+
}>;
|
12
|
+
handleAddUserToGroup: (groupID: string, userName: string, userID: string) => Promise<any>;
|
13
|
+
handleRemoveUserFromGroup: (groupID: string, userID: string) => Promise<any>;
|
14
|
+
handleGetGroups: (productID: number, userInfo?: UserType) => Promise<any>;
|
15
|
+
handleGetUsersGroup: (groupID: string) => Promise<any>;
|
16
|
+
handleCreateGroup: (group: any) => Promise<any>;
|
17
|
+
handleGetDevices: (user: UserType, group: string, selected: string) => Promise<any>;
|
18
|
+
handleGetPositions: (devices: any) => Promise<any>;
|
19
|
+
handleAddDevicesToGroup: (user: UserType, group: string, devicesToPatch: any[]) => Promise<any>;
|
20
|
+
handleRemoveDevicesFromGroup: (user: UserType, group: string, devicesToPatch: any[]) => Promise<any>;
|
21
|
+
handleDeleteGroup: (id: string) => Promise<void>;
|
22
|
+
handleUpdateGroup: (id: string, group: any) => Promise<void>;
|
23
|
+
handleUpdateDevice: (id: string, body: any, user: UserType) => Promise<any>;
|
24
|
+
group: string;
|
25
|
+
columnsArray: any[];
|
26
|
+
containerDataGrid?: "Card" | "Box";
|
27
|
+
props?: any;
|
28
|
+
propsDatagrid?: any;
|
29
|
+
loadingComponent?: React.ReactNode;
|
30
|
+
containerProps?: any;
|
31
|
+
enableMaps?: boolean;
|
32
|
+
mapsHeight: string;
|
33
|
+
mapsClickCallback?: (position: Position) => void;
|
34
|
+
theme: Theme;
|
35
|
+
confirmMui?: (options?: any) => Promise<void>;
|
36
|
+
}) => string | number | bigint | boolean | import("react/jsx-runtime").JSX.Element | Iterable<import("react").ReactNode> | Promise<import("react").AwaitedReactNode> | null;
|
37
|
+
export default GroupsDevices;
|