diff options
Diffstat (limited to 'modern/src')
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', }; |