import { useEffect, useMemo, useState } from 'react' import { useQueries, useQuery } from '@tanstack/react-query' import { Link } from '@tanstack/react-router' import { fetchLiveState, fetchLocalMeetings, fetchSeasons, fetchWeekend } from '../api' import { countWeekendStats, formatCoverageHint, sessionTypeAbbrev } from '../lib/coverage' import { currentAndNextSession, focusMeetingKind, formatCountdown, formatSessionScheduleTime, meetingHasStarted, pickFocusMeeting, sessionEndTime, sessionStartTime, sortSessionsByStart, } from '../lib/schedule' import { countryAccent, countryDecal, formatGpDateRange } from '../lib/gpIdentity' import type { Meeting, Session, Weekend, WeekendSession } from '../types' type WeekendStatusKind = 'live' | 'current' | 'next' | 'recent' | 'fallback' 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 collectRecentWeekends(weekends: (Weekend | undefined)[], focusKey: number | null, limit = 6) { const rows: Weekend[] = [] for (const weekend of weekends) { if (!weekend) continue if (focusKey != null && weekend.meeting_key === focusKey) continue if (weekend.source === 'none') continue rows.push(weekend) } return rows .sort((a, b) => { const left = Date.parse(a.meeting.date_start ?? '') const right = Date.parse(b.meeting.date_start ?? '') return right - left }) .slice(0, limit) } 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 meetings = meetingsQuery.data ?? [] const weekendQueries = useQueries({ queries: meetings.map((meeting) => ({ queryKey: ['weekend', meeting.meeting_key], queryFn: () => fetchWeekend(meeting.meeting_key), enabled: meetings.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() meetings.forEach((meeting, i) => { const data = weekendQueries[i]?.data if (data) map.set(meeting.meeting_key, data) }) return map }, [meetings, weekendQueries]) const weekendList = useMemo(() => weekendQueries.map((q) => q.data), [weekendQueries]) const meetingStats = countWeekendStats(weekendList) const focusMeeting = pickFocusMeeting(meetings, nowDate) const focusWeekend = focusMeeting ? weekendsByKey.get(focusMeeting.meeting_key) : undefined const focusKind = focusMeeting ? focusMeetingKind(focusMeeting, nowDate) : null const focusSessions: Session[] = focusWeekend ? sortSessionsByStart(focusWeekend.sessions.map((s) => s.session)) : [] const { current: currentSession, next: nextSession } = currentAndNextSession(focusSessions, nowDate) const recentWeekends = collectRecentWeekends(weekendList, focusMeeting?.meeting_key ?? null) const analysisSession = pickAnalysisSession(focusWeekend) const analysisSessionKey = analysisSession?.session.session_key ?? focusWeekend?.default_session_key ?? focusWeekend?.sessions[0]?.session.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 {analysisSession ? `${analysisSession.session.session_name} · session ${analysisSession.session.session_key}` : 'Pick a session'} {nextSession && sessionStartTime(nextSession) && ( { e.preventDefault() document.getElementById('cc-schedule')?.scrollIntoView({ behavior: 'smooth' }) }} > Schedule next: {nextSession.session_name} )}
)} {focusWeekend && focusWeekend.sessions.length > 0 && (
Weekend Schedule {focusWeekend.sessions.length} sessions
{focusWeekend.sessions.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)}
) })}
)} {(weekendsLoading || recentWeekends.length > 0) && (
Recent Local Weekends {recentWeekends.length}
{weekendsLoading && recentWeekends.length === 0 ? (
loading weekends…
) : recentWeekends.length === 0 ? (
No additional local weekends.
) : (
{recentWeekends.map((weekend) => { const finished = meetingHasStarted(weekend.meeting, nowDate) const target = pickAnalysisSession(weekend) const target_key = target?.session.session_key ?? weekend.default_session_key return (
{countryDecal(weekend.meeting)}
{weekend.meeting.meeting_name}
{formatGpDateRange(weekend.meeting)} {' · '} {finished ? 'Past' : 'Upcoming'}
) })}
)}
)}
) } 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 }