diff options
author | Anton Tananaev <anton@traccar.org> | 2023-02-02 15:34:48 -0800 |
---|---|---|
committer | Anton Tananaev <anton@traccar.org> | 2023-02-02 15:34:48 -0800 |
commit | 5d3d2df0d0ee2d95cc6e0f855f9f163be48d5ddd (patch) | |
tree | 6358da297316e105de55aa80e22953e3feb10c2d /modern | |
parent | cdfa3e5a8fca1b3b8ed59881adcaa8c3d686de50 (diff) | |
download | trackermap-web-5d3d2df0d0ee2d95cc6e0f855f9f163be48d5ddd.tar.gz trackermap-web-5d3d2df0d0ee2d95cc6e0f855f9f163be48d5ddd.tar.bz2 trackermap-web-5d3d2df0d0ee2d95cc6e0f855f9f163be48d5ddd.zip |
Base combined report
Diffstat (limited to 'modern')
-rw-r--r-- | modern/src/Navigation.js | 2 | ||||
-rw-r--r-- | modern/src/common/components/BottomMenu.js | 2 | ||||
-rw-r--r-- | modern/src/map/MapCamera.js | 10 | ||||
-rw-r--r-- | modern/src/map/MapRoutePath.js | 10 | ||||
-rw-r--r-- | modern/src/reports/CombinedReportPage.js | 91 | ||||
-rw-r--r-- | modern/src/reports/components/ReportsMenu.js | 7 | ||||
-rw-r--r-- | modern/src/resources/l10n/en.json | 1 |
7 files changed, 114 insertions, 9 deletions
diff --git a/modern/src/Navigation.js b/modern/src/Navigation.js index f5b21dd9..6d467dc0 100644 --- a/modern/src/Navigation.js +++ b/modern/src/Navigation.js @@ -49,6 +49,7 @@ import App from './App'; import ChangeServerPage from './other/ChangeServerPage'; import DevicesPage from './settings/DevicesPage'; import ScheduledPage from './reports/ScheduledPage'; +import CombinedReportPage from './reports/CombinedReportPage'; const Navigation = () => { const navigate = useNavigate(); @@ -139,6 +140,7 @@ const Navigation = () => { </Route> <Route path="reports"> + <Route path="combined" element={<CombinedReportPage />} /> <Route path="chart" element={<ChartReportPage />} /> <Route path="event" element={<EventReportPage />} /> <Route path="route" element={<RouteReportPage />} /> diff --git a/modern/src/common/components/BottomMenu.js b/modern/src/common/components/BottomMenu.js index e2b75959..cb9e34e1 100644 --- a/modern/src/common/components/BottomMenu.js +++ b/modern/src/common/components/BottomMenu.js @@ -82,7 +82,7 @@ const BottomMenu = () => { navigate('/'); break; case 'reports': - navigate('/reports/route'); + navigate('/reports/combined'); break; case 'settings': navigate('/settings/preferences'); diff --git a/modern/src/map/MapCamera.js b/modern/src/map/MapCamera.js index 46936513..afb52f89 100644 --- a/modern/src/map/MapCamera.js +++ b/modern/src/map/MapCamera.js @@ -3,11 +3,13 @@ import maplibregl from 'maplibre-gl'; import { map } from './core/MapView'; const MapCamera = ({ - latitude, longitude, positions, + latitude, longitude, positions, coordinates, }) => { useEffect(() => { - if (positions) { - const coordinates = positions.map((item) => [item.longitude, item.latitude]); + if (coordinates || positions) { + if (!coordinates) { + coordinates = positions.map((item) => [item.longitude, item.latitude]); + } if (coordinates.length) { const bounds = coordinates.reduce((bounds, item) => bounds.extend(item), new maplibregl.LngLatBounds(coordinates[0], coordinates[0])); const canvas = map.getCanvas(); @@ -22,7 +24,7 @@ const MapCamera = ({ zoom: Math.max(map.getZoom(), 10), }); } - }, [latitude, longitude, positions]); + }, [latitude, longitude, positions, coordinates]); return null; }; diff --git a/modern/src/map/MapRoutePath.js b/modern/src/map/MapRoutePath.js index 0eb48bdc..ffd3ce0c 100644 --- a/modern/src/map/MapRoutePath.js +++ b/modern/src/map/MapRoutePath.js @@ -3,13 +3,13 @@ import { useId, useEffect } from 'react'; import { useSelector } from 'react-redux'; import { map } from './core/MapView'; -const MapRoutePath = ({ positions }) => { +const MapRoutePath = ({ positions, coordinates }) => { const id = useId(); const theme = useTheme(); const reportColor = useSelector((state) => { - const position = positions.find(() => true); + const position = positions?.find(() => true); if (position) { const attributes = state.devices.items[position.deviceId]?.attributes; if (attributes) { @@ -58,7 +58,9 @@ const MapRoutePath = ({ positions }) => { }, []); useEffect(() => { - const coordinates = positions.map((item) => [item.longitude, item.latitude]); + if (!coordinates) { + coordinates = positions.map((item) => [item.longitude, item.latitude]); + } map.getSource(id).setData({ type: 'Feature', geometry: { @@ -69,7 +71,7 @@ const MapRoutePath = ({ positions }) => { color: reportColor, }, }); - }, [theme, positions, reportColor]); + }, [theme, positions, coordinates, reportColor]); return null; }; diff --git a/modern/src/reports/CombinedReportPage.js b/modern/src/reports/CombinedReportPage.js new file mode 100644 index 00000000..a5d994fe --- /dev/null +++ b/modern/src/reports/CombinedReportPage.js @@ -0,0 +1,91 @@ +import React, { useState } from 'react'; +import { useSelector } from 'react-redux'; +import { + Table, TableBody, TableCell, TableHead, TableRow, +} from '@mui/material'; +import ReportFilter from './components/ReportFilter'; +import { useTranslation } from '../common/components/LocalizationProvider'; +import PageLayout from '../common/components/PageLayout'; +import ReportsMenu from './components/ReportsMenu'; +import { useCatch } from '../reactHelper'; +import MapView from '../map/core/MapView'; +import MapRoutePath from '../map/MapRoutePath'; +import useReportStyles from './common/useReportStyles'; +import TableShimmer from '../common/components/TableShimmer'; +import MapCamera from '../map/MapCamera'; +import MapGeofence from '../map/MapGeofence'; +import { formatTime } from '../common/util/formatter'; +import { usePreference } from '../common/util/preferences'; +import { prefixString } from '../common/util/stringUtils'; + +const CombinedReportPage = () => { + const classes = useReportStyles(); + const t = useTranslation(); + + const devices = useSelector((state) => state.devices.items); + + const hours12 = usePreference('twelveHourFormat'); + + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(false); + + const handleSubmit = useCatch(async ({ deviceIds, groupIds, from, to }) => { + const query = new URLSearchParams({ from, to }); + deviceIds.forEach((deviceId) => query.append('deviceId', deviceId)); + groupIds.forEach((groupId) => query.append('groupId', groupId)); + setLoading(true); + try { + const response = await fetch(`/api/reports/combined?${query.toString()}`); + if (response.ok) { + setItems(await response.json()); + } else { + throw Error(await response.text()); + } + } finally { + setLoading(false); + } + }); + + return ( + <PageLayout menu={<ReportsMenu />} breadcrumbs={['reportTitle', 'reportRoute']}> + <div className={classes.container}> + {Boolean(items.length) && ( + <div className={classes.containerMap}> + <MapView> + <MapGeofence /> + {items.map((item) => ( + <MapRoutePath key={item.deviceId} coordinates={item.route} /> + ))} + </MapView> + <MapCamera coordinates={items.flatMap((item) => item.route)} /> + </div> + )} + <div className={classes.containerMain}> + <div className={classes.header}> + <ReportFilter handleSubmit={handleSubmit} showOnly multiDevice includeGroups /> + </div> + <Table> + <TableHead> + <TableRow> + <TableCell>{t('sharedDevice')}</TableCell> + <TableCell>{t('positionFixTime')}</TableCell> + <TableCell>{t('sharedType')}</TableCell> + </TableRow> + </TableHead> + <TableBody> + {!loading ? items.flatMap((item) => item.events.map((event, index) => ( + <TableRow key={event.id}> + <TableCell>{index ? '' : devices[item.deviceId].name}</TableCell> + <TableCell>{formatTime(event.eventTime, 'seconds', hours12)}</TableCell> + <TableCell>{t(prefixString('event', event.type))}</TableCell> + </TableRow> + ))) : (<TableShimmer columns={3} />)} + </TableBody> + </Table> + </div> + </div> + </PageLayout> + ); +}; + +export default CombinedReportPage; diff --git a/modern/src/reports/components/ReportsMenu.js b/modern/src/reports/components/ReportsMenu.js index a31c3afe..c65a4c12 100644 --- a/modern/src/reports/components/ReportsMenu.js +++ b/modern/src/reports/components/ReportsMenu.js @@ -11,6 +11,7 @@ import TrendingUpIcon from '@mui/icons-material/TrendingUp'; import BarChartIcon from '@mui/icons-material/BarChart'; import RouteIcon from '@mui/icons-material/Route'; import EventRepeatIcon from '@mui/icons-material/EventRepeat'; +import StarIcon from '@mui/icons-material/Star'; import { Link, useLocation } from 'react-router-dom'; import { useTranslation } from '../../common/components/LocalizationProvider'; import { useAdministrator, useRestriction } from '../../common/util/permissions'; @@ -35,6 +36,12 @@ const ReportsMenu = () => { <> <List> <MenuItem + title={t('reportCombined')} + link="/reports/combined" + icon={<StarIcon />} + selected={location.pathname === '/reports/combined'} + /> + <MenuItem title={t('reportRoute')} link="/reports/route" icon={<TimelineIcon />} diff --git a/modern/src/resources/l10n/en.json b/modern/src/resources/l10n/en.json index 26530133..308d92d8 100644 --- a/modern/src/resources/l10n/en.json +++ b/modern/src/resources/l10n/en.json @@ -493,6 +493,7 @@ "notificatorTelegram": "Telegram", "notificatorPushover": "Pushover", "reportReplay": "Replay", + "reportCombined": "Combined", "reportRoute": "Route", "reportEvents": "Events", "reportTrips": "Trips", |