diff options
author | Matjaž Črnko <matjaz.crnko@gmail.com> | 2024-03-08 10:59:29 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-03-08 10:59:29 +0100 |
commit | fe7b4800224352c628fb925c85a937684af58e6f (patch) | |
tree | d5a111f5cedbba4a10278695227447ee12113db0 /modern/src/settings | |
parent | b87f2387ae597e57d9e2591ab01595e76b65d7c5 (diff) | |
parent | c9da10062998a231c038cd3a519f72128fcea2bb (diff) | |
download | trackermap-web-fe7b4800224352c628fb925c85a937684af58e6f.tar.gz trackermap-web-fe7b4800224352c628fb925c85a937684af58e6f.tar.bz2 trackermap-web-fe7b4800224352c628fb925c85a937684af58e6f.zip |
Merge branch 'traccar:master' into autocomplete-instead-of-single-select
Diffstat (limited to 'modern/src/settings')
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')} |