import { Archive, ChevronRight, Flag, Radio } from 'lucide-react' import type { LiveTimingRow } from '../../lib/live' import { driverCode } from '../../lib/live' import type { TransportHealth } from '../../lib/liveState' import { feedHealthLabel } from '../../lib/liveState' import type { ContextSession, WeekendContext } from '../../types' import { analysisSessionKey } from '../../lib/weekendContext' import { formatSessionScheduleTime } from '../../lib/schedule' interface Props { /** 'settling' immediately after a session; 'inactive' when nothing is live. */ phase: 'settling' | 'inactive' transport: TransportHealth /** Canonical weekend context (issue #72). May be undefined before it loads. */ context: WeekendContext | undefined /** Top rows of the final snapshot (settling only), already position-sorted. */ rows: LiveTimingRow[] capturedAt?: string | null hasArchive: boolean onViewArchive: () => void } function formatCapturedAt(capturedAt: string | null | undefined): string { if (!capturedAt) return '' const date = new Date(capturedAt) if (Number.isNaN(date.getTime())) return '' return date.toLocaleString() } /** * Fully ingested analysis (local_analysis === complete). Stricter than * hasLocalAnalysis, which also treats partial as link-worthy — Live polling * and "ready" chrome wait for the complete state. */ export function analysisIsReady(session: ContextSession | undefined): boolean { return session?.availability?.local_analysis === 'complete' } export function LiveHandoff({ phase, transport, context, rows, capturedAt, hasArchive, onViewArchive, }: Props) { const isSettling = phase === 'settling' const testid = isSettling ? 'live-settling' : 'live-inactive' const capturedLabel = formatCapturedAt(capturedAt) const focusName = context?.focus_meeting?.meeting_name const activeName = context?.active_session?.meeting?.meeting_name const title = focusName || activeName || 'Live Timing' const topRows = rows.slice(0, 3) // Shared analysisSessionKey prefers default_analysis_session, then previous. const analysisKey = context ? analysisSessionKey(context) : undefined const analysis = !context || !analysisKey ? undefined : context.default_analysis_session?.session.session_key === analysisKey ? context.default_analysis_session : context.previous_completed_session const analysisName = analysis?.session.session_name || 'session' const analysisReady = analysisIsReady(analysis) const next = context?.next_session const previous = context?.previous_completed_session // Avoid a duplicate recap card when previous == the analysis target. const showRecap = previous && previous.session.session_key !== analysisKey && previous.session.session_key !== 0 return (
{isSettling ? ( <> SESSION SETTLING ) : ( <> NO LIVE SESSION )}

{title}

{isSettling && capturedLabel && (

Final feed snapshot captured {capturedLabel}

)} {!isSettling && (

The timing feed is quiet between sessions. Here's where the weekend stands.

)}
{isSettling && topRows.length > 0 && (
Provisional order at chequered
    {topRows.map((row) => (
  1. P{row.Position} {driverCode(row)}
  2. ))}
)}
{analysisKey ? ( {isSettling ? `Open ${analysisName} analysis` : `Open ${analysisName} in Race Hub`} {analysisReady ? 'Full timing, strategy & story ready' : isSettling ? 'Settling — analysis will fill in as data ingests' : 'Analysis will fill in as data ingests'} ) : (
Analysis not ready yet The completed session will appear in Race Hub once it is ingested.
)} {next && (
Up next · {next.session.session_name} {formatSessionScheduleTime(next.session.date_start)}
)} {showRecap && ( Recap · {previous!.session.session_name} Review the last completed session )} {hasArchive && ( )} {!analysisKey && !next && !showRecap && !hasArchive && (
Weekend Weekend schedule & standings
)}
) }