diff --git a/drb-frontend/components/MapView.tsx b/drb-frontend/components/MapView.tsx
index 10682fd..4e9e326 100644
--- a/drb-frontend/components/MapView.tsx
+++ b/drb-frontend/components/MapView.tsx
@@ -1,6 +1,7 @@
"use client";
-import { useCallback, useEffect, useMemo, useState } from "react";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
+import { createPortal } from "react-dom";
import {
FeatureGroup,
LayersControl,
@@ -11,6 +12,7 @@ import {
TileLayer,
Tooltip,
useMap,
+ useMapEvents,
} from "react-leaflet";
import L from "leaflet";
import type { AircraftTrack, CallRecord, IncidentRecord, NodeRecord, NodeStatus } from "@/lib/types";
@@ -158,47 +160,81 @@ function AircraftTrail({ icao, current }: { icao: string; current: AircraftTrack
);
}
+function Stat({ label, value }: { label: string; value: string }) {
+ return (
+
+
{label}
+
{value}
+
+ );
+}
+
+// Details dock at the right edge instead of a popup over the plane, so the
+// trail stays visible and the map can be panned to follow it.
+function AircraftPanel({ a, onClose }: { a: AircraftTrack; onClose: () => void }) {
+ const ref = useRef(null);
+ useEffect(() => {
+ // The panel is portaled into the Leaflet container, whose native listeners
+ // would otherwise treat clicks/scrolls here as map clicks (deselect) or zoom.
+ if (!ref.current) return;
+ L.DomEvent.disableClickPropagation(ref.current);
+ L.DomEvent.disableScrollPropagation(ref.current);
+ }, []);
+ const fmt = (n: number | null, unit: string) => (n == null ? "—" : `${Math.round(n).toLocaleString()} ${unit}`);
+ return (
+