From ba895e4ba40afca9568db5ae4d889d0a2f0812d1 Mon Sep 17 00:00:00 2001 From: natib21 Date: Tue, 7 Jul 2026 08:19:30 +0000 Subject: [PATCH] add tracker --- .../src/pages/fleet/TrackingPage.tsx | 82 ++++++++++++++++--- 1 file changed, 72 insertions(+), 10 deletions(-) diff --git a/apps/edr-freight-web/backoffice/src/pages/fleet/TrackingPage.tsx b/apps/edr-freight-web/backoffice/src/pages/fleet/TrackingPage.tsx index 78039cb7d..658af8f2b 100644 --- a/apps/edr-freight-web/backoffice/src/pages/fleet/TrackingPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/fleet/TrackingPage.tsx @@ -19,6 +19,7 @@ import { } from "@mantine/core"; import { APIProvider, + InfoWindow, Map as GoogleMap, Marker, useMap, @@ -76,6 +77,51 @@ function FitBounds({ points }: { points: LatLng[] }) { return null; } +/** Lazily reverse-geocode a coordinate to a human address. */ +function useAddress(lat: number, lng: number): string | null { + const [addr, setAddr] = useState(null); + useEffect(() => { + if (typeof google === "undefined") return; + setAddr(null); + let cancelled = false; + new google.maps.Geocoder().geocode({ location: { lat, lng } }, (res, status) => { + if (cancelled) return; + setAddr(status === "OK" && res?.[0] ? res[0].formatted_address : "Unknown location"); + }); + return () => { cancelled = true; }; + }, [lat, lng]); + return addr; +} + +/** Hover popup: label, coordinates, speed/course, and the reverse-geocoded place. */ +function HoverInfo({ + device, + lat, + lng, + onClose, +}: { + device: GpsDevice; + lat: number; + lng: number; + onClose: () => void; +}) { + const address = useAddress(lat, lng); + return ( + +
+
{deviceLabel(device)}
+
+ {lat.toFixed(5)}, {lng.toFixed(5)} +
+
+ {toNum(device.lastSpeed) ?? 0} km/h · {device.lastCourse ?? 0}° +
+
{address ?? "Locating…"}
+
+
+ ); +} + /** Draw the selected vehicle's recent path as a polyline. */ function RouteTrail({ path }: { path: LatLng[] }) { const map = useMap(); @@ -97,6 +143,7 @@ export function TrackingPage() { const { toast } = useToast(); const qc = useQueryClient(); const [selectedId, setSelectedId] = useState(null); + const [hoverId, setHoverId] = useState(null); const [modalOpen, setModalOpen] = useState(false); const [editDevice, setEditDevice] = useState(null); const [form, setForm] = useState({ imei: "", name: "", vehicleId: "" }); @@ -154,17 +201,25 @@ export function TrackingPage() { [history], ); - const markerIcon = (d: GpsDevice, selectedFlag: boolean) => { + // Teardrop pin colored by state with a white truck glyph inside. + const markerIcon = (d: GpsDevice, selectedFlag: boolean): google.maps.Icon | undefined => { if (typeof google === "undefined") return undefined; + const color = selectedFlag ? freightBrand.primary : d.online ? "#2f80ed" : "#95a5a6"; + const svg = ` + + + + + + + + + `; return { - path: "M 0,-9 L 6,9 L 0,4 L -6,9 Z", // arrow - rotation: d.lastCourse ?? 0, - fillColor: selectedFlag ? freightBrand.primary : d.online ? "#2f80ed" : "#95a5a6", - fillOpacity: 1, - strokeColor: "#ffffff", - strokeWeight: 1.5, - scale: 1.4, - } as google.maps.Symbol; + url: `data:image/svg+xml,${encodeURIComponent(svg)}`, + scaledSize: new google.maps.Size(40, 48), + anchor: new google.maps.Point(20, 48), + }; }; const saveMutation = useMutation({ @@ -253,11 +308,18 @@ export function TrackingPage() { setSelectedId(d.id)} + onMouseOver={() => setHoverId(d.id)} /> ))} + {(() => { + const h = positioned.find((p) => p.d.id === hoverId); + return h ? ( + setHoverId(null)} /> + ) : null; + })()} ({ lat: p.lat, lng: p.lng }))} /> {selected?.vehicleId && trail.length > 1 && }