diff --git a/drb-frontend/app/incidents/[id]/page.tsx b/drb-frontend/app/incidents/[id]/page.tsx index 01c961a..2221983 100644 --- a/drb-frontend/app/incidents/[id]/page.tsx +++ b/drb-frontend/app/incidents/[id]/page.tsx @@ -13,22 +13,12 @@ import { SeverityMark } from "@/components/marks/SeverityMark"; import { isKnownSeverity } from "@/lib/severity"; import { Button } from "@/components/ui/Button"; import { MachineOutputNotice } from "@/components/ui/MachineOutputNotice"; -import type { CallRecord } from "@/lib/types"; +import { MAY_BE_MERGED_KM, pathStops, stopNumbers } from "@/lib/liveWall"; const MapView = dynamic(() => import("@/components/MapView"), { ssr: false }); const EARLIER_PAGE_SIZE = 8; -function haversineKm(a: { lat: number; lng: number }, b: { lat: number; lng: number }): number { - const R = 6371; - const dLat = ((b.lat - a.lat) * Math.PI) / 180; - const dLng = ((b.lng - a.lng) * Math.PI) / 180; - const s = - Math.sin(dLat / 2) ** 2 + - Math.cos((a.lat * Math.PI) / 180) * Math.cos((b.lat * Math.PI) / 180) * Math.sin(dLng / 2) ** 2; - return R * 2 * Math.atan2(Math.sqrt(s), Math.sqrt(1 - s)); -} - function elapsedLabel(startedAt: string, active: boolean, updatedAt: string): string { const start = new Date(startedAt).getTime(); const end = active ? Date.now() : new Date(updatedAt).getTime(); @@ -51,29 +41,12 @@ export default function IncidentDetailPage() { const [resolving, setResolving] = useState(false); const [earlierShown, setEarlierShown] = useState(EARLIER_PAGE_SIZE); - // Same ordering/filtering MapView's IncidentPathLayer uses, so the stop - // number shown on a spine entry matches the number on its map marker. - const geocodedCalls = useMemo( - () => - calls - .filter((c): c is CallRecord & { location_coords: { lat: number; lng: number } } => !!c.location_coords) - .slice() - .sort((a, b) => a.started_at.localeCompare(b.started_at)), - [calls] - ); - const stopNumberByCallId = useMemo(() => { - const m = new Map(); - geocodedCalls.forEach((c, i) => m.set(c.call_id, i + 1)); - return m; - }, [geocodedCalls]); - - const pathLengthKm = useMemo(() => { - let total = 0; - for (let i = 1; i < geocodedCalls.length; i++) { - total += haversineKm(geocodedCalls[i - 1].location_coords!, geocodedCalls[i].location_coords!); - } - return total; - }, [geocodedCalls]); + // Stop numbers and path length come from the same liveWall.pathStops() the + // map's IncidentPathLayer draws from (server-26#236), so the spine numbers + // exactly what the map numbers: only a movement incident (police/accident + // with a pursuit tag) is numbered, and an impossible-speed stop never is. + const path = useMemo(() => (incident ? pathStops(incident, calls) : null), [incident, calls]); + const stopNumberByCallId = useMemo(() => (path ? stopNumbers(path) : new Map()), [path]); const newestFirst = useMemo( () => calls.slice().sort((a, b) => b.started_at.localeCompare(a.started_at)), @@ -130,7 +103,12 @@ export default function IncidentDetailPage() {

{elapsedLabel(incident.started_at, active, incident.updated_at)} elapsed - {pathLengthKm > 0 && <> · {pathLengthKm.toFixed(1)} km path} + {path?.movement && path.lengthKm > 0 && <> · {path.lengthKm.toFixed(1)} km path} + {path?.mayBeMerged && ( + + {" · "}may be merged + + )} {" · "}{incident.call_ids.length} call{incident.call_ids.length !== 1 ? "s" : ""}

diff --git a/drb-frontend/components/MapView.tsx b/drb-frontend/components/MapView.tsx index f0d7696..aae37ee 100644 --- a/drb-frontend/components/MapView.tsx +++ b/drb-frontend/components/MapView.tsx @@ -1658,7 +1658,7 @@ export default function MapView({
- {p === "rooftop" ? "Street address" : p === "interpolated" ? "Interpolated" : p === "centre" ? "Road/place centre ≈" : "Unverified (older)"} + {p === "rooftop" ? "Street address / not recorded" : p === "interpolated" ? "Interpolated" : p === "centre" ? "Road/place centre ≈" : "Unverified (older)"}
))}