@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,299 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Box, CircularProgress, Autocomplete, TextField, Input, IconButton, Button, Card, CardContent, CardHeader, Modal, Typography, Stack, Tab } from "@mui/material";
4
+ import { cloneElement, isValidElement, useEffect, useMemo, useState } from "react";
5
+ import AddIcon from '@mui/icons-material/Add';
6
+ import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
7
+ import EditIcon from '@mui/icons-material/Edit';
8
+ import SettingsIcon from '@mui/icons-material/Settings';
9
+ import ReadMoreIcon from '@mui/icons-material/ReadMore';
10
+ import PlaylistRemoveIcon from '@mui/icons-material/PlaylistRemove';
11
+ import CloseIcon from '@mui/icons-material/Close';
12
+ import { LoadingButton, TabContext, TabList, TabPanel } from "@mui/lab";
13
+ import { DataGrid, GridToolbar } from "@mui/x-data-grid";
14
+ import GroupUpdate from "./GroupUpdate";
15
+ import BackIcon from '@mui/icons-material/ArrowBack';
16
+ import dynamic from "next/dynamic";
17
+ import { ThemeProvider } from '@mui/material/styles';
18
+ const GroupsDevices = ({ userInfo, handleGetUsersList, handleAddUserToGroup, handleRemoveUserFromGroup, handleGetGroups, handleGetUsersGroup, handleGetDevices, handleUpdateDevice, handleGetPositions, handleAddDevicesToGroup, handleRemoveDevicesFromGroup, handleCreateGroup, handleDeleteGroup, handleUpdateGroup, group = 'all', columnsArray = [], containerDataGrid = 'Card', props = {}, propsDatagrid = {}, loadingComponent = _jsx(CircularProgress, {}), containerProps = {}, enableMaps = true, mapsHeight = '400px', mapsClickCallback = () => { }, theme, confirmMui }) => {
19
+ const [devices, setDevices] = useState(null);
20
+ const [positions, setPositions] = useState([]);
21
+ const [loadingDevices, setLoadingDevices] = useState(false);
22
+ const [loadingGroups, setLoadingGroups] = useState(false);
23
+ const [loadingAdd, setLoadingAdd] = useState(false);
24
+ const [editGroup, setEditGroup] = useState(false);
25
+ const [checkboxSelection, setCheckboxSelection] = useState(false);
26
+ const [devicesToAdd, setDevicesToAdd] = useState([]);
27
+ const [currentGroup, setCurrentGroup] = useState(group);
28
+ const [selectedGroup, setSelectedGroup] = useState(group);
29
+ const [groups, setGroups] = useState(null);
30
+ const [usersList, setUsersList] = useState([]);
31
+ const [usersGroup, setUsersGroup] = useState([]);
32
+ const [groupInfo, setGroupInfo] = useState(null);
33
+ const [openAdd, setOpenAdd] = useState(false);
34
+ const [groupName, setGroupName] = useState('');
35
+ const [description, setDescription] = useState('');
36
+ const [groupUpdateVisible, setGroupUpdateVisible] = useState(false);
37
+ const [valueTab, setValueTab] = useState('1');
38
+ const handleChangeTab = (event, newValue) => {
39
+ setValueTab(newValue);
40
+ };
41
+ const Map = useMemo(() => dynamic(() => import("./Map"), {
42
+ loading: () => isValidElement(loadingComponent) ? cloneElement(loadingComponent, { height: mapsHeight }) : null,
43
+ ssr: false
44
+ }), []);
45
+ const getUsersList = async () => {
46
+ try {
47
+ let page = 1;
48
+ const pageSize = 100;
49
+ let usersData = [];
50
+ let moreData = true;
51
+ while (moreData) {
52
+ const usersList = await handleGetUsersList({
53
+ page,
54
+ pageSize,
55
+ });
56
+ if (usersList.users && usersList.users.length > 0) {
57
+ usersData = [...usersData, ...usersList.users];
58
+ }
59
+ // Se il numero di utenti restituiti è minore di pageSize, non ci sono più pagine da caricare
60
+ moreData = usersList.users.length === pageSize;
61
+ page++;
62
+ }
63
+ const userGroupsUserIds = usersGroup.map((ug) => ug.user.userId);
64
+ setUsersList(usersData.filter((user) => user.role === 'support' && !userGroupsUserIds.includes(user.uid)).map((user) => {
65
+ const userName = user.name ? user.name.replace("/", " ") : "";
66
+ return { label: `${userName} (${user.email})`, id: user.uid };
67
+ }));
68
+ }
69
+ catch (err) {
70
+ console.log(err);
71
+ }
72
+ };
73
+ const fetchDevices = async (group, selected) => {
74
+ try {
75
+ setLoadingDevices(true);
76
+ const devices = await handleGetDevices(userInfo, group, selected);
77
+ setDevices(devices);
78
+ if (enableMaps) {
79
+ const positions = await handleGetPositions(devices);
80
+ setPositions(positions);
81
+ }
82
+ }
83
+ catch (error) {
84
+ console.log(error);
85
+ }
86
+ finally {
87
+ setLoadingDevices(false);
88
+ }
89
+ };
90
+ const getGroups = async () => {
91
+ try {
92
+ const responseData = await handleGetGroups(1008, userInfo);
93
+ setGroups(responseData);
94
+ }
95
+ catch (err) {
96
+ console.error(err);
97
+ }
98
+ };
99
+ const getUsersGroup = async (groupID) => {
100
+ try {
101
+ const responseData = await handleGetUsersGroup(groupID);
102
+ setUsersGroup(responseData);
103
+ }
104
+ catch (err) {
105
+ console.error(err);
106
+ }
107
+ };
108
+ useEffect(() => {
109
+ if (userInfo) {
110
+ getGroups();
111
+ }
112
+ }, [userInfo]);
113
+ useEffect(() => {
114
+ if (usersGroup) {
115
+ getUsersList();
116
+ }
117
+ }, [usersGroup]);
118
+ useEffect(() => {
119
+ if (currentGroup && !checkboxSelection) {
120
+ setSelectedGroup(currentGroup);
121
+ setGroupInfo(groups && groups.find((g) => g.id === currentGroup));
122
+ getUsersGroup(currentGroup);
123
+ fetchDevices(currentGroup, currentGroup);
124
+ }
125
+ }, [currentGroup]);
126
+ const handleSubmit = async (e) => {
127
+ e.preventDefault();
128
+ setLoadingAdd(true);
129
+ try {
130
+ const newGroup = {
131
+ name: groupName,
132
+ description,
133
+ productID: 1008, // Assuming this is the correct productID
134
+ };
135
+ const id = await handleCreateGroup(newGroup);
136
+ await getGroups();
137
+ setCurrentGroup(id);
138
+ setGroupInfo({ ...newGroup, id });
139
+ setSelectedGroup(id);
140
+ setDevicesToAdd([]), setEditGroup(!editGroup);
141
+ setCheckboxSelection(!checkboxSelection);
142
+ fetchDevices(id, id);
143
+ handleCloseAdd();
144
+ }
145
+ catch (error) {
146
+ console.error('Error creating group:', error);
147
+ // Handle error (e.g., show error message to user)
148
+ }
149
+ finally {
150
+ setLoadingAdd(false);
151
+ }
152
+ };
153
+ const handleChangeGroupSelect = (event, newValue) => {
154
+ setCurrentGroup(newValue?.id || 'all');
155
+ };
156
+ const handleOpenAdd = () => setOpenAdd(true);
157
+ const handleCloseAdd = () => {
158
+ setOpenAdd(false);
159
+ setGroupName('');
160
+ setDescription('');
161
+ };
162
+ const addDevicesToCurrentGroup = () => {
163
+ setGroupUpdateVisible(false);
164
+ setCheckboxSelection(true);
165
+ setDevicesToAdd([]);
166
+ setSelectedGroup(currentGroup);
167
+ if (currentGroup !== 'all') {
168
+ setCurrentGroup('all');
169
+ fetchDevices('all', currentGroup);
170
+ }
171
+ };
172
+ const addToSelectedGroup = async () => {
173
+ setLoadingGroups(true);
174
+ try {
175
+ const selectedData = devices.filter((row) => devicesToAdd.includes(row.id));
176
+ const devicesToPatch = selectedData?.map((device) => ({ id: device.id, groups: device.state.groups, managers: device.managers }));
177
+ const devicesPatched = await handleAddDevicesToGroup(userInfo, selectedGroup, devicesToPatch);
178
+ if (devicesPatched && devicesPatched.length > 0) {
179
+ // get all members of the group
180
+ const userIdsToAdd = usersGroup.map((ug) => ug.user.userId);
181
+ if (userIdsToAdd.length > 0) {
182
+ for (const device of devicesToPatch) {
183
+ // add members to devices managers
184
+ await handleUpdateDevice(device.id, {
185
+ managers: [
186
+ ...device.managers,
187
+ ...userIdsToAdd
188
+ ]
189
+ }, userInfo);
190
+ }
191
+ }
192
+ setLoadingGroups(false);
193
+ setCurrentGroup(selectedGroup);
194
+ setDevicesToAdd([...[]]);
195
+ fetchDevices(selectedGroup, selectedGroup);
196
+ }
197
+ }
198
+ catch (err) {
199
+ console.error(err);
200
+ }
201
+ finally {
202
+ setLoadingGroups(false);
203
+ }
204
+ };
205
+ const removeFromCurrentGroup = async () => {
206
+ setLoadingGroups(true);
207
+ try {
208
+ const devicesToRemove = devices.filter((row) => devicesToAdd.includes(row.id));
209
+ const devicesToPatch = devicesToRemove?.map((device) => ({ id: device.id, groups: device.state.groups, managers: device.managers }));
210
+ const devicesPatched = await handleRemoveDevicesFromGroup(userInfo, selectedGroup, devicesToPatch);
211
+ if (devicesPatched && devicesPatched.length > 0) {
212
+ for (const device of devicesToPatch) {
213
+ if (device.managers.length > 0) {
214
+ let managersToKeep = device.managers || [];
215
+ for (const manager of device.managers) {
216
+ // should check if device is present in other groups the user is member of
217
+ const groupsData = await handleGetGroups(1008, { role: 'support', uid: manager });
218
+ const userGroupIds = groupsData.map((group) => group.id);
219
+ const found = device.groups && device.groups.some((gID) => userGroupIds.includes(gID));
220
+ if (!found) {
221
+ // @ts-ignore
222
+ managersToKeep = managersToKeep.filter(man => man !== manager);
223
+ }
224
+ }
225
+ await handleUpdateDevice(device.id, {
226
+ managers: managersToKeep
227
+ }, userInfo);
228
+ }
229
+ }
230
+ setLoadingGroups(false);
231
+ setDevicesToAdd([]);
232
+ fetchDevices(currentGroup, selectedGroup);
233
+ }
234
+ }
235
+ catch (err) {
236
+ console.error(err);
237
+ }
238
+ finally {
239
+ setLoadingGroups(false);
240
+ }
241
+ };
242
+ const closeEditGroup = () => {
243
+ setCurrentGroup(selectedGroup);
244
+ setGroupUpdateVisible(false);
245
+ setDevicesToAdd([]);
246
+ setEditGroup(!editGroup);
247
+ setCheckboxSelection(!checkboxSelection);
248
+ };
249
+ const renderMaps = (_jsx(Map, { positions: positions, mapsClickCallback: mapsClickCallback, height: mapsHeight }));
250
+ const renderDataGrid = (_jsx("div", { style: { display: 'flex', flexDirection: 'column', minHeight: 323 }, children: _jsx(DataGrid, { checkboxSelection: checkboxSelection, onRowSelectionModelChange: (ids) => {
251
+ if (checkboxSelection) {
252
+ setDevicesToAdd(ids);
253
+ }
254
+ }, rowSelectionModel: devicesToAdd, disableDensitySelector: true, disableColumnSelector: true, disableRowSelectionOnClick: true, loading: loadingDevices, rows: devices, columns: checkboxSelection ? columnsArray.filter(col => col.field !== 'id') : columnsArray, slots: {
255
+ noRowsOverlay: () => (_jsx(Stack, { height: "100%", alignItems: "center", justifyContent: "center", children: currentGroup !== 'all' ? _jsxs(_Fragment, { children: [_jsx(Box, { children: "No devices in group" }), editGroup ? _jsxs(Button, { onClick: () => addDevicesToCurrentGroup(), variant: "outlined", color: "primary", size: "small", sx: { mt: 2 }, children: [_jsx(PlaylistAddIcon, { fontSize: "small", sx: { mr: 1 } }), " Add devices"] }) : ''] }) : "No devices" })),
256
+ noResultsOverlay: () => (_jsx(Stack, { height: "100%", alignItems: "center", justifyContent: "center", children: "Filter returns no result" })),
257
+ toolbar: GridToolbar,
258
+ }, slotProps: {
259
+ toolbar: {
260
+ showQuickFilter: true,
261
+ csvOptions: { disableToolbarButton: true },
262
+ printOptions: { disableToolbarButton: true },
263
+ },
264
+ }, initialState: {
265
+ sorting: {
266
+ sortModel: [{ field: 'online', sort: 'desc' }],
267
+ },
268
+ }, ...propsDatagrid }) }));
269
+ const renderTabContext = (_jsxs(TabContext, { value: valueTab, children: [_jsx(Box, { sx: { borderBottom: 1, borderColor: 'divider' }, children: _jsxs(TabList, { onChange: handleChangeTab, sx: { minHeight: 20 }, children: [_jsx(Tab, { label: "List", value: "1", sx: { minHeight: 20 } }), _jsx(Tab, { label: "Map", value: "2", sx: { minHeight: 20 } })] }) }), _jsx(TabPanel, { value: "1", sx: { p: 0 }, children: renderDataGrid }), _jsx(TabPanel, { value: "2", sx: { p: 0 }, children: renderMaps })] }));
270
+ const renderDatagridAndMaps = (enableMaps ?
271
+ containerDataGrid === 'Card' ?
272
+ _jsx(Card, { ...containerProps, children: renderTabContext }) : _jsx(Box, { ...containerProps, children: renderTabContext }) : containerDataGrid === 'Card' ?
273
+ _jsx(Card, { ...containerProps, children: renderDataGrid }) : _jsx(Box, { ...containerProps, children: renderDataGrid }));
274
+ if (!userInfo || !groups || !devices)
275
+ return loadingComponent;
276
+ return (_jsx(ThemeProvider, { theme: theme, children: _jsxs(Box, { ...props, children: [_jsxs(Box, { component: "div", sx: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1, backgroundColor: editGroup ? 'secondary.light' : '', borderRadius: 3, px: editGroup ? 2 : 0, py: 1, height: 50 }, children: [editGroup ? _jsxs(Typography, { variant: 'subtitle1', children: [_jsx(Box, { component: 'span', sx: { display: { xs: 'none' } }, children: "Edit group" }), " ", _jsx("b", { children: groupInfo?.name })] }) : '', groups && !checkboxSelection && !(editGroup && currentGroup === 'all') ?
277
+ _jsx(Autocomplete, { disablePortal: true, onChange: handleChangeGroupSelect, value: groupInfo?.name || 'All devices', disableClearable: currentGroup === 'all', isOptionEqualToValue: (o, v) => o.label === v, options: [{ label: 'All devices', id: 'all' }, ...groups.map((grp) => ({ label: grp.name, id: grp.id }))], sx: { width: { xs: 200, sm: 300 }, textAlign: 'left' }, size: 'small', renderInput: (params) => _jsx(TextField, { ...params, label: "Group" }) })
278
+ : '', userInfo.role === 'admin' &&
279
+ _jsxs(Box, { children: [checkboxSelection ?
280
+ _jsx(Input, { value: selectedGroup, type: 'hidden', size: 'small' })
281
+ : '', selectedGroup === 'all' ? _jsxs(Button, { variant: "contained", color: "primary", onClick: () => { handleOpenAdd(); }, children: [_jsx(AddIcon, { sx: { mr: 1 } }), _jsx(Box, { component: 'span', sx: { display: { xs: 'none', sm: 'inline' } }, children: "Create\u00A0" }), " group"] }) : '', editGroup ?
282
+ currentGroup === 'all' ?
283
+ checkboxSelection ? _jsxs(_Fragment, { children: [_jsxs(Button, { onClick: () => { setGroupUpdateVisible(false); setDevicesToAdd([]); setCurrentGroup(selectedGroup); setCheckboxSelection(true); fetchDevices(selectedGroup, selectedGroup); }, variant: "text", color: "inherit", size: "small", children: [_jsx(BackIcon, { fontSize: "small" }), _jsx(Box, { component: 'span', sx: { display: { xs: 'none', sm: 'inline' } }, children: "\u00A0Group devices" })] }), _jsxs(LoadingButton, { loading: loadingGroups, onClick: () => addToSelectedGroup(), variant: 'contained', size: "small", disabled: devicesToAdd.length === 0 || selectedGroup === 'all' || loadingGroups, sx: { ml: 2 }, children: [_jsx(ReadMoreIcon, { sx: { mr: 1 } }), " Add to group"] })] }) : ''
284
+ : devicesToAdd.length === 0 ? groupUpdateVisible ? _jsxs(Button, { onClick: () => setGroupUpdateVisible(false), sx: { ml: 2 }, variant: "text", color: "inherit", size: "small", children: [_jsx(BackIcon, { fontSize: "small" }), _jsx(Box, { component: 'span', sx: { display: { xs: 'none', sm: 'inline' } }, children: "\u00A0Group devices" })] }) :
285
+ _jsxs(_Fragment, { children: [_jsxs(Button, { onClick: () => addDevicesToCurrentGroup(), sx: { ml: 2 }, variant: "outlined", color: "primary", size: "small", children: [_jsx(PlaylistAddIcon, { fontSize: "small", sx: { mr: 1 } }), " Add devices"] }), _jsxs(Button, { onClick: () => setGroupUpdateVisible(true), sx: { ml: 2 }, variant: "outlined", color: "success", size: "small", children: [_jsx(SettingsIcon, { fontSize: "small" }), _jsx(Box, { component: 'span', sx: { display: { xs: 'none', sm: 'inline' } }, children: "\u00A0Manage group" })] })] })
286
+ : _jsxs(LoadingButton, { loading: loadingGroups, onClick: async () => { setLoadingGroups(true); await removeFromCurrentGroup(); }, variant: "contained", color: "error", size: "small", disabled: loadingGroups, sx: { ml: 2 }, children: [_jsx(PlaylistRemoveIcon, { fontSize: "small", sx: { mr: 1 } }), " Remove from group"] })
287
+ : '', selectedGroup !== 'all' ?
288
+ _jsx(_Fragment, { children: editGroup ?
289
+ _jsxs(Button, { variant: "text", color: "inherit", onClick: () => closeEditGroup(), sx: { ml: 2 }, children: [_jsx(CloseIcon, { fontSize: "small" }), _jsx(Box, { component: 'span', sx: { display: { xs: 'none', sm: 'inline' } }, children: "\u00A0Close edit" })] })
290
+ : _jsxs(Button, { variant: "contained", color: "secondary", onClick: () => { setDevicesToAdd([]); setEditGroup(!editGroup); setCheckboxSelection(!checkboxSelection); }, children: [_jsx(EditIcon, { sx: { mr: 1 } }), _jsx(Box, { component: 'span', sx: { display: { xs: 'none', sm: 'inline' } }, children: "Edit\u00A0" }), " group"] }) }) : ''] })] }), _jsx(Modal, { open: openAdd, onClose: handleCloseAdd, children: _jsxs(Card, { sx: {
291
+ position: 'absolute',
292
+ top: '50%',
293
+ left: '50%',
294
+ transform: 'translate(-50%, -50%)',
295
+ width: 400,
296
+ borderRadius: 4
297
+ }, children: [_jsx(CardHeader, { title: "New Group", action: _jsx(IconButton, { onClick: handleCloseAdd, children: _jsx(CloseIcon, {}) }) }), _jsx(CardContent, { children: _jsxs("form", { onSubmit: handleSubmit, children: [_jsx(TextField, { fullWidth: true, label: "Group Name", value: groupName, onChange: (e) => setGroupName(e.target.value), required: true }), _jsx(TextField, { fullWidth: true, label: "Description", value: description, onChange: (e) => setDescription(e.target.value), margin: "normal", multiline: true, rows: 4 }), _jsxs(Box, { mt: 2, display: "flex", justifyContent: "space-between", children: [_jsx(Button, { variant: "contained", color: "info", onClick: handleCloseAdd, children: "Cancel" }), _jsx(LoadingButton, { loading: loadingAdd, variant: "contained", color: "primary", type: "submit", children: "Create" })] })] }) })] }) }), !groupUpdateVisible && renderDatagridAndMaps, groupInfo && editGroup && groupUpdateVisible && _jsx(GroupUpdate, { confirmMui: confirmMui, userInfo: userInfo, usersGroup: usersGroup, usersList: usersList, handleGetGroups: handleGetGroups, handleUpdateDevice: handleUpdateDevice, handleAddUserToGroup: handleAddUserToGroup, handleRemoveUserFromGroup: handleRemoveUserFromGroup, groupInfo: groupInfo, afterUpdateCallback: async (groupInfo) => { setGroupInfo(groupInfo); await getGroups(); await getUsersGroup(groupInfo.id); }, afterRemoveCallback: async () => { closeEditGroup(); await getGroups(); setCurrentGroup('all'); }, container: 'Card', handleDeleteGroup: handleDeleteGroup, handleUpdateGroup: handleUpdateGroup, devicesList: devices.map((device) => ({ id: device.id, state: device.state, managers: device.managers })) })] }) }));
298
+ };
299
+ export default GroupsDevices;
@@ -0,0 +1,14 @@
1
+ import 'leaflet/dist/leaflet.css';
2
+ import "leaflet-defaulticon-compatibility";
3
+ import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
4
+ export type Position = {
5
+ deviceID: string;
6
+ name: string;
7
+ lat: number;
8
+ lng: number;
9
+ };
10
+ export default function Map({ positions, height, mapsClickCallback }: {
11
+ positions: Position[];
12
+ height: string;
13
+ mapsClickCallback: (position: Position) => void;
14
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { MapContainer, Marker, TileLayer, Popup } from "react-leaflet";
3
+ import L from 'leaflet';
4
+ import MarkerClusterGroup from 'react-leaflet-cluster';
5
+ import 'leaflet/dist/leaflet.css';
6
+ import "leaflet-defaulticon-compatibility";
7
+ import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
8
+ import { Box, Link } from "@mui/material";
9
+ export default function Map({ positions, height, mapsClickCallback = () => { } }) {
10
+ return positions.filter(pos => pos.lat !== null)?.length ? _jsxs(MapContainer, { bounds: L.latLngBounds(positions.filter(pos => pos.lat !== null)), zoom: 13, scrollWheelZoom: false, style: { width: '100%', height }, children: [_jsx(TileLayer, { attribution: '\u00A9 <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" }), _jsx(MarkerClusterGroup, { chunkedLoading: true, children: positions.filter(pos => pos.lat !== null).map((position) => (_jsx(Marker, { position: [position.lat, position.lng], children: _jsx(Popup, { children: _jsxs(Link, { href: "#", onClick: () => mapsClickCallback(position), children: [_jsx("b", { children: position.name }), _jsx("br", {}), position.deviceID] }) }) }, position.deviceID))) })] }) : _jsx(Box, { sx: {
11
+ display: 'flex',
12
+ flexDirection: 'column',
13
+ alignItems: 'center',
14
+ justifyContent: 'center',
15
+ height,
16
+ }, children: _jsx("small", { children: "No positions for devices" }) });
17
+ }
@@ -0,0 +1,3 @@
1
+ export { default as GroupUpdate } from "./GroupUpdate";
2
+ export { default as GroupsDevices } from "./GroupsDevices";
3
+ export { default as Map } from "./Map";
@@ -0,0 +1,3 @@
1
+ export { default as GroupUpdate } from "./GroupUpdate";
2
+ export { default as GroupsDevices } from "./GroupsDevices";
3
+ export { default as Map } from "./Map";
@@ -0,0 +1,5 @@
1
+ export * from "./accounts";
2
+ export * from "./charts";
3
+ export * from "./groups";
4
+ export * from "./settings";
5
+ export * from "./users";
@@ -0,0 +1,5 @@
1
+ export * from "./accounts";
2
+ export * from "./charts";
3
+ export * from "./groups";
4
+ export * from "./settings";
5
+ export * from "./users";
@@ -0,0 +1,17 @@
1
+ import { SvgIconProps } from "@mui/material/SvgIcon";
2
+ import { Theme } from "@emotion/react";
3
+ interface MenuItem {
4
+ name: string;
5
+ description: string;
6
+ page: ({ handleBack }: {
7
+ handleBack?: () => void;
8
+ }) => JSX.Element;
9
+ icon?: React.ReactElement<SvgIconProps>;
10
+ }
11
+ interface DynamicMenuProps {
12
+ menuItems: MenuItem[];
13
+ selectedIndex?: number | null;
14
+ theme: Theme;
15
+ }
16
+ declare const DynamicMenu: React.FC<DynamicMenuProps>;
17
+ export default DynamicMenu;
@@ -0,0 +1,42 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { createElement, useState } from "react";
4
+ import { Box, Card, CardContent, List, ListItemButton, ListItemText, styled, ThemeProvider } from "@mui/material";
5
+ import Grid from "@mui/material/Grid2";
6
+ import NavigateNextIcon from "@mui/icons-material/NavigateNext";
7
+ const ListItemButtonSettings = styled(ListItemButton)(({ theme, selected }) => ({
8
+ backgroundColor: theme.palette.primary.main,
9
+ color: theme.palette.primary.contrastText,
10
+ '& span': {
11
+ fontWeight: 'bold',
12
+ color: theme.palette.primary.contrastText,
13
+ },
14
+ '& p': {
15
+ color: theme.palette.primary.contrastText,
16
+ },
17
+ '&:hover': {
18
+ backgroundColor: theme.palette.primary.light,
19
+ color: theme.palette.primary.contrastText
20
+ },
21
+ ...(selected && {
22
+ '& span': {
23
+ fontWeight: 'bold',
24
+ color: '#000',
25
+ },
26
+ '& p': {
27
+ color: '#000'
28
+ },
29
+ '&.Mui-selected, &.Mui-selected:hover': {
30
+ backgroundColor: '#fff',
31
+ },
32
+ })
33
+ }));
34
+ // Stile personalizzato per ListItemButton
35
+ const DynamicMenu = ({ menuItems, selectedIndex = null, theme }) => {
36
+ const [selected, setSelected] = useState(selectedIndex);
37
+ const handleMenuClick = (index) => {
38
+ setSelected(index); // Assicurati di non chiamarlo nel rendering
39
+ };
40
+ return (_jsx(ThemeProvider, { theme: theme, children: _jsx(Grid, { container: true, spacing: 2, sx: { width: '100%' }, children: _jsx(Grid, { size: { xs: 12, lg: 9 }, children: _jsx(Card, { sx: { borderRadius: 2 }, children: _jsx(CardContent, { sx: { p: 0, "&:last-child": { p: 0 } }, children: _jsxs(Grid, { container: true, spacing: 2, children: [_jsx(Grid, { size: { xs: 12, sm: 4 }, sx: { display: { xs: selected !== null ? 'none' : 'block', sm: 'block' } }, children: _jsx(List, { sx: { p: 0, height: "100%", backgroundColor: "primary.main" }, children: menuItems.map((item, index) => (_jsxs(ListItemButtonSettings, { selected: selected === index, onClick: () => handleMenuClick(index), children: [item.icon && _jsx(Box, { sx: { mr: 2 }, children: item.icon }), _jsx(ListItemText, { primary: _jsx("b", { children: item.name }), secondary: item.description }), _jsx(NavigateNextIcon, { sx: { display: { xs: "block", sm: "none" } } })] }, index))) }) }), _jsx(Grid, { size: { xs: 12, sm: 8, md: 6 }, sx: { display: { xs: selected === null ? 'none' : 'block', sm: 'block' } }, children: _jsx(Box, { component: "div", sx: { p: { xs: 2 }, py: { sm: 3 } }, children: selected !== null ? createElement(menuItems[selected].page, { handleBack: () => { setSelected(null); } }) : "" }) })] }) }) }) }) }) }));
41
+ };
42
+ export default DynamicMenu;
@@ -0,0 +1 @@
1
+ export { default as DynamicMenu } from "./DynamicMenu";
@@ -0,0 +1 @@
1
+ export { default as DynamicMenu } from "./DynamicMenu";
@@ -0,0 +1,11 @@
1
+ import { UserType } from '../../types/user';
2
+ import { Theme } from '@emotion/react';
3
+ declare const UserUpdate: ({ userInfo, handleUpdateUser, container, containerProps, roles, theme }: {
4
+ userInfo: UserType;
5
+ handleUpdateUser: (userId: string, userInfo: Partial<UserType>) => Promise<void>;
6
+ container?: "Card" | "Box";
7
+ containerProps?: object | undefined;
8
+ roles?: string[];
9
+ theme: Theme;
10
+ }) => import("react/jsx-runtime").JSX.Element;
11
+ export default UserUpdate;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Box, Card, CardContent, FormControl, InputLabel, MenuItem, Select, ThemeProvider } from '@mui/material';
4
+ import { LoadingButton } from '@mui/lab';
5
+ const UserUpdate = ({ userInfo, handleUpdateUser, container = 'Box', containerProps = {}, roles = ['customer', 'support', 'admin'], theme }) => {
6
+ const [userRole, setUserRole] = useState(userInfo.role || roles[0]);
7
+ const [loadingButton, setLoadingButton] = useState(false);
8
+ const handleChangeRole = (event) => {
9
+ setUserRole(event.target.value);
10
+ };
11
+ const handleUpdate = async () => {
12
+ setLoadingButton(true);
13
+ try {
14
+ await handleUpdateUser(userInfo.id, { role: userRole });
15
+ }
16
+ catch (error) {
17
+ console.error(error);
18
+ }
19
+ setLoadingButton(false);
20
+ };
21
+ const renderUserUpdate = () => {
22
+ return (_jsxs(Box, { component: "div", sx: { mt: 2, mb: 0 }, children: [_jsxs(FormControl, { children: [_jsx(InputLabel, { id: "select-role-label", children: "Role" }), _jsx(Select, { labelId: "select-role-label", id: "select-role", value: userRole || '', label: "Role", onChange: handleChangeRole, children: roles.map((role) => (_jsx(MenuItem, { value: role, children: role.charAt(0).toUpperCase() + role.slice(1) }, role))) })] }), _jsx(Box, { component: "div", sx: { mt: 2, mb: 0 }, children: _jsx(LoadingButton, { variant: "contained", color: "primary", loading: loadingButton, onClick: () => handleUpdate(), children: "Update" }) })] }));
23
+ };
24
+ return _jsx(ThemeProvider, { theme: theme, children: container === "Card" ? (_jsx(Card, { ...containerProps, children: _jsx(CardContent, { sx: { py: 0 }, children: renderUserUpdate() }) })) : (_jsx(Box, { ...containerProps, children: renderUserUpdate() })) });
25
+ };
26
+ export default UserUpdate;
@@ -0,0 +1,23 @@
1
+ import { UserType } from '../../types/user';
2
+ import { Theme } from '@emotion/react';
3
+ declare const UsersDataGrid: ({ handleGetUsersList, pageSize, container, containerProps, props, loadingComponent, theme }: {
4
+ handleGetUsersList: ({ page, pageSize, filter }: {
5
+ page: number;
6
+ pageSize: number;
7
+ filter?: {
8
+ field?: string;
9
+ operator?: string;
10
+ value: string;
11
+ };
12
+ }) => Promise<{
13
+ users: UserType[];
14
+ rowCount: number;
15
+ }>;
16
+ pageSize: number;
17
+ container?: "Card" | "Box";
18
+ containerProps?: object | undefined;
19
+ props?: object | undefined;
20
+ loadingComponent?: React.ReactNode;
21
+ theme: Theme;
22
+ }) => string | number | bigint | boolean | import("react/jsx-runtime").JSX.Element | Iterable<import("react").ReactNode> | Promise<import("react").AwaitedReactNode> | null;
23
+ export default UsersDataGrid;
@@ -0,0 +1,76 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { DataGrid, getGridStringOperators, GridLogicOperator, GridToolbar } from '@mui/x-data-grid';
4
+ import { Card, Box, Button, CircularProgress, ThemeProvider } from '@mui/material';
5
+ import moment from 'moment';
6
+ import EditIcon from '@mui/icons-material/Edit';
7
+ import { useRouter } from 'next/navigation';
8
+ const UsersDataGrid = ({ handleGetUsersList, pageSize, container = 'Box', containerProps = {}, props = {}, loadingComponent = _jsx(CircularProgress, {}), theme }) => {
9
+ const router = useRouter();
10
+ const [usersList, setUsersList] = useState(undefined);
11
+ const [userCount, setUserCount] = useState(undefined);
12
+ const [isLoading, setIsLoading] = useState(true);
13
+ const [paginationModel, setPaginationModel] = useState({
14
+ page: 0,
15
+ pageSize: pageSize || 1,
16
+ });
17
+ const [filterModel, setFilterModel] = useState({
18
+ items: [],
19
+ quickFilterLogicOperator: GridLogicOperator.Or,
20
+ quickFilterValues: []
21
+ });
22
+ const getUsersList = async () => {
23
+ setIsLoading(true);
24
+ try {
25
+ const usersList = await handleGetUsersList({
26
+ page: paginationModel.page + 1,
27
+ pageSize: paginationModel.pageSize,
28
+ filter: filterModel.quickFilterValues && filterModel.quickFilterValues.length > 0 ?
29
+ { value: filterModel.quickFilterValues[0] }
30
+ : filterModel.items.length > 0 ?
31
+ { field: filterModel.items[0].field, operator: filterModel.items[0].operator, value: filterModel.items[0].value }
32
+ : undefined
33
+ });
34
+ setUsersList(usersList.users);
35
+ setUserCount(usersList.rowCount);
36
+ }
37
+ catch (err) {
38
+ console.log(err);
39
+ }
40
+ finally {
41
+ setIsLoading(false);
42
+ }
43
+ };
44
+ useEffect(() => {
45
+ getUsersList();
46
+ }, [paginationModel, filterModel]);
47
+ if (!usersList && isLoading) {
48
+ return loadingComponent;
49
+ }
50
+ const renderUsersList = () => (_jsx("div", { style: { display: 'flex', flexDirection: 'column', minHeight: 323 }, children: _jsx(DataGrid, { disableDensitySelector: true, disableColumnSelector: true, disableRowSelectionOnClick: true, disableColumnResize: true, disableColumnSorting: true, columns: [
51
+ {
52
+ field: 'role', filterable: false, disableColumnMenu: true, display: 'flex', flex: 0.125, minWidth: 80, headerName: 'Role', renderCell: (params) => (_jsx(_Fragment, { children: params.value ? params.value.charAt(0).toUpperCase() + params.value.slice(1) : '' }))
53
+ },
54
+ {
55
+ field: 'name', filterOperators: getGridStringOperators().filter((operator) => operator.value === 'contains' || operator.value === 'equals'), display: 'flex', flex: 0.400, minWidth: 140, headerName: 'Full name', renderCell: (params) => (_jsx(_Fragment, { children: params.value ? params.value.replace('/', ' ') : '' }))
56
+ },
57
+ {
58
+ field: 'email', filterOperators: getGridStringOperators().filter((operator) => operator.value === 'contains' || operator.value === 'equals'), display: 'flex', flex: 0.400, minWidth: 180, headerName: 'Email address', renderCell: (params) => (_jsx(_Fragment, { children: params.value }))
59
+ },
60
+ {
61
+ field: 'lastSignInAt', filterable: false, disableColumnMenu: true, display: 'flex', minWidth: 160, headerName: 'Last Signed In', renderCell: (params) => (_jsx(_Fragment, { children: params.value ? moment(params.value).format('DD/MM/YYYY HH:mm:ss') : '' }))
62
+ },
63
+ {
64
+ field: 'id', filterable: false, disableExport: true, disableColumnMenu: true, headerName: '', display: 'flex', align: 'right', renderCell: (params) => (_jsxs(Button, { onClick: () => router.push(`/users/${params.value}`), variant: 'contained', color: 'secondary', size: 'small', children: [_jsx(EditIcon, { fontSize: 'small', sx: { mr: 1 } }), " EDIT"] }))
65
+ }
66
+ ], rows: usersList, slots: {
67
+ toolbar: GridToolbar,
68
+ }, slotProps: {
69
+ toolbar: {
70
+ printOptions: { disableToolbarButton: true },
71
+ showQuickFilter: true,
72
+ },
73
+ }, rowCount: userCount, loading: isLoading, pageSizeOptions: [], paginationModel: paginationModel, paginationMode: "server", onPaginationModelChange: setPaginationModel, filterMode: "server", onFilterModelChange: setFilterModel, filterModel: filterModel, ...props }) }));
74
+ return _jsx(ThemeProvider, { theme: theme, children: container === "Card" ? (_jsx(Card, { ...containerProps, children: renderUsersList() })) : (_jsx(Box, { ...containerProps, children: renderUsersList() })) });
75
+ };
76
+ export default UsersDataGrid;
@@ -0,0 +1,2 @@
1
+ export { default as UsersDataGrid } from "./UsersDataGrid";
2
+ export { default as UserUpdate } from "./UserUpdate";
@@ -0,0 +1,2 @@
1
+ export { default as UsersDataGrid } from "./UsersDataGrid";
2
+ export { default as UserUpdate } from "./UserUpdate";
package/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./components";
2
+ export * from "./server-actions";
3
+ export * from "./types";
package/index.js ADDED
@@ -0,0 +1,4 @@
1
+ // index.ts nella root della libreria
2
+ export * from "./components";
3
+ export * from "./server-actions";
4
+ export * from "./types";
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@iotready/nextjs-components-library",
3
+ "version": "1.0.0-preview1",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "build": "rm -rf dist && tsc --project tsconfig.build.json && cp package.json dist/",
7
+ "dev": "next dev",
8
+ "start": "next start",
9
+ "lint": "next lint"
10
+ },
11
+ "dependencies": {
12
+ "@emotion/react": "^11.13.3",
13
+ "@emotion/styled": "^11.13.0",
14
+ "@mui/icons-material": "^6.1.5",
15
+ "@mui/lab": "^6.0.0-beta.13",
16
+ "@mui/material": "^6.1.5",
17
+ "@mui/x-data-grid": "^7.21.0",
18
+ "@mui/x-date-pickers": "^7.21.0",
19
+ "chartjs-adapter-moment": "^1.0.1",
20
+ "chartjs-plugin-annotation": "^3.1.0",
21
+ "chartjs-plugin-zoom": "^2.0.1",
22
+ "firebase": "^10.13.1",
23
+ "leaflet": "^1.9.4",
24
+ "leaflet-defaulticon-compatibility": "^0.1.2",
25
+ "material-ui-confirm": "^3.0.16",
26
+ "moment": "^2.30.1",
27
+ "next": "^14.2.9",
28
+ "react": "^18.3.1",
29
+ "react-chartjs-2": "^5.2.0",
30
+ "react-csv": "^2.2.2",
31
+ "react-dom": "^18",
32
+ "react-leaflet-cluster": "^2.1.0",
33
+ "wretch": "^2.10.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/leaflet": "^1.9.13",
37
+ "@types/node": "^20",
38
+ "@types/react": "^18",
39
+ "@types/react-csv": "^1.1.10",
40
+ "@types/react-dom": "^18",
41
+ "eslint": "^8",
42
+ "eslint-config-next": "14.2.9",
43
+ "typescript": "^5"
44
+ }
45
+ }