diff options
-rw-r--r-- | modern/src/common/components/SelectField.js | 3 | ||||
-rw-r--r-- | modern/src/common/components/SplitButton.js | 11 | ||||
-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 | ||||
-rw-r--r-- | modern/src/resources/l10n/en.json | 1 | ||||
-rw-r--r-- | modern/src/store/reports.js | 4 |
11 files changed, 234 insertions, 89 deletions
diff --git a/modern/src/common/components/SelectField.js b/modern/src/common/components/SelectField.js index 2ee2f147..35f817a0 100644 --- a/modern/src/common/components/SelectField.js +++ b/modern/src/common/components/SelectField.js @@ -6,6 +6,7 @@ import { useEffectAsync } from '../../reactHelper'; const SelectField = ({ label, + fullWidth, multiple, value, emptyValue = 0, @@ -31,7 +32,7 @@ const SelectField = ({ if (items) { return ( - <FormControl> + <FormControl fullWidth={fullWidth}> <InputLabel>{label}</InputLabel> <Select label={label} diff --git a/modern/src/common/components/SplitButton.js b/modern/src/common/components/SplitButton.js index bdf12870..114370c7 100644 --- a/modern/src/common/components/SplitButton.js +++ b/modern/src/common/components/SplitButton.js @@ -1,16 +1,17 @@ import React, { useRef, useState } from 'react'; -import { Button, ButtonGroup, Menu, MenuItem, Typography } from '@mui/material'; +import { + Button, ButtonGroup, Menu, MenuItem, Typography, +} from '@mui/material'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; -const SplitButton = ({ fullWidth, variant, color, disabled, onClick, options }) => { +const SplitButton = ({ fullWidth, variant, color, disabled, onClick, options, selected, setSelected }) => { const anchorRef = useRef(); const [menuAnchorEl, setMenuAnchorEl] = useState(null); - const [selected, setSelected] = useState(Object.keys(options)[0]); return ( <> - <ButtonGroup fullWidth={fullWidth} variant={variant} color={color} disabled={disabled} ref={anchorRef}> - <Button onClick={() => onClick(selected)}> + <ButtonGroup fullWidth={fullWidth} variant={variant} color={color} ref={anchorRef}> + <Button disabled={disabled} onClick={() => onClick(selected)}> <Typography variant="button" noWrap>{options[selected]}</Typography> </Button> <Button fullWidth={false} size="small" onClick={() => setMenuAnchorEl(anchorRef.current)}> 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'), }} /> )} diff --git a/modern/src/resources/l10n/en.json b/modern/src/resources/l10n/en.json index d93ad5bf..26530133 100644 --- a/modern/src/resources/l10n/en.json +++ b/modern/src/resources/l10n/en.json @@ -506,6 +506,7 @@ "reportShowMarkers": "Show Markers", "reportExport": "Export", "reportEmail": "Email Report", + "reportSchedule": "Schedule", "reportPeriod": "Period", "reportCustom": "Custom", "reportToday": "Today", diff --git a/modern/src/store/reports.js b/modern/src/store/reports.js index 0c336f7a..540c4d4e 100644 --- a/modern/src/store/reports.js +++ b/modern/src/store/reports.js @@ -10,6 +10,7 @@ const { reducer, actions } = createSlice({ period: 'today', from: moment().subtract(1, 'hour').locale('en').format(moment.HTML5_FMT.DATETIME_LOCAL), to: moment().locale('en').format(moment.HTML5_FMT.DATETIME_LOCAL), + button: 'json', }, reducers: { updateDeviceId(state, action) { @@ -30,6 +31,9 @@ const { reducer, actions } = createSlice({ updateTo(state, action) { state.to = action.payload; }, + updateButton(state, action) { + state.button = action.payload; + }, }, }); |