diff options
author | Anton Tananaev <anton@traccar.org> | 2022-05-22 15:55:45 -0700 |
---|---|---|
committer | Anton Tananaev <anton@traccar.org> | 2022-05-22 15:55:45 -0700 |
commit | dd18f6264b1a5eb006b4b57951176f7da9cf4fe1 (patch) | |
tree | 19fc46d444890680eeff40dd1059a9f92c723ebe /modern | |
parent | 729c219369e288a60432f0e7722dae36088c0bda (diff) | |
download | trackermap-web-dd18f6264b1a5eb006b4b57951176f7da9cf4fe1.tar.gz trackermap-web-dd18f6264b1a5eb006b4b57951176f7da9cf4fe1.tar.bz2 trackermap-web-dd18f6264b1a5eb006b4b57951176f7da9cf4fe1.zip |
Reorganize collections
Diffstat (limited to 'modern')
-rw-r--r-- | modern/src/main/DevicesList.js | 11 | ||||
-rw-r--r-- | modern/src/main/StatusCard.js | 25 | ||||
-rw-r--r-- | modern/src/other/GeofencesList.js | 17 | ||||
-rw-r--r-- | modern/src/settings/CalendarsPage.js | 60 | ||||
-rw-r--r-- | modern/src/settings/CommandsPage.js | 68 | ||||
-rw-r--r-- | modern/src/settings/ComputedAttributesPage.js | 76 | ||||
-rw-r--r-- | modern/src/settings/DriversPage.js | 64 | ||||
-rw-r--r-- | modern/src/settings/GroupsPage.js | 60 | ||||
-rw-r--r-- | modern/src/settings/MaintenancesPage.js | 73 | ||||
-rw-r--r-- | modern/src/settings/NotificationsPage.js | 72 | ||||
-rw-r--r-- | modern/src/settings/UsersPage.js | 72 | ||||
-rw-r--r-- | modern/src/settings/components/CollectionActions.js | 48 | ||||
-rw-r--r-- | modern/src/settings/components/CollectionFab.js | 35 | ||||
-rw-r--r-- | modern/src/settings/components/EditCollectionView.js | 89 |
14 files changed, 365 insertions, 405 deletions
diff --git a/modern/src/main/DevicesList.js b/modern/src/main/DevicesList.js index 6ff08c6a..b80eaf8b 100644 --- a/modern/src/main/DevicesList.js +++ b/modern/src/main/DevicesList.js @@ -21,7 +21,6 @@ import FlashOffIcon from '@material-ui/icons/FlashOff'; import ErrorIcon from '@material-ui/icons/Error'; import { devicesActions } from '../store'; -import EditCollectionView from '../settings/components/EditCollectionView'; import { useEffectAsync } from '../reactHelper'; import { formatAlarm, formatBoolean, formatPercentage, formatStatus, getStatusColor, @@ -139,7 +138,7 @@ const DeviceRow = ({ data, index, style }) => { ); }; -const DeviceView = ({ updateTimestamp, onMenuClick, filter }) => { +const DevicesList = ({ filter }) => { const classes = useStyles(); const dispatch = useDispatch(); const listInnerEl = useRef(null); @@ -167,7 +166,7 @@ const DeviceView = ({ updateTimestamp, onMenuClick, filter }) => { } else { throw Error(await response.text()); } - }, [updateTimestamp]); + }, []); return ( <AutoSizer className={classes.list}> @@ -177,7 +176,7 @@ const DeviceView = ({ updateTimestamp, onMenuClick, filter }) => { width={width} height={height} itemCount={filteredItems.length} - itemData={{ items: filteredItems, onMenuClick }} + itemData={{ items: filteredItems }} itemSize={72} overscanCount={10} innerRef={listInnerEl} @@ -190,8 +189,4 @@ const DeviceView = ({ updateTimestamp, onMenuClick, filter }) => { ); }; -const DevicesList = ({ filter }) => ( - <EditCollectionView content={DeviceView} editPath="/settings/device" endpoint="devices" disableAdd filter={filter} /> -); - export default DevicesList; diff --git a/modern/src/main/StatusCard.js b/modern/src/main/StatusCard.js index b470890e..d23bc8c3 100644 --- a/modern/src/main/StatusCard.js +++ b/modern/src/main/StatusCard.js @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { makeStyles, Card, CardContent, Typography, CardActions, CardHeader, IconButton, Avatar, Table, TableBody, TableRow, TableCell, TableContainer, @@ -19,6 +19,8 @@ import dimensions from '../common/theme/dimensions'; import { useDeviceReadonly, useReadonly } from '../common/util/permissions'; import usePersistedState from '../common/util/usePersistedState'; import usePositionAttributes from '../common/attributes/usePositionAttributes'; +import { devicesActions } from '../store'; +import { useCatch } from '../reactHelper'; const useStyles = makeStyles((theme) => ({ card: { @@ -64,6 +66,7 @@ const StatusRow = ({ name, content }) => { const StatusCard = ({ deviceId, onClose }) => { const classes = useStyles(); const history = useHistory(); + const dispatch = useDispatch(); const t = useTranslation(); const readonly = useReadonly(); @@ -75,7 +78,19 @@ const StatusCard = ({ deviceId, onClose }) => { const positionAttributes = usePositionAttributes(t); const [positionItems] = usePersistedState('positionItems', ['speed', 'address', 'totalDistance', 'course']); - const [removeDialogShown, setRemoveDialogShown] = useState(false); + const [removing, setRemoving] = useState(false); + + const handleRemove = useCatch(async (removed) => { + if (removed) { + const response = await fetch('/api/devices'); + if (response.ok) { + dispatch(devicesActions.refresh(await response.json())); + } else { + throw Error(await response.text()); + } + } + setRemoving(false); + }); return ( <> @@ -131,17 +146,17 @@ const StatusCard = ({ deviceId, onClose }) => { <IconButton onClick={() => history.push(`/settings/device/${deviceId}`)} disabled={deviceReadonly}> <EditIcon /> </IconButton> - <IconButton onClick={() => setRemoveDialogShown(true)} disabled={deviceReadonly} className={classes.negative}> + <IconButton onClick={() => setRemoving(true)} disabled={deviceReadonly} className={classes.negative}> <DeleteIcon /> </IconButton> </CardActions> </Card> )} <RemoveDialog - open={removeDialogShown} + open={removing} endpoint="devices" itemId={deviceId} - onResult={() => setRemoveDialogShown(false)} + onResult={(removed) => handleRemove(removed)} /> </> ); diff --git a/modern/src/other/GeofencesList.js b/modern/src/other/GeofencesList.js index d0d0853f..202ae3b1 100644 --- a/modern/src/other/GeofencesList.js +++ b/modern/src/other/GeofencesList.js @@ -2,15 +2,12 @@ import React, { Fragment } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { makeStyles } from '@material-ui/core/styles'; import Divider from '@material-ui/core/Divider'; -import IconButton from '@material-ui/core/IconButton'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; -import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; import ListItemText from '@material-ui/core/ListItemText'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; import { devicesActions } from '../store'; -import EditCollectionView from '../settings/components/EditCollectionView'; +import CollectionActions from '../settings/components/CollectionActions'; const useStyles = makeStyles(() => ({ list: { @@ -24,7 +21,7 @@ const useStyles = makeStyles(() => ({ }, })); -const GeofenceView = ({ onMenuClick }) => { +const GeofencesList = () => { const classes = useStyles(); const dispatch = useDispatch(); @@ -36,11 +33,7 @@ const GeofenceView = ({ onMenuClick }) => { <Fragment key={item.id}> <ListItem button key={item.id} onClick={() => dispatch(devicesActions.select(item.id))}> <ListItemText primary={item.name} /> - <ListItemSecondaryAction> - <IconButton size="small" onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </ListItemSecondaryAction> + <CollectionActions itemId={item.id} editPath="/settings/geofence" endpoint="geofences" /> </ListItem> {index < list.length - 1 ? <Divider /> : null} </Fragment> @@ -49,8 +42,4 @@ const GeofenceView = ({ onMenuClick }) => { ); }; -const GeofencesList = () => ( - <EditCollectionView content={GeofenceView} editPath="/settings/geofence" endpoint="geofences" disableAdd /> -); - export default GeofencesList; diff --git a/modern/src/settings/CalendarsPage.js b/modern/src/settings/CalendarsPage.js index 06697647..a5072277 100644 --- a/modern/src/settings/CalendarsPage.js +++ b/modern/src/settings/CalendarsPage.js @@ -1,13 +1,13 @@ import React, { useState } from 'react'; import { - TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, } from '@material-ui/core'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; import { useEffectAsync } from '../reactHelper'; -import EditCollectionView from './components/EditCollectionView'; import { useTranslation } from '../common/components/LocalizationProvider'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; +import CollectionFab from './components/CollectionFab'; +import CollectionActions from './components/CollectionActions'; const useStyles = makeStyles((theme) => ({ columnAction: { @@ -16,10 +16,11 @@ const useStyles = makeStyles((theme) => ({ }, })); -const CalendarsView = ({ updateTimestamp, onMenuClick }) => { +const CalendarsPage = () => { const classes = useStyles(); const t = useTranslation(); + const [timestamp, setTimestamp] = useState(Date.now()); const [items, setItems] = useState([]); useEffectAsync(async () => { @@ -29,38 +30,33 @@ const CalendarsView = ({ updateTimestamp, onMenuClick }) => { } else { throw Error(await response.text()); } - }, [updateTimestamp]); + }, [timestamp]); return ( - <TableContainer> - <Table> - <TableHead> - <TableRow> - <TableCell className={classes.columnAction} /> - <TableCell>{t('sharedName')}</TableCell> - </TableRow> - </TableHead> - <TableBody> - {items.map((item) => ( - <TableRow key={item.id}> - <TableCell className={classes.columnAction} padding="none"> - <IconButton size="small" onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{item.name}</TableCell> + <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedCalendars']}> + <TableContainer> + <Table> + <TableHead> + <TableRow> + <TableCell className={classes.columnAction} /> + <TableCell>{t('sharedName')}</TableCell> </TableRow> - ))} - </TableBody> - </Table> - </TableContainer> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <CollectionActions itemId={item.id} editPath="/settings/calendar" endpoint="calendars" setTimestamp={setTimestamp} /> + </TableCell> + <TableCell>{item.name}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + <CollectionFab editPath="/settings/calendar" /> + </PageLayout> ); }; -const CalendarsPage = () => ( - <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedCalendars']}> - <EditCollectionView content={CalendarsView} editPath="/settings/calendar" endpoint="calendars" /> - </PageLayout> -); - export default CalendarsPage; diff --git a/modern/src/settings/CommandsPage.js b/modern/src/settings/CommandsPage.js index 1b09a8bd..64a29bf6 100644 --- a/modern/src/settings/CommandsPage.js +++ b/modern/src/settings/CommandsPage.js @@ -1,15 +1,15 @@ import React, { useState } from 'react'; import { - TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, } from '@material-ui/core'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; import { useEffectAsync } from '../reactHelper'; -import EditCollectionView from './components/EditCollectionView'; import { useTranslation } from '../common/components/LocalizationProvider'; import { formatBoolean } from '../common/util/formatter'; import { prefixString } from '../common/util/stringUtils'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; +import CollectionFab from './components/CollectionFab'; +import CollectionActions from './components/CollectionActions'; const useStyles = makeStyles((theme) => ({ columnAction: { @@ -18,10 +18,11 @@ const useStyles = makeStyles((theme) => ({ }, })); -const CommandsView = ({ updateTimestamp, onMenuClick }) => { +const CommandsPage = () => { const classes = useStyles(); const t = useTranslation(); + const [timestamp, setTimestamp] = useState(Date.now()); const [items, setItems] = useState([]); useEffectAsync(async () => { @@ -31,42 +32,37 @@ const CommandsView = ({ updateTimestamp, onMenuClick }) => { } else { throw Error(await response.text()); } - }, [updateTimestamp]); + }, [timestamp]); return ( - <TableContainer> - <Table> - <TableHead> - <TableRow> - <TableCell className={classes.columnAction} /> - <TableCell>{t('sharedDescription')}</TableCell> - <TableCell>{t('sharedType')}</TableCell> - <TableCell>{t('commandSendSms')}</TableCell> - </TableRow> - </TableHead> - <TableBody> - {items.map((item) => ( - <TableRow key={item.id}> - <TableCell className={classes.columnAction} padding="none"> - <IconButton size="small" onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{item.description}</TableCell> - <TableCell>{t(prefixString('command', item.type))}</TableCell> - <TableCell>{formatBoolean(item.textChannel, t)}</TableCell> + <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedSavedCommands']}> + <TableContainer> + <Table> + <TableHead> + <TableRow> + <TableCell className={classes.columnAction} /> + <TableCell>{t('sharedDescription')}</TableCell> + <TableCell>{t('sharedType')}</TableCell> + <TableCell>{t('commandSendSms')}</TableCell> </TableRow> - ))} - </TableBody> - </Table> - </TableContainer> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <CollectionActions itemId={item.id} editPath="/settings/command" endpoint="commands" setTimestamp={setTimestamp} /> + </TableCell> + <TableCell>{item.description}</TableCell> + <TableCell>{t(prefixString('command', item.type))}</TableCell> + <TableCell>{formatBoolean(item.textChannel, t)}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + <CollectionFab editPath="/settings/command" /> + </PageLayout> ); }; -const CommandsPage = () => ( - <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedSavedCommands']}> - <EditCollectionView content={CommandsView} editPath="/settings/command" endpoint="commands" /> - </PageLayout> -); - export default CommandsPage; diff --git a/modern/src/settings/ComputedAttributesPage.js b/modern/src/settings/ComputedAttributesPage.js index 86704c3b..0b029308 100644 --- a/modern/src/settings/ComputedAttributesPage.js +++ b/modern/src/settings/ComputedAttributesPage.js @@ -1,14 +1,14 @@ import React, { useState } from 'react'; import { - TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, } from '@material-ui/core'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; import { useEffectAsync } from '../reactHelper'; -import EditCollectionView from './components/EditCollectionView'; import { useTranslation } from '../common/components/LocalizationProvider'; import { useAdministrator } from '../common/util/permissions'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; +import CollectionFab from './components/CollectionFab'; +import CollectionActions from './components/CollectionActions'; const useStyles = makeStyles((theme) => ({ columnAction: { @@ -17,10 +17,11 @@ const useStyles = makeStyles((theme) => ({ }, })); -const ComputedAttributeView = ({ updateTimestamp, onMenuClick }) => { +const ComputedAttributesPage = () => { const classes = useStyles(); const t = useTranslation(); + const [timestamp, setTimestamp] = useState(Date.now()); const [items, setItems] = useState([]); const administrator = useAdministrator(); @@ -31,46 +32,41 @@ const ComputedAttributeView = ({ updateTimestamp, onMenuClick }) => { } else { throw Error(await response.text()); } - }, [updateTimestamp]); + }, [timestamp]); return ( - <TableContainer> - <Table> - <TableHead> - <TableRow> - {administrator && <TableCell className={classes.columnAction} />} - <TableCell>{t('sharedDescription')}</TableCell> - <TableCell>{t('sharedAttribute')}</TableCell> - <TableCell>{t('sharedExpression')}</TableCell> - <TableCell>{t('sharedType')}</TableCell> - </TableRow> - </TableHead> - <TableBody> - {items.map((item) => ( - <TableRow key={item.id}> - {administrator && ( - <TableCell className={classes.columnAction} padding="none"> - <IconButton size="small" onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - )} - <TableCell>{item.description}</TableCell> - <TableCell>{item.attribute}</TableCell> - <TableCell>{item.expression}</TableCell> - <TableCell>{item.type}</TableCell> + <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedComputedAttributes']}> + <TableContainer> + <Table> + <TableHead> + <TableRow> + {administrator && <TableCell className={classes.columnAction} />} + <TableCell>{t('sharedDescription')}</TableCell> + <TableCell>{t('sharedAttribute')}</TableCell> + <TableCell>{t('sharedExpression')}</TableCell> + <TableCell>{t('sharedType')}</TableCell> </TableRow> - ))} - </TableBody> - </Table> - </TableContainer> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + {administrator && ( + <TableCell className={classes.columnAction} padding="none"> + <CollectionActions itemId={item.id} editPath="/settings/attribute" endpoint="attributes/computed" setTimestamp={setTimestamp} /> + </TableCell> + )} + <TableCell>{item.description}</TableCell> + <TableCell>{item.attribute}</TableCell> + <TableCell>{item.expression}</TableCell> + <TableCell>{item.type}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + <CollectionFab editPath="/settings/attribute" /> + </PageLayout> ); }; -const ComputedAttributesPage = () => ( - <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedComputedAttributes']}> - <EditCollectionView content={ComputedAttributeView} editPath="/settings/attribute" endpoint="attributes/computed" /> - </PageLayout> -); - export default ComputedAttributesPage; diff --git a/modern/src/settings/DriversPage.js b/modern/src/settings/DriversPage.js index 26601777..b5c30a17 100644 --- a/modern/src/settings/DriversPage.js +++ b/modern/src/settings/DriversPage.js @@ -1,13 +1,13 @@ import React, { useState } from 'react'; import { - TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, } from '@material-ui/core'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; import { useEffectAsync } from '../reactHelper'; -import EditCollectionView from './components/EditCollectionView'; import { useTranslation } from '../common/components/LocalizationProvider'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; +import CollectionFab from './components/CollectionFab'; +import CollectionActions from './components/CollectionActions'; const useStyles = makeStyles((theme) => ({ columnAction: { @@ -16,10 +16,11 @@ const useStyles = makeStyles((theme) => ({ }, })); -const DriversView = ({ updateTimestamp, onMenuClick }) => { +const DriversPage = () => { const classes = useStyles(); const t = useTranslation(); + const [timestamp, setTimestamp] = useState(Date.now()); const [items, setItems] = useState([]); useEffectAsync(async () => { @@ -29,40 +30,35 @@ const DriversView = ({ updateTimestamp, onMenuClick }) => { } else { throw Error(await response.text()); } - }, [updateTimestamp]); + }, [timestamp]); return ( - <TableContainer> - <Table> - <TableHead> - <TableRow> - <TableCell className={classes.columnAction} /> - <TableCell>{t('sharedName')}</TableCell> - <TableCell>{t('deviceIdentifier')}</TableCell> - </TableRow> - </TableHead> - <TableBody> - {items.map((item) => ( - <TableRow key={item.id}> - <TableCell className={classes.columnAction} padding="none"> - <IconButton size="small" onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{item.name}</TableCell> - <TableCell>{item.uniqueId}</TableCell> + <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedDrivers']}> + <TableContainer> + <Table> + <TableHead> + <TableRow> + <TableCell className={classes.columnAction} /> + <TableCell>{t('sharedName')}</TableCell> + <TableCell>{t('deviceIdentifier')}</TableCell> </TableRow> - ))} - </TableBody> - </Table> - </TableContainer> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <CollectionActions itemId={item.id} editPath="/settings/driver" endpoint="drivers" setTimestamp={setTimestamp} /> + </TableCell> + <TableCell>{item.name}</TableCell> + <TableCell>{item.uniqueId}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + <CollectionFab editPath="/settings/driver" /> + </PageLayout> ); }; -const DriversPage = () => ( - <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedDrivers']}> - <EditCollectionView content={DriversView} editPath="/settings/driver" endpoint="drivers" /> - </PageLayout> -); - export default DriversPage; diff --git a/modern/src/settings/GroupsPage.js b/modern/src/settings/GroupsPage.js index 257d0bca..ae17bd78 100644 --- a/modern/src/settings/GroupsPage.js +++ b/modern/src/settings/GroupsPage.js @@ -1,13 +1,13 @@ import React, { useState } from 'react'; import { - TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, } from '@material-ui/core'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; import { useEffectAsync } from '../reactHelper'; -import EditCollectionView from './components/EditCollectionView'; import { useTranslation } from '../common/components/LocalizationProvider'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; +import CollectionFab from './components/CollectionFab'; +import CollectionActions from './components/CollectionActions'; const useStyles = makeStyles((theme) => ({ columnAction: { @@ -16,10 +16,11 @@ const useStyles = makeStyles((theme) => ({ }, })); -const GroupsView = ({ updateTimestamp, onMenuClick }) => { +const GroupsPage = () => { const classes = useStyles(); const t = useTranslation(); + const [timestamp, setTimestamp] = useState(Date.now()); const [items, setItems] = useState([]); useEffectAsync(async () => { @@ -29,38 +30,33 @@ const GroupsView = ({ updateTimestamp, onMenuClick }) => { } else { throw Error(await response.text()); } - }, [updateTimestamp]); + }, [timestamp]); return ( - <TableContainer> - <Table> - <TableHead> - <TableRow> - <TableCell className={classes.columnAction} /> - <TableCell>{t('sharedName')}</TableCell> - </TableRow> - </TableHead> - <TableBody> - {items.map((item) => ( - <TableRow key={item.id}> - <TableCell className={classes.columnAction} padding="none"> - <IconButton size="small" onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{item.name}</TableCell> + <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'settingsGroups']}> + <TableContainer> + <Table> + <TableHead> + <TableRow> + <TableCell className={classes.columnAction} /> + <TableCell>{t('sharedName')}</TableCell> </TableRow> - ))} - </TableBody> - </Table> - </TableContainer> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <CollectionActions itemId={item.id} editPath="/settings/group" endpoint="groups" setTimestamp={setTimestamp} /> + </TableCell> + <TableCell>{item.name}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + <CollectionFab editPath="/settings/group" /> + </PageLayout> ); }; -const GroupsPage = () => ( - <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'settingsGroups']}> - <EditCollectionView content={GroupsView} editPath="/settings/group" endpoint="groups" /> - </PageLayout> -); - export default GroupsPage; diff --git a/modern/src/settings/MaintenancesPage.js b/modern/src/settings/MaintenancesPage.js index ea00e7e1..43a37b60 100644 --- a/modern/src/settings/MaintenancesPage.js +++ b/modern/src/settings/MaintenancesPage.js @@ -1,17 +1,16 @@ import React, { useState } from 'react'; import { - TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, } from '@material-ui/core'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; import { useEffectAsync } from '../reactHelper'; -import EditCollectionView from './components/EditCollectionView'; - import usePositionAttributes from '../common/attributes/usePositionAttributes'; import { formatDistance, formatSpeed } from '../common/util/formatter'; import { useAttributePreference } from '../common/util/preferences'; import { useTranslation } from '../common/components/LocalizationProvider'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; +import CollectionFab from './components/CollectionFab'; +import CollectionActions from './components/CollectionActions'; const useStyles = makeStyles((theme) => ({ columnAction: { @@ -20,12 +19,13 @@ const useStyles = makeStyles((theme) => ({ }, })); -const MaintenancesView = ({ updateTimestamp, onMenuClick }) => { +const MaintenacesPage = () => { const classes = useStyles(); const t = useTranslation(); const positionAttributes = usePositionAttributes(t); + const [timestamp, setTimestamp] = useState(Date.now()); const [items, setItems] = useState([]); const speedUnit = useAttributePreference('speedUnit'); const distanceUnit = useAttributePreference('distanceUnit'); @@ -37,7 +37,7 @@ const MaintenancesView = ({ updateTimestamp, onMenuClick }) => { } else { throw Error(await response.text()); } - }, [updateTimestamp]); + }, [timestamp]); const convertAttribute = (key, value) => { const attribute = positionAttributes[key]; @@ -56,41 +56,36 @@ const MaintenancesView = ({ updateTimestamp, onMenuClick }) => { }; return ( - <TableContainer> - <Table> - <TableHead> - <TableRow> - <TableCell className={classes.columnAction} /> - <TableCell>{t('sharedName')}</TableCell> - <TableCell>{t('sharedType')}</TableCell> - <TableCell>{t('maintenanceStart')}</TableCell> - <TableCell>{t('maintenancePeriod')}</TableCell> - </TableRow> - </TableHead> - <TableBody> - {items.map((item) => ( - <TableRow key={item.id}> - <TableCell className={classes.columnAction} padding="none"> - <IconButton size="small" onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{item.name}</TableCell> - <TableCell>{item.type}</TableCell> - <TableCell>{convertAttribute(item.type, item.start)}</TableCell> - <TableCell>{convertAttribute(item.type, item.period)}</TableCell> + <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedMaintenance']}> + <TableContainer> + <Table> + <TableHead> + <TableRow> + <TableCell className={classes.columnAction} /> + <TableCell>{t('sharedName')}</TableCell> + <TableCell>{t('sharedType')}</TableCell> + <TableCell>{t('maintenanceStart')}</TableCell> + <TableCell>{t('maintenancePeriod')}</TableCell> </TableRow> - ))} - </TableBody> - </Table> - </TableContainer> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <CollectionActions itemId={item.id} editPath="/settings/maintenance" endpoint="maintenance" setTimestamp={setTimestamp} /> + </TableCell> + <TableCell>{item.name}</TableCell> + <TableCell>{item.type}</TableCell> + <TableCell>{convertAttribute(item.type, item.start)}</TableCell> + <TableCell>{convertAttribute(item.type, item.period)}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + <CollectionFab editPath="/settings/maintenance" /> + </PageLayout> ); }; -const MaintenacesPage = () => ( - <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedMaintenance']}> - <EditCollectionView content={MaintenancesView} editPath="/settings/maintenance" endpoint="maintenance" /> - </PageLayout> -); - export default MaintenacesPage; diff --git a/modern/src/settings/NotificationsPage.js b/modern/src/settings/NotificationsPage.js index de3e762f..ec0df1cb 100644 --- a/modern/src/settings/NotificationsPage.js +++ b/modern/src/settings/NotificationsPage.js @@ -1,15 +1,15 @@ import React, { useState } from 'react'; import { - TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, } from '@material-ui/core'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; import { useEffectAsync } from '../reactHelper'; -import EditCollectionView from './components/EditCollectionView'; import { prefixString } from '../common/util/stringUtils'; import { formatBoolean } from '../common/util/formatter'; import { useTranslation } from '../common/components/LocalizationProvider'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; +import CollectionFab from './components/CollectionFab'; +import CollectionActions from './components/CollectionActions'; const useStyles = makeStyles((theme) => ({ columnAction: { @@ -18,10 +18,11 @@ const useStyles = makeStyles((theme) => ({ }, })); -const NotificationsView = ({ updateTimestamp, onMenuClick }) => { +const NotificationsPage = () => { const classes = useStyles(); const t = useTranslation(); + const [timestamp, setTimestamp] = useState(Date.now()); const [items, setItems] = useState([]); useEffectAsync(async () => { @@ -31,7 +32,7 @@ const NotificationsView = ({ updateTimestamp, onMenuClick }) => { } else { throw Error(await response.text()); } - }, [updateTimestamp]); + }, [timestamp]); const formatList = (prefix, value) => { if (value) { @@ -45,41 +46,36 @@ const NotificationsView = ({ updateTimestamp, onMenuClick }) => { }; return ( - <TableContainer> - <Table> - <TableHead> - <TableRow> - <TableCell className={classes.columnAction} /> - <TableCell>{t('notificationType')}</TableCell> - <TableCell>{t('notificationAlways')}</TableCell> - <TableCell>{t('sharedAlarms')}</TableCell> - <TableCell>{t('notificationNotificators')}</TableCell> - </TableRow> - </TableHead> - <TableBody> - {items.map((item) => ( - <TableRow key={item.id}> - <TableCell className={classes.columnAction} padding="none"> - <IconButton size="small" onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{t(prefixString('event', item.type))}</TableCell> - <TableCell>{formatBoolean(item.always, t)}</TableCell> - <TableCell>{formatList('alarm', item.attributes.alarms)}</TableCell> - <TableCell>{formatList('notificator', item.notificators)}</TableCell> + <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedNotifications']}> + <TableContainer> + <Table> + <TableHead> + <TableRow> + <TableCell className={classes.columnAction} /> + <TableCell>{t('notificationType')}</TableCell> + <TableCell>{t('notificationAlways')}</TableCell> + <TableCell>{t('sharedAlarms')}</TableCell> + <TableCell>{t('notificationNotificators')}</TableCell> </TableRow> - ))} - </TableBody> - </Table> - </TableContainer> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <CollectionActions itemId={item.id} editPath="/settings/notification" endpoint="notifications" setTimestamp={setTimestamp} /> + </TableCell> + <TableCell>{t(prefixString('event', item.type))}</TableCell> + <TableCell>{formatBoolean(item.always, t)}</TableCell> + <TableCell>{formatList('alarm', item.attributes.alarms)}</TableCell> + <TableCell>{formatList('notificator', item.notificators)}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + <CollectionFab editPath="/settings/notification" /> + </PageLayout> ); }; -const NotificationsPage = () => ( - <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedNotifications']}> - <EditCollectionView content={NotificationsView} editPath="/settings/notification" endpoint="notifications" /> - </PageLayout> -); - export default NotificationsPage; diff --git a/modern/src/settings/UsersPage.js b/modern/src/settings/UsersPage.js index 235ae4c7..059b6bcc 100644 --- a/modern/src/settings/UsersPage.js +++ b/modern/src/settings/UsersPage.js @@ -1,14 +1,14 @@ import React, { useState } from 'react'; import { - TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, } from '@material-ui/core'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; import { useEffectAsync } from '../reactHelper'; -import EditCollectionView from './components/EditCollectionView'; import { formatBoolean } from '../common/util/formatter'; import { useTranslation } from '../common/components/LocalizationProvider'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; +import CollectionFab from './components/CollectionFab'; +import CollectionActions from './components/CollectionActions'; const useStyles = makeStyles((theme) => ({ columnAction: { @@ -17,10 +17,11 @@ const useStyles = makeStyles((theme) => ({ }, })); -const UsersView = ({ updateTimestamp, onMenuClick }) => { +const UsersPage = () => { const classes = useStyles(); const t = useTranslation(); + const [timestamp, setTimestamp] = useState(Date.now()); const [items, setItems] = useState([]); useEffectAsync(async () => { @@ -30,44 +31,39 @@ const UsersView = ({ updateTimestamp, onMenuClick }) => { } else { throw Error(await response.text()); } - }, [updateTimestamp]); + }, [timestamp]); return ( - <TableContainer> - <Table> - <TableHead> - <TableRow> - <TableCell className={classes.columnAction} /> - <TableCell>{t('sharedName')}</TableCell> - <TableCell>{t('userEmail')}</TableCell> - <TableCell>{t('userAdmin')}</TableCell> - <TableCell>{t('sharedDisabled')}</TableCell> - </TableRow> - </TableHead> - <TableBody> - {items.map((item) => ( - <TableRow key={item.id}> - <TableCell className={classes.columnAction} padding="none"> - <IconButton size="small" onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{item.name}</TableCell> - <TableCell>{item.email}</TableCell> - <TableCell>{formatBoolean(item.administrator, t)}</TableCell> - <TableCell>{formatBoolean(item.disabled, t)}</TableCell> + <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'settingsUsers']}> + <TableContainer> + <Table> + <TableHead> + <TableRow> + <TableCell className={classes.columnAction} /> + <TableCell>{t('sharedName')}</TableCell> + <TableCell>{t('userEmail')}</TableCell> + <TableCell>{t('userAdmin')}</TableCell> + <TableCell>{t('sharedDisabled')}</TableCell> </TableRow> - ))} - </TableBody> - </Table> - </TableContainer> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <CollectionActions itemId={item.id} editPath="/settings/user" endpoint="users" setTimestamp={setTimestamp} /> + </TableCell> + <TableCell>{item.name}</TableCell> + <TableCell>{item.email}</TableCell> + <TableCell>{formatBoolean(item.administrator, t)}</TableCell> + <TableCell>{formatBoolean(item.disabled, t)}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + <CollectionFab editPath="/settings/user" /> + </PageLayout> ); }; -const UsersPage = () => ( - <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'settingsUsers']}> - <EditCollectionView content={UsersView} editPath="/settings/user" endpoint="users" /> - </PageLayout> -); - export default UsersPage; diff --git a/modern/src/settings/components/CollectionActions.js b/modern/src/settings/components/CollectionActions.js new file mode 100644 index 00000000..6bf9ddb1 --- /dev/null +++ b/modern/src/settings/components/CollectionActions.js @@ -0,0 +1,48 @@ +import React, { useState } from 'react'; +import { IconButton, Menu, MenuItem } from '@material-ui/core'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import { useHistory } from 'react-router-dom'; +import RemoveDialog from '../../common/components/RemoveDialog'; +import { useTranslation } from '../../common/components/LocalizationProvider'; + +const CollectionActions = ({ + itemId, editPath, endpoint, setTimestamp, +}) => { + const history = useHistory(); + const t = useTranslation(); + + const [menuAnchorEl, setMenuAnchorEl] = useState(null); + const [removing, setRemoving] = useState(false); + + const handleEdit = () => { + history.push(`${editPath}/${itemId}`); + setMenuAnchorEl(null); + }; + + const handleRemove = () => { + setRemoving(true); + setMenuAnchorEl(null); + }; + + const handleRemoveResult = (removed) => { + setRemoving(false); + if (removed && setTimestamp) { + setTimestamp(Date.now()); + } + }; + + return ( + <> + <IconButton size="small" onClick={(event) => setMenuAnchorEl(event.currentTarget)}> + <MoreVertIcon /> + </IconButton> + <Menu open={!!menuAnchorEl} anchorEl={menuAnchorEl} onClose={() => setMenuAnchorEl(null)}> + <MenuItem onClick={handleEdit}>{t('sharedEdit')}</MenuItem> + <MenuItem onClick={handleRemove}>{t('sharedRemove')}</MenuItem> + </Menu> + <RemoveDialog style={{ transform: 'none' }} open={removing} endpoint={endpoint} itemId={itemId} onResult={handleRemoveResult} /> + </> + ); +}; + +export default CollectionActions; diff --git a/modern/src/settings/components/CollectionFab.js b/modern/src/settings/components/CollectionFab.js new file mode 100644 index 00000000..f52bed38 --- /dev/null +++ b/modern/src/settings/components/CollectionFab.js @@ -0,0 +1,35 @@ +import React from 'react'; +import { Fab, makeStyles } from '@material-ui/core'; +import AddIcon from '@material-ui/icons/Add'; +import { useHistory } from 'react-router-dom'; +import { useReadonly } from '../../common/util/permissions'; +import dimensions from '../../common/theme/dimensions'; + +const useStyles = makeStyles((theme) => ({ + fab: { + position: 'fixed', + bottom: theme.spacing(2), + right: theme.spacing(2), + [theme.breakpoints.down('sm')]: { + bottom: dimensions.bottomBarHeight + theme.spacing(2), + }, + }, +})); + +const CollectionFab = ({ editPath, disabled }) => { + const classes = useStyles(); + const history = useHistory(); + + const readonly = useReadonly(); + + if (!readonly && !disabled) { + return ( + <Fab size="medium" color="primary" className={classes.fab} onClick={() => history.push(editPath)}> + <AddIcon /> + </Fab> + ); + } + return ''; +}; + +export default CollectionFab; diff --git a/modern/src/settings/components/EditCollectionView.js b/modern/src/settings/components/EditCollectionView.js deleted file mode 100644 index b0bf3ea0..00000000 --- a/modern/src/settings/components/EditCollectionView.js +++ /dev/null @@ -1,89 +0,0 @@ -import React, { useState } from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import { useHistory } from 'react-router-dom'; -import Menu from '@material-ui/core/Menu'; -import MenuItem from '@material-ui/core/MenuItem'; -import Fab from '@material-ui/core/Fab'; -import AddIcon from '@material-ui/icons/Add'; - -import RemoveDialog from '../../common/components/RemoveDialog'; -import { useTranslation } from '../../common/components/LocalizationProvider'; -import dimensions from '../../common/theme/dimensions'; -import { useReadonly } from '../../common/util/permissions'; - -const useStyles = makeStyles((theme) => ({ - fab: { - position: 'fixed', - bottom: theme.spacing(2), - right: theme.spacing(2), - [theme.breakpoints.down('sm')]: { - bottom: dimensions.bottomBarHeight + theme.spacing(2), - }, - }, -})); - -const EditCollectionView = ({ - content, editPath, endpoint, disableAdd, filter, -}) => { - const classes = useStyles(); - const history = useHistory(); - const t = useTranslation(); - - const readonly = useReadonly(); - - const [selectedId, setSelectedId] = useState(null); - const [selectedAnchorEl, setSelectedAnchorEl] = useState(null); - const [removeDialogShown, setRemoveDialogShown] = useState(false); - const [updateTimestamp, setUpdateTimestamp] = useState(Date.now()); - - const menuShow = (anchorId, itemId) => { - setSelectedAnchorEl(anchorId); - setSelectedId(itemId); - }; - - const menuHide = () => { - setSelectedAnchorEl(null); - }; - - const handleAdd = () => { - history.push(editPath); - menuHide(); - }; - - const handleEdit = () => { - history.push(`${editPath}/${selectedId}`); - menuHide(); - }; - - const handleRemove = () => { - setRemoveDialogShown(true); - menuHide(); - }; - - const handleRemoveResult = (removed) => { - setRemoveDialogShown(false); - if (removed) { - setUpdateTimestamp(Date.now()); - } - }; - - const Content = content; - - return ( - <> - <Content updateTimestamp={updateTimestamp} onMenuClick={menuShow} filter={filter} /> - {!readonly && !disableAdd && ( - <Fab size="medium" color="primary" className={classes.fab} onClick={handleAdd}> - <AddIcon /> - </Fab> - )} - <Menu open={!!selectedAnchorEl} anchorEl={selectedAnchorEl} onClose={menuHide}> - <MenuItem onClick={handleEdit}>{t('sharedEdit')}</MenuItem> - <MenuItem onClick={handleRemove}>{t('sharedRemove')}</MenuItem> - </Menu> - <RemoveDialog open={removeDialogShown} endpoint={endpoint} itemId={selectedId} onResult={handleRemoveResult} /> - </> - ); -}; - -export default EditCollectionView; |