diff options
-rw-r--r-- | modern/src/App.js | 68 | ||||
-rw-r--r-- | modern/src/MainToolbar.js | 176 | ||||
-rw-r--r-- | modern/src/reports/EventReportPage.js | 95 | ||||
-rw-r--r-- | modern/src/reports/ReportFilter.js | 119 |
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; |