diff options
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, + ]); +}; |