"use client"; /** * Live's time scrubber — call-density bars over the selected window, tinted * by the worst severity in each bucket, playhead pinned to NOW. Range * presets change which window is densitized; the playhead itself does not * move in this build — history scrub needs `resolved_at` on incidents, * which doesn't exist yet (UI_REDESIGN.md chunk 13, blocked on backend, * tracked in DEFERRED.md). This is real call density from live data, not a * fixture — only the scrub interaction is deferred. */ import { useMemo, useState } from "react"; import type { CallRecord } from "@/lib/types"; import { SEVERITY_COLORS, severityRank } from "@/lib/severity"; const RANGES = [ { label: "1h", ms: 60 * 60 * 1000 }, { label: "6h", ms: 6 * 60 * 60 * 1000 }, { label: "24h", ms: 24 * 60 * 60 * 1000 }, { label: "7d", ms: 7 * 24 * 60 * 60 * 1000 }, ] as const; const BUCKETS = 48; export function TimeScrubber({ calls }: { calls: CallRecord[] }) { const [rangeIdx, setRangeIdx] = useState(2); // default 24h const range = RANGES[rangeIdx]; const buckets = useMemo(() => { const now = Date.now(); const start = now - range.ms; const bucketMs = range.ms / BUCKETS; const counts = Array.from({ length: BUCKETS }, () => ({ count: 0, worstRank: -1 })); for (const c of calls) { const t = new Date(c.started_at).getTime(); if (Number.isNaN(t) || t < start || t > now) continue; const idx = Math.min(BUCKETS - 1, Math.floor((t - start) / bucketMs)); counts[idx].count += 1; const rank = severityRank(c.severity); if (rank > counts[idx].worstRank) counts[idx].worstRank = rank; } return counts; // eslint-disable-next-line react-hooks/exhaustive-deps }, [calls, rangeIdx]); const maxCount = Math.max(1, ...buckets.map((b) => b.count)); return (
{buckets.map((b, i) => { const heightPct = b.count === 0 ? 4 : Math.max(10, (b.count / maxCount) * 100); const color = b.worstRank === 3 ? SEVERITY_COLORS.major : b.worstRank === 2 ? SEVERITY_COLORS.moderate : b.worstRank >= 0 ? "var(--ink-2)" : "var(--line-strong)"; return (
0 ? `${b.count} call${b.count !== 1 ? "s" : ""}` : undefined} style={{ height: `${heightPct}%`, background: color, flex: 1, borderRadius: 1, minWidth: 2 }} /> ); })} {/* Playhead — pinned to NOW; doesn't move yet (see file header) */}
{RANGES.map((r, i) => ( ))} NOW
); }