import { useEffect, useMemo, useState } from 'react' import { useQuery } from '@tanstack/react-query' import { useNavigate } from '@tanstack/react-router' import { fetchLocalMeetings, fetchRaceHub, fetchSeasons, fetchWeekend, } from '../api' import { DatasetStrip } from '../components/DatasetStrip' import { RaceStoryCanvas } from '../components/RaceStoryCanvas' import { TabBar, type Tab } from '../components/TabBar' import { DatasetStatusView } from '../components/DatasetStatusView' import { StrategyView } from '../components/StrategyView' import { LapsView } from '../components/LapsView' import { CompareView } from '../components/CompareView' import { RaceControlView } from '../components/RaceControlView' import { WeatherView } from '../components/WeatherView' import { OverviewView } from '../components/OverviewView' import { WeekendSwitcher } from '../components/WeekendSwitcher' import { SourceBadge } from '../components/SourceBadge' import { countryAccent, countryDecal, formatGpDateRange } from '../lib/gpIdentity' import { formatCoverageHint, sessionTypeAbbrev } from '../lib/coverage' import { formatSessionScheduleTime, pickFocusMeeting, sortSessionsByStart, } from '../lib/schedule' import type { Weekend, WeekendSession } from '../types' interface Props { sessionKey: number } 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 RaceHubPage({ sessionKey }: Props) { const navigate = useNavigate() const [activeTab, setActiveTab] = useState('overview') const [switcherOpen, setSwitcherOpen] = useState(false) // ─── Auto-redirect when no session_key is supplied ─── const seasonsQuery = useQuery({ queryKey: ['seasons'], queryFn: fetchSeasons, enabled: sessionKey === 0, }) const latestSeason = seasonsQuery.data?.[0] ?? null const meetingsQuery = useQuery({ queryKey: ['meetings', latestSeason], queryFn: () => fetchLocalMeetings(latestSeason!), enabled: sessionKey === 0 && latestSeason != null, }) const focusMeeting = useMemo(() => { if (sessionKey !== 0 || !meetingsQuery.data) return null return pickFocusMeeting(meetingsQuery.data, new Date()) }, [sessionKey, meetingsQuery.data]) const fallbackWeekendQuery = useQuery({ queryKey: ['weekend', focusMeeting?.meeting_key], queryFn: () => fetchWeekend(focusMeeting!.meeting_key), enabled: sessionKey === 0 && focusMeeting != null, }) useEffect(() => { if (sessionKey !== 0) return const weekend = fallbackWeekendQuery.data if (!weekend) return const target = pickAnalysisSession(weekend)?.session.session_key ?? weekend.default_session_key ?? weekend.sessions[0]?.session.session_key if (target) { navigate({ to: '/race-hub', search: { session_key: target }, replace: true }) } }, [sessionKey, fallbackWeekendQuery.data, navigate]) // ─── Active session payload ─── const raceHubQuery = useQuery({ queryKey: ['race-hub', sessionKey], queryFn: () => fetchRaceHub(sessionKey), enabled: sessionKey > 0, staleTime: 30_000, }) const meetingKey = raceHubQuery.data?.meeting?.meeting_key const weekendQuery = useQuery({ queryKey: ['weekend', meetingKey], queryFn: () => fetchWeekend(meetingKey!), enabled: meetingKey != null && meetingKey > 0, staleTime: 60_000, }) const data = raceHubQuery.data const weekend = weekendQuery.data const accent = countryAccent(data?.meeting ?? null) const accentStyle = { '--gp-accent': accent } as React.CSSProperties // ─── No session_key: show resolving state, fall back to switcher if no local data ─── if (sessionKey === 0) { if (seasonsQuery.isLoading || meetingsQuery.isLoading || fallbackWeekendQuery.isLoading) { return (
resolving latest local weekend…
) } const seasons = seasonsQuery.data ?? [] if (seasons.length === 0) { return (
box-box · race hub

No local sessions yet

The Race Hub reads from local ingest only. Once a weekend is ingested it will open here automatically.

Open Admin · Data Health Back to Command Center
) } return (
resolving latest local weekend…
) } // ─── Loading / error for the requested session_key ─── if (raceHubQuery.isLoading) { return (
loading session {sessionKey}…
) } if (raceHubQuery.isError || !data) { return (
{raceHubQuery.error instanceof Error ? raceHubQuery.error.message : `Failed to load session ${sessionKey}.`}
) } const decal = countryDecal(data.meeting ?? null) const sessions = weekend ? sortSessionsByStart(weekend.sessions.map((w) => w.session)) : [] const sessionMeta = weekend ? Object.fromEntries(weekend.sessions.map((w) => [w.session.session_key, w])) : {} const activeSessionMeta = sessionMeta[sessionKey] return (
{/* Topbar */}
box-box · race hub {data.meeting?.year ? ` · ${data.meeting.year}` : ''}
{switcherOpen && ( setSwitcherOpen(false)} /> )} {/* GP Identity band */} {data.meeting && (
)} {/* Session rail */} {sessions.length > 0 && ( )} {/* Active session sub-bar */} {data.session && (
{data.session.session_name} {formatSessionScheduleTime(data.session.date_start)} {activeSessionMeta && ( )} key {sessionKey}
)} {activeTab === 'overview' && } {activeTab === 'race_story' && (
)} {activeTab === 'strategy' && (
Race Strategy
)} {activeTab === 'compare' && (
Driver Compare
)} {activeTab === 'lap_data' && (
Lap Data {data.laps.length > 0 && ( {data.laps.length} samples )}
)} {activeTab === 'conditions' && (
Conditions {data.weather.length > 0 && ( {data.weather.length} samples )}
)} {activeTab === 'race_control' && (
Race Control {data.race_control.length > 0 && ( {data.race_control.length} messages )}
)} {activeTab === 'data_status' && (
Data Status
)}
) }