aboutsummaryrefslogtreecommitdiff
path: root/modern/src
diff options
context:
space:
mode:
authorAnton Tananaev <anton@traccar.org>2022-04-17 15:38:40 -0700
committerAnton Tananaev <anton@traccar.org>2022-04-17 15:38:40 -0700
commitad8484a08ef7eabb41afb33a9774ba8502da1d53 (patch)
tree957204d055150ee29251b24b28a9ed1d0afe88d0 /modern/src
parent914f3dae0e34304eebbfaae2b7740e53206ce527 (diff)
downloadtrackermap-web-ad8484a08ef7eabb41afb33a9774ba8502da1d53.tar.gz
trackermap-web-ad8484a08ef7eabb41afb33a9774ba8502da1d53.tar.bz2
trackermap-web-ad8484a08ef7eabb41afb33a9774ba8502da1d53.zip
Handle query parameters
Diffstat (limited to 'modern/src')
-rw-r--r--modern/src/App.js44
-rw-r--r--modern/src/EventPage.js77
-rw-r--r--modern/src/PositionPage.js12
-rw-r--r--modern/src/common/useQuery.js6
-rw-r--r--modern/src/index.js6
-rw-r--r--modern/src/map/Map.js4
-rw-r--r--modern/src/reports/ReplayPage.js3
7 files changed, 132 insertions, 20 deletions
diff --git a/modern/src/App.js b/modern/src/App.js
index a53ffc6c..9417feab 100644
--- a/modern/src/App.js
+++ b/modern/src/App.js
@@ -1,8 +1,8 @@
-import React from 'react';
+import React, { useState } from 'react';
import { ThemeProvider } from '@material-ui/core/styles';
-import { Switch, Route } from 'react-router-dom';
+import { Switch, Route, useHistory } from 'react-router-dom';
import CssBaseline from '@material-ui/core/CssBaseline';
-import { useSelector } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
import { LinearProgress } from '@material-ui/core';
import MainPage from './MainPage';
import RouteReportPage from './reports/RouteReportPage';
@@ -41,11 +41,44 @@ import theme from './theme';
import GeofencesPage from './GeofencesPage';
import GeofencePage from './GeofencePage';
import { LocalizationProvider } from './LocalizationProvider';
+import useQuery from './common/useQuery';
+import { useEffectAsync } from './reactHelper';
+import { devicesActions } from './store';
+import EventPage from './EventPage';
const App = () => {
+ const history = useHistory();
+ const dispatch = useDispatch();
+
const initialized = useSelector((state) => !!state.session.server && !!state.session.user);
+ const [redirectsHandled, setRedirectsHandled] = useState(false);
+
+ const query = useQuery();
+
+ useEffectAsync(async () => {
+ if (query.get('token')) {
+ const token = query.get('token');
+ await fetch(`/api/session?token=${encodeURIComponent(token)}`);
+ history.push('/');
+ } else if (query.get('deviceId')) {
+ const deviceId = query.get('deviceId');
+ const response = await fetch(`/api/devices?uniqueId=${deviceId}`);
+ if (response.ok) {
+ const items = await response.json();
+ if (items.length > 0) {
+ dispatch(devicesActions.select(items[0]));
+ }
+ }
+ history.push('/');
+ } else if (query.get('eventId')) {
+ const eventId = parseInt(query.get('eventId'), 10);
+ history.push(`/event/${eventId}`);
+ } else {
+ setRedirectsHandled(true);
+ }
+ }, [query]);
- return (
+ return (!redirectsHandled ? (<LinearProgress />) : (
<LocalizationProvider>
<ThemeProvider theme={theme}>
<CssBaseline />
@@ -61,6 +94,7 @@ const App = () => {
<Route exact path="/" component={MainPage} />
<Route exact path="/replay" component={ReplayPage} />
<Route exact path="/position/:id?" component={PositionPage} />
+ <Route exact path="/event/:id?" component={EventPage} />
<Route exact path="/user/:id?" component={UserPage} />
<Route exact path="/device/:id?" component={DevicePage} />
<Route exact path="/geofence/:id?" component={GeofencePage} />
@@ -92,7 +126,7 @@ const App = () => {
</Switch>
</ThemeProvider>
</LocalizationProvider>
- );
+ ));
};
export default App;
diff --git a/modern/src/EventPage.js b/modern/src/EventPage.js
new file mode 100644
index 00000000..48c6d807
--- /dev/null
+++ b/modern/src/EventPage.js
@@ -0,0 +1,77 @@
+import React, { useState } from 'react';
+
+import {
+ makeStyles, Typography, AppBar, Toolbar, IconButton,
+} from '@material-ui/core';
+import ArrowBackIcon from '@material-ui/icons/ArrowBack';
+import { useHistory, useParams } from 'react-router-dom';
+import { useEffectAsync } from './reactHelper';
+import { useTranslation } from './LocalizationProvider';
+import ContainerDimensions from 'react-container-dimensions';
+import Map from './map/Map';
+import PositionsMap from './map/PositionsMap';
+
+const useStyles = makeStyles(() => ({
+ root: {
+ height: '100%',
+ display: 'flex',
+ flexDirection: 'column',
+ },
+ mapContainer: {
+ flexGrow: 1,
+ },
+}));
+
+const EventPage = () => {
+ const classes = useStyles();
+ const history = useHistory();
+ const t = useTranslation();
+
+ const { id } = useParams();
+
+ const [event, setEvent] = useState();
+ const [position, setPosition] = useState();
+
+ useEffectAsync(async () => {
+ if (id) {
+ const response = await fetch(`/api/events/${id}`);
+ if (response.ok) {
+ setEvent(await response.json());
+ }
+ }
+ }, [id]);
+
+ useEffectAsync(async () => {
+ if (event && event.positionId) {
+ const response = await fetch(`/api/positions?id=${event.positionId}`);
+ if (response.ok) {
+ const positions = await response.json();
+ if (positions.length > 0) {
+ setPosition(positions[0]);
+ }
+ }
+ }
+ }, [event]);
+
+ return (
+ <div className={classes.root}>
+ <AppBar color="inherit" position="static">
+ <Toolbar>
+ <IconButton color="inherit" edge="start" onClick={() => history.push('/')}>
+ <ArrowBackIcon />
+ </IconButton>
+ <Typography variant="h6">{t('positionEvent')}</Typography>
+ </Toolbar>
+ </AppBar>
+ <div className={classes.mapContainer}>
+ <ContainerDimensions>
+ <Map>
+ {position && <PositionsMap positions={[position]} />}
+ </Map>
+ </ContainerDimensions>
+ </div>
+ </div>
+ );
+};
+
+export default EventPage;
diff --git a/modern/src/PositionPage.js b/modern/src/PositionPage.js
index 37b0145f..08502668 100644
--- a/modern/src/PositionPage.js
+++ b/modern/src/PositionPage.js
@@ -29,14 +29,12 @@ const PositionPage = () => {
useEffectAsync(async () => {
if (id) {
- const response = await fetch(`/api/positions?id=${id}`, {
- headers: {
- Accept: 'application/json',
- },
- });
+ const response = await fetch(`/api/positions?id=${id}`);
if (response.ok) {
- const items = await response.json();
- setItem(items[0]);
+ const positions = await response.json();
+ if (positions.length > 0) {
+ setItem(positions[0]);
+ }
}
} else {
setItem({});
diff --git a/modern/src/common/useQuery.js b/modern/src/common/useQuery.js
index 92398bf8..ed3f74c1 100644
--- a/modern/src/common/useQuery.js
+++ b/modern/src/common/useQuery.js
@@ -1,3 +1,7 @@
+import { useMemo } from 'react';
import { useLocation } from 'react-router-dom';
-export default () => new URLSearchParams(useLocation().search);
+export default () => {
+ const { search } = useLocation();
+ return useMemo(() => new URLSearchParams(search), [search]);
+}
diff --git a/modern/src/index.js b/modern/src/index.js
index 155bf623..28f0bbf1 100644
--- a/modern/src/index.js
+++ b/modern/src/index.js
@@ -1,7 +1,7 @@
import 'typeface-roboto';
import React from 'react';
import ReactDOM from 'react-dom';
-import { HashRouter } from 'react-router-dom';
+import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import App from './App';
@@ -10,9 +10,9 @@ import store from './store';
ReactDOM.render((
<Provider store={store}>
- <HashRouter>
+ <BrowserRouter>
<App />
- </HashRouter>
+ </BrowserRouter>
</Provider>
), document.getElementById('root'));
diff --git a/modern/src/map/Map.js b/modern/src/map/Map.js
index 7c87797d..2555e23f 100644
--- a/modern/src/map/Map.js
+++ b/modern/src/map/Map.js
@@ -42,14 +42,14 @@ const updateReadyValue = (value) => {
const initMap = async () => {
if (ready) return;
if (!map.hasImage('background')) {
- const background = await loadImage('images/background.svg');
+ const background = await loadImage('/images/background.svg');
map.addImage('background', await prepareIcon(background), {
pixelRatio: window.devicePixelRatio,
});
await Promise.all(deviceCategories.map(async (category) => {
const results = [];
['positive', 'negative', 'neutral'].forEach((color) => {
- results.push(loadImage(`images/icon/${category}.svg`).then((icon) => {
+ results.push(loadImage(`/images/icon/${category}.svg`).then((icon) => {
map.addImage(`${category}-${color}`, prepareIcon(background, icon, palette.colors[color]), {
pixelRatio: window.devicePixelRatio,
});
diff --git a/modern/src/reports/ReplayPage.js b/modern/src/reports/ReplayPage.js
index 53c223d8..9840ea8e 100644
--- a/modern/src/reports/ReplayPage.js
+++ b/modern/src/reports/ReplayPage.js
@@ -116,8 +116,7 @@ const ReplayPage = () => {
<div className={classes.root}>
<Map>
<ReplayPathMap positions={positions} />
- {index < positions.length
- && <PositionsMap positions={[positions[index]]} />}
+ {index < positions.length && <PositionsMap positions={[positions[index]]} />}
</Map>
<div className={classes.sidebar}>
<Grid container direction="column" spacing={1}>