'use client'; import { useQuery } from '@tanstack/react-query'; import { format, formatDistanceToNowStrict } from 'date-fns'; import { Pencil, FileText, Clock, PlusCircle, Archive, RotateCcw, Trophy, XCircle, RefreshCcw, Bot, } from 'lucide-react'; import { apiFetch } from '@/lib/api/client'; interface TimelineEvent { id: string; type: 'audit' | 'document_event'; action: string; description: string; userId: string | null; /** Resolved display name (server-side join). Falls back to userId when null. */ userName?: string | null; createdAt: string; metadata: Record; } interface InterestTimelineProps { interestId: string; } const LOST_OUTCOMES = new Set([ 'lost_other_marina', 'lost_unqualified', 'lost_no_response', 'lost_other', 'cancelled', ]); function eventIcon(event: TimelineEvent) { const type = event.metadata?.type as string | undefined; if (type === 'outcome_set') { const outcome = (event.metadata as Record).outcome as string | undefined; if (outcome === 'won') return ; if (outcome && LOST_OUTCOMES.has(outcome)) return ; return ; } if (type === 'outcome_cleared') return ; if (event.type === 'document_event') return ; if (event.action === 'create') return ; if (event.action === 'archive') return ; if (event.action === 'restore') return ; if (type === 'stage_change') return ; return ; } function actorLabel(event: TimelineEvent): string | null { if (event.userName) return event.userName; if (!event.userId) return null; if (event.userId === 'system') return 'system'; // Last-resort fallback when the user row was deleted: show a short token // instead of a 36-char UUID. The server-side join is authoritative; this // path should be rare in practice. return 'a teammate'; } export function InterestTimeline({ interestId }: InterestTimelineProps) { const { data, isLoading } = useQuery<{ data: TimelineEvent[] }>({ queryKey: ['interest-timeline', interestId], queryFn: () => apiFetch(`/api/v1/interests/${interestId}/timeline`), }); if (isLoading) { return (
{[...Array(5)].map((_, i) => (
))}
); } const events = data?.data ?? []; if (events.length === 0) { return (

No activity yet.

); } return (
{events.map((event, idx) => { const actor = actorLabel(event); const isAuto = event.userId === 'system'; const isLast = idx === events.length - 1; return (
{/* Vertical line - only between bubbles, never trailing past the last. */} {!isLast && ( )} {/* Icon */}
{eventIcon(event)}

{event.description} {isAuto ? ( Auto ) : null}

{actor ? ยท by {actor} : null}

); })}
); }