import { createFileRoute, Link } from "@tanstack/react-router"; import { useEffect, useState } from "react"; import { api, type SessionEventData } from "@/lib/api"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { RatioBar, formatDuration, COLORS } from "@/components/analytics"; import { ArrowLeft, ChevronDown, ChevronRight, Zap, Clock, Layers } from "lucide-react"; export const Route = createFileRoute("/sessions_/$dbHash/$sessionId")({ component: EventView }); function EventRow({ e }: { e: { id: number; type: string; priority: number; data: string | null; created_at: string | null } }) { const [expanded, setExpanded] = useState(false); const raw = e.data || "-"; const truncated = raw.length > 100 ? raw.slice(0, 100) + "..." : raw; const needsExpand = raw.length > 100; return (
needsExpand && setExpanded(prev => !prev)} > {e.created_at || "-"} = 3 ? "default" : "secondary"} className="text-[10px] shrink-0 h-5"> {e.type}
= 4 ? "text-red-500 font-semibold" : e.priority >= 3 ? "text-amber-500" : "text-muted-foreground" }`}> {expanded ? raw : truncated}
{needsExpand && ( {expanded ? : } )}
); } function EventView() { const { dbHash, sessionId } = Route.useParams(); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { api.events(dbHash, sessionId).then(d => { setData(d); setLoading(false); }); }, [dbHash, sessionId]); if (loading || !data) return

Loading session...

; // Compute type counts const typeCounts: Record = {}; data.events.forEach(e => { typeCounts[e.type] = (typeCounts[e.type] || 0) + 1; }); const typeEntries = Object.entries(typeCounts).sort((a, b) => b[1] - a[1]); const uniqueTypes = typeEntries.length; // Compute duration from first to last event let durationMin = 0; if (data.events.length >= 2) { const first = data.events[0]?.created_at; const last = data.events[data.events.length - 1]?.created_at; if (first && last) { const start = new Date(first).getTime(); const end = new Date(last).getTime(); if (!isNaN(start) && !isNaN(end) && end > start) { durationMin = (end - start) / 60000; } } } return (
Back to Sessions

Session Detail

{data.resume && Resume available}
{/* KPI Strip */}
Events
{data.events.length}

total in session

Duration
{formatDuration(durationMin)}

first to last event

Types
{uniqueTypes}

unique event types

{/* Event type breakdown as ratio bar */} {typeEntries.length > 0 && ( Event Breakdown ({ label: type, value: count, color: COLORS[i % COLORS.length], }))} /> )} {/* Event type badges */}
{typeEntries.map(([type, count]) => ( {type}: {count} ))}
{/* Event list */}
{data.events.map(e => ( ))}
{data.resume?.snapshot && ( Resume Snapshot ({data.resume.event_count} events, {data.resume.consumed ? "consumed" : "pending"})
                    {data.resume.snapshot}
                  
)}
); }