"use client"; /** * Live — the default landing for every authenticated user (UI_REDESIGN.md * §3, §5.1). Full-bleed map with the incident rail/legend MapView already * renders (chunk 5), plus a time-density scrubber strip below it. */ import { useEffect, useState } from "react"; import dynamic from "next/dynamic"; import { useNodes } from "@/lib/useNodes"; import { useActiveCalls, useCalls } from "@/lib/useCalls"; import { useActiveIncidents } from "@/lib/useIncidents"; import { TimeScrubber } from "@/components/TimeScrubber"; const MapView = dynamic(() => import("@/components/MapView"), { ssr: false }); function timeAgo(date: Date): string { const s = Math.floor((Date.now() - date.getTime()) / 1000); if (s < 60) return `${s}s ago`; if (s < 3600) return `${Math.floor(s / 60)}m ago`; return `${Math.floor(s / 3600)}h ago`; } export function LiveView() { const { nodes, loading: nodesLoading } = useNodes(); const activeCalls = useActiveCalls(); const activeIncidents = useActiveIncidents(); // Wide-ish recent window: feeds both the incident-path polyline (chunk 5) // and the scrubber's density bars. Not a fixture — real recent calls. const { calls: recentCalls, loading: callsLoading } = useCalls(500); const [lastUpdated, setLastUpdated] = useState(null); useEffect(() => { if (!nodesLoading) setLastUpdated(new Date()); }, [nodes, activeCalls, activeIncidents, nodesLoading]); const configuredButQuiet = !nodesLoading && nodes.length > 0 && activeIncidents.length === 0; const mostRecentSeen = configuredButQuiet ? nodes .map((n) => n.last_seen) .filter((s): s is string => !!s) .sort() .at(-1) : null; return (
{nodesLoading || callsLoading ? (
Loading map…
) : ( )} {/* Configured-and-quiet — distinct from "no nodes at all" (chunk 10's Activation screen handles the zero-node case). A node that's online but has heard nothing is NOT the same empty state as an org with no equipment. */} {configuredButQuiet && (
● Listening{mostRecentSeen ? ` — last check-in ${timeAgo(new Date(mostRecentSeen))}` : ""}
)}
); }