import { useMemo } from "react"
import { Inbox } from "lucide-react"
import { useDashboard } from "@/lib/dashboard"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Badge } from "@/components/ui/badge"
// deterministic label → hue (matches the spirit of the backend's md5 colouring)
function hueFor(label: string): number {
let h = 0
for (let i = 0; i < label.length; i++) h = (h * 31 + label.charCodeAt(i)) % 360
return h
}
function StatusBadge({ status }: { status: string }) {
const variant = status.includes("fail") || status.includes("missing")
? "danger"
: status.includes("posted")
? "info"
: status.includes("block")
? "warning"
: "live"
return {status}
}
export function ActivityPanel() {
const { result } = useDashboard()
const detections = result?.detections ?? []
const events = result?.events ?? []
const maxT = useMemo(
() =>
Math.max(
0.001,
...detections.map((d) => d.timestamp_sec),
...events.map((e) => e.timestamp_sec),
),
[detections, events],
)
const labels = useMemo(
() => Array.from(new Set(detections.map((d) => d.label))),
[detections],
)
if (!result) {
return (
Run detection to populate the activity timeline.
)
}
return (
{/* timeline track */}
0.0s
detection timeline
{maxT.toFixed(1)}s
{/* gridlines */}
{[0.25, 0.5, 0.75].map((f) => (
))}
{/* detection ticks */}
{detections.map((d, i) => (
))}
{/* event markers */}
{events.map((e, i) => (
))}
{/* legend */}
{labels.map((l) => (
{l}
))}
{/* tables */}
Actions
{events.length}
Detections
{detections.length}
{events.length ? (
Time
Rule
Action
Status
Frame
{events.map((e, i) => (
{e.timestamp_sec.toFixed(1)}s
{e.rule}
{e.action}
{e.frame_index}
))}
) : (
No actions fired this run.
)}
Frame
Label
Conf.
Time
{detections.slice(0, 200).map((d, i) => (
{d.frame_index}
{d.label}
{d.confidence.toFixed(2)}
{d.timestamp_sec.toFixed(1)}s
))}
)
}