"use client"; /** * Live ops wall side panel — Level 1 (region) and Level 2 (incident) of the * approved mockup (design-canvas/live-ops-wall-mockup.html). */ import Link from "next/link"; import { useEffect, useMemo, useState } from "react"; import type { AircraftTrack, CallRecord, Camera, CameraCredit, CameraProviderFeed, IncidentRecord, Ny511Event, NodeRecord, VesselTrack, } from "@/lib/types"; import { isKnownSeverity, SEVERITY_COLORS, SEVERITY_LABEL } from "@/lib/severity"; import { useAuth } from "@/components/AuthProvider"; import { CameraFeed } from "@/components/CameraFeed"; import { MachineOutputNotice } from "@/components/ui/MachineOutputNotice"; import { SourceCredit, fetchedLabel } from "@/components/SourceCredit"; import { InlinePlayer } from "@/components/InlinePlayer"; import { canPlayAudio, hasAudio, playIncident, stop as stopAudio, useAudio } from "@/lib/audioQueue"; import { CONTEXT_KM, NEAR_CAMERA_KM, contextFor, sampleRadarDbz, type LatLon, type NearCamera } from "@/lib/liveWall"; function sevColor(s: string | null | undefined) { return isKnownSeverity(s) ? SEVERITY_COLORS[s] : "var(--ink-muted)"; } function sevLabel(s: string | null | undefined) { return isKnownSeverity(s) ? SEVERITY_LABEL[s] : "Unknown"; } function elapsed(iso: string): string { const m = Math.max(0, Math.round((Date.now() - new Date(iso).getTime()) / 60_000)); return m < 60 ? `${m}m` : `${Math.floor(m / 60)}h ${m % 60}m`; } function hhmm(iso: string): string { return new Date(iso).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: false }); } const H3 = "text-[11px] uppercase tracking-wide text-ink-muted font-medium mb-1.5"; // Spine rows rendered before "show all loaded" (server-26#239). const SPINE_CAP = 30; const SEC = "px-4 py-3 border-b border-line"; export function CameraTile({ cam, feed, km, onOpen }: { cam: Camera; feed?: CameraProviderFeed; km?: number; onOpen: () => void }) { return ( ); } function NoCameras({ servingCount, radiusKm }: { servingCount: number; radiusKm: number }) { // Only admins can open /admin (everyone else is bounced to /), so only they get the link. const { isAdmin } = useAuth(); return (

{servingCount !== 0 ? `No public camera within ${radiusKm} km.` : isAdmin ? <>No camera provider is enabled. Turn one on under Admin → Camera providers. : "No camera provider is enabled. A platform admin can turn them on."}

); } // ── Level 1: region ────────────────────────────────────────────────────────── export function RegionPanel({ incidents, calls, onAirCount, nodes, wallCameras, feeds, servingCount, aircraftCount, vesselCount, events, eventsOff, pinnedIds, onOpenCamera, }: { incidents: IncidentRecord[]; calls: CallRecord[]; onAirCount: number; nodes: NodeRecord[]; wallCameras: { cam: Camera; km?: number }[]; feeds: Map; servingCount: number; aircraftCount: number; vesselCount: number; events: Ny511Event[]; eventsOff: boolean; pinnedIds: Set; onOpenCamera: (c: Camera) => void; }) { const calls6h = useMemo(() => { const cutoff = Date.now() - 6 * 3600_000; return calls.filter((c) => new Date(c.started_at).getTime() >= cutoff).length; }, [calls]); const providers = Array.from(feeds.values()); return (

Region

{[[incidents.length, "active incidents"], [calls6h, "calls · last 6 h"], [onAirCount, "on air now"]].map(([v, l]) => (
{v}
{l}
))}

Camera wall · pinned + nearest to major/moderate incidents

{wallCameras.length ? (
{wallCameras.map(({ cam, km }) => (
onOpenCamera(cam)} /> {pinnedIds.has(cam.id) && PINNED}
))}
) : ( )}

Off-screen and hidden-tab tiles stop fetching; at most two live streams play at once.

Feeds

  • ADS-B{aircraftCount} aircraft heard by your nodes
  • AIS{vesselCount} vessels / aids to navigation
  • 511NY{eventsOff ? "road events off (Admin → Camera providers)" : `${events.length} road events in region`}
  • {providers.map((p) => (
  • CAM{p.name}: {p.total_in_bbox} in region{p.error ? " · feed error" : ""}{p.fetched_at ? ` · list ${fetchedLabel(p.fetched_at)}` : ""}
  • ))}
  • NEXRADWeather Radar layer (map menu); sampled per incident

Nodes

    {nodes.map((n) => (
  • {n.name} {n.status}
  • ))}

