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