aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDesmond Kyeremeh <elDekyfin@gmail.com>2021-07-20 16:02:48 +0000
committerDesmond Kyeremeh <elDekyfin@gmail.com>2021-07-20 16:02:48 +0000
commitfbf1be890ad30cd3a294c969fed3efe61ad59974 (patch)
treeb6b014e98b6f79bd3d9b05aac4a1da355a11804b
parent4a6ed2462ed5ed2960fc8245ac3c5bae967e685b (diff)
parent16d579d1628bd8aa4a8d1f262ed70face50af907 (diff)
downloadetbsa-traccar-web-fbf1be890ad30cd3a294c969fed3efe61ad59974.tar.gz
etbsa-traccar-web-fbf1be890ad30cd3a294c969fed3efe61ad59974.tar.bz2
etbsa-traccar-web-fbf1be890ad30cd3a294c969fed3efe61ad59974.zip
Merge remote-tracking branch 'origin/master' into device_list
-rw-r--r--modern/.eslintrc.js35
-rw-r--r--modern/package.json3
-rw-r--r--modern/public/styles.css4
-rw-r--r--modern/src/EditItemView.js7
-rw-r--r--modern/src/GeofencesPage.js24
-rw-r--r--modern/src/MainToolbar.js97
-rw-r--r--modern/src/StartPage.js2
-rw-r--r--modern/src/admin/ServerPage.js12
-rw-r--r--modern/src/admin/StatisticsPage.js7
-rw-r--r--modern/src/admin/UsersPage.js7
-rw-r--r--modern/src/attributes/EditAttributesView.js2
-rw-r--r--modern/src/common/selectors.js3
-rw-r--r--modern/src/components/NavBar.js (renamed from modern/src/components/reports/ReportNavbar.js)9
-rw-r--r--modern/src/components/SideNav.js34
-rw-r--r--modern/src/components/registration/LoginForm.js5
-rw-r--r--modern/src/components/reports/ReportSidebar.js31
-rw-r--r--modern/src/map/GeofenceEditMap.js1
-rw-r--r--modern/src/reports/ChartReportPage.js6
-rw-r--r--modern/src/reports/EventReportPage.js6
-rw-r--r--modern/src/reports/ReportFilter.js2
-rw-r--r--modern/src/reports/ReportLayout.js (renamed from modern/src/reports/ReportLayoutPage.js)52
-rw-r--r--modern/src/reports/RouteReportPage.js6
-rw-r--r--modern/src/reports/StopReportPage.js6
-rw-r--r--modern/src/reports/SummaryReportPage.js6
-rw-r--r--modern/src/reports/TripReportPage.js6
-rw-r--r--modern/src/settings/ComputedAttributesPage.js8
-rw-r--r--modern/src/settings/DriversPage.js7
-rw-r--r--modern/src/settings/GroupsPage.js7
-rw-r--r--modern/src/settings/MaintenancesPage.js7
-rw-r--r--modern/src/settings/NotificationsPage.js7
-rw-r--r--modern/src/settings/OptionsLayout/index.js106
-rw-r--r--modern/src/settings/OptionsLayout/useRoutes.js86
-rw-r--r--web/app/store/MapTypes.js6
-rw-r--r--web/app/view/map/BaseMap.js30
34 files changed, 403 insertions, 234 deletions
diff --git a/modern/.eslintrc.js b/modern/.eslintrc.js
index a108980..a4cf305 100644
--- a/modern/.eslintrc.js
+++ b/modern/.eslintrc.js
@@ -1,18 +1,21 @@
module.exports = {
- "extends": "airbnb",
- "plugins": [
- "react"
- ],
- "ignorePatterns": ["serviceWorker.js", "localization.js", "switcher.js"],
- "rules": {
- "max-len": [0],
- "no-shadow": [0],
- "no-return-assign": [0],
- "no-param-reassign": [0],
- "no-prototype-builtins": [0],
- "react/jsx-filename-extension": [1, { "extensions": [".js"] }],
- "react/prop-types": [0],
- "react/jsx-props-no-spreading": [0],
- "jsx-a11y/anchor-is-valid": [0]
- }
+ extends: 'airbnb',
+ parserOptions: {
+ ecmaVersion: 2020,
+ },
+ plugins: [
+ 'react',
+ ],
+ ignorePatterns: ['serviceWorker.js', 'localization.js', 'switcher.js'],
+ rules: {
+ 'max-len': [0],
+ 'no-shadow': [0],
+ 'no-return-assign': [0],
+ 'no-param-reassign': [0],
+ 'no-prototype-builtins': [0],
+ 'react/jsx-filename-extension': [1, { extensions: ['.js'] }],
+ 'react/prop-types': [0],
+ 'react/jsx-props-no-spreading': [0],
+ 'jsx-a11y/anchor-is-valid': [0],
+ },
};
diff --git a/modern/package.json b/modern/package.json
index 365d986..6606649 100644
--- a/modern/package.json
+++ b/modern/package.json
@@ -13,6 +13,7 @@
"@turf/circle": "^6.5.0",
"@turf/turf": "^6.4.0",
"canvas-tint-image": "^2.0.1",
+ "mapbox-gl": "^1.13.1",
"maplibre-gl": "^1.15.0",
"moment": "^2.29.1",
"react": "^17.0.2",
@@ -49,7 +50,7 @@
]
},
"devDependencies": {
- "eslint": "^6.8.0",
+ "eslint": "^7.30.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-react": "^7.24.0",
diff --git a/modern/public/styles.css b/modern/public/styles.css
index 7e9de65..e7aee86 100644
--- a/modern/public/styles.css
+++ b/modern/public/styles.css
@@ -6,3 +6,7 @@ canvas {
.maplibregl-popup-content {
padding: 10px !important;
}
+
+.mapboxgl-ctrl {
+ margin: 10px;
+}
diff --git a/modern/src/EditItemView.js b/modern/src/EditItemView.js
index 9bf91a6..e067a39 100644
--- a/modern/src/EditItemView.js
+++ b/modern/src/EditItemView.js
@@ -4,10 +4,10 @@ import { makeStyles } from '@material-ui/core/styles';
import Container from '@material-ui/core/Container';
import Button from '@material-ui/core/Button';
import FormControl from '@material-ui/core/FormControl';
-import MainToolbar from './MainToolbar';
import t from './common/localization';
import { useEffectAsync } from './reactHelper';
+import OptionsLayout from './settings/OptionsLayout';
const useStyles = makeStyles((theme) => ({
container: {
@@ -58,8 +58,7 @@ const EditItemView = ({
};
return (
- <>
- <MainToolbar />
+ <OptionsLayout>
<Container maxWidth="xs" className={classes.container}>
{children}
<FormControl fullWidth margin="normal">
@@ -73,7 +72,7 @@ const EditItemView = ({
</div>
</FormControl>
</Container>
- </>
+ </OptionsLayout>
);
};
diff --git a/modern/src/GeofencesPage.js b/modern/src/GeofencesPage.js
index 95c7151..71219c1 100644
--- a/modern/src/GeofencesPage.js
+++ b/modern/src/GeofencesPage.js
@@ -1,13 +1,17 @@
import React from 'react';
-import { isWidthUp, makeStyles, withWidth } from '@material-ui/core';
+import {
+ Divider, isWidthUp, makeStyles, withWidth, Typography, IconButton,
+} from '@material-ui/core';
import Drawer from '@material-ui/core/Drawer';
import ContainerDimensions from 'react-container-dimensions';
-import MainToolbar from './MainToolbar';
+import ArrowBackIcon from '@material-ui/icons/ArrowBack';
import Map from './map/Map';
import CurrentLocationMap from './map/CurrentLocationMap';
import GeofenceEditMap from './map/GeofenceEditMap';
import GeofencesList from './GeofencesList';
+import t from './common/localization';
+
const useStyles = makeStyles((theme) => ({
root: {
height: '100%',
@@ -32,6 +36,12 @@ const useStyles = makeStyles((theme) => ({
height: 250,
},
},
+ drawerHeader: {
+ ...theme.mixins.toolbar,
+ display: 'flex',
+ alignItems: 'center',
+ padding: theme.spacing(0, 1),
+ },
mapContainer: {
flexGrow: 1,
},
@@ -42,13 +52,21 @@ const GeofencesPage = ({ width }) => {
return (
<div className={classes.root}>
- <MainToolbar />
<div className={classes.content}>
<Drawer
anchor={isWidthUp('sm', width) ? 'left' : 'bottom'}
variant="permanent"
classes={{ paper: classes.drawerPaper }}
>
+ <div className={classes.drawerHeader}>
+ <IconButton component="a" href="/">
+ <ArrowBackIcon />
+ </IconButton>
+ <Typography variant="h6" color="inherit" noWrap>
+ {t('sharedGeofences')}
+ </Typography>
+ </div>
+ <Divider />
<GeofencesList />
</Drawer>
<div className={classes.mapContainer}>
diff --git a/modern/src/MainToolbar.js b/modern/src/MainToolbar.js
index da7c42c..59c2eca 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 3edc5b3..1472f50 100644
--- a/modern/src/StartPage.js
+++ b/modern/src/StartPage.js
@@ -16,7 +16,7 @@ const useStyles = makeStyles((theme) => ({
paddingBottom: theme.spacing(5),
width: theme.dimensions.sidebarWidth,
[theme.breakpoints.down('md')]: {
- width: theme.dimensions.tabletSidebarWidth,
+ width: theme.dimensions.sidebarWidthTablet,
},
[theme.breakpoints.down('xs')]: {
width: '0px',
diff --git a/modern/src/admin/ServerPage.js b/modern/src/admin/ServerPage.js
index 7decac1..ec0034f 100644
--- a/modern/src/admin/ServerPage.js
+++ b/modern/src/admin/ServerPage.js
@@ -8,11 +8,11 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { useHistory } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import t from '../common/localization';
-import MainToolbar from '../MainToolbar';
import { sessionActions } from '../store';
import EditAttributesView from '../attributes/EditAttributesView';
import deviceAttributes from '../attributes/deviceAttributes';
import userAttributes from '../attributes/userAttributes';
+import OptionsLayout from '../settings/OptionsLayout';
const useStyles = makeStyles((theme) => ({
container: {
@@ -51,11 +51,9 @@ const ServerPage = () => {
};
return (
- <>
- <MainToolbar />
+ <OptionsLayout>
<Container maxWidth="xs" className={classes.container}>
- {item
- && (
+ {item && (
<>
<Accordion defaultExpanded>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
@@ -113,7 +111,7 @@ const ServerPage = () => {
</AccordionDetails>
</Accordion>
</>
- )}
+ )}
<FormControl fullWidth margin="normal">
<div className={classes.buttons}>
<Button type="button" color="primary" variant="outlined" onClick={() => history.goBack()}>
@@ -125,7 +123,7 @@ const ServerPage = () => {
</div>
</FormControl>
</Container>
- </>
+ </OptionsLayout>
);
};
diff --git a/modern/src/admin/StatisticsPage.js b/modern/src/admin/StatisticsPage.js
index 131ab7e..1f32c3d 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 fc6e673..a8b3c84 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 e38c02a..13ea9bd 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 0000000..44a0c54
--- /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/reports/ReportNavbar.js b/modern/src/components/NavBar.js
index 16807e8..93e79bb 100644
--- a/modern/src/components/reports/ReportNavbar.js
+++ b/modern/src/components/NavBar.js
@@ -3,9 +3,8 @@ import {
AppBar, Toolbar, Typography, IconButton,
} from '@material-ui/core';
import MenuIcon from '@material-ui/icons/Menu';
-import t from '../../common/localization';
-const ReportNavbar = ({ setOpenDrawer, reportTitle }) => (
+const Navbar = ({ setOpenDrawer, title }) => (
<AppBar position="fixed" color="inherit">
<Toolbar>
<IconButton
@@ -17,12 +16,10 @@ const ReportNavbar = ({ setOpenDrawer, reportTitle }) => (
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap>
- {t('reportTitle')}
- {' '}
- {` / ${reportTitle}`}
+ {title}
</Typography>
</Toolbar>
</AppBar>
);
-export default ReportNavbar;
+export default Navbar;
diff --git a/modern/src/components/SideNav.js b/modern/src/components/SideNav.js
new file mode 100644
index 0000000..bcf8ecd
--- /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 e083541..e6da05e 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 686fc04..0000000
--- a/modern/src/components/reports/ReportSidebar.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react';
-import {
- List, ListItem, ListItemText, ListItemIcon,
-} from '@material-ui/core';
-import { Link, useLocation } from 'react-router-dom';
-
-const ReportSidebar = ({ routes }) => {
- const location = useLocation();
-
- return (
- <List disablePadding style={{ paddingTop: '16px' }}>
- {routes.map((route) => (
- <ListItem
- disableRipple
- component={Link}
- key={route}
- button
- to={route.href}
- selected={route.href === location.pathname}
- >
- <ListItemIcon>
- {route.icon}
- </ListItemIcon>
- <ListItemText primary={route.name} />
- </ListItem>
- ))}
- </List>
- );
-};
-
-export default ReportSidebar;
diff --git a/modern/src/map/GeofenceEditMap.js b/modern/src/map/GeofenceEditMap.js
index d639c19..3d1822f 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 70ce780..c55ed6d 100644
--- a/modern/src/reports/ChartReportPage.js
+++ b/modern/src/reports/ChartReportPage.js
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
import {
Grid, FormControl, InputLabel, Select, MenuItem,
} from '@material-ui/core';
-import ReportLayoutPage from './ReportLayoutPage';
+import ReportLayout from './ReportLayout';
import ReportFilter from './ReportFilter';
import Graph from './Graph';
import { useAttributePreference } from '../common/preferences';
@@ -57,14 +57,14 @@ const ChartReportPage = () => {
const [type, setType] = useState('speed');
return (
- <ReportLayoutPage filter={(
+ <ReportLayout filter={(
<Filter setItems={setItems}>
<ChartType type={type} setType={setType} />
</Filter>
)}
>
<Graph items={items} type={type} />
- </ReportLayoutPage>
+ </ReportLayout>
);
};
diff --git a/modern/src/reports/EventReportPage.js b/modern/src/reports/EventReportPage.js
index 89efcf7..b5c3dae 100644
--- a/modern/src/reports/EventReportPage.js
+++ b/modern/src/reports/EventReportPage.js
@@ -7,7 +7,7 @@ import { useTheme } from '@material-ui/core/styles';
import { useSelector } from 'react-redux';
import { formatDate } from '../common/formatter';
import ReportFilter from './ReportFilter';
-import ReportLayoutPage from './ReportLayoutPage';
+import ReportLayout from './ReportLayout';
import { prefixString } from '../common/stringUtils';
import t from '../common/localization';
@@ -101,14 +101,14 @@ const EventReportPage = () => {
}];
return (
- <ReportLayoutPage filter={<Filter setItems={setItems} />}>
+ <ReportLayout filter={<Filter setItems={setItems} />}>
<DataGrid
rows={items}
columns={columns}
hideFooter
autoHeight
/>
- </ReportLayoutPage>
+ </ReportLayout>
);
};
diff --git a/modern/src/reports/ReportFilter.js b/modern/src/reports/ReportFilter.js
index 1e2eb88..dec8d01 100644
--- a/modern/src/reports/ReportFilter.js
+++ b/modern/src/reports/ReportFilter.js
@@ -58,7 +58,7 @@ const ReportFilter = ({ children, handleSubmit, showOnly }) => {
};
return (
- <Grid container spacing={2}>
+ <Grid container spacing={2} justify="flex-end">
<Grid item xs={12} sm={period === 'custom' ? 3 : 6}>
<FormControl variant="filled" fullWidth>
<InputLabel>{t('reportDevice')}</InputLabel>
diff --git a/modern/src/reports/ReportLayoutPage.js b/modern/src/reports/ReportLayout.js
index d25a887..c231cd8 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 dce08cb..fffcdcb 100644
--- a/modern/src/reports/RouteReportPage.js
+++ b/modern/src/reports/RouteReportPage.js
@@ -6,7 +6,7 @@ import {
formatDistance, formatSpeed, formatBoolean, formatDate, formatCoordinate,
} from '../common/formatter';
import ReportFilter from './ReportFilter';
-import ReportLayoutPage from './ReportLayoutPage';
+import ReportLayout from './ReportLayout';
import { useAttributePreference, usePreference } from '../common/preferences';
import t from '../common/localization';
@@ -86,7 +86,7 @@ const RouteReportPage = () => {
const [items, setItems] = useState([]);
return (
- <ReportLayoutPage filter={<Filter setItems={setItems} />}>
+ <ReportLayout filter={<Filter setItems={setItems} />}>
<Paper>
<DataGrid
rows={items}
@@ -95,7 +95,7 @@ const RouteReportPage = () => {
autoHeight
/>
</Paper>
- </ReportLayoutPage>
+ </ReportLayout>
);
};
diff --git a/modern/src/reports/StopReportPage.js b/modern/src/reports/StopReportPage.js
index 078a5e5..d2e7e7e 100644
--- a/modern/src/reports/StopReportPage.js
+++ b/modern/src/reports/StopReportPage.js
@@ -5,7 +5,7 @@ import {
formatDistance, formatHours, formatDate, formatVolume,
} from '../common/formatter';
import ReportFilter from './ReportFilter';
-import ReportLayoutPage from './ReportLayoutPage';
+import ReportLayout from './ReportLayout';
import { useAttributePreference } from '../common/preferences';
import t from '../common/localization';
@@ -84,7 +84,7 @@ const StopReportPage = () => {
}];
return (
- <ReportLayoutPage filter={<Filter setItems={setItems} />}>
+ <ReportLayout filter={<Filter setItems={setItems} />}>
<DataGrid
rows={items}
columns={columns}
@@ -92,7 +92,7 @@ const StopReportPage = () => {
autoHeight
getRowId={() => Math.random()}
/>
- </ReportLayoutPage>
+ </ReportLayout>
);
};
diff --git a/modern/src/reports/SummaryReportPage.js b/modern/src/reports/SummaryReportPage.js
index 0e88705..4523e65 100644
--- a/modern/src/reports/SummaryReportPage.js
+++ b/modern/src/reports/SummaryReportPage.js
@@ -6,7 +6,7 @@ import {
formatDistance, formatHours, formatDate, formatSpeed, formatVolume,
} from '../common/formatter';
import ReportFilter from './ReportFilter';
-import ReportLayoutPage from './ReportLayoutPage';
+import ReportLayout from './ReportLayout';
import { useAttributePreference } from '../common/preferences';
import t from '../common/localization';
@@ -103,7 +103,7 @@ const SummaryReportPage = () => {
}];
return (
- <ReportLayoutPage filter={<Filter setItems={setItems} />}>
+ <ReportLayout filter={<Filter setItems={setItems} />}>
<DataGrid
rows={items}
columns={columns}
@@ -111,7 +111,7 @@ const SummaryReportPage = () => {
autoHeight
getRowId={() => Math.random()}
/>
- </ReportLayoutPage>
+ </ReportLayout>
);
};
diff --git a/modern/src/reports/TripReportPage.js b/modern/src/reports/TripReportPage.js
index c10e6b1..f611dde 100644
--- a/modern/src/reports/TripReportPage.js
+++ b/modern/src/reports/TripReportPage.js
@@ -5,7 +5,7 @@ import {
formatDistance, formatSpeed, formatHours, formatDate, formatVolume,
} from '../common/formatter';
import ReportFilter from './ReportFilter';
-import ReportLayoutPage from './ReportLayoutPage';
+import ReportLayout from './ReportLayout';
import { useAttributePreference } from '../common/preferences';
import t from '../common/localization';
@@ -115,7 +115,7 @@ const TripReportPage = () => {
}];
return (
- <ReportLayoutPage filter={<Filter setItems={setItems} />}>
+ <ReportLayout filter={<Filter setItems={setItems} />}>
<DataGrid
rows={items}
columns={columns}
@@ -123,7 +123,7 @@ const TripReportPage = () => {
autoHeight
getRowId={() => Math.random()}
/>
- </ReportLayoutPage>
+ </ReportLayout>
);
};
diff --git a/modern/src/settings/ComputedAttributesPage.js b/modern/src/settings/ComputedAttributesPage.js
index f555259..9f131d3 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 36fc12d..03bf49c 100644
--- a/modern/src/settings/DriversPage.js
+++ b/modern/src/settings/DriversPage.js
@@ -3,10 +3,10 @@ import {
TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton,
} from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
-import MainToolbar from '../MainToolbar';
import t from '../common/localization';
import { useEffectAsync } from '../reactHelper';
import EditCollectionView from '../EditCollectionView';
+import OptionsLayout from './OptionsLayout';
const useStyles = makeStyles((theme) => ({
columnAction: {
@@ -56,10 +56,9 @@ const DriversView = ({ updateTimestamp, onMenuClick }) => {
};
const DriversPage = () => (
- <>
- <MainToolbar />
+ <OptionsLayout>
<EditCollectionView content={DriversView} editPath="/settings/driver" endpoint="drivers" />
- </>
+ </OptionsLayout>
);
export default DriversPage;
diff --git a/modern/src/settings/GroupsPage.js b/modern/src/settings/GroupsPage.js
index d592184..ebebd40 100644
--- a/modern/src/settings/GroupsPage.js
+++ b/modern/src/settings/GroupsPage.js
@@ -3,10 +3,10 @@ import {
TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton,
} from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
-import MainToolbar from '../MainToolbar';
import t from '../common/localization';
import { useEffectAsync } from '../reactHelper';
import EditCollectionView from '../EditCollectionView';
+import OptionsLayout from './OptionsLayout';
const useStyles = makeStyles((theme) => ({
columnAction: {
@@ -54,10 +54,9 @@ const GroupsView = ({ updateTimestamp, onMenuClick }) => {
};
const GroupsPage = () => (
- <>
- <MainToolbar />
+ <OptionsLayout>
<EditCollectionView content={GroupsView} editPath="/settings/group" endpoint="groups" />
- </>
+ </OptionsLayout>
);
export default GroupsPage;
diff --git a/modern/src/settings/MaintenancesPage.js b/modern/src/settings/MaintenancesPage.js
index e598bc9..357d079 100644
--- a/modern/src/settings/MaintenancesPage.js
+++ b/modern/src/settings/MaintenancesPage.js
@@ -3,7 +3,6 @@ import {
TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton,
} from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
-import MainToolbar from '../MainToolbar';
import t from '../common/localization';
import { useEffectAsync } from '../reactHelper';
import EditCollectionView from '../EditCollectionView';
@@ -11,6 +10,7 @@ import EditCollectionView from '../EditCollectionView';
import positionAttributes from '../attributes/positionAttributes';
import { formatDistance, formatSpeed } from '../common/formatter';
import { useAttributePreference } from '../common/preferences';
+import OptionsLayout from './OptionsLayout';
const useStyles = makeStyles((theme) => ({
columnAction: {
@@ -82,10 +82,9 @@ const MaintenancesView = ({ updateTimestamp, onMenuClick }) => {
};
const MaintenacesPage = () => (
- <>
- <MainToolbar />
+ <OptionsLayout>
<EditCollectionView content={MaintenancesView} editPath="/settings/maintenance" endpoint="maintenance" />
- </>
+ </OptionsLayout>
);
export default MaintenacesPage;
diff --git a/modern/src/settings/NotificationsPage.js b/modern/src/settings/NotificationsPage.js
index 4e05292..2f1ee8b 100644
--- a/modern/src/settings/NotificationsPage.js
+++ b/modern/src/settings/NotificationsPage.js
@@ -3,12 +3,12 @@ import {
TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton,
} from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
-import MainToolbar from '../MainToolbar';
import t from '../common/localization';
import { useEffectAsync } from '../reactHelper';
import EditCollectionView from '../EditCollectionView';
import { prefixString } from '../common/stringUtils';
import { formatBoolean } from '../common/formatter';
+import OptionsLayout from './OptionsLayout';
const useStyles = makeStyles((theme) => ({
columnAction: {
@@ -73,10 +73,9 @@ const NotificationsView = ({ updateTimestamp, onMenuClick }) => {
};
const NotificationsPage = () => (
- <>
- <MainToolbar />
+ <OptionsLayout>
<EditCollectionView content={NotificationsView} editPath="/settings/notification" endpoint="notifications" />
- </>
+ </OptionsLayout>
);
export default NotificationsPage;
diff --git a/modern/src/settings/OptionsLayout/index.js b/modern/src/settings/OptionsLayout/index.js
new file mode 100644
index 0000000..4a42e58
--- /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 0000000..1832081
--- /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/web/app/store/MapTypes.js b/web/app/store/MapTypes.js
index dd889d4..9cca525 100644
--- a/web/app/store/MapTypes.js
+++ b/web/app/store/MapTypes.js
@@ -38,6 +38,12 @@ Ext.define('Traccar.store.MapTypes', {
key: 'bingHybrid',
name: Strings.mapBingHybrid
}, {
+ key: 'yandexMap',
+ name: Strings.mapYandexMap
+ }, {
+ key: 'yandexSat',
+ name: Strings.mapYandexSat
+ }, {
key: 'custom',
name: Strings.mapCustom
}, {
diff --git a/web/app/view/map/BaseMap.js b/web/app/view/map/BaseMap.js
index 6891598..0107acd 100644
--- a/web/app/view/map/BaseMap.js
+++ b/web/app/view/map/BaseMap.js
@@ -103,9 +103,29 @@ Ext.define('Traccar.view.map.BaseMap', {
})
}),
new ol.layer.Tile({
+ title: Strings.mapYandexMap,
+ type: 'base',
+ visible: type === 'yandexMap',
+ source: new ol.source.XYZ({
+ url: 'https://core-renderer-tiles.maps.yandex.net/tiles?l=map&x={x}&y={y}&z={z}',
+ projection: 'EPSG:3395',
+ attributions: '&copy; <a href="https://yandex.com/maps/">Yandex</a>'
+ })
+ }),
+ new ol.layer.Tile({
+ title: Strings.mapYandexSat,
+ type: 'base',
+ visible: type === 'yandexSat',
+ source: new ol.source.XYZ({
+ url: 'https://core-sat.maps.yandex.net/tiles?l=sat&x={x}&y={y}&z={z}',
+ projection: 'EPSG:3395',
+ attributions: '&copy; <a href="https://yandex.com/maps/">Yandex</a>'
+ })
+ }),
+ new ol.layer.Tile({
title: Strings.mapOsm,
type: 'base',
- visible: type === 'osm' || type === 'yandexMap' || type === 'yandexSat' || type === 'wikimedia' || !type,
+ visible: type === 'osm' || type === 'wikimedia' || !type,
source: new ol.source.OSM({})
})
]
@@ -204,4 +224,12 @@ Ext.define('Traccar.view.map.BaseMap', {
this.map.updateSize();
}
}
+}, function () {
+ var projection;
+ proj4.defs('EPSG:3395', '+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
+ ol.proj.proj4.register(proj4);
+ projection = ol.proj.get('EPSG:3395');
+ if (projection) {
+ projection.setExtent([-20037508.342789244, -20037508.342789244, 20037508.342789244, 20037508.342789244]);
+ }
});