aboutsummaryrefslogtreecommitdiff
path: root/modern/src
diff options
context:
space:
mode:
Diffstat (limited to 'modern/src')
-rw-r--r--modern/src/App.js68
-rw-r--r--modern/src/MainToolbar.js176
-rw-r--r--modern/src/reports/EventReportPage.js95
-rw-r--r--modern/src/reports/ReportFilter.js119
4 files changed, 343 insertions, 115 deletions
diff --git a/modern/src/App.js b/modern/src/App.js
index 1f6b55a4..a173a500 100644
--- a/modern/src/App.js
+++ b/modern/src/App.js
@@ -1,19 +1,20 @@
-import React from 'react';
-import { Switch, Route } from 'react-router-dom'
-import CssBaseline from '@material-ui/core/CssBaseline';
-import MainPage from './MainPage';
-import LoginPage from './LoginPage';
-import RouteReportPage from './reports/RouteReportPage';
-import ServerPage from './admin/ServerPage';
-import UsersPage from './admin/UsersPage';
-import DevicePage from './DevicePage';
-import UserPage from './UserPage';
-import SocketController from './SocketController';
-import NotificationsPage from './settings/NotificationsPage';
-import NotificationPage from './settings/NotificationPage';
-import GroupsPage from './settings/GroupsPage';
-import GroupPage from './settings/GroupPage';
-import PositionPage from './PositionPage';
+import React from "react";
+import { Switch, Route } from "react-router-dom";
+import CssBaseline from "@material-ui/core/CssBaseline";
+import MainPage from "./MainPage";
+import LoginPage from "./LoginPage";
+import RouteReportPage from "./reports/RouteReportPage";
+import EventReportPage from "./reports/EventReportPage";
+import ServerPage from "./admin/ServerPage";
+import UsersPage from "./admin/UsersPage";
+import DevicePage from "./DevicePage";
+import UserPage from "./UserPage";
+import SocketController from "./SocketController";
+import NotificationsPage from "./settings/NotificationsPage";
+import NotificationPage from "./settings/NotificationPage";
+import GroupsPage from "./settings/GroupsPage";
+import GroupPage from "./settings/GroupPage";
+import PositionPage from "./PositionPage";
const App = () => {
return (
@@ -21,21 +22,30 @@ const App = () => {
<CssBaseline />
<SocketController />
<Switch>
- <Route exact path='/' component={MainPage} />
- <Route exact path='/login' component={LoginPage} />
- <Route exact path='/position/:id?' component={PositionPage} />
- <Route exact path='/user/:id?' component={UserPage} />
- <Route exact path='/device/:id?' component={DevicePage} />
- <Route exact path='/reports/route' component={RouteReportPage} />
- <Route exact path='/settings/notifications' component={NotificationsPage} />
- <Route exact path='/settings/notification/:id?' component={NotificationPage} />
- <Route exact path='/settings/groups' component={GroupsPage} />
- <Route exact path='/settings/group/:id?' component={GroupPage} />
- <Route exact path='/admin/server' component={ServerPage} />
- <Route exact path='/admin/users' component={UsersPage} />
+ <Route exact path="/" component={MainPage} />
+ <Route exact path="/login" component={LoginPage} />
+ <Route exact path="/position/:id?" component={PositionPage} />
+ <Route exact path="/user/:id?" component={UserPage} />
+ <Route exact path="/device/:id?" component={DevicePage} />
+ <Route exact path="/reports/route" component={RouteReportPage} />
+ <Route exact path="/reports/event" component={EventReportPage} />
+ <Route
+ exact
+ path="/settings/notifications"
+ component={NotificationsPage}
+ />
+ <Route
+ exact
+ path="/settings/notification/:id?"
+ component={NotificationPage}
+ />
+ <Route exact path="/settings/groups" component={GroupsPage} />
+ <Route exact path="/settings/group/:id?" component={GroupPage} />
+ <Route exact path="/admin/server" component={ServerPage} />
+ <Route exact path="/admin/users" component={UsersPage} />
</Switch>
</>
);
-}
+};
export default App;
diff --git a/modern/src/MainToolbar.js b/modern/src/MainToolbar.js
index 374c694c..46d46496 100644
--- a/modern/src/MainToolbar.js
+++ b/modern/src/MainToolbar.js
@@ -1,45 +1,45 @@
-import React, { useState } from 'react';
-import { useHistory } from 'react-router-dom';
-import { makeStyles } from '@material-ui/core/styles';
-import { useDispatch, useSelector } from 'react-redux';
-import { sessionActions } from './store';
-import AppBar from '@material-ui/core/AppBar';
-import Toolbar from '@material-ui/core/Toolbar';
-import Typography from '@material-ui/core/Typography';
-import Button from '@material-ui/core/Button';
-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 TimelineIcon from '@material-ui/icons/Timeline';
-import PauseCircleFilledIcon from '@material-ui/icons/PauseCircleFilled';
-import PlayCircleFilledIcon from '@material-ui/icons/PlayCircleFilled';
-import NotificationsActiveIcon from '@material-ui/icons/NotificationsActive';
-import FormatListBulletedIcon from '@material-ui/icons/FormatListBulleted';
-import TrendingUpIcon from '@material-ui/icons/TrendingUp';
-import FolderIcon from '@material-ui/icons/Folder';
-import t from './common/localization';
+import React, { useState } from "react";
+import { useHistory } from "react-router-dom";
+import { makeStyles } from "@material-ui/core/styles";
+import { useDispatch, useSelector } from "react-redux";
+import { sessionActions } from "./store";
+import AppBar from "@material-ui/core/AppBar";
+import Toolbar from "@material-ui/core/Toolbar";
+import Typography from "@material-ui/core/Typography";
+import Button from "@material-ui/core/Button";
+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 TimelineIcon from "@material-ui/icons/Timeline";
+import PauseCircleFilledIcon from "@material-ui/icons/PauseCircleFilled";
+import PlayCircleFilledIcon from "@material-ui/icons/PlayCircleFilled";
+import NotificationsActiveIcon from "@material-ui/icons/NotificationsActive";
+import FormatListBulletedIcon from "@material-ui/icons/FormatListBulleted";
+import TrendingUpIcon from "@material-ui/icons/TrendingUp";
+import FolderIcon from "@material-ui/icons/Folder";
+import t from "./common/localization";
-const useStyles = makeStyles(theme => ({
+const useStyles = makeStyles((theme) => ({
flex: {
- flexGrow: 1
+ flexGrow: 1,
},
appBar: {
zIndex: theme.zIndex.drawer + 1,
},
list: {
- width: 250
+ width: 250,
},
menuButton: {
marginLeft: -12,
@@ -52,19 +52,27 @@ 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(
+ (state) => state.session.user && state.session.user.administrator
+ );
+ const userId = useSelector(
+ (state) => state.session.user && state.session.user.id
+ );
- 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' });
+ const response = await fetch("/api/session", { method: "DELETE" });
if (response.ok) {
dispatch(sessionActions.updateUser(null));
- history.push('/login');
+ history.push("/login");
}
- }
+ };
return (
<>
@@ -73,13 +81,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>
+ </Typography>
+ <Button color="inherit" onClick={handleLogout}>
+ {t("loginLogout")}
+ </Button>
</Toolbar>
</AppBar>
<Drawer open={drawer} onClose={closeDrawer}>
@@ -88,111 +99,104 @@ const MainToolbar = () => {
className={classes.list}
role="button"
onClick={closeDrawer}
- onKeyDown={closeDrawer}>
+ onKeyDown={closeDrawer}
+ >
<List>
- <ListItem button onClick={() => history.push('/')}>
+ <ListItem button onClick={() => history.push("/")}>
<ListItemIcon>
<MapIcon />
</ListItemIcon>
- <ListItemText primary={t('mapTitle')} />
+ <ListItemText primary={t("mapTitle")} />
</ListItem>
</List>
<Divider />
- <List
- subheader={
- <ListSubheader>
- {t('reportTitle')}
- </ListSubheader>
- }>
- <ListItem button onClick={() => history.push('/reports/route')}>
+ <List subheader={<ListSubheader>{t("reportTitle")}</ListSubheader>}>
+ <ListItem button onClick={() => history.push("/reports/route")}>
<ListItemIcon>
<TimelineIcon />
</ListItemIcon>
- <ListItemText primary={t('reportRoute')} />
+ <ListItemText primary={t("reportRoute")} />
</ListItem>
- <ListItem button disabled>
+ <ListItem button onClick={() => history.push("/reports/event")}>
<ListItemIcon>
<NotificationsActiveIcon />
</ListItemIcon>
- <ListItemText primary={t('reportEvents')} />
+ <ListItemText primary={t("reportEvents")} />
</ListItem>
<ListItem button disabled>
<ListItemIcon>
<PlayCircleFilledIcon />
</ListItemIcon>
- <ListItemText primary={t('reportTrips')} />
+ <ListItemText primary={t("reportTrips")} />
</ListItem>
<ListItem button disabled>
<ListItemIcon>
<PauseCircleFilledIcon />
</ListItemIcon>
- <ListItemText primary={t('reportStops')} />
+ <ListItemText primary={t("reportStops")} />
</ListItem>
<ListItem button disabled>
<ListItemIcon>
<FormatListBulletedIcon />
</ListItemIcon>
- <ListItemText primary={t('reportSummary')} />
+ <ListItemText primary={t("reportSummary")} />
</ListItem>
<ListItem button disabled>
<ListItemIcon>
<TrendingUpIcon />
</ListItemIcon>
- <ListItemText primary={t('reportChart')} />
+ <ListItemText primary={t("reportChart")} />
</ListItem>
</List>
<Divider />
- <List
- subheader={
- <ListSubheader>
- {t('settingsTitle')}
- </ListSubheader>
- }>
- <ListItem button disabled={!userId} onClick={() => history.push(`/user/${userId}`)}>
+ <List subheader={<ListSubheader>{t("settingsTitle")}</ListSubheader>}>
+ <ListItem
+ button
+ disabled={!userId}
+ onClick={() => history.push(`/user/${userId}`)}
+ >
<ListItemIcon>
<PersonIcon />
</ListItemIcon>
- <ListItemText primary={t('settingsUser')} />
+ <ListItemText primary={t("settingsUser")} />
</ListItem>
- <ListItem button onClick={() => history.push('/settings/notifications')}>
+ <ListItem
+ button
+ onClick={() => history.push("/settings/notifications")}
+ >
<ListItemIcon>
<NotificationsIcon />
</ListItemIcon>
- <ListItemText primary={t('sharedNotifications')} />
+ <ListItemText primary={t("sharedNotifications")} />
</ListItem>
- <ListItem button onClick={() => history.push('/settings/groups')}>
+ <ListItem button onClick={() => history.push("/settings/groups")}>
<ListItemIcon>
<FolderIcon />
</ListItemIcon>
- <ListItemText primary={t('settingsGroups')} />
+ <ListItemText primary={t("settingsGroups")} />
</ListItem>
</List>
{adminEnabled && (
<>
<Divider />
- <List
- subheader={
- <ListSubheader>
- {t('userAdmin')}
- </ListSubheader>
- }>
- <ListItem button onClick={() => history.push('/admin/server')}>
+ <List subheader={<ListSubheader>{t("userAdmin")}</ListSubheader>}>
+ <ListItem button onClick={() => history.push("/admin/server")}>
<ListItemIcon>
<StorageIcon />
</ListItemIcon>
- <ListItemText primary={t('settingsServer')} />
+ <ListItemText primary={t("settingsServer")} />
</ListItem>
- <ListItem button onClick={() => history.push('/admin/users')}>
+ <ListItem button onClick={() => history.push("/admin/users")}>
<ListItemIcon>
<PeopleIcon />
</ListItemIcon>
- <ListItemText primary={t('settingsUsers')} />
+ <ListItemText primary={t("settingsUsers")} />
</ListItem>
<ListItem button disabled>
<ListItemIcon>
<BarChartIcon />
</ListItemIcon>
- <ListItemText primary={t('statisticsTitle')} />
+ <ListItemText primary={t("statisticsTitle")} />
</ListItem>
</List>
</>
@@ -201,6 +205,6 @@ const MainToolbar = () => {
</Drawer>
</>
);
-}
+};
export default MainToolbar;
diff --git a/modern/src/reports/EventReportPage.js b/modern/src/reports/EventReportPage.js
new file mode 100644
index 00000000..acd6f637
--- /dev/null
+++ b/modern/src/reports/EventReportPage.js
@@ -0,0 +1,95 @@
+import React, { useState } from "react";
+import MainToolbar from "../MainToolbar";
+import {
+ Grid,
+ TableContainer,
+ Table,
+ TableRow,
+ TableCell,
+ TableHead,
+ TableBody,
+ Paper,
+ makeStyles,
+} from "@material-ui/core";
+import t from "../common/localization";
+import { formatPosition } from "../common/formatter";
+import ReportFilter from "./ReportFilter";
+
+const useStyles = makeStyles((theme) => ({
+ root: {
+ height: "100%",
+ display: "flex",
+ flexDirection: "column",
+ },
+ content: {
+ flex: 1,
+ overflow: "auto",
+ padding: theme.spacing(2),
+ },
+ form: {
+ padding: theme.spacing(1, 2, 2),
+ },
+}));
+
+const EventReportPage = () => {
+ const classes = useStyles();
+ const [data, setData] = useState([]);
+
+ const handleSubmit = (deviceId, from, to) => {
+ const query = new URLSearchParams({
+ deviceId,
+ from: from.toISOString(),
+ to: to.toISOString(),
+ });
+ fetch(`/api/reports/events?${query.toString()}`, {
+ headers: { Accept: "application/json" },
+ }).then((response) => {
+ if (response.ok) {
+ response.json().then(setData);
+ }
+ });
+ };
+
+ return (
+ <div className={classes.root}>
+ <MainToolbar />
+ <div className={classes.content}>
+ <Grid container spacing={2}>
+ <Grid item xs={12} md={3} lg={2}>
+ <Paper className={classes.form}>
+ <ReportFilter handleSubmit={handleSubmit} />
+ </Paper>
+ </Grid>
+ <Grid item xs={12} md={9} lg={10}>
+ <TableContainer component={Paper}>
+ <Table>
+ <TableHead>
+ <TableRow>
+ <TableCell>{t("positionFixTime")}</TableCell>
+ <TableCell>{t("sharedType")}</TableCell>
+ <TableCell>{t("sharedGeofence")}</TableCell>
+ <TableCell>{t("sharedMaintenance")}</TableCell>
+ </TableRow>
+ </TableHead>
+ <TableBody>
+ {data.map((item) => (
+ <TableRow key={item.id}>
+ <TableCell>
+ {formatPosition(item, "serverTime")}
+ </TableCell>
+ <TableCell>{item.type}</TableCell>
+ <TableCell>{}</TableCell>
+ <TableCell>{}</TableCell>
+ </TableRow>
+ ))}
+ </TableBody>
+ </Table>
+ </TableContainer>
+ </Grid>
+ </Grid>
+ </div>
+ </div>
+ );
+};
+
+export default EventReportPage;
diff --git a/modern/src/reports/ReportFilter.js b/modern/src/reports/ReportFilter.js
new file mode 100644
index 00000000..91fc9fa0
--- /dev/null
+++ b/modern/src/reports/ReportFilter.js
@@ -0,0 +1,119 @@
+import React, { useState } from "react";
+import {
+ FormControl,
+ InputLabel,
+ Select,
+ MenuItem,
+ Button,
+ TextField,
+} from "@material-ui/core";
+import t from "../common/localization";
+import { useSelector } from "react-redux";
+import moment from "moment";
+
+const ReportFilter = (props) => {
+ const devices = useSelector((state) => Object.values(state.devices.items));
+ const [deviceId, setDeviceId] = useState();
+ const [period, setPeriod] = useState("today");
+ const [from, setFrom] = useState(moment().subtract(1, "hour"));
+ const [to, setTo] = useState(moment());
+ const handleShow = () => {
+ let selectedFrom;
+ let selectedTo;
+ switch (period) {
+ case "today":
+ selectedFrom = moment().startOf("day");
+ selectedTo = moment().endOf("day");
+ break;
+ case "yesterday":
+ 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");
+ 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");
+ break;
+ default:
+ selectedFrom = from;
+ selectedTo = to;
+ break;
+ }
+
+ props.handleSubmit(deviceId, selectedFrom, selectedTo);
+ };
+ return (
+ <>
+ <FormControl variant="filled" margin="normal" fullWidth>
+ <InputLabel>{t("reportDevice")}</InputLabel>
+ <Select value={deviceId} onChange={(e) => setDeviceId(e.target.value)}>
+ {devices.map((device) => (
+ <MenuItem value={device.id}>{device.name}</MenuItem>
+ ))}
+ </Select>
+ </FormControl>
+ <FormControl variant="filled" margin="normal" fullWidth>
+ <InputLabel>{t("reportPeriod")}</InputLabel>
+ <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>
+ <MenuItem value="previousWeek">{t("reportPreviousWeek")}</MenuItem>
+ <MenuItem value="thisMonth">{t("reportThisMonth")}</MenuItem>
+ <MenuItem value="previousMonth">{t("reportPreviousMonth")}</MenuItem>
+ <MenuItem value="custom">{t("reportCustom")}</MenuItem>
+ </Select>
+ </FormControl>
+ {period === "custom" && (
+ <TextField
+ margin="normal"
+ variant="filled"
+ 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
+ />
+ )}
+ {period === "custom" && (
+ <TextField
+ margin="normal"
+ variant="filled"
+ 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
+ />
+ )}
+ <FormControl margin="normal" fullWidth>
+ <Button
+ type="button"
+ color="primary"
+ variant="contained"
+ disabled={!deviceId}
+ onClick={handleShow}
+ >
+ {t("reportShow")}
+ </Button>
+ </FormControl>
+ </>
+ );
+};
+
+export default ReportFilter;