summaryrefslogtreecommitdiff
path: root/web/app/App.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'web/app/App.jsx')
-rw-r--r--web/app/App.jsx97
1 files changed, 97 insertions, 0 deletions
diff --git a/web/app/App.jsx b/web/app/App.jsx
new file mode 100644
index 0000000..a31545e
--- /dev/null
+++ b/web/app/App.jsx
@@ -0,0 +1,97 @@
+import React, { useState, useEffect } from "react"
+import { Map, Marker, GeoJson, ZoomControl } from "pigeon-maps"
+import useLocalState from "@phntms/use-local-state";
+import moment from "moment";
+import 'moment/min/locales';
+
+export default () => {
+ moment.locale('ru');
+ const [point, setPoint] = useState({
+ "id": "",
+ "user_id": "",
+ "lat": 0,
+ "lon": 0,
+ "time": "2024-08-15T09:54:00.666Z",
+ "speed": 0,
+ "direction": 0,
+ "accuracy": 0,
+ });
+ const [selected, setSelected] = useState(null)
+ const [points, setPoints] = useState([])
+ const [loaded, setLoaded] = useState(false);
+ const [zoom, setZoom] = useLocalState("zoom", 5);
+ const [center, setCenter] = useState([])
+ const [page, setPage] = useState(0)
+ const updatePosition = () => {
+ fetch("/api/points/last").then(x => x.json()).then((p) => {
+ setPoint(p);
+ setCenter([point.lat, point.lon])
+ setLoaded(true);
+ })
+ fetch("/api/points").then(x => x.json()).then(p => {
+ setPoints(p.reverse())
+ })
+ }
+ useEffect(updatePosition, [])
+ if (!loaded) {
+ return <h1>Загрузка карты</h1>
+ }
+ const path = {
+ type: "FeatureCollection",
+ features: [
+ {
+ type: "Feature",
+ geometry: {
+ type: "LineString",
+ coordinates: points.map((pp) => [pp.lon, pp.lat]),
+ },
+ properties: { prop0: "value0" },
+ },
+ ],
+ };
+
+ return (
+ <div className="container">
+ <div className="column">
+ <Map height={'100vh'} center={center} defaultCenter={[point.lat, point.lon]} defaultZoom={zoom} onBoundsChanged={({ center, zoom, bounds, initial, }) => {setZoom(zoom); setCenter(center)}}>
+ <ZoomControl />
+ <GeoJson
+ data={path}
+ styleCallback={(feature, hover) => {
+ return { strokeWidth: "2", stroke: "red" };
+ }}
+ />
+ <Marker
+ width={32}
+ anchor={[point.lat, point.lon]}
+ />
+ {selected ? (<Marker
+ width={32}
+ anchor={[selected.lat, selected.lon]}
+ />):null}
+ </Map>
+ </div>
+ <div className="column list">
+ <div>Дата последней точки: <b>{moment(point.time).format("LTS L")}</b></div>
+ <div>Точность: <b>{point.accuracy.toFixed(2)}м</b></div>
+ <div>
+ <button onClick={updatePosition}>Обновить</button>
+ <button onClick={() => setCenter([point.lat, point.lon])}>Перейти к последней позиции</button>
+ </div>
+ <div>История перемещения:</div>
+ <ul>
+ {points.slice(page*50, (page+1)*50).map(p => <li key={p.id}>
+ <a href="#" onClick={(ev) => {setSelected(p);setCenter([p.lat, p.lon]);ev.preventDefault(); }}>
+ {moment(p.time).format("LTS L")}&nbsp;—&nbsp;
+ {p.lat.toFixed(5)},
+ {p.lon.toFixed(5)}
+ &nbsp;(точность: {p.accuracy.toFixed(2)}м)
+ </a>
+ </li>)}
+ </ul>
+ <button onClick={() => setPage(Math.max(0, page-1))}>←</button>
+ <button onClick={() => setPage(Math.min(page+1, Math.floor(points.length / 50)))}>→</button>
+ </div>
+ </div >
+ )
+} \ No newline at end of file