aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modern/.eslintrc.js32
-rw-r--r--modern/src/EditItemView.js7
-rw-r--r--modern/src/GeofencesPage.js24
-rw-r--r--modern/src/MainToolbar.js109
-rw-r--r--modern/src/StartPage.js2
-rw-r--r--modern/src/admin/ServerPage.js12
-rw-r--r--modern/src/admin/StatisticsPage.js40
-rw-r--r--modern/src/admin/UsersPage.js13
-rw-r--r--modern/src/attributes/EditAttributesView.js3
-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/reports/ReportSidebar.js31
-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)76
-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.js16
-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
28 files changed, 420 insertions, 249 deletions
diff --git a/modern/.eslintrc.js b/modern/.eslintrc.js
index a108980..d6380a8 100644
--- a/modern/.eslintrc.js
+++ b/modern/.eslintrc.js
@@ -1,18 +1,18 @@
module.exports = {
- "extends": "airbnb",
- "plugins": [
- "react"
- ],
- "ignorePatterns": ["serviceWorker.js", "localization.js", "switcher.js"],
- "rules": {
- "max-len": [0],
- "no-shadow": [0],
- "no-return-assign": [0],
- "no-param-reassign": [0],
- "no-prototype-builtins": [0],
- "react/jsx-filename-extension": [1, { "extensions": [".js"] }],
- "react/prop-types": [0],
- "react/jsx-props-no-spreading": [0],
- "jsx-a11y/anchor-is-valid": [0]
- }
+ extends: 'airbnb',
+ plugins: [
+ 'react',
+ ],
+ ignorePatterns: ['serviceWorker.js', 'localization.js', 'switcher.js'],
+ rules: {
+ 'max-len': [0],
+ 'no-shadow': [0],
+ 'no-return-assign': [0],
+ 'no-param-reassign': [0],
+ 'no-prototype-builtins': [0],
+ 'react/jsx-filename-extension': [1, { extensions: ['.js'] }],
+ 'react/prop-types': [0],
+ 'react/jsx-props-no-spreading': [0],
+ 'jsx-a11y/anchor-is-valid': [0],
+ },
};
diff --git a/modern/src/EditItemView.js b/modern/src/EditItemView.js
index 9bf91a6..e067a39 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 95c7151..71219c1 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/MainToolbar.js b/modern/src/MainToolbar.js
index da7c42c..dacd668 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 './selectors';
const useStyles = makeStyles((theme) => ({
flex: {
@@ -50,11 +42,14 @@ 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); };
+ const openDrawer = () => {
+ setDrawer(true);
+ };
+ const closeDrawer = () => {
+ setDrawer(false);
+ };
const handleLogout = async () => {
const response = await fetch('/api/session', { method: 'DELETE' });
@@ -78,7 +73,9 @@ const MainToolbar = () => {
<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}>
@@ -108,89 +105,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 3edc5b3..1472f50 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 7decac1..ec0034f 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 131ab7e..20974f4 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');
@@ -21,24 +21,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;
@@ -46,8 +58,13 @@ 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());
}
@@ -98,7 +115,8 @@ const StatisticsPage = () => {
const [items, setItems] = useState([]);
return (
- <ReportLayoutPage filter={<Filter setItems={setItems} />}>
+ <OptionsLayout>
+ <Filter setItems={setItems} />
<TableContainer component={Paper}>
<Table>
<TableHead>
@@ -133,7 +151,7 @@ const StatisticsPage = () => {
</TableBody>
</Table>
</TableContainer>
- </ReportLayoutPage>
+ </OptionsLayout>
);
};
diff --git a/modern/src/admin/UsersPage.js b/modern/src/admin/UsersPage.js
index fc6e673..9b51cdd 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,13 @@ const UsersView = ({ updateTimestamp, onMenuClick }) => {
};
const UsersPage = () => (
- <>
- <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/attributes/EditAttributesView.js b/modern/src/attributes/EditAttributesView.js
index e38c02a..e8d427b 100644
--- a/modern/src/attributes/EditAttributesView.js
+++ b/modern/src/attributes/EditAttributesView.js
@@ -43,7 +43,8 @@ const EditAttributesView = ({ attributes, setAttributes, definitions }) => {
const getAttributeType = (value) => {
if (typeof value === 'number') {
return 'number';
- } if (typeof value === 'boolean') {
+ }
+ if (typeof value === 'boolean') {
return 'boolean';
}
return 'string';
diff --git a/modern/src/components/reports/ReportNavbar.js b/modern/src/components/NavBar.js
index 16807e8..93e79bb 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 0000000..aa07808
--- /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/ReportSidebar.js b/modern/src/components/reports/ReportSidebar.js
deleted file mode 100644
index 686fc04..0000000
--- 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/reports/ChartReportPage.js b/modern/src/reports/ChartReportPage.js
index 70ce780..c55ed6d 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 89efcf7..b5c3dae 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 1e2eb88..dec8d01 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 d25a887..e8dda63 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) => ({
@@ -53,14 +53,30 @@ const useStyles = makeStyles((theme) => ({
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('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();
@@ -79,41 +95,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 +140,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 dce08cb..fffcdcb 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 078a5e5..d2e7e7e 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 0e88705..4523e65 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 c10e6b1..f611dde 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/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 f555259..d376f23 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: {
@@ -20,7 +20,9 @@ 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');
@@ -47,7 +49,9 @@ const ComputedAttributeView = ({ updateTimestamp, onMenuClick }) => {
{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>
@@ -65,10 +69,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 36fc12d..03bf49c 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 d592184..ebebd40 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 e598bc9..357d079 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 4e05292..2f1ee8b 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 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..f2603dd
--- /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,
+ ]);
+};