aboutsummaryrefslogtreecommitdiff
path: root/modern/src/reports
diff options
context:
space:
mode:
authorAnton Tananaev <anton@traccar.org>2023-01-31 07:38:18 -0800
committerAnton Tananaev <anton@traccar.org>2023-01-31 07:38:18 -0800
commitdcb1fa21441cb292fd66253a97b083e5d47604e5 (patch)
tree6ed2170b9737b4cb44293d2b9b3e92395db8ba23 /modern/src/reports
parentea34943121cab989e5ced540139c4fd39527cbde (diff)
downloadtrackermap-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.js18
-rw-r--r--modern/src/reports/RouteReportPage.js15
-rw-r--r--modern/src/reports/StopReportPage.js15
-rw-r--r--modern/src/reports/SummaryReportPage.js16
-rw-r--r--modern/src/reports/TripReportPage.js15
-rw-r--r--modern/src/reports/common/scheduleReport.js27
-rw-r--r--modern/src/reports/components/ReportFilter.js198
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'),
}}
/>
)}