import { useEffect, useState } from 'react' import { useQuery } from '@tanstack/react-query' import { useNavigate } from '@tanstack/react-router' import { fetchLocalMeetings, fetchSeasons, fetchWeekend } from '../api' import type { DatasetInfo, Meeting, WeekendSession } from '../types' const RACE_HUB_DATASETS = [ 'meeting', 'session', 'drivers', 'results', 'starting_grid', 'stints', 'pit_stops', 'positions', 'race_control', 'weather', 'laps', ] as const export function countRaceHubDatasets(datasets: Record): { available: number; total: number } { const total = RACE_HUB_DATASETS.length const available = RACE_HUB_DATASETS.filter((key) => datasets[key]?.status === 'available').length return { available, total } } export function formatCoverageHint(datasets: Record): string { const { available, total } = countRaceHubDatasets(datasets) return `${available}/${total}` } function sourceBadge(source: WeekendSession['source']) { switch (source) { case 'local': return Local case 'partial': return Partial default: return None } } function formatMeetingDates(meeting: Meeting): string { const start = meeting.date_start?.slice(0, 10) const end = meeting.date_end?.slice(0, 10) if (start && end && start !== end) return `${start} – ${end}` return start || end || '—' } interface Props { onSelectSession?: (sessionKey: number) => void } export function LocalDataNavigator({ onSelectSession }: Props) { const navigate = useNavigate() const [selectedYear, setSelectedYear] = useState(null) const [selectedMeetingKey, setSelectedMeetingKey] = useState(null) const seasonsQuery = useQuery({ queryKey: ['seasons'], queryFn: fetchSeasons, }) const meetingsQuery = useQuery({ queryKey: ['meetings', selectedYear], queryFn: () => fetchLocalMeetings(selectedYear!), enabled: selectedYear != null, }) const weekendQuery = useQuery({ queryKey: ['weekend', selectedMeetingKey], queryFn: () => fetchWeekend(selectedMeetingKey!), enabled: selectedMeetingKey != null, }) useEffect(() => { if (seasonsQuery.data?.length && selectedYear == null) { setSelectedYear(seasonsQuery.data[0]) } }, [seasonsQuery.data, selectedYear]) function handleSelectSession(sessionKey: number) { if (onSelectSession) { onSelectSession(sessionKey) return } navigate({ to: '/race-hub', search: { session_key: sessionKey } }) } function handleSelectYear(year: number) { setSelectedYear(year) setSelectedMeetingKey(null) } function handleSelectMeeting(meetingKey: number) { setSelectedMeetingKey((prev) => (prev === meetingKey ? null : meetingKey)) } if (seasonsQuery.isLoading) { return
loading local seasons…
} if (seasonsQuery.isError) { return (
{seasonsQuery.error instanceof Error ? seasonsQuery.error.message : 'Failed to load seasons'}
) } const seasons = seasonsQuery.data ?? [] if (seasons.length === 0) { return (
Local Data
No ingested seasons yet
Ingest a session with box-box --ingest-session <key>, then browse here or enter a session key below.
) } const meetings = meetingsQuery.data ?? [] const weekend = weekendQuery.data return (
Local Data
{seasons.map((year) => ( ))}
{meetingsQuery.isLoading && (
loading meetings…
)} {meetingsQuery.isError && (
{meetingsQuery.error instanceof Error ? meetingsQuery.error.message : 'Failed to load meetings'}
)} {!meetingsQuery.isLoading && !meetingsQuery.isError && meetings.length === 0 && (
No meetings ingested for {selectedYear}.
)} {meetings.length > 0 && (
Meetings {meetings.length}
{meetings.map((meeting) => { const selected = meeting.meeting_key === selectedMeetingKey return ( ) })}
Grand Prix Country Dates Open
{meeting.meeting_name} {meeting.circuit_short_name && meeting.circuit_short_name !== meeting.meeting_name && ( {meeting.circuit_short_name} )} {meeting.country_code || meeting.country_name} {formatMeetingDates(meeting)}
)} {selectedMeetingKey != null && weekendQuery.isLoading && (
loading sessions…
)} {selectedMeetingKey != null && weekendQuery.isError && (
{weekendQuery.error instanceof Error ? weekendQuery.error.message : 'Failed to load weekend'}
)} {weekend && (
{weekend.meeting.meeting_name} Sessions {weekend.sessions.length}
{weekend.sessions.length === 0 ? (
No sessions stored for this meeting.
) : (
{weekend.sessions.map(({ session, source, datasets }) => { const coverage = formatCoverageHint(datasets) const isDefault = session.session_key === weekend.default_session_key return ( ) })}
Session Type Coverage Source Open
{session.session_name} {isDefault && default} {session.session_key} {session.session_type} {coverage} {sourceBadge(source)}
)}
)}
) } function SessionCoverageDots({ datasets }: { datasets: Record }) { return (