aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Tananaev <anton.tananaev@gmail.com>2021-06-27 11:08:26 -0700
committerAnton Tananaev <anton.tananaev@gmail.com>2021-06-27 11:08:26 -0700
commit26916278758cd5e4abb16aa31e31099e066ea8d5 (patch)
tree5119a65f67cd426ed06b9cb553b7d24ba6dce093
parentd86a3ef187359fbe83a5dd950295868c4ef39d09 (diff)
downloadtrackermap-web-26916278758cd5e4abb16aa31e31099e066ea8d5.tar.gz
trackermap-web-26916278758cd5e4abb16aa31e31099e066ea8d5.tar.bz2
trackermap-web-26916278758cd5e4abb16aa31e31099e066ea8d5.zip
Add geofences screen
-rw-r--r--modern/src/App.js4
-rw-r--r--modern/src/EditCollectionView.js4
-rw-r--r--modern/src/GeofencePage.js61
-rw-r--r--modern/src/GeofencesList.js58
-rw-r--r--modern/src/GeofencesPage.js66
-rw-r--r--modern/src/MainToolbar.js7
-rw-r--r--modern/src/attributes/geofenceAttributes.js8
-rw-r--r--modern/src/map/GeofenceMap.js13
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, {