aboutsummaryrefslogtreecommitdiff
path: root/modern/src/settings
diff options
context:
space:
mode:
Diffstat (limited to 'modern/src/settings')
-rw-r--r--modern/src/settings/AccumulatorsPage.jsx25
-rw-r--r--modern/src/settings/AnnouncementPage.jsx106
-rw-r--r--modern/src/settings/CalendarPage.jsx13
-rw-r--r--modern/src/settings/CommandDevicePage.jsx25
-rw-r--r--modern/src/settings/CommandGroupPage.jsx25
-rw-r--r--modern/src/settings/CommandPage.jsx13
-rw-r--r--modern/src/settings/ComputedAttributePage.jsx13
-rw-r--r--modern/src/settings/DeviceConnectionsPage.jsx17
-rw-r--r--modern/src/settings/DevicePage.jsx13
-rw-r--r--modern/src/settings/DevicesPage.jsx13
-rw-r--r--modern/src/settings/DriverPage.jsx13
-rw-r--r--modern/src/settings/GeofencePage.jsx13
-rw-r--r--modern/src/settings/GroupConnectionsPage.jsx17
-rw-r--r--modern/src/settings/GroupPage.jsx13
-rw-r--r--modern/src/settings/MaintenancePage.jsx15
-rw-r--r--modern/src/settings/NotificationPage.jsx13
-rw-r--r--modern/src/settings/PreferencesPage.jsx31
-rw-r--r--modern/src/settings/ServerPage.jsx25
-rw-r--r--modern/src/settings/SharePage.jsx109
-rw-r--r--modern/src/settings/UserConnectionsPage.jsx18
-rw-r--r--modern/src/settings/UserPage.jsx15
-rw-r--r--modern/src/settings/UsersPage.jsx22
-rw-r--r--modern/src/settings/common/useSettingsStyles.js22
-rw-r--r--modern/src/settings/components/EditAttributesAccordion.jsx16
-rw-r--r--modern/src/settings/components/EditItemView.jsx23
-rw-r--r--modern/src/settings/components/SettingsMenu.jsx21
26 files changed, 331 insertions, 318 deletions
diff --git a/modern/src/settings/AccumulatorsPage.jsx b/modern/src/settings/AccumulatorsPage.jsx
index 5067e4fd..1c9b6e65 100644
--- a/modern/src/settings/AccumulatorsPage.jsx
+++ b/modern/src/settings/AccumulatorsPage.jsx
@@ -10,7 +10,6 @@ import {
TextField,
Button,
} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { useTranslation } from '../common/components/LocalizationProvider';
import PageLayout from '../common/components/PageLayout';
@@ -18,31 +17,11 @@ import SettingsMenu from './components/SettingsMenu';
import { useCatch } from '../reactHelper';
import { useAttributePreference } from '../common/util/preferences';
import { distanceFromMeters, distanceToMeters, distanceUnitString } from '../common/util/converter';
-
-const useStyles = makeStyles((theme) => ({
- container: {
- marginTop: theme.spacing(2),
- },
- buttons: {
- marginTop: theme.spacing(2),
- marginBottom: theme.spacing(2),
- display: 'flex',
- justifyContent: 'space-evenly',
- '& > *': {
- flexBasis: '33%',
- },
- },
- details: {
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
- paddingBottom: theme.spacing(3),
- },
-}));
+import useSettingsStyles from './common/useSettingsStyles';
const AccumulatorsPage = () => {
const navigate = useNavigate();
- const classes = useStyles();
+ const classes = useSettingsStyles();
const t = useTranslation();
const distanceUnit = useAttributePreference('distanceUnit');
diff --git a/modern/src/settings/AnnouncementPage.jsx b/modern/src/settings/AnnouncementPage.jsx
new file mode 100644
index 00000000..39488f02
--- /dev/null
+++ b/modern/src/settings/AnnouncementPage.jsx
@@ -0,0 +1,106 @@
+import React, { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import {
+ Accordion,
+ AccordionSummary,
+ AccordionDetails,
+ Typography,
+ Container,
+ TextField,
+ Button,
+} from '@mui/material';
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
+import { useTranslation } from '../common/components/LocalizationProvider';
+import PageLayout from '../common/components/PageLayout';
+import SettingsMenu from './components/SettingsMenu';
+import { useCatchCallback } from '../reactHelper';
+import useSettingsStyles from './common/useSettingsStyles';
+import SelectField from '../common/components/SelectField';
+import { prefixString } from '../common/util/stringUtils';
+
+const AnnouncementPage = () => {
+ const navigate = useNavigate();
+ const classes = useSettingsStyles();
+ const t = useTranslation();
+
+ const [users, setUsers] = useState([]);
+ const [notificator, setNotificator] = useState();
+ const [message, setMessage] = useState({});
+
+ const handleSend = useCatchCallback(async () => {
+ const query = new URLSearchParams();
+ users.forEach((userId) => query.append('userId', userId));
+ const response = await fetch(`/api/notifications/send/${notificator}?${query.toString()}`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(message),
+ });
+ if (response.ok) {
+ navigate(-1);
+ } else {
+ throw Error(await response.text());
+ }
+ }, [users, notificator, message, navigate]);
+
+ return (
+ <PageLayout menu={<SettingsMenu />} breadcrumbs={['serverAnnouncement']}>
+ <Container maxWidth="xs" className={classes.container}>
+ <Accordion defaultExpanded>
+ <AccordionSummary expandIcon={<ExpandMoreIcon />}>
+ <Typography variant="subtitle1">
+ {t('sharedRequired')}
+ </Typography>
+ </AccordionSummary>
+ <AccordionDetails className={classes.details}>
+ <SelectField
+ multiple
+ value={users}
+ onChange={(e) => setUsers(e.target.value)}
+ endpoint="/api/users"
+ label={t('settingsUsers')}
+ />
+ <SelectField
+ value={notificator}
+ onChange={(e) => setNotificator(e.target.value)}
+ endpoint="/api/notifications/notificators"
+ keyGetter={(it) => it.type}
+ titleGetter={(it) => t(prefixString('notificator', it.type))}
+ label={t('notificationNotificators')}
+ />
+ <TextField
+ value={message.subject}
+ onChange={(e) => setMessage({ ...message, subject: e.target.value })}
+ label={t('sharedSubject')}
+ />
+ <TextField
+ value={message.body}
+ onChange={(e) => setMessage({ ...message, body: e.target.value })}
+ label={t('commandMessage')}
+ />
+ </AccordionDetails>
+ </Accordion>
+ <div className={classes.buttons}>
+ <Button
+ type="button"
+ color="primary"
+ variant="outlined"
+ onClick={() => navigate(-1)}
+ >
+ {t('sharedCancel')}
+ </Button>
+ <Button
+ type="button"
+ color="primary"
+ variant="contained"
+ onClick={handleSend}
+ disabled={!notificator || !message.subject || !message.body}
+ >
+ {t('commandSend')}
+ </Button>
+ </div>
+ </Container>
+ </PageLayout>
+ );
+};
+
+export default AnnouncementPage;
diff --git a/modern/src/settings/CalendarPage.jsx b/modern/src/settings/CalendarPage.jsx
index b7dcdd04..8a3dc986 100644
--- a/modern/src/settings/CalendarPage.jsx
+++ b/modern/src/settings/CalendarPage.jsx
@@ -5,7 +5,6 @@ import TextField from '@mui/material/TextField';
import {
Accordion, AccordionSummary, AccordionDetails, Typography, FormControl, InputLabel, Select, MenuItem,
} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { DropzoneArea } from 'react-mui-dropzone';
import EditItemView from './components/EditItemView';
@@ -15,6 +14,7 @@ import SettingsMenu from './components/SettingsMenu';
import { prefixString } from '../common/util/stringUtils';
import { calendarsActions } from '../store';
import { useCatch } from '../reactHelper';
+import useSettingsStyles from './common/useSettingsStyles';
const formatCalendarTime = (time) => {
const tzid = Intl.DateTimeFormat().resolvedOptions().timeZone;
@@ -61,17 +61,8 @@ const simpleCalendar = () => window.btoa([
'END:VCALENDAR',
].join('\n'));
-const useStyles = makeStyles((theme) => ({
- details: {
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
- paddingBottom: theme.spacing(3),
- },
-}));
-
const CalendarPage = () => {
- const classes = useStyles();
+ const classes = useSettingsStyles();
const dispatch = useDispatch();
const t = useTranslation();
diff --git a/modern/src/settings/CommandDevicePage.jsx b/modern/src/settings/CommandDevicePage.jsx
index ed802bfa..b3144cd0 100644
--- a/modern/src/settings/CommandDevicePage.jsx
+++ b/modern/src/settings/CommandDevicePage.jsx
@@ -8,7 +8,6 @@ import {
Container,
Button,
} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { useTranslation } from '../common/components/LocalizationProvider';
import BaseCommandView from './components/BaseCommandView';
@@ -17,31 +16,11 @@ import PageLayout from '../common/components/PageLayout';
import SettingsMenu from './components/SettingsMenu';
import { useCatch } from '../reactHelper';
import { useRestriction } from '../common/util/permissions';
-
-const useStyles = makeStyles((theme) => ({
- container: {
- marginTop: theme.spacing(2),
- },
- buttons: {
- marginTop: theme.spacing(2),
- marginBottom: theme.spacing(2),
- display: 'flex',
- justifyContent: 'space-evenly',
- '& > *': {
- flexBasis: '33%',
- },
- },
- details: {
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
- paddingBottom: theme.spacing(3),
- },
-}));
+import useSettingsStyles from './common/useSettingsStyles';
const CommandDevicePage = () => {
const navigate = useNavigate();
- const classes = useStyles();
+ const classes = useSettingsStyles();
const t = useTranslation();
const { id } = useParams();
diff --git a/modern/src/settings/CommandGroupPage.jsx b/modern/src/settings/CommandGroupPage.jsx
index e2ba3946..e55a235d 100644
--- a/modern/src/settings/CommandGroupPage.jsx
+++ b/modern/src/settings/CommandGroupPage.jsx
@@ -16,37 +16,16 @@ import {
Checkbox,
TextField,
} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { useTranslation } from '../common/components/LocalizationProvider';
import PageLayout from '../common/components/PageLayout';
import SettingsMenu from './components/SettingsMenu';
import { useCatch } from '../reactHelper';
-
-const useStyles = makeStyles((theme) => ({
- container: {
- marginTop: theme.spacing(2),
- },
- buttons: {
- marginTop: theme.spacing(2),
- marginBottom: theme.spacing(2),
- display: 'flex',
- justifyContent: 'space-evenly',
- '& > *': {
- flexBasis: '33%',
- },
- },
- details: {
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
- paddingBottom: theme.spacing(3),
- },
-}));
+import useSettingsStyles from './common/useSettingsStyles';
const CommandDevicePage = () => {
const navigate = useNavigate();
- const classes = useStyles();
+ const classes = useSettingsStyles();
const t = useTranslation();
const { id } = useParams();
diff --git a/modern/src/settings/CommandPage.jsx b/modern/src/settings/CommandPage.jsx
index 1d788610..e65ecd76 100644
--- a/modern/src/settings/CommandPage.jsx
+++ b/modern/src/settings/CommandPage.jsx
@@ -2,24 +2,15 @@ import React, { useState } from 'react';
import {
Accordion, AccordionSummary, AccordionDetails, Typography, TextField,
} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import EditItemView from './components/EditItemView';
import { useTranslation } from '../common/components/LocalizationProvider';
import BaseCommandView from './components/BaseCommandView';
import SettingsMenu from './components/SettingsMenu';
-
-const useStyles = makeStyles((theme) => ({
- details: {
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
- paddingBottom: theme.spacing(3),
- },
-}));
+import useSettingsStyles from './common/useSettingsStyles';
const CommandPage = () => {
- const classes = useStyles();
+ const classes = useSettingsStyles();
const t = useTranslation();
const [item, setItem] = useState();
diff --git a/modern/src/settings/ComputedAttributePage.jsx b/modern/src/settings/ComputedAttributePage.jsx
index e3a5e5a2..1b19033c 100644
--- a/modern/src/settings/ComputedAttributePage.jsx
+++ b/modern/src/settings/ComputedAttributePage.jsx
@@ -14,7 +14,6 @@ import {
Button,
Snackbar,
} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import EditItemView from './components/EditItemView';
import { useTranslation } from '../common/components/LocalizationProvider';
@@ -23,20 +22,12 @@ import SettingsMenu from './components/SettingsMenu';
import SelectField from '../common/components/SelectField';
import { useCatch } from '../reactHelper';
import { snackBarDurationLongMs } from '../common/util/duration';
+import useSettingsStyles from './common/useSettingsStyles';
const allowedProperties = ['valid', 'latitude', 'longitude', 'altitude', 'speed', 'course', 'address', 'accuracy'];
-const useStyles = makeStyles((theme) => ({
- details: {
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
- paddingBottom: theme.spacing(3),
- },
-}));
-
const ComputedAttributePage = () => {
- const classes = useStyles();
+ const classes = useSettingsStyles();
const t = useTranslation();
const positionAttributes = usePositionAttributes(t);
diff --git a/modern/src/settings/DeviceConnectionsPage.jsx b/modern/src/settings/DeviceConnectionsPage.jsx
index 88d47872..10d0c3f6 100644
--- a/modern/src/settings/DeviceConnectionsPage.jsx
+++ b/modern/src/settings/DeviceConnectionsPage.jsx
@@ -7,7 +7,6 @@ import {
Typography,
Container,
} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import LinkField from '../common/components/LinkField';
import { useTranslation } from '../common/components/LocalizationProvider';
@@ -15,21 +14,10 @@ import SettingsMenu from './components/SettingsMenu';
import { formatNotificationTitle } from '../common/util/formatter';
import PageLayout from '../common/components/PageLayout';
import useFeatures from '../common/util/useFeatures';
-
-const useStyles = makeStyles((theme) => ({
- container: {
- marginTop: theme.spacing(2),
- },
- details: {
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
- paddingBottom: theme.spacing(3),
- },
-}));
+import useSettingsStyles from './common/useSettingsStyles';
const DeviceConnectionsPage = () => {
- const classes = useStyles();
+ const classes = useSettingsStyles();
const t = useTranslation();
const { id } = useParams();
@@ -73,6 +61,7 @@ const DeviceConnectionsPage = () => {
baseId={id}
keyBase="deviceId"
keyLink="driverId"
+ titleGetter={(it) => `${it.name} (${it.uniqueId})`}
label={t('sharedDrivers')}
/>
)}
diff --git a/modern/src/settings/DevicePage.jsx b/modern/src/settings/DevicePage.jsx
index 72624270..0feb000b 100644
--- a/modern/src/settings/DevicePage.jsx
+++ b/modern/src/settings/DevicePage.jsx
@@ -9,7 +9,6 @@ import {
Checkbox,
TextField,
} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { DropzoneArea } from 'react-mui-dropzone';
import EditItemView from './components/EditItemView';
@@ -23,18 +22,10 @@ import SettingsMenu from './components/SettingsMenu';
import useCommonDeviceAttributes from '../common/attributes/useCommonDeviceAttributes';
import { useCatch } from '../reactHelper';
import useQuery from '../common/util/useQuery';
-
-const useStyles = makeStyles((theme) => ({
- details: {
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
- paddingBottom: theme.spacing(3),
- },
-}));
+import useSettingsStyles from './common/useSettingsStyles';
const DevicePage = () => {
- const classes = useStyles();
+ const classes = useSettingsStyles();
const t = useTranslation();
const admin = useAdministrator();
diff --git a/modern/src/settings/DevicesPage.jsx b/modern/src/settings/DevicesPage.jsx
index 5ef5aae5..c0da0ba7 100644
--- a/modern/src/settings/DevicesPage.jsx
+++ b/modern/src/settings/DevicesPage.jsx
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import {
- Table, TableRow, TableCell, TableHead, TableBody,
+ Table, TableRow, TableCell, TableHead, TableBody, Button, TableFooter,
} from '@mui/material';
import LinkIcon from '@mui/icons-material/Link';
import { useEffectAsync } from '../reactHelper';
@@ -48,6 +48,10 @@ const DevicesPage = () => {
}
}, [timestamp]);
+ const handleExport = () => {
+ window.location.assign('/api/reports/devices/xlsx');
+ };
+
const actionConnections = {
key: 'connections',
title: t('sharedConnections'),
@@ -94,6 +98,13 @@ const DevicesPage = () => {
</TableRow>
)) : (<TableShimmer columns={7} endAction />)}
</TableBody>
+ <TableFooter>
+ <TableRow>
+ <TableCell colSpan={8} align="right">
+ <Button onClick={handleExport} variant="text">{t('reportExport')}</Button>
+ </TableCell>
+ </TableRow>
+ </TableFooter>
</Table>
<CollectionFab editPath="/settings/device" />
</PageLayout>
diff --git a/modern/src/settings/DriverPage.jsx b/modern/src/settings/DriverPage.jsx
index 83d1f88f..5f70a44a 100644
--- a/modern/src/settings/DriverPage.jsx
+++ b/modern/src/settings/DriverPage.jsx
@@ -3,24 +3,15 @@ import TextField from '@mui/material/TextField';
import {
Accordion, AccordionSummary, AccordionDetails, Typography,
} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import EditItemView from './components/EditItemView';
import EditAttributesAccordion from './components/EditAttributesAccordion';
import { useTranslation } from '../common/components/LocalizationProvider';
import SettingsMenu from './components/SettingsMenu';
-
-const useStyles = makeStyles((theme) => ({
- details: {
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
- paddingBottom: theme.spacing(3),
- },
-}));
+import useSettingsStyles from './common/useSettingsStyles';
const DriverPage = () => {
- const classes = useStyles();
+ const classes = useSettingsStyles();
const t = useTranslation();
const [item, setItem] = useState();
diff --git a/modern/src/settings/GeofencePage.jsx b/modern/src/settings/GeofencePage.jsx
index 1d9e614b..c3c96ef8 100644
--- a/modern/src/settings/GeofencePage.jsx
+++ b/modern/src/settings/GeofencePage.jsx
@@ -3,7 +3,6 @@ import { useDispatch } from 'react-redux';
import {
Accordion, AccordionSummary, AccordionDetails, Typography, TextField,
} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import EditItemView from './components/EditItemView';
import EditAttributesAccordion from './components/EditAttributesAccordion';
@@ -12,18 +11,10 @@ import useGeofenceAttributes from '../common/attributes/useGeofenceAttributes';
import SettingsMenu from './components/SettingsMenu';
import SelectField from '../common/components/SelectField';
import { geofencesActions } from '../store';
-
-const useStyles = makeStyles((theme) => ({
- details: {
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
- paddingBottom: theme.spacing(3),
- },
-}));
+import useSettingsStyles from './common/useSettingsStyles';
const GeofencePage = () => {
- const classes = useStyles();
+ const classes = useSettingsStyles();
const dispatch = useDispatch();
const t = useTranslation();
diff --git a/modern/src/settings/GroupConnectionsPage.jsx b/modern/src/settings/GroupConnectionsPage.jsx
index 8ea3b88e..cd4e743b 100644
--- a/modern/src/settings/GroupConnectionsPage.jsx
+++ b/modern/src/settings/GroupConnectionsPage.jsx
@@ -7,7 +7,6 @@ import {
Typography,
Container,
} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import LinkField from '../common/components/LinkField';
import { useTranslation } from '../common/components/LocalizationProvider';
@@ -15,21 +14,10 @@ import SettingsMenu from './components/SettingsMenu';
import { formatNotificationTitle } from '../common/util/formatter';
import PageLayout from '../common/components/PageLayout';
import useFeatures from '../common/util/useFeatures';
-
-const useStyles = makeStyles((theme) => ({
- container: {
- marginTop: theme.spacing(2),
- },
- details: {
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
- paddingBottom: theme.spacing(3),
- },
-}));
+import useSettingsStyles from './common/useSettingsStyles';
const GroupConnectionsPage = () => {
- const classes = useStyles();
+ const classes = useSettingsStyles();
const t = useTranslation();
const { id } = useParams();
@@ -73,6 +61,7 @@ const GroupConnectionsPage = () => {
baseId={id}
keyBase="groupId"
keyLink="driverId"
+ titleGetter={(it) => `${it.name} (${it.uniqueId})`}
label={t('sharedDrivers')}
/>
)}
diff --git a/modern/src/settings/GroupPage.jsx b/modern/src/settings/GroupPage.jsx
index 64498b9c..ba1cbc76 100644
--- a/modern/src/settings/GroupPage.jsx
+++ b/modern/src/settings/GroupPage.jsx
@@ -5,7 +5,6 @@ import TextField from '@mui/material/TextField';
import {
Accordion, AccordionSummary, AccordionDetails, Typography,
} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import EditItemView from './components/EditItemView';
import EditAttributesAccordion from './components/EditAttributesAccordion';
@@ -16,18 +15,10 @@ import useCommonDeviceAttributes from '../common/attributes/useCommonDeviceAttri
import useGroupAttributes from '../common/attributes/useGroupAttributes';
import { useCatch } from '../reactHelper';
import { groupsActions } from '../store';
-
-const useStyles = makeStyles((theme) => ({
- details: {
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
- paddingBottom: theme.spacing(3),
- },
-}));
+import useSettingsStyles from './common/useSettingsStyles';
const GroupPage = () => {
- const classes = useStyles();
+ const classes = useSettingsStyles();
const dispatch = useDispatch();
const t = useTranslation();
diff --git a/modern/src/settings/MaintenancePage.jsx b/modern/src/settings/MaintenancePage.jsx
index 987789d5..420e2b82 100644
--- a/modern/src/settings/MaintenancePage.jsx
+++ b/modern/src/settings/MaintenancePage.jsx
@@ -10,7 +10,6 @@ import {
MenuItem,
Select,
} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { prefixString } from '../common/util/stringUtils';
import EditItemView from './components/EditItemView';
@@ -22,18 +21,10 @@ import {
import { useTranslation } from '../common/components/LocalizationProvider';
import usePositionAttributes from '../common/attributes/usePositionAttributes';
import SettingsMenu from './components/SettingsMenu';
-
-const useStyles = makeStyles((theme) => ({
- details: {
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
- paddingBottom: theme.spacing(3),
- },
-}));
+import useSettingsStyles from './common/useSettingsStyles';
const MaintenancePage = () => {
- const classes = useStyles();
+ const classes = useSettingsStyles();
const t = useTranslation();
const positionAttributes = usePositionAttributes(t);
@@ -48,7 +39,7 @@ const MaintenancePage = () => {
const otherList = [];
Object.keys(attributes).forEach((key) => {
const value = attributes[key];
- if (value.type === 'number') {
+ if (value.type === 'number' || key.endsWith('Time')) {
otherList.push({ key, name: value.name, type: value.type });
}
});
diff --git a/modern/src/settings/NotificationPage.jsx b/modern/src/settings/NotificationPage.jsx
index 44399f92..63aa9b95 100644
--- a/modern/src/settings/NotificationPage.jsx
+++ b/modern/src/settings/NotificationPage.jsx
@@ -10,7 +10,6 @@ import {
FormGroup,
Button,
} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { useTranslation, useTranslationKeys } from '../common/components/LocalizationProvider';
import EditItemView from './components/EditItemView';
@@ -18,18 +17,10 @@ import { prefixString, unprefixString } from '../common/util/stringUtils';
import SelectField from '../common/components/SelectField';
import SettingsMenu from './components/SettingsMenu';
import { useCatch } from '../reactHelper';
-
-const useStyles = makeStyles((theme) => ({
- details: {
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
- paddingBottom: theme.spacing(3),
- },
-}));
+import useSettingsStyles from './common/useSettingsStyles';
const NotificationPage = () => {
- const classes = useStyles();
+ const classes = useSettingsStyles();
const t = useTranslation();
const [item, setItem] = useState();
diff --git a/modern/src/settings/PreferencesPage.jsx b/modern/src/settings/PreferencesPage.jsx
index cfdcf885..2d6df62f 100644
--- a/modern/src/settings/PreferencesPage.jsx
+++ b/modern/src/settings/PreferencesPage.jsx
@@ -5,7 +5,6 @@ import { useNavigate } from 'react-router-dom';
import {
Accordion, AccordionSummary, AccordionDetails, Typography, Container, FormControl, InputLabel, Select, MenuItem, Checkbox, FormControlLabel, FormGroup, InputAdornment, IconButton, OutlinedInput, Autocomplete, TextField, createFilterOptions, Button,
} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import CachedIcon from '@mui/icons-material/Cached';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
@@ -20,6 +19,7 @@ import useMapOverlays from '../map/overlay/useMapOverlays';
import { useCatch } from '../reactHelper';
import { sessionActions } from '../store';
import { useRestriction } from '../common/util/permissions';
+import useSettingsStyles from './common/useSettingsStyles';
const deviceFields = [
{ id: 'name', name: 'sharedName' },
@@ -29,33 +29,8 @@ const deviceFields = [
{ id: 'contact', name: 'deviceContact' },
];
-const useStyles = makeStyles((theme) => ({
- container: {
- marginTop: theme.spacing(2),
- },
- buttons: {
- marginTop: theme.spacing(2),
- marginBottom: theme.spacing(2),
- display: 'flex',
- justifyContent: 'space-evenly',
- '& > *': {
- flexBasis: '33%',
- },
- },
- details: {
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
- paddingBottom: theme.spacing(3),
- },
- tokenActions: {
- display: 'flex',
- flexDirection: 'column',
- },
-}));
-
const PreferencesPage = () => {
- const classes = useStyles();
+ const classes = useSettingsStyles();
const dispatch = useDispatch();
const navigate = useNavigate();
const t = useTranslation();
@@ -332,7 +307,7 @@ const PreferencesPage = () => {
value={token || ''}
endAdornment={(
<InputAdornment position="end">
- <div className={classes.tokenActions}>
+ <div className={classes.verticalActions}>
<IconButton size="small" edge="end" onClick={generateToken} disabled={!!token}>
<CachedIcon fontSize="small" />
</IconButton>
diff --git a/modern/src/settings/ServerPage.jsx b/modern/src/settings/ServerPage.jsx
index 1020092b..0ac76334 100644
--- a/modern/src/settings/ServerPage.jsx
+++ b/modern/src/settings/ServerPage.jsx
@@ -16,7 +16,6 @@ import {
MenuItem,
FormGroup,
} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { useNavigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
@@ -33,30 +32,10 @@ import { useCatch } from '../reactHelper';
import useServerAttributes from '../common/attributes/useServerAttributes';
import useMapStyles from '../map/core/useMapStyles';
import { map } from '../map/core/MapView';
-
-const useStyles = makeStyles((theme) => ({
- container: {
- marginTop: theme.spacing(2),
- },
- buttons: {
- marginTop: theme.spacing(2),
- marginBottom: theme.spacing(2),
- display: 'flex',
- justifyContent: 'space-evenly',
- '& > *': {
- flexBasis: '33%',
- },
- },
- details: {
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
- paddingBottom: theme.spacing(3),
- },
-}));
+import useSettingsStyles from './common/useSettingsStyles';
const ServerPage = () => {
- const classes = useStyles();
+ const classes = useSettingsStyles();
const navigate = useNavigate();
const dispatch = useDispatch();
const t = useTranslation();
diff --git a/modern/src/settings/SharePage.jsx b/modern/src/settings/SharePage.jsx
new file mode 100644
index 00000000..d16fe44d
--- /dev/null
+++ b/modern/src/settings/SharePage.jsx
@@ -0,0 +1,109 @@
+import React, { useState } from 'react';
+import { useSelector } from 'react-redux';
+import { useNavigate, useParams } from 'react-router-dom';
+import dayjs from 'dayjs';
+import {
+ Accordion,
+ AccordionSummary,
+ AccordionDetails,
+ Typography,
+ Container,
+ TextField,
+ Button,
+} from '@mui/material';
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
+import { useTranslation } from '../common/components/LocalizationProvider';
+import PageLayout from '../common/components/PageLayout';
+import SettingsMenu from './components/SettingsMenu';
+import { useCatchCallback } from '../reactHelper';
+import useSettingsStyles from './common/useSettingsStyles';
+
+const SharePage = () => {
+ const navigate = useNavigate();
+ const classes = useSettingsStyles();
+ const t = useTranslation();
+
+ const { id } = useParams();
+
+ const device = useSelector((state) => state.devices.items[id]);
+
+ const [expiration, setExpiration] = useState(dayjs().add(1, 'week').locale('en').format('YYYY-MM-DD'));
+ const [link, setLink] = useState();
+
+ const handleShare = useCatchCallback(async () => {
+ const expirationTime = dayjs(expiration).toISOString();
+ const response = await fetch('/api/devices/share', {
+ method: 'POST',
+ body: new URLSearchParams(`deviceId=${id}&expiration=${expirationTime}`),
+ });
+ if (response.ok) {
+ const token = await response.text();
+ setLink(`${window.location.origin}?token=${token}`);
+ } else {
+ throw Error(await response.text());
+ }
+ }, [id, expiration, setLink]);
+
+ return (
+ <PageLayout menu={<SettingsMenu />} breadcrumbs={['deviceShare']}>
+ <Container maxWidth="xs" className={classes.container}>
+ <Accordion defaultExpanded>
+ <AccordionSummary expandIcon={<ExpandMoreIcon />}>
+ <Typography variant="subtitle1">
+ {t('sharedRequired')}
+ </Typography>
+ </AccordionSummary>
+ <AccordionDetails className={classes.details}>
+ <TextField
+ value={device.name}
+ label={t('sharedDevice')}
+ disabled
+ />
+ <TextField
+ label={t('userExpirationTime')}
+ type="date"
+ value={(expiration && dayjs(expiration).locale('en').format('YYYY-MM-DD')) || '2099-01-01'}
+ onChange={(e) => setExpiration(dayjs(e.target.value, 'YYYY-MM-DD').locale('en').format())}
+ />
+ <Button
+ variant="outlined"
+ color="primary"
+ onClick={handleShare}
+ >
+ {t('reportShow')}
+ </Button>
+ <TextField
+ value={link || ''}
+ onChange={(e) => setLink(e.target.value)}
+ label={t('sharedLink')}
+ InputProps={{
+ readOnly: true,
+ }}
+ />
+ </AccordionDetails>
+ </Accordion>
+ <div className={classes.buttons}>
+ <Button
+ type="button"
+ color="primary"
+ variant="outlined"
+ onClick={() => navigate(-1)}
+ >
+ {t('sharedCancel')}
+ </Button>
+ <Button
+ type="button"
+ color="primary"
+ variant="contained"
+ onClick={() => navigator.clipboard?.writeText(link)}
+ disabled={!link}
+ >
+ {t('sharedCopy')}
+ </Button>
+ </div>
+ </Container>
+ </PageLayout>
+ );
+};
+
+export default SharePage;
diff --git a/modern/src/settings/UserConnectionsPage.jsx b/modern/src/settings/UserConnectionsPage.jsx
index 80de8835..3ca0bdc1 100644
--- a/modern/src/settings/UserConnectionsPage.jsx
+++ b/modern/src/settings/UserConnectionsPage.jsx
@@ -7,28 +7,16 @@ import {
Typography,
Container,
} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import LinkField from '../common/components/LinkField';
import { useTranslation } from '../common/components/LocalizationProvider';
import SettingsMenu from './components/SettingsMenu';
import { formatNotificationTitle } from '../common/util/formatter';
import PageLayout from '../common/components/PageLayout';
-
-const useStyles = makeStyles((theme) => ({
- container: {
- marginTop: theme.spacing(2),
- },
- details: {
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
- paddingBottom: theme.spacing(3),
- },
-}));
+import useSettingsStyles from './common/useSettingsStyles';
const UserConnectionsPage = () => {
- const classes = useStyles();
+ const classes = useSettingsStyles();
const t = useTranslation();
const { id } = useParams();
@@ -52,6 +40,7 @@ const UserConnectionsPage = () => {
baseId={id}
keyBase="userId"
keyLink="deviceId"
+ titleGetter={(it) => `${it.name} (${it.uniqueId})`}
label={t('deviceTitle')}
/>
<LinkField
@@ -110,6 +99,7 @@ const UserConnectionsPage = () => {
baseId={id}
keyBase="userId"
keyLink="driverId"
+ titleGetter={(it) => `${it.name} (${it.uniqueId})`}
label={t('sharedDrivers')}
/>
<LinkField
diff --git a/modern/src/settings/UserPage.jsx b/modern/src/settings/UserPage.jsx
index f276beca..6748dd31 100644
--- a/modern/src/settings/UserPage.jsx
+++ b/modern/src/settings/UserPage.jsx
@@ -18,7 +18,6 @@ import {
IconButton,
OutlinedInput,
} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
import CachedIcon from '@mui/icons-material/Cached';
@@ -38,18 +37,10 @@ import useQuery from '../common/util/useQuery';
import { useCatch } from '../reactHelper';
import useMapStyles from '../map/core/useMapStyles';
import { map } from '../map/core/MapView';
-
-const useStyles = makeStyles((theme) => ({
- details: {
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
- paddingBottom: theme.spacing(3),
- },
-}));
+import useSettingsStyles from './common/useSettingsStyles';
const UserPage = () => {
- const classes = useStyles();
+ const classes = useSettingsStyles();
const navigate = useNavigate();
const dispatch = useDispatch();
const t = useTranslation();
@@ -92,7 +83,7 @@ const UserPage = () => {
const handleGenerateTotp = useCatch(async () => {
const response = await fetch('/api/users/totp', { method: 'POST' });
if (response.ok) {
- setItem({ ...item, totpKey: await response.text() })
+ setItem({ ...item, totpKey: await response.text() });
} else {
throw Error(await response.text());
}
diff --git a/modern/src/settings/UsersPage.jsx b/modern/src/settings/UsersPage.jsx
index d04f2a2b..2941965b 100644
--- a/modern/src/settings/UsersPage.jsx
+++ b/modern/src/settings/UsersPage.jsx
@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
- Table, TableRow, TableCell, TableHead, TableBody,
+ Table, TableRow, TableCell, TableHead, TableBody, Switch, TableFooter, FormControlLabel,
} from '@mui/material';
import LoginIcon from '@mui/icons-material/Login';
import LinkIcon from '@mui/icons-material/Link';
@@ -31,6 +31,7 @@ const UsersPage = () => {
const [items, setItems] = useState([]);
const [searchKeyword, setSearchKeyword] = useState('');
const [loading, setLoading] = useState(false);
+ const [temporary, setTemporary] = useState(false);
const handleLogin = useCatch(async (userId) => {
const response = await fetch(`/api/session/${userId}`);
@@ -84,7 +85,7 @@ const UsersPage = () => {
</TableRow>
</TableHead>
<TableBody>
- {!loading ? items.filter(filterByKeyword(searchKeyword)).map((item) => (
+ {!loading ? items.filter((u) => temporary || !u.temporary).filter(filterByKeyword(searchKeyword)).map((item) => (
<TableRow key={item.id}>
<TableCell>{item.name}</TableCell>
<TableCell>{item.email}</TableCell>
@@ -103,6 +104,23 @@ const UsersPage = () => {
</TableRow>
)) : (<TableShimmer columns={6} endAction />)}
</TableBody>
+ <TableFooter>
+ <TableRow>
+ <TableCell colSpan={6} align="right">
+ <FormControlLabel
+ control={(
+ <Switch
+ value={temporary}
+ onChange={(e) => setTemporary(e.target.checked)}
+ size="small"
+ />
+ )}
+ label={t('userTemporary')}
+ labelPlacement="start"
+ />
+ </TableCell>
+ </TableRow>
+ </TableFooter>
</Table>
<CollectionFab editPath="/settings/user" />
</PageLayout>
diff --git a/modern/src/settings/common/useSettingsStyles.js b/modern/src/settings/common/useSettingsStyles.js
index 396af232..b276e0b7 100644
--- a/modern/src/settings/common/useSettingsStyles.js
+++ b/modern/src/settings/common/useSettingsStyles.js
@@ -8,4 +8,26 @@ export default makeStyles((theme) => ({
width: '1%',
paddingRight: theme.spacing(1),
},
+ container: {
+ marginTop: theme.spacing(2),
+ },
+ buttons: {
+ marginTop: theme.spacing(2),
+ marginBottom: theme.spacing(2),
+ display: 'flex',
+ justifyContent: 'space-evenly',
+ '& > *': {
+ flexBasis: '33%',
+ },
+ },
+ details: {
+ display: 'flex',
+ flexDirection: 'column',
+ gap: theme.spacing(2),
+ paddingBottom: theme.spacing(3),
+ },
+ verticalActions: {
+ display: 'flex',
+ flexDirection: 'column',
+ },
}));
diff --git a/modern/src/settings/components/EditAttributesAccordion.jsx b/modern/src/settings/components/EditAttributesAccordion.jsx
index 214ddb0e..4d4ae254 100644
--- a/modern/src/settings/components/EditAttributesAccordion.jsx
+++ b/modern/src/settings/components/EditAttributesAccordion.jsx
@@ -15,7 +15,6 @@ import {
Typography,
AccordionDetails,
} from '@mui/material';
-import makeStyles from '@mui/styles/makeStyles';
import CloseIcon from '@mui/icons-material/Close';
import AddIcon from '@mui/icons-material/Add';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
@@ -26,21 +25,10 @@ import {
distanceFromMeters, distanceToMeters, distanceUnitString, speedFromKnots, speedToKnots, speedUnitString, volumeFromLiters, volumeToLiters, volumeUnitString,
} from '../../common/util/converter';
import useFeatures from '../../common/util/useFeatures';
-
-const useStyles = makeStyles((theme) => ({
- removeButton: {
- marginRight: theme.spacing(1.5),
- },
- details: {
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
- paddingBottom: theme.spacing(3),
- },
-}));
+import useSettingsStyles from '../common/useSettingsStyles';
const EditAttributesAccordion = ({ attribute, attributes, setAttributes, definitions, focusAttribute }) => {
- const classes = useStyles();
+ const classes = useSettingsStyles();
const t = useTranslation();
const features = useFeatures();
diff --git a/modern/src/settings/components/EditItemView.jsx b/modern/src/settings/components/EditItemView.jsx
index d45855dd..61bc4161 100644
--- a/modern/src/settings/components/EditItemView.jsx
+++ b/modern/src/settings/components/EditItemView.jsx
@@ -1,37 +1,18 @@
import React from 'react';
import { useNavigate, useParams } from 'react-router-dom';
-import makeStyles from '@mui/styles/makeStyles';
import {
Container, Button, Accordion, AccordionDetails, AccordionSummary, Skeleton, Typography, TextField,
} from '@mui/material';
import { useCatch, useEffectAsync } from '../../reactHelper';
import { useTranslation } from '../../common/components/LocalizationProvider';
import PageLayout from '../../common/components/PageLayout';
-
-const useStyles = makeStyles((theme) => ({
- container: {
- marginTop: theme.spacing(2),
- },
- buttons: {
- marginTop: theme.spacing(2),
- marginBottom: theme.spacing(2),
- display: 'flex',
- justifyContent: 'space-evenly',
- '& > *': {
- flexBasis: '33%',
- },
- },
- details: {
- display: 'flex',
- flexDirection: 'column',
- },
-}));
+import useSettingsStyles from '../common/useSettingsStyles';
const EditItemView = ({
children, endpoint, item, setItem, defaultItem, validate, onItemSaved, menu, breadcrumbs,
}) => {
const navigate = useNavigate();
- const classes = useStyles();
+ const classes = useSettingsStyles();
const t = useTranslation();
const { id } = useParams();
diff --git a/modern/src/settings/components/SettingsMenu.jsx b/modern/src/settings/components/SettingsMenu.jsx
index 0f3ebbe5..6d8c0fd7 100644
--- a/modern/src/settings/components/SettingsMenu.jsx
+++ b/modern/src/settings/components/SettingsMenu.jsx
@@ -14,6 +14,7 @@ import TodayIcon from '@mui/icons-material/Today';
import PublishIcon from '@mui/icons-material/Publish';
import SmartphoneIcon from '@mui/icons-material/Smartphone';
import HelpIcon from '@mui/icons-material/Help';
+import CampaignIcon from '@mui/icons-material/Campaign';
import { Link, useLocation } from 'react-router-dom';
import { useSelector } from 'react-redux';
import { useTranslation } from '../../common/components/LocalizationProvider';
@@ -139,12 +140,20 @@ const SettingsMenu = () => {
<Divider />
<List>
{admin && (
- <MenuItem
- title={t('settingsServer')}
- link="/settings/server"
- icon={<StorageIcon />}
- selected={location.pathname === '/settings/server'}
- />
+ <>
+ <MenuItem
+ title={t('serverAnnouncement')}
+ link="/settings/announcement"
+ icon={<CampaignIcon />}
+ selected={location.pathname === '/settings/announcement'}
+ />
+ <MenuItem
+ title={t('settingsServer')}
+ link="/settings/server"
+ icon={<StorageIcon />}
+ selected={location.pathname === '/settings/server'}
+ />
+ </>
)}
<MenuItem
title={t('settingsUsers')}