import type { RaceHub } from '../types' import { compareFinishPosition, formatDuration, formatGap, formatLapTime } from '../utils' import { countRaceHubDatasets } from '../lib/coverage' import { Thermometer, Map, Droplets, Wind, CloudRain } from 'lucide-react' interface Props { data: RaceHub } export function OverviewView({ data }: Props) { const sortedResults = [...data.results].sort((a, b) => compareFinishPosition(a.position, b.position), ) const winner = sortedResults[0] const podium = sortedResults.filter((r) => r.position > 0).slice(0, 3) const pole = data.starting_grid.find((g) => g.position === 1) const fastest = pickFastestLap(data) const latestWeather = data.weather.length > 0 ? data.weather[data.weather.length - 1] : null const rcHighlights = data.race_control.slice(-3).reverse() const coverage = countRaceHubDatasets(data.datasets) const sessionType = (data.session?.session_type ?? '').toLowerCase() const isRace = sessionType.includes('race') const sessionLabel = isRace ? 'Race' : data.session?.session_type ?? 'Session' return (
{winner && winner.position > 0 ? ( ) : ( )} {pole ? ( ) : ( )} {fastest ? ( ) : ( )}
Conditions {latestWeather && ( {shortTime(latestWeather.date)} )}
{latestWeather ? (
0 ? 'Yes' : 'No'} accent={latestWeather.rainfall > 0 ? 'wet' : undefined} />
) : (
No weather samples ingested.
)}
Race Control · Latest {data.race_control.length}
{rcHighlights.length === 0 ? (
No race-control messages.
) : (
    {rcHighlights.map((m, i) => (
  • {shortTime(m.date)} {m.flag || m.category || '—'} {m.message}
  • ))}
)}
Local Coverage {coverage.available}/{coverage.total}
) } interface StatCardProps { label: string primary?: string primaryColor?: string secondary?: string tertiary?: string highlight?: string placeholder?: boolean } function StatCard({ label, primary, primaryColor, secondary, tertiary, highlight, placeholder, }: StatCardProps) { if (placeholder) { return (
{label}
No data ingested
) } return (
{label}
{primary}
{secondary &&
{secondary}
} {tertiary &&
{tertiary}
} {highlight &&
{highlight}
}
) } function PodiumCard({ podium }: { podium: Array<{ name_acronym: string; team_colour: string; position: number; full_name: string; gap_to_leader: number | string | number[] | null; duration: number | number[] | null; driver_number: number }> }) { if (podium.length === 0) { return (
Podium
No classified finishers
) } return (
Podium
    {podium.map((r) => (
  1. P{r.position} {r.name_acronym || `#${r.driver_number}`} {r.position === 1 ? formatDuration(r.duration) : formatGap(r.gap_to_leader)}
  2. ))}
) } function ConditionChip({ label, value, accent, icon: Icon, }: { label: string value: string accent?: 'wet' icon?: React.ElementType }) { return (
{Icon && } {label} {value}
) } function shortTime(iso: string): string { if (!iso) return '—' const d = new Date(iso) if (Number.isNaN(d.getTime())) return iso.slice(11, 16) return d.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', hour12: false }) } function pickFastestLap( data: RaceHub, ): { lap: number; time: number; acronym: string; fullName: string; colour: string } | null { const candidates = data.laps.filter( (l) => l.lap_duration != null && l.lap_duration > 0 && !l.is_pit_out_lap, ) if (candidates.length === 0) return null let best = candidates[0] for (const lap of candidates) { if ((lap.lap_duration ?? 0) < (best.lap_duration ?? Infinity)) { best = lap } } const driverInfo = data.results.find((r) => r.driver_number === best.driver_number) ?? data.drivers.find((d) => d.driver_number === best.driver_number) return { lap: best.lap_number, time: best.lap_duration ?? 0, acronym: ('name_acronym' in (driverInfo ?? {}) ? (driverInfo as { name_acronym: string }).name_acronym : '') || `#${best.driver_number}`, fullName: ('full_name' in (driverInfo ?? {}) ? (driverInfo as { full_name: string }).full_name : '') || '', colour: ('team_colour' in (driverInfo ?? {}) ? (driverInfo as { team_colour: string }).team_colour : '') || '', } }