Region → click an incident for its path, summary, units and nearest cameras → click a camera to open it. Esc steps back up. Bookmark a view with #inc=…&cam=…&tour.

); } // ── Level 2: incident ──────────────────────────────────────────────────────── function useRadarDbz(head: LatLon | null): { dbz: number | null; done: boolean } { const [state, setState] = useState<{ dbz: number | null; done: boolean }>({ dbz: null, done: false }); const key = head ? `${head[0].toFixed(3)},${head[1].toFixed(3)}` : ""; useEffect(() => { if (!head) return; let cancelled = false; setState({ dbz: null, done: false }); sampleRadarDbz(head[0], head[1]).then((dbz) => { if (!cancelled) setState({ dbz, done: true }); }); return () => { cancelled = true; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [key]); return state; } export function IncidentPanel({ inc, calls, points, near, feeds, servingCount, onAir, aircraft, vessels, events, eventsCredit, onOpenCamera, }: { inc: IncidentRecord; calls: CallRecord[]; points: LatLon[]; near: NearCamera[]; feeds: Map; servingCount: number; onAir: boolean; aircraft: AircraftTrack[]; vessels: VesselTrack[]; events: Ny511Event[]; eventsCredit: { credit?: CameraCredit; fetched_at: number | null }; onOpenCamera: (c: Camera) => void; }) { const color = sevColor(inc.severity); const ctx = useMemo(() => contextFor(points, aircraft, vessels, events), [points, aircraft, vessels, events]); const radar = useRadarDbz(points.at(-1) ?? null); // Stop numbers share the index with the map path: geocoded calls, oldest first. const stopOf = useMemo(() => { const m = new Map(); calls.filter((c) => c.location_coords).slice().sort((a, b) => a.started_at.localeCompare(b.started_at)) .forEach((c, i) => m.set(c.call_id, i + 1)); return m; }, [calls]); const spine = useMemo(() => calls.slice().sort((a, b) => b.started_at.localeCompare(a.started_at)), [calls]); // "Play incident" (server-26#233): every loaded call with a recording, oldest first. const playable = useMemo(() => spine.filter(hasAudio).map((c) => c.call_id).reverse(), [spine]); const auth = useAuth(); const audioOk = canPlayAudio(auth); const playingCallId = useAudio((s) => s.playingCallId); const queueLen = useAudio((s) => s.queue.length); const playingHere = !!playingCallId && spine.some((c) => c.call_id === playingCallId); // Truncation is said out loud, not silent (server-26#239): the spine shows // what this page has loaded, which can be less than the incident holds. const [showAll, setShowAll] = useState(false); useEffect(() => { setShowAll(false); }, [inc.incident_id]); const totalCalls = Math.max(inc.call_ids?.length ?? 0, calls.length); const outsideWindow = Math.max(0, (inc.call_ids?.length ?? 0) - calls.length); const shown = showAll ? spine : spine.slice(0, SPINE_CAP); const unitsOn = inc.units_active?.length ? inc.units_active : inc.units ?? []; const cleared = inc.units_cleared ?? []; return (
{sevLabel(inc.severity)} {inc.type ?? "other"} {onAir ? ON AIR : active} {inc.incident_id.slice(0, 8)}

{inc.title ?? "Incident"}

{inc.location && {inc.location}} {elapsed(inc.started_at)} elapsed {inc.call_ids?.length ?? 0} calls
Open incident page →
{inc.summary ?

{inc.summary}

:

No AI summary yet.

}

Nearest cameras · auto-surfaced{points.length > 1 ? " (weighted to the path's head)" : ""}

{near.length ? (
{near.map(({ cam, km }) => ( onOpenCamera(cam)} /> ))}
) : points.length ? ( ) : (

This incident has no geocoded location, so no cameras can be matched.

)}
Camera signals (detections as incident evidence) · future — blocked on server-26#199 and #200.

Situational context · fused layers

    {ctx.aircraft.map(({ a, km }) => (
  • ADS-B {a.callsign || a.icao}{a.altitude_ft != null ? ` · ${Math.round(a.altitude_ft).toLocaleString()} ft` : ""} · {km.toFixed(1)} km
  • ))} {ctx.vessels.map(({ v, km }) => (
  • AIS {v.name || v.mmsi}{v.speed_kt != null ? ` · ${Math.round(v.speed_kt)} kt` : ""} · {km.toFixed(1)} km
  • ))} {ctx.road.map(({ e, km }) => (
  • 511NY {/* DAA: NYSDOT's text as published, with its update time and source credit. */} {e.description} · {km.toFixed(1)} km
  • ))}
  • NEXRAD {!points.length ? "—" : !radar.done ? "sampling…" : radar.dbz == null ? "radar sample unavailable" : radar.dbz === 0 ? "no precipitation echo within ~5 km" : `up to ~${radar.dbz} dBZ within ~5 km (approx., from the radar tile)`}
{!ctx.aircraft.length && !ctx.vessels.length && !ctx.road.length && (

No aircraft within {CONTEXT_KM.aircraft} km, vessels within {CONTEXT_KM.vessel} km, or road events within {CONTEXT_KM.road} km.

)}

On scene

{unitsOn.length ? unitsOn.map((u) => {u}) : —}

Cleared

{cleared.length ? cleared.map((u) => {u}) : —}

Call spine · newest first

{audioOk && playable.length > 0 && ( playingHere ? ( ) : ( ) )}
{spine.length > SPINE_CAP || outsideWindow > 0 ? (

{showAll || spine.length <= SPINE_CAP ? `Showing all ${spine.length} loaded of ${totalCalls}` : `Showing latest ${SPINE_CAP} of ${totalCalls}`} {outsideWindow > 0 && ` · ${outsideWindow} older call${outsideWindow !== 1 ? "s" : ""} outside the loaded window`} {spine.length > SPINE_CAP && ( )}

) : null} {spine.length ? (
    {shown.map((c) => { const stop = stopOf.get(c.call_id); const text = c.transcript_corrected || c.transcript; const isPlaying = c.call_id === playingCallId; return (
  1. {stop ?? "·"}
    {hhmm(c.started_at)} {c.talkgroup_name && {c.talkgroup_name}}

    {text || no transcript}

    {isPlaying && hasAudio(c) &&
    }
    {hasAudio(c) && !isPlaying && }
  2. ); })}
) : (

No calls for this incident in the loaded window.

)}
); }