diff options
author | Anton Tananaev <anton@traccar.org> | 2023-01-31 07:38:18 -0800 |
---|---|---|
committer | Anton Tananaev <anton@traccar.org> | 2023-01-31 07:38:18 -0800 |
commit | dcb1fa21441cb292fd66253a97b083e5d47604e5 (patch) | |
tree | 6ed2170b9737b4cb44293d2b9b3e92395db8ba23 /modern/src/reports | |
parent | ea34943121cab989e5ced540139c4fd39527cbde (diff) | |
download | trackermap-web-dcb1fa21441cb292fd66253a97b083e5d47604e5.tar.gz trackermap-web-dcb1fa21441cb292fd66253a97b083e5d47604e5.tar.bz2 trackermap-web-dcb1fa21441cb292fd66253a97b083e5d47604e5.zip |
Add report scheduling
Diffstat (limited to 'modern/src/reports')
-rw-r--r-- | modern/src/reports/EventReportPage.js | 18 | ||||
-rw-r--r-- | modern/src/reports/RouteReportPage.js | 15 | ||||
-rw-r--r-- | modern/src/reports/StopReportPage.js | 15 | ||||
-rw-r--r-- | modern/src/reports/SummaryReportPage.js | 16 | ||||
-rw-r--r-- | modern/src/reports/TripReportPage.js | 15 | ||||
-rw-r--r-- | modern/src/reports/common/scheduleReport.js | 27 | ||||
-rw-r--r-- | modern/src/reports/components/ReportFilter.js | 198 |
7 files changed, 221 insertions, 83 deletions
diff --git a/modern/src/reports/EventReportPage.js b/modern/src/reports/EventReportPage.js index 67bbd888..10b539ab 100644 --- a/modern/src/reports/EventReportPage.js +++ b/modern/src/reports/EventReportPage.js @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; import { FormControl, InputLabel, Select, MenuItem, Table, TableHead, TableRow, TableCell, TableBody, Link, IconButton, } from '@mui/material'; @@ -21,6 +22,7 @@ import MapView from '../map/core/MapView'; import MapGeofence from '../map/MapGeofence'; import MapPositions from '../map/MapPositions'; import MapCamera from '../map/MapCamera'; +import scheduleReport from './common/scheduleReport'; const columnsArray = [ ['eventTime', 'positionFixTime'], @@ -32,6 +34,7 @@ const columnsArray = [ const columnsMap = new Map(columnsArray); const EventReportPage = () => { + const navigate = useNavigate(); const classes = useReportStyles(); const t = useTranslation(); @@ -103,6 +106,19 @@ const EventReportPage = () => { } }); + const handleSchedule = useCatch(async (deviceIds, groupIds, report) => { + report.type = 'events'; + if (eventTypes[0] !== 'allEvents') { + report.attributes.types = eventTypes.join(','); + } + const error = await scheduleReport(deviceIds, groupIds, report); + if (error) { + throw Error(error); + } else { + navigate('/reports/scheduled'); + } + }); + const formatValue = (item, key) => { switch (key) { case 'eventTime': @@ -151,7 +167,7 @@ const EventReportPage = () => { )} <div className={classes.containerMain}> <div className={classes.header}> - <ReportFilter handleSubmit={handleSubmit}> + <ReportFilter handleSubmit={handleSubmit} handleSchedule={handleSchedule}> <div className={classes.filterItem}> <FormControl fullWidth> <InputLabel>{t('reportEventTypes')}</InputLabel> diff --git a/modern/src/reports/RouteReportPage.js b/modern/src/reports/RouteReportPage.js index 64bf0a59..ebd4547a 100644 --- a/modern/src/reports/RouteReportPage.js +++ b/modern/src/reports/RouteReportPage.js @@ -1,5 +1,6 @@ import React, { Fragment, useCallback, useState } from 'react'; import { useSelector } from 'react-redux'; +import { useNavigate } from 'react-router-dom'; import { IconButton, Table, TableBody, TableCell, TableHead, TableRow, } from '@mui/material'; @@ -22,8 +23,10 @@ import useReportStyles from './common/useReportStyles'; import TableShimmer from '../common/components/TableShimmer'; import MapCamera from '../map/MapCamera'; import MapGeofence from '../map/MapGeofence'; +import scheduleReport from './common/scheduleReport'; const RouteReportPage = () => { + const navigate = useNavigate(); const classes = useReportStyles(); const t = useTranslation(); @@ -67,6 +70,16 @@ const RouteReportPage = () => { } }); + const handleSchedule = useCatch(async (deviceIds, groupIds, report) => { + report.type = 'route'; + const error = await scheduleReport(deviceIds, groupIds, report); + if (error) { + throw Error(error); + } else { + navigate('/reports/scheduled'); + } + }); + return ( <PageLayout menu={<ReportsMenu />} breadcrumbs={['reportTitle', 'reportRoute']}> <div className={classes.container}> @@ -90,7 +103,7 @@ const RouteReportPage = () => { )} <div className={classes.containerMain}> <div className={classes.header}> - <ReportFilter handleSubmit={handleSubmit} multiDevice> + <ReportFilter handleSubmit={handleSubmit} handleSchedule={handleSchedule} multiDevice> <ColumnSelect columns={columns} setColumns={setColumns} diff --git a/modern/src/reports/StopReportPage.js b/modern/src/reports/StopReportPage.js index ce790401..3a6ee18d 100644 --- a/modern/src/reports/StopReportPage.js +++ b/modern/src/reports/StopReportPage.js @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; import { IconButton, Table, TableBody, TableCell, TableHead, TableRow, @@ -23,6 +24,7 @@ import MapCamera from '../map/MapCamera'; import AddressValue from '../common/components/AddressValue'; import TableShimmer from '../common/components/TableShimmer'; import MapGeofence from '../map/MapGeofence'; +import scheduleReport from './common/scheduleReport'; const columnsArray = [ ['startTime', 'reportStartTime'], @@ -36,6 +38,7 @@ const columnsArray = [ const columnsMap = new Map(columnsArray); const StopReportPage = () => { + const navigate = useNavigate(); const classes = useReportStyles(); const t = useTranslation(); @@ -74,6 +77,16 @@ const StopReportPage = () => { } }); + const handleSchedule = useCatch(async (deviceIds, groupIds, report) => { + report.type = 'stops'; + const error = await scheduleReport(deviceIds, groupIds, report); + if (error) { + throw Error(error); + } else { + navigate('/reports/scheduled'); + } + }); + const formatValue = (item, key) => { switch (key) { case 'startTime': @@ -116,7 +129,7 @@ const StopReportPage = () => { )} <div className={classes.containerMain}> <div className={classes.header}> - <ReportFilter handleSubmit={handleSubmit}> + <ReportFilter handleSubmit={handleSubmit} handleSchedule={handleSchedule}> <ColumnSelect columns={columns} setColumns={setColumns} columnsArray={columnsArray} /> </ReportFilter> </div> diff --git a/modern/src/reports/SummaryReportPage.js b/modern/src/reports/SummaryReportPage.js index d66d58c6..ec5395f1 100644 --- a/modern/src/reports/SummaryReportPage.js +++ b/modern/src/reports/SummaryReportPage.js @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import { useSelector } from 'react-redux'; +import { useNavigate } from 'react-router-dom'; import { FormControl, InputLabel, Select, MenuItem, Table, TableHead, TableRow, TableBody, TableCell, } from '@mui/material'; @@ -16,6 +17,7 @@ import ColumnSelect from './components/ColumnSelect'; import { useCatch } from '../reactHelper'; import useReportStyles from './common/useReportStyles'; import TableShimmer from '../common/components/TableShimmer'; +import scheduleReport from './common/scheduleReport'; const columnsArray = [ ['startTime', 'reportStartDate'], @@ -30,6 +32,7 @@ const columnsArray = [ const columnsMap = new Map(columnsArray); const SummaryReportPage = () => { + const navigate = useNavigate(); const classes = useReportStyles(); const t = useTranslation(); @@ -73,6 +76,17 @@ const SummaryReportPage = () => { } }); + const handleSchedule = useCatch(async (deviceIds, groupIds, report) => { + report.type = 'summary'; + report.attributes.daily = daily; + const error = await scheduleReport(deviceIds, groupIds, report); + if (error) { + throw Error(error); + } else { + navigate('/reports/scheduled'); + } + }); + const formatValue = (item, key) => { switch (key) { case 'deviceId': @@ -98,7 +112,7 @@ const SummaryReportPage = () => { return ( <PageLayout menu={<ReportsMenu />} breadcrumbs={['reportTitle', 'reportSummary']}> <div className={classes.header}> - <ReportFilter handleSubmit={handleSubmit} multiDevice includeGroups> + <ReportFilter handleSubmit={handleSubmit} handleSchedule={handleSchedule} multiDevice includeGroups> <div className={classes.filterItem}> <FormControl fullWidth> <InputLabel>{t('sharedType')}</InputLabel> diff --git a/modern/src/reports/TripReportPage.js b/modern/src/reports/TripReportPage.js index ffe16241..a1420591 100644 --- a/modern/src/reports/TripReportPage.js +++ b/modern/src/reports/TripReportPage.js @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; import { IconButton, Table, TableBody, TableCell, TableHead, TableRow, } from '@mui/material'; @@ -23,6 +24,7 @@ import TableShimmer from '../common/components/TableShimmer'; import MapMarkers from '../map/MapMarkers'; import MapCamera from '../map/MapCamera'; import MapGeofence from '../map/MapGeofence'; +import scheduleReport from './common/scheduleReport'; const columnsArray = [ ['startTime', 'reportStartTime'], @@ -41,6 +43,7 @@ const columnsArray = [ const columnsMap = new Map(columnsArray); const TripReportPage = () => { + const navigate = useNavigate(); const classes = useReportStyles(); const t = useTranslation(); @@ -116,6 +119,16 @@ const TripReportPage = () => { } }); + const handleSchedule = useCatch(async (deviceIds, groupIds, report) => { + report.type = 'trips'; + const error = await scheduleReport(deviceIds, groupIds, report); + if (error) { + throw Error(error); + } else { + navigate('/reports/scheduled'); + } + }); + const formatValue = (item, key) => { switch (key) { case 'startTime': @@ -160,7 +173,7 @@ const TripReportPage = () => { )} <div className={classes.containerMain}> <div className={classes.header}> - <ReportFilter handleSubmit={handleSubmit}> + <ReportFilter handleSubmit={handleSubmit} handleSchedule={handleSchedule}> <ColumnSelect columns={columns} setColumns={setColumns} columnsArray={columnsArray} /> </ReportFilter> </div> diff --git a/modern/src/reports/common/scheduleReport.js b/modern/src/reports/common/scheduleReport.js new file mode 100644 index 00000000..169190e2 --- /dev/null +++ b/modern/src/reports/common/scheduleReport.js @@ -0,0 +1,27 @@ +export default async (deviceIds, groupIds, report) => { + console.log(JSON.stringify(report)); + const response = await fetch('/api/reports', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(report), + }); + if (response.ok) { + report = await response.json(); + if (deviceIds.length) { + await fetch('/api/permissions/bulk', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(deviceIds.map((id) => ({ deviceId: id, reportId: report.id }))), + }); + } + if (groupIds.length) { + await fetch('/api/permissions/bulk', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(groupIds.map((id) => ({ groupId: id, reportId: report.id }))), + }); + } + return null; + } + return response.text(); +}; diff --git a/modern/src/reports/components/ReportFilter.js b/modern/src/reports/components/ReportFilter.js index 67ea3d2f..6ffb56d9 100644 --- a/modern/src/reports/components/ReportFilter.js +++ b/modern/src/reports/components/ReportFilter.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { FormControl, InputLabel, Select, MenuItem, Button, TextField, Typography, } from '@mui/material'; @@ -8,8 +8,9 @@ import { useTranslation } from '../../common/components/LocalizationProvider'; import useReportStyles from '../common/useReportStyles'; import { reportsActions } from '../../store'; import SplitButton from '../../common/components/SplitButton'; +import SelectField from '../../common/components/SelectField'; -const ReportFilter = ({ children, handleSubmit, showOnly, ignoreDevice, multiDevice, includeGroups }) => { +const ReportFilter = ({ children, handleSubmit, handleSchedule, showOnly, ignoreDevice, multiDevice, includeGroups }) => { const classes = useReportStyles(); const dispatch = useDispatch(); const t = useTranslation(); @@ -29,51 +30,65 @@ const ReportFilter = ({ children, handleSubmit, showOnly, ignoreDevice, multiDev const period = useSelector((state) => state.reports.period); const from = useSelector((state) => state.reports.from); const to = useSelector((state) => state.reports.to); + const button = useSelector((state) => state.reports.button); - const disabled = !ignoreDevice && !selectedDeviceId && !deviceId && !deviceIds.length && !groupIds.length; + const [description, setDescription] = useState(); + const [calendarId, setCalendarId] = useState(); + + const scheduleDisabled = button === 'schedule' && (!description || !calendarId); + const disabled = (!ignoreDevice && !selectedDeviceId && !deviceId && !deviceIds.length && !groupIds.length) || scheduleDisabled; const handleClick = (type) => { - 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 = moment(from, moment.HTML5_FMT.DATETIME_LOCAL); - selectedTo = moment(to, moment.HTML5_FMT.DATETIME_LOCAL); - break; - } + if (type === 'schedule') { + handleSchedule(deviceIds, groupIds, { + description, + calendarId, + attributes: {}, + }); + } else { + 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 = moment(from, moment.HTML5_FMT.DATETIME_LOCAL); + selectedTo = moment(to, moment.HTML5_FMT.DATETIME_LOCAL); + break; + } - handleSubmit({ - deviceId, - deviceIds, - groupIds, - from: selectedFrom.toISOString(), - to: selectedTo.toISOString(), - type, - }); + handleSubmit({ + deviceId, + deviceIds, + groupIds, + from: selectedFrom.toISOString(), + to: selectedTo.toISOString(), + calendarId, + type, + }); + } }; return ( @@ -112,41 +127,65 @@ const ReportFilter = ({ children, handleSubmit, showOnly, ignoreDevice, multiDev </FormControl> </div> )} - <div className={classes.filterItem}> - <FormControl fullWidth> - <InputLabel>{t('reportPeriod')}</InputLabel> - <Select label={t('reportPeriod')} value={period} onChange={(e) => dispatch(reportsActions.updatePeriod(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> - </div> - {period === 'custom' && ( - <div className={classes.filterItem}> - <TextField - label={t('reportFrom')} - type="datetime-local" - value={from} - onChange={(e) => dispatch(reportsActions.updateFrom(e.target.value))} - fullWidth - /> - </div> - )} - {period === 'custom' && ( - <div className={classes.filterItem}> - <TextField - label={t('reportTo')} - type="datetime-local" - value={to} - onChange={(e) => dispatch(reportsActions.updateTo(e.target.value))} - fullWidth - /> - </div> + {button !== 'schedule' ? ( + <> + <div className={classes.filterItem}> + <FormControl fullWidth> + <InputLabel>{t('reportPeriod')}</InputLabel> + <Select label={t('reportPeriod')} value={period} onChange={(e) => dispatch(reportsActions.updatePeriod(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> + </div> + {period === 'custom' && ( + <div className={classes.filterItem}> + <TextField + label={t('reportFrom')} + type="datetime-local" + value={from} + onChange={(e) => dispatch(reportsActions.updateFrom(e.target.value))} + fullWidth + /> + </div> + )} + {period === 'custom' && ( + <div className={classes.filterItem}> + <TextField + label={t('reportTo')} + type="datetime-local" + value={to} + onChange={(e) => dispatch(reportsActions.updateTo(e.target.value))} + fullWidth + /> + </div> + )} + </> + ) : ( + <> + <div className={classes.filterItem}> + <TextField + value={description || ''} + onChange={(event) => setDescription(event.target.value)} + label={t('sharedDescription')} + fullWidth + /> + </div> + <div className={classes.filterItem}> + <SelectField + value={calendarId || 0} + onChange={(event) => setCalendarId(Number(event.target.value))} + endpoint="/api/calendars" + label={t('sharedCalendar')} + fullWidth + /> + </div> + </> )} {children} <div className={classes.filterItem}> @@ -167,10 +206,13 @@ const ReportFilter = ({ children, handleSubmit, showOnly, ignoreDevice, multiDev color="secondary" disabled={disabled} onClick={handleClick} + selected={button} + setSelected={(value) => dispatch(reportsActions.updateButton(value))} options={{ json: t('reportShow'), export: t('reportExport'), mail: t('reportEmail'), + schedule: t('reportSchedule'), }} /> )} |