Frontend redesign chunk 6: Live view
New components/LiveView.tsx renders the default landing at "/": full-bleed MapView (rail + legend from chunk 5) plus a new TimeScrubber strip below it — real call-density bars over the selected 1h/6h/24h/7d window, tinted by the worst severity in each bucket, playhead pinned to NOW. The playhead doesn't scrub yet; that needs `resolved_at` on incidents, which doesn't exist server-side (blocked chunk 13, in DEFERRED.md) — the density data itself is live, not a fixture. Distinguishes the two empty states UI_REDESIGN.md §4 calls out: a configured-but-quiet org (nodes online, zero active incidents) now shows "Listening — last check-in Xm ago" instead of rendering nothing, separate from the zero-node case (chunk 10's Activation screen). app/page.tsx's HomePage now renders LiveView directly for a signed-in, provisioned user instead of the chunk-4 interim redirect to /incidents. Per UI_REDESIGN.md chunk 6.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
"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<Date | null>(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 (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="relative flex-1 min-h-0">
|
||||
{nodesLoading || callsLoading ? (
|
||||
<div className="w-full h-full flex items-center justify-center text-ink-muted text-sm">
|
||||
Loading map…
|
||||
</div>
|
||||
) : (
|
||||
<MapView
|
||||
nodes={nodes}
|
||||
activeCalls={activeCalls}
|
||||
incidents={activeIncidents}
|
||||
calls={recentCalls}
|
||||
lastUpdated={lastUpdated}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 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 && (
|
||||
<div className="absolute top-12 left-1/2 -translate-x-1/2 z-[1001] pointer-events-none">
|
||||
<span className="bg-surface/90 border border-line rounded-full px-3 py-1 text-xs text-ink-2 whitespace-nowrap">
|
||||
● Listening{mostRecentSeen ? ` — last check-in ${timeAgo(new Date(mostRecentSeen))}` : ""}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<TimeScrubber calls={recentCalls} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user