import { useEffect, useMemo, useState } from 'react' import { useQueries, useQuery } from '@tanstack/react-query' import { Link } from '@tanstack/react-router' import { fetchLiveState, fetchLocalMeetings, fetchSeasonMeetings, fetchSeasons, fetchSessions, fetchWeekend } from '../api' import { RACE_HUB_DATASETS, countWeekendStats, formatCoverageHint, sessionTypeAbbrev } from '../lib/coverage' import { currentAndNextSession, focusMeetingKind, formatCountdown, formatSessionScheduleTime, meetingHasStarted, pickFocusMeeting, sessionEndTime, sessionStartTime, sortSessionsByStart, } from '../lib/schedule' import { countryAccent, countryDecal, countryFlag, formatGpDateRange } from '../lib/gpIdentity' import type { Meeting, Session, Weekend, WeekendSession } from '../types' import { PaddockBriefing } from '../components/PaddockBriefing' type WeekendStatusKind = 'live' | 'current' | 'next' | 'recent' | 'fallback' const missingDatasets = Object.fromEntries( RACE_HUB_DATASETS.map((dataset) => [dataset, { status: 'missing', source: 'none', count: 0 }]), ) as WeekendSession['datasets'] function classifySessionStatus(session: Session, now: Date): 'live' | 'done' | 'upcoming' { const start = sessionStartTime(session) const end = sessionEndTime(session) if (start && end && now >= start && now < end) return 'live' if (start && now >= start) return 'done' return 'upcoming' } function meetingStatus(meeting: Meeting, focusKey: number | undefined, now: Date) { if (meeting.meeting_key === focusKey) return 'focus' if (meetingHasStarted(meeting, now)) return 'past' return 'future' } function pickAnalysisSession(weekend: Weekend | undefined): WeekendSession | undefined { if (!weekend) return undefined const local = weekend.sessions.filter((s) => s.source === 'local') const partial = weekend.sessions.filter((s) => s.source === 'partial') const pool = local.length > 0 ? local : partial.length > 0 ? partial : weekend.sessions const race = pool.find((s) => s.session.session_type?.toLowerCase().includes('race')) if (race) return race const qual = pool.find((s) => s.session.session_type?.toLowerCase().includes('qualifying')) if (qual) return qual return pool[0] } export function CommandCenterPage() { const [now, setNow] = useState(() => Date.now()) const seasonsQuery = useQuery({ queryKey: ['seasons'], queryFn: fetchSeasons, }) const latestSeason = seasonsQuery.data?.[0] ?? null const meetingsQuery = useQuery({ queryKey: ['meetings', latestSeason], queryFn: () => fetchLocalMeetings(latestSeason!), enabled: latestSeason != null, }) const seasonMeetingsQuery = useQuery({ queryKey: ['season-meetings', latestSeason], queryFn: () => fetchSeasonMeetings(latestSeason!), enabled: latestSeason != null, }) const localMeetings = meetingsQuery.data ?? [] const seasonMeetings = seasonMeetingsQuery.data?.length ? seasonMeetingsQuery.data : localMeetings const focusMeetings = seasonMeetings.length > 0 ? seasonMeetings : localMeetings const weekendQueries = useQueries({ queries: localMeetings.map((meeting) => ({ queryKey: ['weekend', meeting.meeting_key], queryFn: () => fetchWeekend(meeting.meeting_key), enabled: localMeetings.length > 0, staleTime: 60_000, })), }) const liveQuery = useQuery({ queryKey: ['live-state'], queryFn: fetchLiveState, staleTime: 5_000, }) useEffect(() => { const timer = window.setInterval(() => setNow(Date.now()), 1000) return () => window.clearInterval(timer) }, []) const nowDate = useMemo(() => new Date(now), [now]) const weekendsByKey = useMemo(() => { const map = new Map() localMeetings.forEach((meeting, i) => { const data = weekendQueries[i]?.data if (data) map.set(meeting.meeting_key, data) }) return map }, [localMeetings, weekendQueries]) const weekendList = useMemo(() => weekendQueries.map((q) => q.data), [weekendQueries]) const meetingStats = countWeekendStats(weekendList) const focusMeeting = pickFocusMeeting(focusMeetings, nowDate) const focusWeekend = focusMeeting ? weekendsByKey.get(focusMeeting.meeting_key) : undefined const openF1SessionsQuery = useQuery({ queryKey: ['sessions', focusMeeting?.meeting_key, 'openf1'], queryFn: () => fetchSessions(focusMeeting!.meeting_key, 'openf1'), enabled: focusMeeting != null && focusWeekend == null, staleTime: 60_000, }) const openF1WeekendSessions: WeekendSession[] = useMemo( () => (openF1SessionsQuery.data ?? []).map((session) => ({ session, source: 'none', datasets: missingDatasets, })), [openF1SessionsQuery.data], ) const focusWeekendSessions = focusWeekend?.sessions ?? openF1WeekendSessions const focusKind = focusMeeting ? focusMeetingKind(focusMeeting, nowDate) : null const focusSessions: Session[] = sortSessionsByStart(focusWeekendSessions.map((s) => s.session)) const { current: currentSession, next: nextSession } = currentAndNextSession(focusSessions, nowDate) const analysisSession = pickAnalysisSession(focusWeekend) const actionSession = analysisSession?.session ?? currentSession ?? nextSession ?? focusWeekendSessions.find((s) => s.session.session_key === focusWeekend?.default_session_key)?.session ?? focusWeekendSessions[0]?.session const analysisSessionKey = actionSession?.session_key const weekendsLoading = weekendQueries.some((q) => q.isLoading) if (seasonsQuery.isLoading) { return
loading command center…
} if (seasonsQuery.isError) { return (
{seasonsQuery.error instanceof Error ? seasonsQuery.error.message : 'Failed to load seasons'}
) } const seasons = seasonsQuery.data ?? [] if (seasons.length === 0) { return (
box-box · command center

No local data yet

Ingest a race weekend from the CLI to populate this screen with live status, next-session countdowns, and analysis links.

Live Timing Standby Admin · Data Health Ingestion guidance
) } const liveActive = liveQuery.data?.is_live === true const statusKind: WeekendStatusKind = liveActive ? 'live' : focusKind === 'current' ? 'current' : focusKind === 'next' ? 'next' : focusKind === 'recent' ? 'recent' : 'fallback' const accent = countryAccent(focusMeeting ?? null) const decal = countryDecal(focusMeeting ?? null) const accentStyle = { '--gp-accent': accent } as React.CSSProperties return (
box-box · command center {latestSeason} season · {meetingStats.full}/{meetingStats.total || 0} weekends full {liveActive ? 'Live session active' : 'No live session'}
{!focusMeeting && (
No meetings ingested for {latestSeason}. Run{' '} box-box --ingest-year {latestSeason}
)} {focusMeeting && (
)} {focusMeeting && (
Watch Live {liveActive ? 'Feed active' : 'Standby'} Open Analysis {actionSession ? `${actionSession.session_name} · session ${actionSession.session_key}` : 'Pick a session'} {nextSession && sessionStartTime(nextSession) && ( { e.preventDefault() document.getElementById('cc-schedule')?.scrollIntoView({ behavior: 'smooth' }) }} > Schedule next: {nextSession.session_name} )}
)} {focusWeekendSessions.length > 0 && (
Weekend Schedule {focusWeekendSessions.length} sessions
{focusWeekendSessions.map(({ session, source, datasets }) => { const status = classifySessionStatus(session, nowDate) const isNext = nextSession?.session_key === session.session_key const isCurrent = currentSession?.session_key === session.session_key return (
{sessionTypeAbbrev(session.session_type, session.session_name)} {isCurrent ? 'On track' : status === 'done' ? 'Done' : isNext ? 'Next' : 'Upcoming'}
{session.session_name}
{formatSessionScheduleTime(session.date_start)}
) })}
)} {seasonMeetings.length > 0 && (
Season Calendar {seasonMeetings.length} rounds · {meetingStats.full}/{meetingStats.total || 0} local
{seasonMeetings.map((meeting, index) => { const weekend = weekendsByKey.get(meeting.meeting_key) const target = pickAnalysisSession(weekend) const targetKey = target?.session.session_key ?? weekend?.default_session_key const status = meetingStatus(meeting, focusMeeting?.meeting_key, nowDate) const cardAccent = countryAccent(meeting) return ( {seasonMeetingsQuery.isError && (
Using local meetings because the full calendar could not load.
)}
)}
) } function WeekendKindLabel({ kind }: { kind: WeekendStatusKind }) { switch (kind) { case 'live': return ● Live now case 'current': return Current weekend case 'next': return Next weekend case 'recent': return Recent weekend default: return Weekend } } interface CountdownBlockProps { liveActive: boolean currentSession: Session | null nextSession: Session | null meeting: Meeting now: Date } function CountdownBlock({ liveActive, currentSession, nextSession, meeting, now }: CountdownBlockProps) { if (liveActive) { return (
SignalR
LIVE
{currentSession?.session_name ?? 'Feed connected'}
) } if (currentSession) { return (
On Track
{currentSession.session_name}
In session
) } if (nextSession && sessionStartTime(nextSession)) { return (
Next · {nextSession.session_name}
{formatCountdown(sessionStartTime(nextSession)!, now)}
{formatSessionScheduleTime(nextSession.date_start)}
) } if (meetingHasStarted(meeting, now)) { return (
Status
Complete
Weekend finished
) } return null }