aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Tananaev <anton@traccar.org>2022-05-22 15:55:45 -0700
committerAnton Tananaev <anton@traccar.org>2022-05-22 15:55:45 -0700
commitdd18f6264b1a5eb006b4b57951176f7da9cf4fe1 (patch)
tree19fc46d444890680eeff40dd1059a9f92c723ebe
parent729c219369e288a60432f0e7722dae36088c0bda (diff)
downloadtrackermap-web-dd18f6264b1a5eb006b4b57951176f7da9cf4fe1.tar.gz
trackermap-web-dd18f6264b1a5eb006b4b57951176f7da9cf4fe1.tar.bz2
trackermap-web-dd18f6264b1a5eb006b4b57951176f7da9cf4fe1.zip
Reorganize collections
-rw-r--r--modern/src/main/DevicesList.js11
-rw-r--r--modern/src/main/StatusCard.js25
-rw-r--r--modern/src/other/GeofencesList.js17
-rw-r--r--modern/src/settings/CalendarsPage.js60
-rw-r--r--modern/src/settings/CommandsPage.js68
-rw-r--r--modern/src/settings/ComputedAttributesPage.js76
-rw-r--r--modern/src/settings/DriversPage.js64
-rw-r--r--modern/src/settings/GroupsPage.js60
-rw-r--r--modern/src/settings/MaintenancesPage.js73
-rw-r--r--modern/src/settings/NotificationsPage.js72
-rw-r--r--modern/src/settings/UsersPage.js72
-rw-r--r--modern/src/settings/components/CollectionActions.js48
-rw-r--r--modern/src/settings/components/CollectionFab.js35
-rw-r--r--modern/src/settings/components/EditCollectionView.js89
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;