aboutsummaryrefslogtreecommitdiff
path: root/modern/src
diff options
context:
space:
mode:
Diffstat (limited to 'modern/src')
-rw-r--r--modern/src/DevicesList.js15
-rw-r--r--modern/src/EditItemView.js7
-rw-r--r--modern/src/GeofencesPage.js24
-rw-r--r--modern/src/MainPage.js248
-rw-r--r--modern/src/MainToolbar.js97
-rw-r--r--modern/src/StartPage.js2
-rw-r--r--modern/src/admin/ServerPage.js12
-rw-r--r--modern/src/admin/StatisticsPage.js7
-rw-r--r--modern/src/admin/UsersPage.js7
-rw-r--r--modern/src/attributes/EditAttributesView.js2
-rw-r--r--modern/src/common/selectors.js3
-rw-r--r--modern/src/components/BottomNav.js109
-rw-r--r--modern/src/components/NavBar.js (renamed from modern/src/components/reports/ReportNavbar.js)9
-rw-r--r--modern/src/components/SideNav.js34
-rw-r--r--modern/src/components/registration/LoginForm.js5
-rw-r--r--modern/src/components/reports/ReportSidebar.js31
-rw-r--r--modern/src/map/GeofenceEditMap.js1
-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)52
-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/settings/ComputedAttributesPage.js8
-rw-r--r--modern/src/settings/DriversPage.js7
-rw-r--r--modern/src/settings/GroupsPage.js7
-rw-r--r--modern/src/settings/MaintenancesPage.js7
-rw-r--r--modern/src/settings/NotificationsPage.js7
-rw-r--r--modern/src/settings/OptionsLayout/index.js106
-rw-r--r--modern/src/settings/OptionsLayout/useRoutes.js86
-rw-r--r--modern/src/theme/dimensions.js1
33 files changed, 545 insertions, 387 deletions
diff --git a/modern/src/DevicesList.js b/modern/src/DevicesList.js
index b06f7f76..aa856306 100644
--- a/modern/src/DevicesList.js
+++ b/modern/src/DevicesList.js
@@ -9,10 +9,11 @@ import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
import Grid from '@material-ui/core/Grid';
import ListItemText from '@material-ui/core/ListItemText';
+import SvgIcon from '@material-ui/core/SvgIcon';
import { FixedSizeList } from 'react-window';
import AutoSizer from 'react-virtualized-auto-sizer';
import BatteryFullIcon from '@material-ui/icons/BatteryFull';
-import VpnKeyIcon from '@material-ui/icons/VpnKey';
+import { ReactComponent as IgnitionIcon } from '../public/images/icon/ignition.svg';
import { devicesActions } from './store';
import EditCollectionView from './EditCollectionView';
@@ -48,6 +49,9 @@ const useStyles = makeStyles((theme) => ({
gray: {
color: theme.palette.common.gray,
},
+ indicators: {
+ lineHeight: 1,
+ },
}));
const getStatusColor = (status) => {
@@ -79,6 +83,7 @@ const DeviceRow = ({ data, index, style }) => {
const { items } = data;
const item = items[index];
const position = useSelector((state) => state.positions.items[item.id]);
+ const showIgnition = position?.attributes.hasOwnProperty('ignition') && position.attributes.ignition
return (
<div style={style}>
@@ -90,12 +95,12 @@ const DeviceRow = ({ data, index, style }) => {
</Avatar>
</ListItemAvatar>
<ListItemText primary={item.name} secondary={item.status} classes={{ secondary: classes[getStatusColor(item.status)] }} />
- <ListItemSecondaryAction>
+ <ListItemSecondaryAction className={classes.indicators}>
{position && (
- <Grid container direction="row" alignItems="center" alignContent="center" spacing={1}>
- {position.attributes.hasOwnProperty('ignition') && (
+ <Grid container direction="row" alignItems="center" alignContent="center" spacing={2}>
+ {showIgnition && (
<Grid item>
- <VpnKeyIcon className={`${position.attributes.ignition ? classes.green : classes.gray}`} />
+ <SvgIcon component={IgnitionIcon} />
</Grid>
)}
{position.attributes.hasOwnProperty('batteryLevel') && (
diff --git a/modern/src/EditItemView.js b/modern/src/EditItemView.js
index 9bf91a6a..e067a39f 100644
--- a/modern/src/EditItemView.js
+++ b/modern/src/EditItemView.js
@@ -4,10 +4,10 @@ import { makeStyles } from '@material-ui/core/styles';
import Container from '@material-ui/core/Container';
import Button from '@material-ui/core/Button';
import FormControl from '@material-ui/core/FormControl';
-import MainToolbar from './MainToolbar';
import t from './common/localization';
import { useEffectAsync } from './reactHelper';
+import OptionsLayout from './settings/OptionsLayout';
const useStyles = makeStyles((theme) => ({
container: {
@@ -58,8 +58,7 @@ const EditItemView = ({
};
return (
- <>
- <MainToolbar />
+ <OptionsLayout>
<Container maxWidth="xs" className={classes.container}>
{children}
<FormControl fullWidth margin="normal">
@@ -73,7 +72,7 @@ const EditItemView = ({
</div>
</FormControl>
</Container>
- </>
+ </OptionsLayout>
);
};
diff --git a/modern/src/GeofencesPage.js b/modern/src/GeofencesPage.js
index 95c7151e..71219c16 100644
--- a/modern/src/GeofencesPage.js
+++ b/modern/src/GeofencesPage.js
@@ -1,13 +1,17 @@
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 ArrowBackIcon from '@material-ui/icons/ArrowBack';
import Map from './map/Map';
import CurrentLocationMap from './map/CurrentLocationMap';
import GeofenceEditMap from './map/GeofenceEditMap';
import GeofencesList from './GeofencesList';
+import t from './common/localization';
+
const useStyles = makeStyles((theme) => ({
root: {
height: '100%',
@@ -32,6 +36,12 @@ const useStyles = makeStyles((theme) => ({
height: 250,
},
},
+ drawerHeader: {
+ ...theme.mixins.toolbar,
+ display: 'flex',
+ alignItems: 'center',
+ padding: theme.spacing(0, 1),
+ },
mapContainer: {
flexGrow: 1,
},
@@ -42,13 +52,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 }}
>
+ <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}>
diff --git a/modern/src/MainPage.js b/modern/src/MainPage.js
index d5447e55..417cdb03 100644
--- a/modern/src/MainPage.js
+++ b/modern/src/MainPage.js
@@ -1,7 +1,7 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import {
- makeStyles, Paper, Toolbar, Grid, TextField, IconButton, Button,
+ makeStyles, Paper, Toolbar, TextField, IconButton, Button,
} from '@material-ui/core';
import { useTheme } from '@material-ui/core/styles';
@@ -9,117 +9,78 @@ import useMediaQuery from '@material-ui/core/useMediaQuery';
import AddIcon from '@material-ui/icons/Add';
import CloseIcon from '@material-ui/icons/Close';
import ArrowBackIcon from '@material-ui/icons/ArrowBack';
-import ListIcon from '@material-ui/icons/List';
-import ReplayIcon from '@material-ui/icons/Replay';
-import DescriptionIcon from '@material-ui/icons/Description';
-import ShuffleIcon from '@material-ui/icons/Shuffle';
-import PersonIcon from '@material-ui/icons/Person';
+import ListIcon from '@material-ui/icons/ViewList';
import DevicesList from './DevicesList';
-import MainToolbar from './MainToolbar';
import Map from './map/Map';
import SelectedDeviceMap from './map/SelectedDeviceMap';
import AccuracyMap from './map/AccuracyMap';
import GeofenceMap from './map/GeofenceMap';
import CurrentPositionsMap from './map/CurrentPositionsMap';
import CurrentLocationMap from './map/CurrentLocationMap';
+import BottomNav from './components/BottomNav';
import t from './common/localization';
const useStyles = makeStyles((theme) => ({
root: {
height: '100vh',
+ },
+ sidebar: {
display: 'flex',
flexDirection: 'column',
- },
- drawerPaper: {
- position: 'relative',
- [theme.breakpoints.up('sm')]: {
- width: 350,
- },
- [theme.breakpoints.down('xs')]: {
- height: 250,
- },
- overflow: 'hidden',
- },
- listContainer: {
position: 'absolute',
- left: theme.spacing(1.5),
- top: theme.spacing(10.5),
+ left: 0,
+ top: 0,
+ margin: theme.spacing(1.5),
width: theme.dimensions.drawerWidthDesktop,
+ bottom: theme.spacing(8),
zIndex: 1301,
- height: '100%',
- maxHeight: `calc(100vh - ${theme.spacing(20)}px)`,
+ transition: 'transform .5s ease',
[theme.breakpoints.down('md')]: {
- top: theme.spacing(7),
- left: '0px',
width: '100%',
- maxHeight: `calc(100vh - ${theme.spacing(14)}px)`,
- },
- },
- paper: {
- borderRadius: '0px',
- },
- toolbar: {
- paddingLeft: theme.spacing(2),
- paddingRight: theme.spacing(2),
- },
- deviceList: {
- height: '100%',
- backgroundColor: 'transparent',
- [theme.breakpoints.down('md')]: {
+ margin: 0,
backgroundColor: 'white',
},
},
- collapsed: {
- transform: `translateX(-${360 + 16}px)`,
- transition: 'transform .5s ease',
+ sidebarCollapsed: {
+ transform: `translateX(-${theme.dimensions.drawerWidthDesktop})`,
+ marginLeft: 0,
[theme.breakpoints.down('md')]: {
transform: 'translateX(-100vw)',
},
},
- uncollapsed: {
- transform: `translateX(${theme.spacing(1.5)})`,
- transition: 'transform .5s ease',
- [theme.breakpoints.down('md')]: {
- transform: 'translateX(0)',
+ paper: {
+ borderRadius: '0px',
+ },
+ toolbar: {
+ display: 'flex',
+ padding: theme.spacing(0, 1),
+ '& > *': {
+ margin: theme.spacing(0, 1),
},
},
- deviceButton: {
+ deviceList: {
+ flex: 1,
+ overflow: 'auto',
+ padding: theme.spacing(1.5, 0),
+ },
+ sidebarToggle: {
position: 'absolute',
- left: theme.spacing(1),
- top: theme.spacing(10.5),
+ left: theme.spacing(1.5),
+ top: theme.spacing(3),
borderRadius: '0px',
+ minWidth: 0,
[theme.breakpoints.down('md')]: {
left: theme.spacing(0),
- top: theme.spacing(7),
},
},
- deviceButtonBackground: {
+ sidebarToggleBg: {
backgroundColor: 'white',
color: '#777777',
'&:hover': {
backgroundColor: 'white',
},
},
- bottomMenuContainer: {
- position: 'absolute',
- left: theme.spacing(1.5),
- bottom: theme.spacing(1.5),
- width: theme.dimensions.drawerWidthDesktop,
- zIndex: 1301,
- [theme.breakpoints.down('md')]: {
- bottom: theme.spacing(0),
- left: '0px',
- width: '100%',
- },
- },
- menuButton: {
- display: 'flex',
- flexDirection: 'column',
- fontSize: '0.75rem',
- fontWeight: 'normal',
- color: '#222222',
- },
}));
const MainPage = () => {
@@ -127,19 +88,23 @@ const MainPage = () => {
const history = useHistory();
const theme = useTheme();
- const [deviceName, setDeviceName] = useState();
- const [collapsed, setCollapsed] = useState(false);
-
const isTablet = useMediaQuery(theme.breakpoints.down('md'));
const isPhone = useMediaQuery(theme.breakpoints.down('xs'));
+ const [deviceName, setDeviceName] = useState('');
+ const [collapsed, setCollapsed] = useState(false);
+
const handleClose = () => {
setCollapsed(!collapsed);
};
+ // Collapse sidebar for tablets and phones
+ useEffect(() => {
+ setCollapsed(isTablet);
+ }, [isTablet]);
+
return (
<div className={classes.root}>
- <MainToolbar />
<Map>
<CurrentLocationMap />
<GeofenceMap />
@@ -147,100 +112,51 @@ const MainPage = () => {
<CurrentPositionsMap />
<SelectedDeviceMap />
</Map>
- {collapsed
- && (
- <Button
- variant="contained"
- color={isPhone ? 'secondary' : 'primary'}
- classes={{ containedPrimary: classes.deviceButtonBackground }}
- className={classes.deviceButton}
- onClick={handleClose}
- startIcon={<ListIcon />}
- disableElevation
- >
- {!isPhone ? t('deviceTitle') : ''}
- </Button>
- )}
- <div className={`${classes.listContainer} ${collapsed ? classes.collapsed : classes.uncollapsed}`}>
- <Grid container direction="column" spacing={isTablet ? 0 : 2} style={{ height: '100%' }}>
- <Grid item>
- <Paper className={classes.paper}>
- <Toolbar className={classes.toolbar} disableGutters>
- <Grid container direction="row" alignItems="center" spacing={2}>
- {isTablet && (
- <Grid item>
- <IconButton onClick={handleClose}>
- <ArrowBackIcon />
- </IconButton>
- </Grid>
- )}
- <Grid item xs>
- <TextField
- fullWidth
- name="deviceName"
- value={deviceName || ''}
- autoComplete="deviceName"
- autoFocus
- onChange={(event) => setDeviceName(event.target.value)}
- placeholder="Search Devices"
- variant="filled"
- />
- </Grid>
- <Grid item>
- <IconButton onClick={() => history.push('/device')}>
- <AddIcon />
- </IconButton>
- </Grid>
- {!isTablet && (
- <Grid item>
- <IconButton onClick={handleClose}>
- <CloseIcon />
- </IconButton>
- </Grid>
- )}
- </Grid>
- </Toolbar>
- </Paper>
- </Grid>
- <Grid item xs>
- <div className={classes.deviceList}>
- <DevicesList />
- </div>
- </Grid>
- </Grid>
- </div>
- <div className={classes.bottomMenuContainer}>
- <Paper className={classes.paper}>
+ <Button
+ variant="contained"
+ color={isPhone ? 'secondary' : 'primary'}
+ classes={{ containedPrimary: classes.sidebarToggleBg }}
+ className={classes.sidebarToggle}
+ onClick={handleClose}
+ disableElevation
+ >
+ <ListIcon />
+ {!isPhone ? t('deviceTitle') : ''}
+ </Button>
+ <div className={`${classes.sidebar} ${collapsed && classes.sidebarCollapsed}`}>
+ <Paper className={classes.paper} elevation={isTablet ? 3 : 1}>
<Toolbar className={classes.toolbar} disableGutters>
- <Grid container justify="space-around">
- <Grid item>
- <IconButton classes={{ label: classes.menuButton }}>
- <ReplayIcon />
- {t('reportReplay')}
- </IconButton>
- </Grid>
- <Grid item>
- <IconButton classes={{ label: classes.menuButton }}>
- <DescriptionIcon />
- {t('reportTitle')}
- </IconButton>
- </Grid>
- <Grid item>
- <IconButton classes={{ label: classes.menuButton }}>
- <ShuffleIcon />
- Options
- </IconButton>
- </Grid>
- <Grid item>
- <IconButton classes={{ label: classes.menuButton }}>
- <PersonIcon />
- {t('settingsUser')}
- </IconButton>
- </Grid>
- </Grid>
+ {isTablet && (
+ <IconButton onClick={handleClose}>
+ <ArrowBackIcon />
+ </IconButton>
+ )}
+ <TextField
+ fullWidth
+ name="deviceName"
+ value={deviceName}
+ autoComplete="deviceName"
+ autoFocus
+ onChange={(event) => setDeviceName(event.target.value)}
+ placeholder="Search Devices"
+ variant="filled"
+ />
+ <IconButton onClick={() => history.push('/device')}>
+ <AddIcon />
+ </IconButton>
+ {!isTablet && (
+ <IconButton onClick={handleClose}>
+ <CloseIcon />
+ </IconButton>
+ )}
</Toolbar>
</Paper>
+ <div className={classes.deviceList}>
+ <DevicesList />
+ </div>
</div>
+
+ <BottomNav showOnDesktop />
</div>
);
};
diff --git a/modern/src/MainToolbar.js b/modern/src/MainToolbar.js
index da7c42c5..59c2eca8 100644
--- a/modern/src/MainToolbar.js
+++ b/modern/src/MainToolbar.js
@@ -10,24 +10,16 @@ import IconButton from '@material-ui/core/IconButton';
import MenuIcon from '@material-ui/icons/Menu';
import Drawer from '@material-ui/core/Drawer';
import List from '@material-ui/core/List';
-import ListSubheader from '@material-ui/core/ListSubheader';
-import Divider from '@material-ui/core/Divider';
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 { sessionActions } from './store';
import t from './common/localization';
+import * as selectors from './common/selectors';
const useStyles = makeStyles((theme) => ({
flex: {
@@ -50,8 +42,7 @@ 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 userId = useSelector(selectors.getUserId);
const openDrawer = () => { setDrawer(true); };
const closeDrawer = () => { setDrawer(false); };
@@ -108,89 +99,17 @@ const MainToolbar = () => {
</ListItemIcon>
<ListItemText primary={t('reportTitle')} />
</ListItem>
- </List>
- <Divider />
- <List
- subheader={(
- <ListSubheader>
- {t('settingsTitle')}
- </ListSubheader>
- )}
- >
- <ListItem button disabled={!userId} onClick={() => history.push(`/user/${userId}`)}>
+ <ListItem
+ button
+ disabled={!userId}
+ onClick={() => history.push('/settings/notifications')}
+ >
<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')}>
- <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>
</>
diff --git a/modern/src/StartPage.js b/modern/src/StartPage.js
index 3edc5b31..1472f501 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 7decac1a..ec0034f5 100644
--- a/modern/src/admin/ServerPage.js
+++ b/modern/src/admin/ServerPage.js
@@ -8,11 +8,11 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { useHistory } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import t from '../common/localization';
-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: {
@@ -51,11 +51,9 @@ const ServerPage = () => {
};
return (
- <>
- <MainToolbar />
+ <OptionsLayout>
<Container maxWidth="xs" className={classes.container}>
- {item
- && (
+ {item && (
<>
<Accordion defaultExpanded>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
@@ -113,7 +111,7 @@ const ServerPage = () => {
</AccordionDetails>
</Accordion>
</>
- )}
+ )}
<FormControl fullWidth margin="normal">
<div className={classes.buttons}>
<Button type="button" color="primary" variant="outlined" onClick={() => history.goBack()}>
@@ -125,7 +123,7 @@ const ServerPage = () => {
</div>
</FormControl>
</Container>
- </>
+ </OptionsLayout>
);
};
diff --git a/modern/src/admin/StatisticsPage.js b/modern/src/admin/StatisticsPage.js
index 131ab7e4..1f32c3d9 100644
--- a/modern/src/admin/StatisticsPage.js
+++ b/modern/src/admin/StatisticsPage.js
@@ -5,7 +5,7 @@ import {
import moment from 'moment';
import t from '../common/localization';
import { formatDate } from '../common/formatter';
-import ReportLayoutPage from '../reports/ReportLayoutPage';
+import OptionsLayout from '../settings/OptionsLayout';
const Filter = ({ setItems }) => {
const [period, setPeriod] = useState('today');
@@ -98,7 +98,8 @@ const StatisticsPage = () => {
const [items, setItems] = useState([]);
return (
- <ReportLayoutPage filter={<Filter setItems={setItems} />}>
+ <OptionsLayout>
+ <Filter setItems={setItems} />
<TableContainer component={Paper}>
<Table>
<TableHead>
@@ -133,7 +134,7 @@ const StatisticsPage = () => {
</TableBody>
</Table>
</TableContainer>
- </ReportLayoutPage>
+ </OptionsLayout>
);
};
diff --git a/modern/src/admin/UsersPage.js b/modern/src/admin/UsersPage.js
index fc6e6736..a8b3c849 100644
--- a/modern/src/admin/UsersPage.js
+++ b/modern/src/admin/UsersPage.js
@@ -3,11 +3,11 @@ import {
TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton,
} from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
-import MainToolbar from '../MainToolbar';
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: {
@@ -61,10 +61,9 @@ const UsersView = ({ updateTimestamp, onMenuClick }) => {
};
const UsersPage = () => (
- <>
- <MainToolbar />
+ <OptionsLayout>
<EditCollectionView content={UsersView} editPath="/user" endpoint="users" />
- </>
+ </OptionsLayout>
);
export default UsersPage;
diff --git a/modern/src/attributes/EditAttributesView.js b/modern/src/attributes/EditAttributesView.js
index e38c02ae..13ea9bd4 100644
--- a/modern/src/attributes/EditAttributesView.js
+++ b/modern/src/attributes/EditAttributesView.js
@@ -52,7 +52,7 @@ const EditAttributesView = ({ attributes, setAttributes, definitions }) => {
const convertToList = (attributes) => {
const booleanList = [];
const otherList = [];
- Object.keys(attributes).forEach((key) => {
+ Object.keys(attributes || []).forEach((key) => {
const value = attributes[key];
const type = getAttributeType(value);
if (type === 'boolean') {
diff --git a/modern/src/common/selectors.js b/modern/src/common/selectors.js
new file mode 100644
index 00000000..44a0c547
--- /dev/null
+++ b/modern/src/common/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/components/BottomNav.js b/modern/src/components/BottomNav.js
new file mode 100644
index 00000000..6aad1dd9
--- /dev/null
+++ b/modern/src/components/BottomNav.js
@@ -0,0 +1,109 @@
+import React from 'react';
+import { useDispatch } from 'react-redux';
+import { Link, useHistory } from 'react-router-dom';
+import {
+ makeStyles, Paper, Toolbar, IconButton, useMediaQuery, useTheme,
+} from '@material-ui/core';
+
+import ReplayIcon from '@material-ui/icons/Replay';
+import DescriptionIcon from '@material-ui/icons/Description';
+import ShuffleIcon from '@material-ui/icons/Shuffle';
+import MapIcon from '@material-ui/icons/Map';
+import LogoutIcon from '@material-ui/icons/ExitToApp';
+
+import { sessionActions } from '../store';
+import t from '../common/localization';
+
+const useStyles = makeStyles((theme) => ({
+ container: {
+ bottom: theme.spacing(0),
+ left: '0px',
+ width: '100%',
+ position: 'fixed',
+ zIndex: 1301,
+ [theme.breakpoints.up('lg')]: {
+ left: theme.spacing(1.5),
+ bottom: theme.spacing(1.5),
+ width: theme.dimensions.drawerWidthDesktop,
+ },
+ },
+ paper: {
+ borderRadius: '0px',
+ },
+ toolbar: {
+ padding: theme.spacing(0, 2),
+ display: 'flex',
+ justifyContent: 'space-around',
+ maxWidth: theme.dimensions.bottomNavMaxWidth,
+ margin: 'auto',
+ },
+ navItem: {
+ display: 'flex',
+ flexDirection: 'column',
+ fontSize: '0.75rem',
+ fontWeight: 'normal',
+ },
+}));
+
+const BottomNav = ({ showOnDesktop }) => {
+ const classes = useStyles();
+ const theme = useTheme();
+ const history = useHistory();
+
+ const isDesktop = useMediaQuery(theme.breakpoints.up('lg'));
+ const dispatch = useDispatch();
+
+ const NavLink = ({ children, location }) => (
+ <IconButton component={Link} classes={{ label: classes.navItem }} to={location}>
+ {children}
+ </IconButton>
+ );
+
+ const handleLogout = async () => {
+ const response = await fetch('/api/session', { method: 'DELETE' });
+ if (response.ok) {
+ dispatch(sessionActions.updateUser(null));
+ history.push('/login');
+ }
+ };
+
+ if (isDesktop && !showOnDesktop) return null;
+
+ return (
+ <div className={classes.container}>
+ <Paper className={classes.paper} elevation={isDesktop ? 1 : 3}>
+ <Toolbar className={classes.toolbar} disableGutters>
+
+ {isDesktop ? (
+ <NavLink location="/replay">
+ <ReplayIcon />
+ {t('reportReplay')}
+ </NavLink>
+ ) : (
+ <NavLink location="/">
+ <MapIcon />
+ {t('mapTitle')}
+ </NavLink>
+ )}
+
+ <NavLink location="/reports/route">
+ <DescriptionIcon />
+ {t('reportTitle')}
+ </NavLink>
+
+ <NavLink location="/settings/notifications">
+ <ShuffleIcon />
+ {t('settingsTitle')}
+ </NavLink>
+
+ <IconButton classes={{ label: classes.navItem }} onClick={handleLogout}>
+ <LogoutIcon />
+ {t('loginLogout')}
+ </IconButton>
+ </Toolbar>
+ </Paper>
+ </div>
+ );
+};
+
+export default BottomNav;
diff --git a/modern/src/components/reports/ReportNavbar.js b/modern/src/components/NavBar.js
index 16807e89..93e79bbb 100644
--- a/modern/src/components/reports/ReportNavbar.js
+++ b/modern/src/components/NavBar.js
@@ -3,9 +3,8 @@ 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 }) => (
+const Navbar = ({ setOpenDrawer, title }) => (
<AppBar position="fixed" color="inherit">
<Toolbar>
<IconButton
@@ -17,12 +16,10 @@ const ReportNavbar = ({ setOpenDrawer, reportTitle }) => (
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap>
- {t('reportTitle')}
- {' '}
- {` / ${reportTitle}`}
+ {title}
</Typography>
</Toolbar>
</AppBar>
);
-export default ReportNavbar;
+export default Navbar;
diff --git a/modern/src/components/SideNav.js b/modern/src/components/SideNav.js
new file mode 100644
index 00000000..bcf8ecd5
--- /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) => (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/registration/LoginForm.js b/modern/src/components/registration/LoginForm.js
index e083541c..e6da05e3 100644
--- a/modern/src/components/registration/LoginForm.js
+++ b/modern/src/components/registration/LoginForm.js
@@ -41,7 +41,10 @@ const LoginForm = () => {
const handleLogin = async (event) => {
event.preventDefault();
- const response = await fetch('/api/session', { method: 'POST', body: new URLSearchParams(`email=${email}&password=${password}`) });
+ const response = await fetch('/api/session', {
+ method: 'POST',
+ body: new URLSearchParams(`email=${encodeURIComponent(email)}&password=${encodeURIComponent(password)}`),
+ });
if (response.ok) {
const user = await response.json();
dispatch(sessionActions.updateUser(user));
diff --git a/modern/src/components/reports/ReportSidebar.js b/modern/src/components/reports/ReportSidebar.js
deleted file mode 100644
index 686fc040..00000000
--- a/modern/src/components/reports/ReportSidebar.js
+++ /dev/null
@@ -1,31 +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) => (
- <ListItem
- disableRipple
- component={Link}
- key={route}
- 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/map/GeofenceEditMap.js b/modern/src/map/GeofenceEditMap.js
index d639c192..3d1822fe 100644
--- a/modern/src/map/GeofenceEditMap.js
+++ b/modern/src/map/GeofenceEditMap.js
@@ -1,3 +1,4 @@
+import 'mapbox-gl/dist/mapbox-gl.css';
import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css';
import MapboxDraw from '@mapbox/mapbox-gl-draw';
import theme from '@mapbox/mapbox-gl-draw/src/lib/theme';
diff --git a/modern/src/reports/ChartReportPage.js b/modern/src/reports/ChartReportPage.js
index 70ce780f..c55ed6d5 100644
--- a/modern/src/reports/ChartReportPage.js
+++ b/modern/src/reports/ChartReportPage.js
@@ -2,7 +2,7 @@ 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';
@@ -57,14 +57,14 @@ 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 89efcf76..b5c3daeb 100644
--- a/modern/src/reports/EventReportPage.js
+++ b/modern/src/reports/EventReportPage.js
@@ -7,7 +7,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';
@@ -101,14 +101,14 @@ 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 1e2eb887..dec8d018 100644
--- a/modern/src/reports/ReportFilter.js
+++ b/modern/src/reports/ReportFilter.js
@@ -58,7 +58,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 d25a8876..c231cd80 100644
--- a/modern/src/reports/ReportLayoutPage.js
+++ b/modern/src/reports/ReportLayout.js
@@ -11,8 +11,8 @@ 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) => ({
@@ -60,7 +60,7 @@ const routes = [
{ 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();
@@ -79,41 +79,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} />
+ <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} />
@@ -126,4 +124,4 @@ const ReportLayoutPage = ({ children, filter }) => {
);
};
-export default ReportLayoutPage;
+export default ReportLayout;
diff --git a/modern/src/reports/RouteReportPage.js b/modern/src/reports/RouteReportPage.js
index dce08cb1..fffcdcb5 100644
--- a/modern/src/reports/RouteReportPage.js
+++ b/modern/src/reports/RouteReportPage.js
@@ -6,7 +6,7 @@ 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';
@@ -86,7 +86,7 @@ const RouteReportPage = () => {
const [items, setItems] = useState([]);
return (
- <ReportLayoutPage filter={<Filter setItems={setItems} />}>
+ <ReportLayout filter={<Filter setItems={setItems} />}>
<Paper>
<DataGrid
rows={items}
@@ -95,7 +95,7 @@ const RouteReportPage = () => {
autoHeight
/>
</Paper>
- </ReportLayoutPage>
+ </ReportLayout>
);
};
diff --git a/modern/src/reports/StopReportPage.js b/modern/src/reports/StopReportPage.js
index 078a5e5e..d2e7e7ed 100644
--- a/modern/src/reports/StopReportPage.js
+++ b/modern/src/reports/StopReportPage.js
@@ -5,7 +5,7 @@ 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';
@@ -84,7 +84,7 @@ const StopReportPage = () => {
}];
return (
- <ReportLayoutPage filter={<Filter setItems={setItems} />}>
+ <ReportLayout filter={<Filter setItems={setItems} />}>
<DataGrid
rows={items}
columns={columns}
@@ -92,7 +92,7 @@ const StopReportPage = () => {
autoHeight
getRowId={() => Math.random()}
/>
- </ReportLayoutPage>
+ </ReportLayout>
);
};
diff --git a/modern/src/reports/SummaryReportPage.js b/modern/src/reports/SummaryReportPage.js
index 0e88705f..4523e652 100644
--- a/modern/src/reports/SummaryReportPage.js
+++ b/modern/src/reports/SummaryReportPage.js
@@ -6,7 +6,7 @@ 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';
@@ -103,7 +103,7 @@ const SummaryReportPage = () => {
}];
return (
- <ReportLayoutPage filter={<Filter setItems={setItems} />}>
+ <ReportLayout filter={<Filter setItems={setItems} />}>
<DataGrid
rows={items}
columns={columns}
@@ -111,7 +111,7 @@ const SummaryReportPage = () => {
autoHeight
getRowId={() => Math.random()}
/>
- </ReportLayoutPage>
+ </ReportLayout>
);
};
diff --git a/modern/src/reports/TripReportPage.js b/modern/src/reports/TripReportPage.js
index c10e6b1a..f611dde1 100644
--- a/modern/src/reports/TripReportPage.js
+++ b/modern/src/reports/TripReportPage.js
@@ -5,7 +5,7 @@ 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';
@@ -115,7 +115,7 @@ const TripReportPage = () => {
}];
return (
- <ReportLayoutPage filter={<Filter setItems={setItems} />}>
+ <ReportLayout filter={<Filter setItems={setItems} />}>
<DataGrid
rows={items}
columns={columns}
@@ -123,7 +123,7 @@ const TripReportPage = () => {
autoHeight
getRowId={() => Math.random()}
/>
- </ReportLayoutPage>
+ </ReportLayout>
);
};
diff --git a/modern/src/settings/ComputedAttributesPage.js b/modern/src/settings/ComputedAttributesPage.js
index f555259b..9f131d33 100644
--- a/modern/src/settings/ComputedAttributesPage.js
+++ b/modern/src/settings/ComputedAttributesPage.js
@@ -4,10 +4,10 @@ import {
} from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import { useSelector } from 'react-redux';
-import MainToolbar from '../MainToolbar';
import t from '../common/localization';
import { useEffectAsync } from '../reactHelper';
import EditCollectionView from '../EditCollectionView';
+import OptionsLayout from './OptionsLayout';
const useStyles = makeStyles((theme) => ({
columnAction: {
@@ -65,10 +65,10 @@ const ComputedAttributeView = ({ updateTimestamp, onMenuClick }) => {
};
const ComputedAttributesPage = () => (
- <>
- <MainToolbar />
+ <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 36fc12d6..03bf49c5 100644
--- a/modern/src/settings/DriversPage.js
+++ b/modern/src/settings/DriversPage.js
@@ -3,10 +3,10 @@ import {
TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton,
} from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
-import MainToolbar from '../MainToolbar';
import t from '../common/localization';
import { useEffectAsync } from '../reactHelper';
import EditCollectionView from '../EditCollectionView';
+import OptionsLayout from './OptionsLayout';
const useStyles = makeStyles((theme) => ({
columnAction: {
@@ -56,10 +56,9 @@ const DriversView = ({ updateTimestamp, onMenuClick }) => {
};
const DriversPage = () => (
- <>
- <MainToolbar />
+ <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 d5921844..ebebd40a 100644
--- a/modern/src/settings/GroupsPage.js
+++ b/modern/src/settings/GroupsPage.js
@@ -3,10 +3,10 @@ import {
TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton,
} from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
-import MainToolbar from '../MainToolbar';
import t from '../common/localization';
import { useEffectAsync } from '../reactHelper';
import EditCollectionView from '../EditCollectionView';
+import OptionsLayout from './OptionsLayout';
const useStyles = makeStyles((theme) => ({
columnAction: {
@@ -54,10 +54,9 @@ const GroupsView = ({ updateTimestamp, onMenuClick }) => {
};
const GroupsPage = () => (
- <>
- <MainToolbar />
+ <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 e598bc90..357d0791 100644
--- a/modern/src/settings/MaintenancesPage.js
+++ b/modern/src/settings/MaintenancesPage.js
@@ -3,7 +3,6 @@ import {
TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton,
} from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
-import MainToolbar from '../MainToolbar';
import t from '../common/localization';
import { useEffectAsync } from '../reactHelper';
import EditCollectionView from '../EditCollectionView';
@@ -11,6 +10,7 @@ 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: {
@@ -82,10 +82,9 @@ const MaintenancesView = ({ updateTimestamp, onMenuClick }) => {
};
const MaintenacesPage = () => (
- <>
- <MainToolbar />
+ <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 4e052927..2f1ee8b7 100644
--- a/modern/src/settings/NotificationsPage.js
+++ b/modern/src/settings/NotificationsPage.js
@@ -3,12 +3,12 @@ import {
TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton,
} from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
-import MainToolbar from '../MainToolbar';
import t from '../common/localization';
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: {
@@ -73,10 +73,9 @@ const NotificationsView = ({ updateTimestamp, onMenuClick }) => {
};
const NotificationsPage = () => (
- <>
- <MainToolbar />
+ <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 00000000..4a42e583
--- /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 = `${t('settingsTitle')} / ${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 00000000..18320813
--- /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 '../../common/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,
+ ]);
+};
diff --git a/modern/src/theme/dimensions.js b/modern/src/theme/dimensions.js
index fcdbaee5..b6edc5e9 100644
--- a/modern/src/theme/dimensions.js
+++ b/modern/src/theme/dimensions.js
@@ -9,4 +9,5 @@ export default {
columnWidthNumber: 130,
columnWidthString: 160,
columnWidthBoolean: 130,
+ bottomNavMaxWidth: '400px',
};