diff options
-rw-r--r-- | modern/src/App.js | 4 | ||||
-rw-r--r-- | modern/src/EditCollectionView.js | 4 | ||||
-rw-r--r-- | modern/src/GeofencePage.js | 61 | ||||
-rw-r--r-- | modern/src/GeofencesList.js | 58 | ||||
-rw-r--r-- | modern/src/GeofencesPage.js | 66 | ||||
-rw-r--r-- | modern/src/MainToolbar.js | 7 | ||||
-rw-r--r-- | modern/src/attributes/geofenceAttributes.js | 8 | ||||
-rw-r--r-- | modern/src/map/GeofenceMap.js | 13 |
8 files changed, 209 insertions, 12 deletions
diff --git a/modern/src/App.js b/modern/src/App.js index 3641339e..b76995ea 100644 --- a/modern/src/App.js +++ b/modern/src/App.js @@ -36,6 +36,8 @@ import RegisterForm from './components/registration/RegisterForm'; import ResetPasswordForm from './components/registration/ResetPasswordForm'; import theme from './theme'; +import GeofencesPage from './GeofencesPage'; +import GeofencePage from './GeofencePage'; const App = () => { const initialized = useSelector(state => !!state.session.server && !!state.session.user); @@ -57,6 +59,8 @@ const App = () => { <Route exact path='/position/:id?' component={PositionPage} /> <Route exact path='/user/:id?' component={UserPage} /> <Route exact path='/device/:id?' component={DevicePage} /> + <Route exact path='/geofence/:id?' component={GeofencePage} /> + <Route exact path='/geofences' component={GeofencesPage} /> <Route exact path='/settings/notifications' component={NotificationsPage} /> <Route exact path='/settings/notification/:id?' component={NotificationPage} /> <Route exact path='/settings/groups' component={GroupsPage} /> diff --git a/modern/src/EditCollectionView.js b/modern/src/EditCollectionView.js index d3d0af17..572a1d19 100644 --- a/modern/src/EditCollectionView.js +++ b/modern/src/EditCollectionView.js @@ -18,7 +18,7 @@ const useStyles = makeStyles(theme => ({ }, })); -const EditCollectionView = ({ content, editPath, endpoint }) => { +const EditCollectionView = ({ content, editPath, endpoint, disableAdd }) => { const classes = useStyles(); const history = useHistory(); @@ -62,7 +62,7 @@ const EditCollectionView = ({ content, editPath, endpoint }) => { return ( <> <Content updateTimestamp={updateTimestamp} onMenuClick={menuShow} /> - {adminEnabled && + {adminEnabled && !disableAdd && <Fab size="medium" color="primary" className={classes.fab} onClick={handleAdd}> <AddIcon /> </Fab> diff --git a/modern/src/GeofencePage.js b/modern/src/GeofencePage.js new file mode 100644 index 00000000..6c5db9bb --- /dev/null +++ b/modern/src/GeofencePage.js @@ -0,0 +1,61 @@ +import React, { useState } from 'react'; +import TextField from '@material-ui/core/TextField'; + +import t from './common/localization'; +import EditItemView from './EditItemView'; +import { Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography } from '@material-ui/core'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import EditAttributesView from './attributes/EditAttributesView'; +import geofenceAttributes from './attributes/geofenceAttributes'; + +const useStyles = makeStyles(() => ({ + details: { + flexDirection: 'column', + }, +})); + +const GeofencePage = () => { + const classes = useStyles(); + + const [item, setItem] = useState(); + + return ( + <EditItemView endpoint="geofences" item={item} setItem={setItem}> + {item && + <> + <Accordion defaultExpanded> + <AccordionSummary expandIcon={<ExpandMoreIcon />}> + <Typography variant="subtitle1"> + {t('sharedRequired')} + </Typography> + </AccordionSummary> + <AccordionDetails className={classes.details}> + <TextField + margin="normal" + value={item.name || ''} + onChange={event => setItem({...item, name: event.target.value})} + label={t('sharedName')} + variant="filled" /> + </AccordionDetails> + </Accordion> + <Accordion> + <AccordionSummary expandIcon={<ExpandMoreIcon />}> + <Typography variant="subtitle1"> + {t('sharedAttributes')} + </Typography> + </AccordionSummary> + <AccordionDetails className={classes.details}> + <EditAttributesView + attributes={item.attributes} + setAttributes={attributes => setItem({...item, attributes})} + definitions={geofenceAttributes} + /> + </AccordionDetails> + </Accordion> + </> + } + </EditItemView> + ); +} + +export default GeofencePage; diff --git a/modern/src/GeofencesList.js b/modern/src/GeofencesList.js new file mode 100644 index 00000000..2988bef1 --- /dev/null +++ b/modern/src/GeofencesList.js @@ -0,0 +1,58 @@ +import React, { Fragment } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { makeStyles } from '@material-ui/core/styles'; +import Divider from '@material-ui/core/Divider'; +import IconButton from '@material-ui/core/IconButton'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; +import ListItemText from '@material-ui/core/ListItemText'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; + +import { devicesActions } from './store'; +import EditCollectionView from './EditCollectionView'; + +const useStyles = makeStyles(() => ({ + list: { + maxHeight: '100%', + overflow: 'auto', + }, + icon: { + width: '25px', + height: '25px', + filter: 'brightness(0) invert(1)', + }, +})); + +const GeofenceView = ({ onMenuClick }) => { + const classes = useStyles(); + const dispatch = useDispatch(); + + const items = useSelector(state => Object.values(state.geofences.items)); + + return ( + <List className={classes.list}> + {items.map((item, index, list) => ( + <Fragment key={item.id}> + <ListItem button key={item.id} onClick={() => dispatch(devicesActions.select(item))}> + <ListItemText primary={item.name} /> + <ListItemSecondaryAction> + <IconButton onClick={(event) => onMenuClick(event.currentTarget, item.id)}> + <MoreVertIcon /> + </IconButton> + </ListItemSecondaryAction> + </ListItem> + {index < list.length - 1 ? <Divider /> : null} + </Fragment> + ))} + </List> + ); +} + +const GeofencesList = () => { + return ( + <EditCollectionView content={GeofenceView} editPath="/geofence" endpoint="geofences" disableAdd /> + ); +} + +export default GeofencesList; diff --git a/modern/src/GeofencesPage.js b/modern/src/GeofencesPage.js new file mode 100644 index 00000000..389ac998 --- /dev/null +++ b/modern/src/GeofencesPage.js @@ -0,0 +1,66 @@ +import React from 'react'; +import { isWidthUp, makeStyles, withWidth } 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'; + +const useStyles = makeStyles(theme => ({ + root: { + height: '100%', + display: 'flex', + flexDirection: 'column', + }, + content: { + flexGrow: 1, + overflow: 'hidden', + display: 'flex', + flexDirection: 'row', + [theme.breakpoints.down('xs')]: { + flexDirection: 'column-reverse', + } + }, + drawerPaper: { + position: 'relative', + [theme.breakpoints.up('sm')]: { + width: 350, + }, + [theme.breakpoints.down('xs')]: { + height: 250, + } + }, + mapContainer: { + flexGrow: 1, + }, +})); + +const GeofencesPage = ({ width }) => { + const classes = useStyles(); + + return ( + <div className={classes.root}> + <MainToolbar /> + <div className={classes.content}> + <Drawer + anchor={isWidthUp('sm', width) ? 'left' : 'bottom'} + variant='permanent' + classes={{ paper: classes.drawerPaper }}> + <GeofencesList /> + </Drawer> + <div className={classes.mapContainer}> + <ContainerDimensions> + <Map> + <CurrentLocationMap /> + <GeofenceEditMap /> + </Map> + </ContainerDimensions> + </div> + </div> + </div> + ); +} + +export default withWidth()(GeofencesPage); diff --git a/modern/src/MainToolbar.js b/modern/src/MainToolbar.js index 63d8efec..064507fb 100644 --- a/modern/src/MainToolbar.js +++ b/modern/src/MainToolbar.js @@ -24,6 +24,7 @@ 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'; @@ -119,6 +120,12 @@ const MainToolbar = () => { </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 /> diff --git a/modern/src/attributes/geofenceAttributes.js b/modern/src/attributes/geofenceAttributes.js new file mode 100644 index 00000000..4b9c9e63 --- /dev/null +++ b/modern/src/attributes/geofenceAttributes.js @@ -0,0 +1,8 @@ +import t from '../common/localization' + +export default { + 'speedLimit': { + name: t('attributeSpeedLimit'), + type: 'string', + }, +}; diff --git a/modern/src/map/GeofenceMap.js b/modern/src/map/GeofenceMap.js index c98a8c16..8db175a2 100644 --- a/modern/src/map/GeofenceMap.js +++ b/modern/src/map/GeofenceMap.js @@ -1,20 +1,13 @@ -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; +import { useSelector } from 'react-redux'; import { map } from './Map'; -import { useEffectAsync } from '../reactHelper'; import { geofenceToFeature } from './mapUtil'; const GeofenceMap = () => { const id = 'geofences'; - const [geofences, setGeofences] = useState([]); - - useEffectAsync(async () => { - const response = await fetch('/api/geofences'); - if (response.ok) { - setGeofences(await response.json()); - } - }, []); + const geofences = useSelector(state => Object.values(state.geofences.items)); useEffect(() => { map.addSource(id, { |