@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.
Files changed (47) hide show
  1. package/components/accounts/AccountMenu.d.ts +9 -0
  2. package/components/accounts/AccountMenu.js +37 -0
  3. package/components/accounts/AccountProfile.d.ts +15 -0
  4. package/components/accounts/AccountProfile.js +153 -0
  5. package/components/accounts/index.d.ts +2 -0
  6. package/components/accounts/index.js +2 -0
  7. package/components/charts/TrendChart.d.ts +17 -0
  8. package/components/charts/TrendChart.js +454 -0
  9. package/components/charts/index.d.ts +1 -0
  10. package/components/charts/index.js +1 -0
  11. package/components/groups/GroupUpdate.d.ts +24 -0
  12. package/components/groups/GroupUpdate.js +134 -0
  13. package/components/groups/GroupsDevices.d.ts +37 -0
  14. package/components/groups/GroupsDevices.js +299 -0
  15. package/components/groups/Map.d.ts +14 -0
  16. package/components/groups/Map.js +17 -0
  17. package/components/groups/index.d.ts +3 -0
  18. package/components/groups/index.js +3 -0
  19. package/components/index.d.ts +5 -0
  20. package/components/index.js +5 -0
  21. package/components/settings/DynamicMenu.d.ts +17 -0
  22. package/components/settings/DynamicMenu.js +42 -0
  23. package/components/settings/index.d.ts +1 -0
  24. package/components/settings/index.js +1 -0
  25. package/components/users/UserUpdate.d.ts +11 -0
  26. package/components/users/UserUpdate.js +26 -0
  27. package/components/users/UsersDataGrid.d.ts +23 -0
  28. package/components/users/UsersDataGrid.js +76 -0
  29. package/components/users/index.d.ts +2 -0
  30. package/components/users/index.js +2 -0
  31. package/index.d.ts +3 -0
  32. package/index.js +4 -0
  33. package/package.json +45 -0
  34. package/server-actions/groups.d.ts +22 -0
  35. package/server-actions/groups.js +109 -0
  36. package/server-actions/index.d.ts +4 -0
  37. package/server-actions/index.js +5 -0
  38. package/server-actions/influx.d.ts +13 -0
  39. package/server-actions/influx.js +145 -0
  40. package/server-actions/logto.d.ts +39 -0
  41. package/server-actions/logto.js +194 -0
  42. package/server-actions/trackle.d.ts +35 -0
  43. package/server-actions/trackle.js +158 -0
  44. package/types/index.d.ts +1 -0
  45. package/types/index.js +1 -0
  46. package/types/user.d.ts +12 -0
  47. 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;