aboutsummaryrefslogtreecommitdiff
path: root/modern
diff options
context:
space:
mode:
Diffstat (limited to 'modern')
-rw-r--r--modern/.eslintrc.js3
-rw-r--r--modern/src/EditItemView.js37
-rw-r--r--modern/src/GeofencesPage.js42
-rw-r--r--modern/src/MainToolbar.js122
-rw-r--r--modern/src/StartPage.js2
-rw-r--r--modern/src/admin/ServerPage.js111
-rw-r--r--modern/src/admin/StatisticsPage.js76
-rw-r--r--modern/src/admin/UsersPage.js27
-rw-r--r--modern/src/components/NavBar.js23
-rw-r--r--modern/src/components/SideNav.js34
-rw-r--r--modern/src/components/reports/ReportNavbar.js26
-rw-r--r--modern/src/components/reports/ReportSidebar.js29
-rw-r--r--modern/src/reports/ChartReportPage.js6
-rw-r--r--modern/src/reports/EventReportPage.js6
-rw-r--r--modern/src/reports/ReportFilter.js2
-rw-r--r--modern/src/reports/ReportLayout.js (renamed from modern/src/reports/ReportLayoutPage.js)118
-rw-r--r--modern/src/reports/RouteReportPage.js6
-rw-r--r--modern/src/reports/StopReportPage.js6
-rw-r--r--modern/src/reports/SummaryReportPage.js6
-rw-r--r--modern/src/reports/TripReportPage.js6
-rw-r--r--modern/src/selectors.js3
-rw-r--r--modern/src/settings/ComputedAttributesPage.js35
-rw-r--r--modern/src/settings/DriversPage.js27
-rw-r--r--modern/src/settings/GroupsPage.js27
-rw-r--r--modern/src/settings/MaintenancesPage.js27
-rw-r--r--modern/src/settings/NotificationsPage.js33
-rw-r--r--modern/src/settings/OptionsLayout/index.js106
-rw-r--r--modern/src/settings/OptionsLayout/useRoutes.js86
28 files changed, 664 insertions, 368 deletions
diff --git a/modern/.eslintrc.js b/modern/.eslintrc.js
new file mode 100644
index 0000000..4ee202f
--- /dev/null
+++ b/modern/.eslintrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ "extends": "airbnb"
+}; \ No newline at end of file
diff --git a/modern/src/EditItemView.js b/modern/src/EditItemView.js
index 16fbbae..bab56c7 100644
--- a/modern/src/EditItemView.js
+++ b/modern/src/EditItemView.js
@@ -1,5 +1,4 @@
import React from 'react';
-import MainToolbar from './MainToolbar';
import { useHistory, useParams } from 'react-router-dom';
import { makeStyles } from '@material-ui/core/styles';
import Container from '@material-ui/core/Container';
@@ -8,18 +7,19 @@ import FormControl from '@material-ui/core/FormControl';
import t from './common/localization';
import { useEffectAsync } from './reactHelper';
+import OptionsLayout from './settings/OptionsLayout';
const useStyles = makeStyles(theme => ({
container: {
- marginTop: theme.spacing(2),
+ marginTop: theme.spacing(2)
},
buttons: {
display: 'flex',
justifyContent: 'space-evenly',
'& > *': {
- flexBasis: '33%',
- },
- },
+ flexBasis: '33%'
+ }
+ }
}));
const EditItemView = ({ children, endpoint, item, setItem }) => {
@@ -47,7 +47,7 @@ const EditItemView = ({ children, endpoint, item, setItem }) => {
const response = await fetch(url, {
method: !id ? 'POST' : 'PUT',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(item),
+ body: JSON.stringify(item)
});
if (response.ok) {
@@ -56,23 +56,32 @@ const EditItemView = ({ children, endpoint, item, setItem }) => {
};
return (
- <>
- <MainToolbar />
- <Container maxWidth='xs' className={classes.container}>
+ <OptionsLayout>
+ <Container maxWidth="xs" className={classes.container}>
{children}
- <FormControl fullWidth margin='normal'>
+ <FormControl fullWidth margin="normal">
<div className={classes.buttons}>
- <Button type='button' color='primary' variant='outlined' onClick={() => history.goBack()}>
+ <Button
+ type="button"
+ color="primary"
+ variant="outlined"
+ onClick={() => history.goBack()}
+ >
{t('sharedCancel')}
</Button>
- <Button type='button' color='primary' variant='contained' onClick={handleSave}>
+ <Button
+ type="button"
+ color="primary"
+ variant="contained"
+ onClick={handleSave}
+ >
{t('sharedSave')}
</Button>
</div>
</FormControl>
</Container>
- </>
+ </OptionsLayout>
);
-}
+};
export default EditItemView;
diff --git a/modern/src/GeofencesPage.js b/modern/src/GeofencesPage.js
index 389ac99..59b8459 100644
--- a/modern/src/GeofencesPage.js
+++ b/modern/src/GeofencesPage.js
@@ -1,18 +1,21 @@
import React from 'react';
-import { isWidthUp, makeStyles, withWidth } from '@material-ui/core';
+import { Divider, isWidthUp, makeStyles, withWidth, Typography, IconButton } from '@material-ui/core';
import Drawer from '@material-ui/core/Drawer';
import ContainerDimensions from 'react-container-dimensions';
-import MainToolbar from './MainToolbar';
import Map from './map/Map';
import CurrentLocationMap from './map/CurrentLocationMap';
import GeofenceEditMap from './map/GeofenceEditMap';
import GeofencesList from './GeofencesList';
+import ArrowBackIcon from '@material-ui/icons/ArrowBack';
+
+import t from './common/localization';
+
const useStyles = makeStyles(theme => ({
root: {
height: '100%',
display: 'flex',
- flexDirection: 'column',
+ flexDirection: 'column'
},
content: {
flexGrow: 1,
@@ -20,21 +23,27 @@ const useStyles = makeStyles(theme => ({
display: 'flex',
flexDirection: 'row',
[theme.breakpoints.down('xs')]: {
- flexDirection: 'column-reverse',
+ flexDirection: 'column-reverse'
}
},
drawerPaper: {
position: 'relative',
[theme.breakpoints.up('sm')]: {
- width: 350,
+ width: 350
},
[theme.breakpoints.down('xs')]: {
- height: 250,
+ height: 250
}
},
- mapContainer: {
- flexGrow: 1,
+ drawerHeader: {
+ ...theme.mixins.toolbar,
+ display: 'flex',
+ alignItems: 'center',
+ padding: theme.spacing(0, 1)
},
+ mapContainer: {
+ flexGrow: 1
+ }
}));
const GeofencesPage = ({ width }) => {
@@ -42,12 +51,21 @@ const GeofencesPage = ({ width }) => {
return (
<div className={classes.root}>
- <MainToolbar />
<div className={classes.content}>
<Drawer
anchor={isWidthUp('sm', width) ? 'left' : 'bottom'}
- variant='permanent'
- classes={{ paper: classes.drawerPaper }}>
+ variant="permanent"
+ classes={{ paper: classes.drawerPaper }}
+ >
+ <div className={classes.drawerHeader}>
+ <IconButton component="a" href="/">
+ <ArrowBackIcon />
+ </IconButton>
+ <Typography variant="h6" color="inherit" noWrap>
+ {t('sharedGeofences')}
+ </Typography>
+ </div>
+ <Divider />
<GeofencesList />
</Drawer>
<div className={classes.mapContainer}>
@@ -61,6 +79,6 @@ const GeofencesPage = ({ width }) => {
</div>
</div>
);
-}
+};
export default withWidth()(GeofencesPage);
diff --git a/modern/src/MainToolbar.js b/modern/src/MainToolbar.js
index 064507f..311f024 100644
--- a/modern/src/MainToolbar.js
+++ b/modern/src/MainToolbar.js
@@ -17,32 +17,26 @@ import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import MapIcon from '@material-ui/icons/Map';
-import BarChartIcon from '@material-ui/icons/BarChart';
-import PeopleIcon from '@material-ui/icons/People';
-import StorageIcon from '@material-ui/icons/Storage';
import PersonIcon from '@material-ui/icons/Person';
-import NotificationsIcon from '@material-ui/icons/Notifications';
import DescriptionIcon from '@material-ui/icons/Description';
-import FolderIcon from '@material-ui/icons/Folder';
-import CreateIcon from '@material-ui/icons/Create';
import ReplayIcon from '@material-ui/icons/Replay';
-import BuildIcon from '@material-ui/icons/Build';
import t from './common/localization';
+import * as selectors from './selectors';
const useStyles = makeStyles(theme => ({
flex: {
flexGrow: 1
},
appBar: {
- zIndex: theme.zIndex.drawer + 1,
+ zIndex: theme.zIndex.drawer + 1
},
list: {
width: 250
},
menuButton: {
marginLeft: -12,
- marginRight: 20,
- },
+ marginRight: 20
+ }
}));
const MainToolbar = () => {
@@ -50,11 +44,15 @@ const MainToolbar = () => {
const [drawer, setDrawer] = useState(false);
const classes = useStyles();
const history = useHistory();
- const adminEnabled = useSelector(state => state.session.user && state.session.user.administrator);
- const userId = useSelector(state => state.session.user && state.session.user.id);
+ const adminEnabled = useSelector(selectors.getIsAdmin);
+ const userId = useSelector(selectors.getUserId);
- const openDrawer = () => { setDrawer(true) }
- const closeDrawer = () => { setDrawer(false) }
+ const openDrawer = () => {
+ setDrawer(true);
+ };
+ const closeDrawer = () => {
+ setDrawer(false);
+ };
const handleLogout = async () => {
const response = await fetch('/api/session', { method: 'DELETE' });
@@ -62,7 +60,7 @@ const MainToolbar = () => {
dispatch(sessionActions.updateUser(null));
history.push('/login');
}
- }
+ };
return (
<>
@@ -71,13 +69,16 @@ const MainToolbar = () => {
<IconButton
className={classes.menuButton}
color="inherit"
- onClick={openDrawer}>
+ onClick={openDrawer}
+ >
<MenuIcon />
</IconButton>
<Typography variant="h6" color="inherit" className={classes.flex}>
Traccar
</Typography>
- <Button color="inherit" onClick={handleLogout}>{t('loginLogout')}</Button>
+ <Button color="inherit" onClick={handleLogout}>
+ {t('loginLogout')}
+ </Button>
</Toolbar>
</AppBar>
<Drawer open={drawer} onClose={closeDrawer}>
@@ -86,7 +87,8 @@ const MainToolbar = () => {
className={classes.list}
role="button"
onClick={closeDrawer}
- onKeyDown={closeDrawer}>
+ onKeyDown={closeDrawer}
+ >
<List>
<ListItem button onClick={() => history.push('/')}>
<ListItemIcon>
@@ -105,92 +107,22 @@ const MainToolbar = () => {
<DescriptionIcon />
</ListItemIcon>
<ListItemText primary={t('reportTitle')} />
- </ListItem>
- </List>
- <Divider />
- <List
- subheader={
- <ListSubheader>
- {t('settingsTitle')}
- </ListSubheader>
- }>
- <ListItem button disabled={!userId} onClick={() => history.push(`/user/${userId}`)}>
- <ListItemIcon>
- <PersonIcon />
- </ListItemIcon>
- <ListItemText primary={t('settingsUser')} />
- </ListItem>
- <ListItem button onClick={() => history.push('/geofences')}>
- <ListItemIcon>
- <CreateIcon />
- </ListItemIcon>
- <ListItemText primary={t('sharedGeofences')} />
- </ListItem>
- <ListItem button onClick={() => history.push('/settings/notifications')}>
- <ListItemIcon>
- <NotificationsIcon />
- </ListItemIcon>
- <ListItemText primary={t('sharedNotifications')} />
- </ListItem>
- <ListItem button onClick={() => history.push('/settings/groups')}>
- <ListItemIcon>
- <FolderIcon />
- </ListItemIcon>
- <ListItemText primary={t('settingsGroups')} />
</ListItem>
- <ListItem button onClick={() => history.push('/settings/drivers')}>
+ <ListItem
+ button
+ disabled={!userId}
+ onClick={() => history.push(`/settings/notifications`)}
+ >
<ListItemIcon>
<PersonIcon />
</ListItemIcon>
- <ListItemText primary={t('sharedDrivers')} />
- </ListItem>
- <ListItem button onClick={() => history.push('/settings/attributes')}>
- <ListItemIcon>
- <StorageIcon />
- </ListItemIcon>
- <ListItemText primary={t('sharedComputedAttributes')} />
- </ListItem>
- <ListItem button onClick={() => history.push('/settings/maintenances')}>
- <ListItemIcon>
- <BuildIcon />
- </ListItemIcon>
- <ListItemText primary={t('sharedMaintenance')} />
+ <ListItemText primary={t('settingsTitle')} />
</ListItem>
</List>
- {adminEnabled && (
- <>
- <Divider />
- <List
- subheader={
- <ListSubheader>
- {t('userAdmin')}
- </ListSubheader>
- }>
- <ListItem button onClick={() => history.push('/admin/server')}>
- <ListItemIcon>
- <StorageIcon />
- </ListItemIcon>
- <ListItemText primary={t('settingsServer')} />
- </ListItem>
- <ListItem button onClick={() => history.push('/admin/users')}>
- <ListItemIcon>
- <PeopleIcon />
- </ListItemIcon>
- <ListItemText primary={t('settingsUsers')} />
- </ListItem>
- <ListItem button onClick={() => history.push('/admin/statistics')}>
- <ListItemIcon>
- <BarChartIcon />
- </ListItemIcon>
- <ListItemText primary={t('statisticsTitle')} />
- </ListItem>
- </List>
- </>
- )}
</div>
</Drawer>
</>
);
-}
+};
export default MainToolbar;
diff --git a/modern/src/StartPage.js b/modern/src/StartPage.js
index 0a08617..53f8c35 100644
--- a/modern/src/StartPage.js
+++ b/modern/src/StartPage.js
@@ -16,7 +16,7 @@ const useStyles = makeStyles(theme => ({
paddingBottom: theme.spacing(5),
width: theme.dimensions.sidebarWidth,
[theme.breakpoints.down('md')]: {
- width: theme.dimensions.tabletSidebarWidth,
+ width: theme.dimensions.sidebarWidthTablet,
},
[theme.breakpoints.down('xs')]: {
width: '0px',
diff --git a/modern/src/admin/ServerPage.js b/modern/src/admin/ServerPage.js
index 43664e5..c33a631 100644
--- a/modern/src/admin/ServerPage.js
+++ b/modern/src/admin/ServerPage.js
@@ -6,26 +6,26 @@ import { Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography,
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { useHistory } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
-import MainToolbar from '../MainToolbar';
import { sessionActions } from '../store';
import EditAttributesView from '../attributes/EditAttributesView';
import deviceAttributes from '../attributes/deviceAttributes';
import userAttributes from '../attributes/userAttributes';
+import OptionsLayout from '../settings/OptionsLayout';
const useStyles = makeStyles(theme => ({
container: {
- marginTop: theme.spacing(2),
+ marginTop: theme.spacing(2)
},
buttons: {
display: 'flex',
justifyContent: 'space-evenly',
'& > *': {
- flexBasis: '33%',
- },
+ flexBasis: '33%'
+ }
},
details: {
- flexDirection: 'column',
- },
+ flexDirection: 'column'
+ }
}));
const ServerPage = () => {
@@ -34,13 +34,14 @@ const ServerPage = () => {
const classes = useStyles();
const item = useSelector(state => state.session.server);
- const setItem = (updatedItem) => dispatch(sessionActions.updateServer(updatedItem));
+ const setItem = updatedItem =>
+ dispatch(sessionActions.updateServer(updatedItem));
const handleSave = async () => {
const response = await fetch('/api/server', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(item),
+ body: JSON.stringify(item)
});
if (response.ok) {
@@ -49,10 +50,9 @@ const ServerPage = () => {
};
return (
- <>
- <MainToolbar />
- <Container maxWidth='xs' className={classes.container}>
- {item &&
+ <OptionsLayout>
+ <Container maxWidth="xs" className={classes.container}>
+ {item && (
<>
<Accordion defaultExpanded>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
@@ -64,9 +64,12 @@ const ServerPage = () => {
<TextField
margin="normal"
value={item.announcement || ''}
- onChange={event => setItem({...item, announcement: event.target.value})}
+ onChange={event =>
+ setItem({ ...item, announcement: event.target.value })
+ }
label={t('serverAnnouncement')}
- variant="filled" />
+ variant="filled"
+ />
</AccordionDetails>
</Accordion>
<Accordion>
@@ -77,17 +80,55 @@ const ServerPage = () => {
</AccordionSummary>
<AccordionDetails className={classes.details}>
<FormControlLabel
- control={<Checkbox checked={item.registration} onChange={event => setItem({...item, registration: event.target.checked})} />}
- label={t('serverRegistration')} />
+ control={
+ <Checkbox
+ checked={item.registration}
+ onChange={event =>
+ setItem({ ...item, registration: event.target.checked })
+ }
+ />
+ }
+ label={t('serverRegistration')}
+ />
<FormControlLabel
- control={<Checkbox checked={item.readonly} onChange={event => setItem({...item, readonly: event.target.checked})} />}
- label={t('serverReadonly')} />
+ control={
+ <Checkbox
+ checked={item.readonly}
+ onChange={event =>
+ setItem({ ...item, readonly: event.target.checked })
+ }
+ />
+ }
+ label={t('serverReadonly')}
+ />
<FormControlLabel
- control={<Checkbox checked={item.deviceReadonly} onChange={event => setItem({...item, deviceReadonly: event.target.checked})} />}
- label={t('userDeviceReadonly')} />
+ control={
+ <Checkbox
+ checked={item.deviceReadonly}
+ onChange={event =>
+ setItem({
+ ...item,
+ deviceReadonly: event.target.checked
+ })
+ }
+ />
+ }
+ label={t('userDeviceReadonly')}
+ />
<FormControlLabel
- control={<Checkbox checked={item.limitCommands} onChange={event => setItem({...item, limitCommands: event.target.checked})} />}
- label={t('userLimitCommands')} />
+ control={
+ <Checkbox
+ checked={item.limitCommands}
+ onChange={event =>
+ setItem({
+ ...item,
+ limitCommands: event.target.checked
+ })
+ }
+ />
+ }
+ label={t('userLimitCommands')}
+ />
</AccordionDetails>
</Accordion>
<Accordion>
@@ -99,26 +140,36 @@ const ServerPage = () => {
<AccordionDetails className={classes.details}>
<EditAttributesView
attributes={item.attributes}
- setAttributes={attributes => setItem({...item, attributes})}
- definitions={{...userAttributes, ...deviceAttributes}}
+ setAttributes={attributes => setItem({ ...item, attributes })}
+ definitions={{ ...userAttributes, ...deviceAttributes }}
/>
</AccordionDetails>
</Accordion>
</>
- }
- <FormControl fullWidth margin='normal'>
+ )}
+ <FormControl fullWidth margin="normal">
<div className={classes.buttons}>
- <Button type='button' color='primary' variant='outlined' onClick={() => history.goBack()}>
+ <Button
+ type="button"
+ color="primary"
+ variant="outlined"
+ onClick={() => history.goBack()}
+ >
{t('sharedCancel')}
</Button>
- <Button type='button' color='primary' variant='contained' onClick={handleSave}>
+ <Button
+ type="button"
+ color="primary"
+ variant="contained"
+ onClick={handleSave}
+ >
{t('sharedSave')}
</Button>
</div>
</FormControl>
</Container>
- </>
+ </OptionsLayout>
);
-}
+};
export default ServerPage;
diff --git a/modern/src/admin/StatisticsPage.js b/modern/src/admin/StatisticsPage.js
index 1e440a4..b1504c8 100644
--- a/modern/src/admin/StatisticsPage.js
+++ b/modern/src/admin/StatisticsPage.js
@@ -3,8 +3,8 @@ import React, { useState } from 'react';
import { FormControl, InputLabel,Select, MenuItem, TextField, Button, TableContainer, Table, TableRow, TableCell, TableHead, TableBody, Paper } from '@material-ui/core';
import t from '../common/localization';
import { formatDate } from '../common/formatter';
-import ReportLayoutPage from '../reports/ReportLayoutPage';
import moment from 'moment';
+import OptionsLayout from '../settings/OptionsLayout';
const Filter = ({ setItems }) => {
const [period, setPeriod] = useState('today');
@@ -20,24 +20,36 @@ const Filter = ({ setItems }) => {
selectedTo = moment().endOf('day');
break;
case 'yesterday':
- selectedFrom = moment().subtract(1, 'day').startOf('day');
- selectedTo = moment().subtract(1, 'day').endOf('day');
+ selectedFrom = moment()
+ .subtract(1, 'day')
+ .startOf('day');
+ selectedTo = moment()
+ .subtract(1, 'day')
+ .endOf('day');
break;
case 'thisWeek':
selectedFrom = moment().startOf('week');
selectedTo = moment().endOf('week');
break;
case 'previousWeek':
- selectedFrom = moment().subtract(1, 'week').startOf('week');
- selectedTo = moment().subtract(1, 'week').endOf('week');
+ selectedFrom = moment()
+ .subtract(1, 'week')
+ .startOf('week');
+ selectedTo = moment()
+ .subtract(1, 'week')
+ .endOf('week');
break;
case 'thisMonth':
selectedFrom = moment().startOf('month');
selectedTo = moment().endOf('month');
break;
case 'previousMonth':
- selectedFrom = moment().subtract(1, 'month').startOf('month');
- selectedTo = moment().subtract(1, 'month').endOf('month');
+ selectedFrom = moment()
+ .subtract(1, 'month')
+ .startOf('month');
+ selectedTo = moment()
+ .subtract(1, 'month')
+ .endOf('month');
break;
default:
selectedFrom = from;
@@ -45,18 +57,23 @@ const Filter = ({ setItems }) => {
break;
}
- const query = new URLSearchParams({ from: selectedFrom.toISOString(), to: selectedTo.toISOString() });
- const response = await fetch(`/api/statistics?${query.toString()}`, { Accept: 'application/json' });
+ const query = new URLSearchParams({
+ from: selectedFrom.toISOString(),
+ to: selectedTo.toISOString()
+ });
+ const response = await fetch(`/api/statistics?${query.toString()}`, {
+ Accept: 'application/json'
+ });
if (response.ok) {
setItems(await response.json());
}
- }
+ };
return (
<>
<FormControl variant="filled" margin="normal" fullWidth>
<InputLabel>{t('reportPeriod')}</InputLabel>
- <Select value={period} onChange={(e) => setPeriod(e.target.value)}>
+ <Select value={period} onChange={e => setPeriod(e.target.value)}>
<MenuItem value="today">{t('reportToday')}</MenuItem>
<MenuItem value="yesterday">{t('reportYesterday')}</MenuItem>
<MenuItem value="thisWeek">{t('reportThisWeek')}</MenuItem>
@@ -73,8 +90,11 @@ const Filter = ({ setItems }) => {
label={t('reportFrom')}
type="datetime-local"
value={from.format(moment.HTML5_FMT.DATETIME_LOCAL)}
- onChange={e => setFrom(moment(e.target.value, moment.HTML5_FMT.DATETIME_LOCAL))}
- fullWidth />
+ onChange={e =>
+ setFrom(moment(e.target.value, moment.HTML5_FMT.DATETIME_LOCAL))
+ }
+ fullWidth
+ />
)}
{period === 'custom' && (
<TextField
@@ -83,20 +103,30 @@ const Filter = ({ setItems }) => {
label={t('reportTo')}
type="datetime-local"
value={to.format(moment.HTML5_FMT.DATETIME_LOCAL)}
- onChange={e => setTo(moment(e.target.value, moment.HTML5_FMT.DATETIME_LOCAL))}
- fullWidth />
+ onChange={e =>
+ setTo(moment(e.target.value, moment.HTML5_FMT.DATETIME_LOCAL))
+ }
+ fullWidth
+ />
)}
- <Button variant="contained" color="primary" onClick={handleClick} fullWidth>{t('reportShow')}</Button>
+ <Button
+ variant="contained"
+ color="primary"
+ onClick={handleClick}
+ fullWidth
+ >
+ {t('reportShow')}
+ </Button>
</>
- )
-}
+ );
+};
const StatisticsPage = () => {
-
const [items, setItems] = useState([]);
return (
- <ReportLayoutPage filter={<Filter setItems={setItems} />}>
+ <OptionsLayout>
+ <Filter setItems={setItems} />
<TableContainer component={Paper}>
<Table>
<TableHead>
@@ -114,7 +144,7 @@ const StatisticsPage = () => {
</TableRow>
</TableHead>
<TableBody>
- {items.map((item) => (
+ {items.map(item => (
<TableRow key={item.id}>
<TableCell>{formatDate(item.captureTime)}</TableCell>
<TableCell>{item.activeUsers}</TableCell>
@@ -131,8 +161,8 @@ const StatisticsPage = () => {
</TableBody>
</Table>
</TableContainer>
- </ReportLayoutPage>
+ </OptionsLayout>
);
-}
+};
export default StatisticsPage;
diff --git a/modern/src/admin/UsersPage.js b/modern/src/admin/UsersPage.js
index 630bea4..ceaab6a 100644
--- a/modern/src/admin/UsersPage.js
+++ b/modern/src/admin/UsersPage.js
@@ -1,17 +1,17 @@
import React, { useState } from 'react';
-import MainToolbar from '../MainToolbar';
import { TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton } from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import t from '../common/localization';
import { useEffectAsync } from '../reactHelper';
import EditCollectionView from '../EditCollectionView';
import { formatBoolean } from '../common/formatter';
+import OptionsLayout from '../settings/OptionsLayout';
const useStyles = makeStyles(theme => ({
columnAction: {
width: theme.spacing(1),
- padding: theme.spacing(0, 1),
- },
+ padding: theme.spacing(0, 1)
+ }
}));
const UsersView = ({ updateTimestamp, onMenuClick }) => {
@@ -39,10 +39,12 @@ const UsersView = ({ updateTimestamp, onMenuClick }) => {
</TableRow>
</TableHead>
<TableBody>
- {items.map((item) => (
+ {items.map(item => (
<TableRow key={item.id}>
<TableCell className={classes.columnAction} padding="none">
- <IconButton onClick={(event) => onMenuClick(event.currentTarget, item.id)}>
+ <IconButton
+ onClick={event => onMenuClick(event.currentTarget, item.id)}
+ >
<MoreVertIcon />
</IconButton>
</TableCell>
@@ -56,15 +58,18 @@ const UsersView = ({ updateTimestamp, onMenuClick }) => {
</Table>
</TableContainer>
);
-}
+};
const UsersPage = () => {
return (
- <>
- <MainToolbar />
- <EditCollectionView content={UsersView} editPath="/user" endpoint="users" />
- </>
+ <OptionsLayout>
+ <EditCollectionView
+ content={UsersView}
+ editPath="/user"
+ endpoint="users"
+ />
+ </OptionsLayout>
);
-}
+};
export default UsersPage;
diff --git a/modern/src/components/NavBar.js b/modern/src/components/NavBar.js
new file mode 100644
index 0000000..68e38bf
--- /dev/null
+++ b/modern/src/components/NavBar.js
@@ -0,0 +1,23 @@
+import React from 'react';
+import { AppBar, Toolbar, Typography, IconButton } from '@material-ui/core';
+import MenuIcon from '@material-ui/icons/Menu';
+
+const Navbar = ({ setOpenDrawer, title }) => (
+ <AppBar position="fixed" color="inherit">
+ <Toolbar>
+ <IconButton
+ color="inherit"
+ aria-label="open drawer"
+ edge="start"
+ onClick={() => setOpenDrawer(true)}
+ >
+ <MenuIcon />
+ </IconButton>
+ <Typography variant="h6" noWrap>
+ {title}
+ </Typography>
+ </Toolbar>
+ </AppBar>
+);
+
+export default Navbar;
diff --git a/modern/src/components/SideNav.js b/modern/src/components/SideNav.js
new file mode 100644
index 0000000..8bd43ac
--- /dev/null
+++ b/modern/src/components/SideNav.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import { List, ListItem, ListItemText, ListItemIcon, Divider, ListSubheader } from '@material-ui/core';
+import { Link, useLocation } from 'react-router-dom';
+
+const SideNav = ({ routes }) => {
+ const location = useLocation();
+
+ return (
+ <List disablePadding style={{ paddingTop: '16px' }}>
+ {routes.map((route, index) =>
+ route.subheader ? (
+ <>
+ <Divider />
+ <ListSubheader>{route.subheader}</ListSubheader>
+ </>
+ ) : (
+ <ListItem
+ disableRipple
+ component={Link}
+ key={route.href || route.subheader}
+ button
+ to={route.href}
+ selected={location.pathname.match(route.match || route.href)}
+ >
+ <ListItemIcon>{route.icon}</ListItemIcon>
+ <ListItemText primary={route.name} />
+ </ListItem>
+ )
+ )}
+ </List>
+ );
+};
+
+export default SideNav;
diff --git a/modern/src/components/reports/ReportNavbar.js b/modern/src/components/reports/ReportNavbar.js
deleted file mode 100644
index ac01fad..0000000
--- a/modern/src/components/reports/ReportNavbar.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-import { AppBar, Toolbar, Typography, IconButton } from '@material-ui/core';
-import MenuIcon from '@material-ui/icons/Menu';
-import t from '../../common/localization';
-
-const ReportNavbar = ({ setOpenDrawer, reportTitle }) => {
-
- return (
- <AppBar position="fixed" color="inherit">
- <Toolbar>
- <IconButton
- color="inherit"
- aria-label="open drawer"
- edge="start"
- onClick={() => setOpenDrawer(true)}>
- <MenuIcon />
- </IconButton>
- <Typography variant="h6" noWrap>
- {t('reportTitle')} {` / ${reportTitle}`}
- </Typography>
- </Toolbar>
- </AppBar>
- )
-}
-
-export default ReportNavbar;
diff --git a/modern/src/components/reports/ReportSidebar.js b/modern/src/components/reports/ReportSidebar.js
deleted file mode 100644
index 90e20c0..0000000
--- a/modern/src/components/reports/ReportSidebar.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react';
-import { List, ListItem, ListItemText, ListItemIcon } from '@material-ui/core';
-import { Link, useLocation } from 'react-router-dom';
-
-const ReportSidebar = ({ routes }) => {
-
- const location = useLocation();
-
- return (
- <List disablePadding style={{paddingTop: '16px'}}>
- {routes.map((route, index) => (
- <ListItem
- disableRipple
- component={Link}
- key={`${route}${index}`}
- button
- to={route.href}
- selected={route.href === location.pathname}>
- <ListItemIcon>
- {route.icon}
- </ListItemIcon>
- <ListItemText primary={route.name} />
- </ListItem>
- ))}
- </List>
- )
-}
-
-export default ReportSidebar;
diff --git a/modern/src/reports/ChartReportPage.js b/modern/src/reports/ChartReportPage.js
index 0a5c8e1..8eeedc4 100644
--- a/modern/src/reports/ChartReportPage.js
+++ b/modern/src/reports/ChartReportPage.js
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { Grid, FormControl, InputLabel, Select, MenuItem } from '@material-ui/core';
-import ReportLayoutPage from './ReportLayoutPage';
+import ReportLayout from './ReportLayout';
import ReportFilter from './ReportFilter';
import Graph from './Graph';
import { useAttributePreference } from '../common/preferences';
@@ -61,13 +61,13 @@ const ChartReportPage = () => {
const [type, setType] = useState('speed');
return (
- <ReportLayoutPage filter={
+ <ReportLayout filter={
<Filter setItems={setItems}>
<ChartType type={type} setType={setType} />
</Filter>
}>
<Graph items={items} type={type} />
- </ReportLayoutPage>
+ </ReportLayout>
)
}
diff --git a/modern/src/reports/EventReportPage.js b/modern/src/reports/EventReportPage.js
index 6d80860..b938dc8 100644
--- a/modern/src/reports/EventReportPage.js
+++ b/modern/src/reports/EventReportPage.js
@@ -5,7 +5,7 @@ import { useTheme } from "@material-ui/core/styles";
import { useSelector } from 'react-redux';
import { formatDate } from '../common/formatter';
import ReportFilter from './ReportFilter';
-import ReportLayoutPage from './ReportLayoutPage';
+import ReportLayout from './ReportLayout';
import { prefixString } from '../common/stringUtils';
import t from '../common/localization';
@@ -99,13 +99,13 @@ const EventReportPage = () => {
}];
return (
- <ReportLayoutPage filter={<Filter setItems={setItems} />}>
+ <ReportLayout filter={<Filter setItems={setItems} />}>
<DataGrid
rows={items}
columns={columns}
hideFooter
autoHeight />
- </ReportLayoutPage>
+ </ReportLayout>
);
}
diff --git a/modern/src/reports/ReportFilter.js b/modern/src/reports/ReportFilter.js
index c92741e..524f7bb 100644
--- a/modern/src/reports/ReportFilter.js
+++ b/modern/src/reports/ReportFilter.js
@@ -56,7 +56,7 @@ const ReportFilter = ({ children, handleSubmit, showOnly }) => {
}
return (
- <Grid container spacing={2}>
+ <Grid container spacing={2} justify="flex-end">
<Grid item xs={12} sm={period === 'custom' ? 3 : 6}>
<FormControl variant="filled" fullWidth>
<InputLabel>{t('reportDevice')}</InputLabel>
diff --git a/modern/src/reports/ReportLayoutPage.js b/modern/src/reports/ReportLayout.js
index 6bab67c..5b54335 100644
--- a/modern/src/reports/ReportLayoutPage.js
+++ b/modern/src/reports/ReportLayout.js
@@ -1,6 +1,14 @@
import React, { useState, useEffect } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
-import { Grid, Typography, Divider, Drawer, makeStyles, IconButton, Hidden } from '@material-ui/core';
+import {
+ Grid,
+ Typography,
+ Divider,
+ Drawer,
+ makeStyles,
+ IconButton,
+ Hidden
+} from '@material-ui/core';
import TimelineIcon from '@material-ui/icons/Timeline';
import PauseCircleFilledIcon from '@material-ui/icons/PauseCircleFilled';
import PlayCircleFilledIcon from '@material-ui/icons/PlayCircleFilled';
@@ -9,56 +17,67 @@ import FormatListBulletedIcon from '@material-ui/icons/FormatListBulleted';
import TrendingUpIcon from '@material-ui/icons/TrendingUp';
import ArrowBackIcon from '@material-ui/icons/ArrowBack';
-import ReportSidebar from '../components/reports/ReportSidebar'
-import ReportNavbar from '../components/reports/ReportNavbar'
+import SideNav from '../components/SideNav';
+import NavBar from '../components/NavBar';
import t from '../common/localization';
const useStyles = makeStyles(theme => ({
root: {
display: 'flex',
- height: '100%',
+ height: '100%'
},
drawerContainer: {
- width: theme.dimensions.drawerWidthDesktop,
+ width: theme.dimensions.drawerWidthDesktop
},
drawer: {
width: theme.dimensions.drawerWidthDesktop,
- [theme.breakpoints.down("md")]: {
- width: theme.dimensions.drawerWidthTablet,
+ [theme.breakpoints.down('md')]: {
+ width: theme.dimensions.drawerWidthTablet
}
- },
+ },
content: {
flex: 1,
- padding: theme.spacing(5, 3, 3, 3),
+ padding: theme.spacing(5, 3, 3, 3)
},
drawerHeader: {
...theme.mixins.toolbar,
display: 'flex',
alignItems: 'center',
- padding: theme.spacing(0, 1),
- },
- backArrowIconContainer: {
- '&:hover': {
- backgroundColor:"transparent"
- }
+ padding: theme.spacing(0, 1)
},
toolbar: {
- [theme.breakpoints.down("md")]: {
- ...theme.mixins.toolbar,
+ [theme.breakpoints.down('md')]: {
+ ...theme.mixins.toolbar
}
- },
+ }
}));
const routes = [
{ name: t('reportRoute'), href: '/reports/route', icon: <TimelineIcon /> },
- { name: t('reportEvents'), href: '/reports/event', icon: <NotificationsActiveIcon /> },
- { name: t('reportTrips'), href: '/reports/trip', icon: <PlayCircleFilledIcon /> },
- { name: t('reportStops'), href: '/reports/stop', icon: <PauseCircleFilledIcon /> },
- { name: t('reportSummary'), href: '/reports/summary', icon: <FormatListBulletedIcon /> },
- { name: t('reportChart'), href: '/reports/chart', icon: <TrendingUpIcon /> },
+ {
+ name: t('reportEvents'),
+ href: '/reports/event',
+ icon: <NotificationsActiveIcon />
+ },
+ {
+ name: t('reportTrips'),
+ href: '/reports/trip',
+ icon: <PlayCircleFilledIcon />
+ },
+ {
+ name: t('reportStops'),
+ href: '/reports/stop',
+ icon: <PauseCircleFilledIcon />
+ },
+ {
+ name: t('reportSummary'),
+ href: '/reports/summary',
+ icon: <FormatListBulletedIcon />
+ },
+ { name: t('reportChart'), href: '/reports/chart', icon: <TrendingUpIcon /> }
];
-const ReportLayoutPage = ({ children, filter, }) => {
+const ReportLayout = ({ children, filter }) => {
const classes = useStyles();
const history = useHistory();
const location = useLocation();
@@ -77,38 +96,39 @@ const ReportLayoutPage = ({ children, filter, }) => {
});
}, [location]);
+ const pageTitle = `${t('reportTitle')} / ${reportTitle}`;
+
return (
<div className={classes.root}>
<Hidden only={['lg', 'xl']}>
- <ReportNavbar setOpenDrawer={setOpenDrawer} reportTitle={reportTitle} />
+ <NavBar setOpenDrawer={setOpenDrawer} title={pageTitle} />
<Drawer
variant="temporary"
open={openDrawer}
onClose={() => setOpenDrawer(!openDrawer)}
- classes={{paper: classes.drawer}}>
- <ReportSidebar routes={routes} />
+ classes={{ paper: classes.drawer }}
+ >
+ <SideNav routes={routes} />
</Drawer>
</Hidden>
<Hidden only={['xs', 'sm', 'md']}>
- <div className={classes.drawerContainer}>
- <Drawer
- variant="permanent"
- classes={{paper: classes.drawer}}>
- <div className={classes.drawerHeader}>
- <IconButton
- onClick={() => history.push('/')}
- className={classes.backArrowIconContainer}
- disableRipple>
- <ArrowBackIcon />
- </IconButton>
- <Typography variant="h6" color="inherit" noWrap>
- {t('reportTitle')}
- </Typography>
- </div>
- <Divider />
- <ReportSidebar routes={routes} />
- </Drawer>
- </div>
+ <Drawer
+ variant="permanent"
+ classes={{ root: classes.drawerContainer, paper: classes.drawer }}
+ >
+ <div className={classes.drawerHeader}>
+ <IconButton
+ onClick={() => history.push('/')}
+ >
+ <ArrowBackIcon />
+ </IconButton>
+ <Typography variant="h6" color="inherit" noWrap>
+ {t('reportTitle')}
+ </Typography>
+ </div>
+ <Divider />
+ <SideNav routes={routes} />
+ </Drawer>
</Hidden>
<div className={classes.content}>
<div className={classes.toolbar} />
@@ -116,9 +136,9 @@ const ReportLayoutPage = ({ children, filter, }) => {
<Grid item>{filter}</Grid>
<Grid item>{children}</Grid>
</Grid>
- </div>
+ </div>
</div>
);
-}
+};
-export default ReportLayoutPage;
+export default ReportLayout;
diff --git a/modern/src/reports/RouteReportPage.js b/modern/src/reports/RouteReportPage.js
index 04b513e..c09d4f5 100644
--- a/modern/src/reports/RouteReportPage.js
+++ b/modern/src/reports/RouteReportPage.js
@@ -4,7 +4,7 @@ import { DataGrid } from '@material-ui/data-grid';
import { useTheme } from "@material-ui/core/styles";
import { formatDistance, formatSpeed, formatBoolean, formatDate, formatCoordinate } from '../common/formatter';
import ReportFilter from './ReportFilter';
-import ReportLayoutPage from './ReportLayoutPage';
+import ReportLayout from './ReportLayout';
import { useAttributePreference, usePreference } from '../common/preferences';
import t from '../common/localization';
@@ -83,7 +83,7 @@ const RouteReportPage = () => {
const [items, setItems] = useState([]);
return (
- <ReportLayoutPage filter={<Filter setItems={setItems} />}>
+ <ReportLayout filter={<Filter setItems={setItems} />}>
<Paper>
<DataGrid
rows={items}
@@ -91,7 +91,7 @@ const RouteReportPage = () => {
hideFooter
autoHeight />
</Paper>
- </ReportLayoutPage>
+ </ReportLayout>
);
};
diff --git a/modern/src/reports/StopReportPage.js b/modern/src/reports/StopReportPage.js
index 6953c46..57f5956 100644
--- a/modern/src/reports/StopReportPage.js
+++ b/modern/src/reports/StopReportPage.js
@@ -3,7 +3,7 @@ import { DataGrid } from '@material-ui/data-grid';
import { useTheme } from "@material-ui/core/styles";
import { formatDistance, formatHours, formatDate, formatVolume } from '../common/formatter';
import ReportFilter from './ReportFilter';
-import ReportLayoutPage from './ReportLayoutPage';
+import ReportLayout from './ReportLayout';
import { useAttributePreference } from '../common/preferences';
import t from '../common/localization';
@@ -82,14 +82,14 @@ const StopReportPage = () => {
}]
return (
- <ReportLayoutPage filter={<Filter setItems={setItems} />}>
+ <ReportLayout filter={<Filter setItems={setItems} />}>
<DataGrid
rows={items}
columns={columns}
hideFooter
autoHeight
getRowId={() => Math.random()} />
- </ReportLayoutPage>
+ </ReportLayout>
);
};
diff --git a/modern/src/reports/SummaryReportPage.js b/modern/src/reports/SummaryReportPage.js
index e3819a5..53e697d 100644
--- a/modern/src/reports/SummaryReportPage.js
+++ b/modern/src/reports/SummaryReportPage.js
@@ -4,7 +4,7 @@ import { Grid, FormControlLabel, Checkbox } from '@material-ui/core';
import { useTheme } from "@material-ui/core/styles";
import { formatDistance, formatHours, formatDate, formatSpeed, formatVolume } from '../common/formatter';
import ReportFilter from './ReportFilter';
-import ReportLayoutPage from './ReportLayoutPage';
+import ReportLayout from './ReportLayout';
import { useAttributePreference } from '../common/preferences';
import t from '../common/localization';
@@ -100,14 +100,14 @@ const SummaryReportPage = () => {
}]
return (
- <ReportLayoutPage filter={<Filter setItems={setItems} />}>
+ <ReportLayout filter={<Filter setItems={setItems} />}>
<DataGrid
rows={items}
columns={columns}
hideFooter
autoHeight
getRowId={() => Math.random()} />
- </ReportLayoutPage>
+ </ReportLayout>
);
}
diff --git a/modern/src/reports/TripReportPage.js b/modern/src/reports/TripReportPage.js
index 5f414f4..e8c9129 100644
--- a/modern/src/reports/TripReportPage.js
+++ b/modern/src/reports/TripReportPage.js
@@ -3,7 +3,7 @@ import { DataGrid } from '@material-ui/data-grid';
import { useTheme } from "@material-ui/core/styles";
import { formatDistance, formatSpeed, formatHours, formatDate, formatVolume } from '../common/formatter';
import ReportFilter from './ReportFilter';
-import ReportLayoutPage from './ReportLayoutPage';
+import ReportLayout from './ReportLayout';
import { useAttributePreference } from '../common/preferences';
import t from '../common/localization';
@@ -113,14 +113,14 @@ const TripReportPage = () => {
}]
return (
- <ReportLayoutPage filter={<Filter setItems={setItems} />}>
+ <ReportLayout filter={<Filter setItems={setItems} />}>
<DataGrid
rows={items}
columns={columns}
hideFooter
autoHeight
getRowId={() => Math.random()} />
- </ReportLayoutPage>
+ </ReportLayout>
);
}
diff --git a/modern/src/selectors.js b/modern/src/selectors.js
new file mode 100644
index 0000000..f0b08f5
--- /dev/null
+++ b/modern/src/selectors.js
@@ -0,0 +1,3 @@
+export const getIsAdmin = state => state.session.user?.administrator;
+
+export const getUserId = state => state.session.user?.id;
diff --git a/modern/src/settings/ComputedAttributesPage.js b/modern/src/settings/ComputedAttributesPage.js
index 1a6feab..d747598 100644
--- a/modern/src/settings/ComputedAttributesPage.js
+++ b/modern/src/settings/ComputedAttributesPage.js
@@ -1,24 +1,26 @@
import React, { useState } from 'react';
-import MainToolbar from '../MainToolbar';
import { TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton } from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import { useSelector } from 'react-redux';
import t from '../common/localization';
import { useEffectAsync } from '../reactHelper';
import EditCollectionView from '../EditCollectionView';
+import OptionsLayout from './OptionsLayout';
const useStyles = makeStyles(theme => ({
columnAction: {
width: theme.spacing(1),
- padding: theme.spacing(0, 1),
- },
+ padding: theme.spacing(0, 1)
+ }
}));
const ComputedAttributeView = ({ updateTimestamp, onMenuClick }) => {
const classes = useStyles();
const [items, setItems] = useState([]);
- const adminEnabled = useSelector(state => state.session.user && state.session.user.administrator);
+ const adminEnabled = useSelector(
+ state => state.session.user && state.session.user.administrator
+ );
useEffectAsync(async () => {
const response = await fetch('/api/attributes/computed');
@@ -40,15 +42,17 @@ const ComputedAttributeView = ({ updateTimestamp, onMenuClick }) => {
</TableRow>
</TableHead>
<TableBody>
- {items.map((item) => (
+ {items.map(item => (
<TableRow key={item.id}>
- {adminEnabled &&
+ {adminEnabled && (
<TableCell className={classes.columnAction} padding="none">
- <IconButton onClick={(event) => onMenuClick(event.currentTarget, item.id)}>
+ <IconButton
+ onClick={event => onMenuClick(event.currentTarget, item.id)}
+ >
<MoreVertIcon />
</IconButton>
</TableCell>
- }
+ )}
<TableCell>{item.description}</TableCell>
<TableCell>{item.attribute}</TableCell>
<TableCell>{item.expression}</TableCell>
@@ -59,15 +63,18 @@ const ComputedAttributeView = ({ updateTimestamp, onMenuClick }) => {
</Table>
</TableContainer>
);
-}
+};
const ComputedAttributesPage = () => {
return (
- <>
- <MainToolbar />
- <EditCollectionView content={ComputedAttributeView} editPath="/settings/attribute" endpoint="attributes/computed" />
- </>
+ <OptionsLayout>
+ <EditCollectionView
+ content={ComputedAttributeView}
+ editPath="/settings/attribute"
+ endpoint="attributes/computed"
+ />
+ </OptionsLayout>
);
-}
+};
export default ComputedAttributesPage;
diff --git a/modern/src/settings/DriversPage.js b/modern/src/settings/DriversPage.js
index 957e225..d5427b2 100644
--- a/modern/src/settings/DriversPage.js
+++ b/modern/src/settings/DriversPage.js
@@ -1,16 +1,16 @@
import React, { useState } from 'react';
-import MainToolbar from '../MainToolbar';
import { TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton } from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import t from '../common/localization';
import { useEffectAsync } from '../reactHelper';
import EditCollectionView from '../EditCollectionView';
+import OptionsLayout from './OptionsLayout';
const useStyles = makeStyles(theme => ({
columnAction: {
width: theme.spacing(1),
- padding: theme.spacing(0, 1),
- },
+ padding: theme.spacing(0, 1)
+ }
}));
const DriversView = ({ updateTimestamp, onMenuClick }) => {
@@ -36,10 +36,12 @@ const DriversView = ({ updateTimestamp, onMenuClick }) => {
</TableRow>
</TableHead>
<TableBody>
- {items.map((item) => (
+ {items.map(item => (
<TableRow key={item.id}>
<TableCell className={classes.columnAction} padding="none">
- <IconButton onClick={(event) => onMenuClick(event.currentTarget, item.id)}>
+ <IconButton
+ onClick={event => onMenuClick(event.currentTarget, item.id)}
+ >
<MoreVertIcon />
</IconButton>
</TableCell>
@@ -51,15 +53,18 @@ const DriversView = ({ updateTimestamp, onMenuClick }) => {
</Table>
</TableContainer>
);
-}
+};
const DriversPage = () => {
return (
- <>
- <MainToolbar />
- <EditCollectionView content={DriversView} editPath="/settings/driver" endpoint="drivers" />
- </>
+ <OptionsLayout>
+ <EditCollectionView
+ content={DriversView}
+ editPath="/settings/driver"
+ endpoint="drivers"
+ />
+ </OptionsLayout>
);
-}
+};
export default DriversPage;
diff --git a/modern/src/settings/GroupsPage.js b/modern/src/settings/GroupsPage.js
index e274062..2fc65c1 100644
--- a/modern/src/settings/GroupsPage.js
+++ b/modern/src/settings/GroupsPage.js
@@ -1,16 +1,16 @@
import React, { useState } from 'react';
-import MainToolbar from '../MainToolbar';
import { TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton } from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import t from '../common/localization';
import { useEffectAsync } from '../reactHelper';
import EditCollectionView from '../EditCollectionView';
+import OptionsLayout from './OptionsLayout';
const useStyles = makeStyles(theme => ({
columnAction: {
width: theme.spacing(1),
- padding: theme.spacing(0, 1),
- },
+ padding: theme.spacing(0, 1)
+ }
}));
const GroupsView = ({ updateTimestamp, onMenuClick }) => {
@@ -35,10 +35,12 @@ const GroupsView = ({ updateTimestamp, onMenuClick }) => {
</TableRow>
</TableHead>
<TableBody>
- {items.map((item) => (
+ {items.map(item => (
<TableRow key={item.id}>
<TableCell className={classes.columnAction} padding="none">
- <IconButton onClick={(event) => onMenuClick(event.currentTarget, item.id)}>
+ <IconButton
+ onClick={event => onMenuClick(event.currentTarget, item.id)}
+ >
<MoreVertIcon />
</IconButton>
</TableCell>
@@ -49,15 +51,18 @@ const GroupsView = ({ updateTimestamp, onMenuClick }) => {
</Table>
</TableContainer>
);
-}
+};
const GroupsPage = () => {
return (
- <>
- <MainToolbar />
- <EditCollectionView content={GroupsView} editPath="/settings/group" endpoint="groups" />
- </>
+ <OptionsLayout>
+ <EditCollectionView
+ content={GroupsView}
+ editPath="/settings/group"
+ endpoint="groups"
+ />
+ </OptionsLayout>
);
-}
+};
export default GroupsPage;
diff --git a/modern/src/settings/MaintenancesPage.js b/modern/src/settings/MaintenancesPage.js
index 7ba4bd2..d713eaa 100644
--- a/modern/src/settings/MaintenancesPage.js
+++ b/modern/src/settings/MaintenancesPage.js
@@ -1,5 +1,4 @@
import React, { useState } from 'react';
-import MainToolbar from '../MainToolbar';
import { TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton } from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import t from '../common/localization';
@@ -9,12 +8,13 @@ import EditCollectionView from '../EditCollectionView';
import positionAttributes from '../attributes/positionAttributes';
import { formatDistance, formatSpeed } from '../common/formatter';
import { useAttributePreference } from '../common/preferences';
+import OptionsLayout from './OptionsLayout';
const useStyles = makeStyles(theme => ({
columnAction: {
width: theme.spacing(1),
- padding: theme.spacing(0, 1),
- },
+ padding: theme.spacing(0, 1)
+ }
}));
const MaintenancesView = ({ updateTimestamp, onMenuClick }) => {
@@ -45,7 +45,7 @@ const MaintenancesView = ({ updateTimestamp, onMenuClick }) => {
}
return value;
- }
+ };
return (
<TableContainer>
@@ -63,7 +63,9 @@ const MaintenancesView = ({ updateTimestamp, onMenuClick }) => {
{items.map(item => (
<TableRow key={item.id}>
<TableCell className={classes.columnAction} padding="none">
- <IconButton onClick={event => onMenuClick(event.currentTarget, item.id)}>
+ <IconButton
+ onClick={event => onMenuClick(event.currentTarget, item.id)}
+ >
<MoreVertIcon />
</IconButton>
</TableCell>
@@ -77,15 +79,18 @@ const MaintenancesView = ({ updateTimestamp, onMenuClick }) => {
</Table>
</TableContainer>
);
-}
+};
const MaintenacesPage = () => {
return (
- <>
- <MainToolbar />
- <EditCollectionView content={MaintenancesView} editPath="/settings/maintenance" endpoint="maintenance" />
- </>
+ <OptionsLayout>
+ <EditCollectionView
+ content={MaintenancesView}
+ editPath="/settings/maintenance"
+ endpoint="maintenance"
+ />
+ </OptionsLayout>
);
-}
+};
export default MaintenacesPage;
diff --git a/modern/src/settings/NotificationsPage.js b/modern/src/settings/NotificationsPage.js
index 15da0de..5707f89 100644
--- a/modern/src/settings/NotificationsPage.js
+++ b/modern/src/settings/NotificationsPage.js
@@ -1,5 +1,4 @@
import React, { useState } from 'react';
-import MainToolbar from '../MainToolbar';
import { TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton } from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import t from '../common/localization';
@@ -7,12 +6,13 @@ import { useEffectAsync } from '../reactHelper';
import EditCollectionView from '../EditCollectionView';
import { prefixString } from '../common/stringUtils';
import { formatBoolean } from '../common/formatter';
+import OptionsLayout from './OptionsLayout';
const useStyles = makeStyles(theme => ({
columnAction: {
width: theme.spacing(1),
- padding: theme.spacing(0, 1),
- },
+ padding: theme.spacing(0, 1)
+ }
}));
const NotificationsView = ({ updateTimestamp, onMenuClick }) => {
@@ -54,29 +54,38 @@ const NotificationsView = ({ updateTimestamp, onMenuClick }) => {
{items.map(item => (
<TableRow key={item.id}>
<TableCell className={classes.columnAction} padding="none">
- <IconButton onClick={(event) => onMenuClick(event.currentTarget, item.id)}>
+ <IconButton
+ onClick={event => onMenuClick(event.currentTarget, item.id)}
+ >
<MoreVertIcon />
</IconButton>
</TableCell>
<TableCell>{t(prefixString('event', item.type))}</TableCell>
<TableCell>{formatBoolean(item.always)}</TableCell>
- <TableCell>{formatList('alarm', item.attributes.alarms)}</TableCell>
- <TableCell>{formatList('notificator', item.notificators)}</TableCell>
+ <TableCell>
+ {formatList('alarm', item.attributes.alarms)}
+ </TableCell>
+ <TableCell>
+ {formatList('notificator', item.notificators)}
+ </TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
-}
+};
const NotificationsPage = () => {
return (
- <>
- <MainToolbar />
- <EditCollectionView content={NotificationsView} editPath="/settings/notification" endpoint="notifications" />
- </>
+ <OptionsLayout>
+ <EditCollectionView
+ content={NotificationsView}
+ editPath="/settings/notification"
+ endpoint="notifications"
+ />
+ </OptionsLayout>
);
-}
+};
export default NotificationsPage;
diff --git a/modern/src/settings/OptionsLayout/index.js b/modern/src/settings/OptionsLayout/index.js
new file mode 100644
index 0000000..61a94cf
--- /dev/null
+++ b/modern/src/settings/OptionsLayout/index.js
@@ -0,0 +1,106 @@
+import React, { useState, useEffect } from 'react';
+import { useLocation } from 'react-router-dom';
+import {
+ Typography,
+ Divider,
+ Drawer,
+ makeStyles,
+ IconButton,
+ Hidden
+} from '@material-ui/core';
+
+import ArrowBackIcon from '@material-ui/icons/ArrowBack';
+
+import SideNav from '../../components/SideNav';
+import NavBar from '../../components/NavBar';
+import t from '../../common/localization';
+import useRoutes from './useRoutes';
+
+const useStyles = makeStyles(theme => ({
+ root: {
+ display: 'flex',
+ height: '100%'
+ },
+ drawerContainer: {
+ width: theme.dimensions.drawerWidthDesktop
+ },
+ drawer: {
+ width: theme.dimensions.drawerWidthDesktop,
+ [theme.breakpoints.down('md')]: {
+ width: theme.dimensions.drawerWidthTablet
+ }
+ },
+ content: {
+ flex: 1,
+ padding: theme.spacing(5, 3, 3, 3),
+ [theme.breakpoints.down('md')]: {
+ paddingTop: theme.spacing(10)
+ }
+ },
+ drawerHeader: {
+ ...theme.mixins.toolbar,
+ display: 'flex',
+ alignItems: 'center',
+ padding: theme.spacing(0, 1)
+ },
+ toolbar: {
+ [theme.breakpoints.down('md')]: {
+ ...theme.mixins.toolbar
+ }
+ }
+}));
+
+const OptionsLayout = ({ children }) => {
+ const classes = useStyles();
+ const location = useLocation();
+ const [openDrawer, setOpenDrawer] = useState(false);
+ const [optionTitle, setOptionTitle] = useState();
+ const routes = useRoutes();
+
+ useEffect(() => {
+ const activeRoute = routes.find(
+ route => route.href && location.pathname.match(route.match || route.href)
+ );
+ setOptionTitle(activeRoute?.name);
+ }, [location, routes]);
+
+ const title = `Options / ${optionTitle}`;
+
+ return (
+ <div className={classes.root}>
+ <Hidden lgUp>
+ <NavBar setOpenDrawer={setOpenDrawer} title={title} />
+ <Drawer
+ variant="temporary"
+ open={openDrawer}
+ onClose={() => setOpenDrawer(!openDrawer)}
+ classes={{ paper: classes.drawer }}
+ >
+ <SideNav routes={routes} />
+ </Drawer>
+ </Hidden>
+
+ <Hidden mdDown>
+ <Drawer
+ variant="permanent"
+ classes={{ root: classes.drawerContainer, paper: classes.drawer }}
+ >
+ <div className={classes.drawerHeader}>
+ <IconButton component="a" href="/">
+ <ArrowBackIcon />
+ </IconButton>
+ <Typography variant="h6" color="inherit" noWrap>
+ {t('settingsTitle')}
+ </Typography>
+ </div>
+ <Divider />
+ <SideNav routes={routes} />
+ </Drawer>
+ </Hidden>
+
+ <div className={classes.content}>{children}</div>
+ </div>
+ );
+};
+
+export default OptionsLayout;
diff --git a/modern/src/settings/OptionsLayout/useRoutes.js b/modern/src/settings/OptionsLayout/useRoutes.js
new file mode 100644
index 0000000..901719f
--- /dev/null
+++ b/modern/src/settings/OptionsLayout/useRoutes.js
@@ -0,0 +1,86 @@
+import React, { useMemo } from 'react';
+import { useSelector } from 'react-redux';
+import CreateIcon from '@material-ui/icons/Create';
+import NotificationsIcon from '@material-ui/icons/Notifications';
+import FolderIcon from '@material-ui/icons/Folder';
+import PersonIcon from '@material-ui/icons/Person';
+import StorageIcon from '@material-ui/icons/Storage';
+import BuildIcon from '@material-ui/icons/Build';
+import PeopleIcon from '@material-ui/icons/People';
+import BarChartIcon from '@material-ui/icons/BarChart';
+import { getIsAdmin, getUserId } from '../../selectors';
+import t from '../../common/localization';
+
+const accountRoute = {
+ name: t('settingsUser'),
+ icon: <PersonIcon />
+};
+
+const adminRoutes = [
+ { subheader: t('userAdmin') },
+ {
+ name: t('settingsServer'),
+ href: '/admin/server',
+ icon: <StorageIcon />
+ },
+ {
+ name: t('settingsUsers'),
+ href: '/admin/users',
+ icon: <PeopleIcon />
+ },
+ {
+ name: t('statisticsTitle'),
+ href: '/admin/statistics',
+ icon: <BarChartIcon />
+ }
+];
+
+const mainRoutes = [
+ accountRoute,
+ {
+ match: 'geofence',
+ name: t('sharedGeofences'),
+ href: '/geofences',
+ icon: <CreateIcon />
+ },
+ {
+ match: 'notification',
+ name: t('sharedNotifications'),
+ href: '/settings/notifications',
+ icon: <NotificationsIcon />
+ },
+ {
+ match: 'group',
+ name: t('settingsGroups'),
+ href: '/settings/groups',
+ icon: <FolderIcon />
+ },
+ {
+ match: 'driver',
+ name: t('sharedDrivers'),
+ href: '/settings/drivers',
+ icon: <PersonIcon />
+ },
+ {
+ match: 'attribute',
+ name: t('sharedComputedAttributes'),
+ href: '/settings/attributes',
+ icon: <StorageIcon />
+ },
+ {
+ match: 'maintenance',
+ name: t('sharedMaintenance'),
+ href: '/settings/maintenances',
+ icon: <BuildIcon />
+ }
+];
+
+export default () => {
+ const isAdmin = useSelector(getIsAdmin);
+ const userId = useSelector(getUserId);
+ accountRoute.href = `/user/${userId}`;
+
+ return useMemo(() => [...mainRoutes, ...(isAdmin ? adminRoutes : [])], [
+ isAdmin
+ ]);
+};