import { useEffect, useMemo, useState } from 'react' import { useQueries, useQuery } from '@tanstack/react-query' import { Link } from '@tanstack/react-router' import { fetchChampionshipHub, fetchLiveState, fetchLocalMeetings, fetchRaceHub, fetchSeasonMeetings, fetchSeasons, fetchSessions, fetchWeekend, } from '../api' import { CommandCenterHero } from '../components/CommandCenterHero' import { PaddockBriefing } from '../components/PaddockBriefing' import { RACE_HUB_DATASETS, countWeekendStats, formatCoverageHint, sessionTypeAbbrev } from '../lib/coverage' import { countryAccent, countryDecal, countryFlag, formatGpDateRange } from '../lib/gpIdentity' import { classifySessionStatus, heroState } from '../lib/hero' import { currentAndNextSession, focusMeetingKind, formatSessionScheduleTime, meetingHasStarted, mostRecentPastMeeting, nextUpcomingMeeting, pickFocusMeeting, sortSessionsByStart, } from '../lib/schedule' import type { Meeting, Session, Weekend, WeekendSession } from '../types' import { Trophy } from 'lucide-react' const missingDatasets = Object.fromEntries( RACE_HUB_DATASETS.map((dataset) => [dataset, { status: 'missing', source: 'none', count: 0 }]), ) as WeekendSession['datasets'] 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, }) const championshipQuery = useQuery({ queryKey: ['championship-hub', latestSeason], queryFn: () => fetchChampionshipHub(latestSeason!), enabled: latestSeason != null, }) const champHub = championshipQuery.data 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 liveActive = liveQuery.data?.is_live === true const heroStateKind = heroState({ now: nowDate, liveActive, currentSession, focusKind, }) const lastPastMeeting = useMemo( () => mostRecentPastMeeting(focusMeetings, nowDate), [focusMeetings, nowDate], ) const lastPastWeekend = lastPastMeeting ? weekendsByKey.get(lastPastMeeting.meeting_key) : undefined const lastRaceAnalysis = pickAnalysisSession(lastPastWeekend) const lastRaceSessionKey = lastRaceAnalysis?.session.session_key const lastRaceHubQuery = useQuery({ queryKey: ['race-hub', lastRaceSessionKey, 'hero-podium'], queryFn: () => fetchRaceHub(lastRaceSessionKey!), enabled: lastRaceSessionKey != null && heroStateKind === 'between', staleTime: 60_000, }) const nextMeetingForHero = useMemo(() => { if (heroStateKind !== 'between') return null return nextUpcomingMeeting(focusMeetings, nowDate) }, [heroStateKind, focusMeetings, nowDate]) const lastRacePodium = lastRaceHubQuery.data?.results ?? [] const lastRaceName = champHub?.last_race ?? lastPastMeeting?.meeting_name ?? '' 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 accent = countryAccent(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 && focusKind && ( )}
{champHub && (
Championship Leaders Full Standings →
Drivers
{champHub.drivers.slice(0, 3).map((d) => (
{d.name_acronym}
{d.position === 1 ? '—' : `-${champHub.drivers[0].points - d.points}`} {d.points}
))}
Constructors
{champHub.teams.slice(0, 3).map((t, i) => (
{t.team_name}
{t.wins}
{i === 0 ? '—' : `-${champHub.teams[0].points - t.points}`} {t.points}
))}
)} {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 const isLive = isCurrent && liveActive return (
{sessionTypeAbbrev(session.session_type, session.session_name)} {isCurrent ? 'On track' : status === 'done' ? 'Done' : isNext ? 'Next' : 'Upcoming'}
{session.session_name}
{formatSessionScheduleTime(session.date_start)}
) })}
)}
) } function FormSparkline({ form, color }: { form: number[], color: string }) { if (!form || form.length === 0) return null const max = Math.max(...form, 26) // 26 is standard max for a race win const width = 45 const height = 14 const step = width / Math.max(form.length - 1, 1) const points = form.map((val, i) => { const x = i * step const y = height - (val / max) * height return `${x},${y}` }).join(' ') return ( ) }