import type { RaceHub } from '../types'
import { compareFinishPosition, formatDuration, formatGap, formatLapTime } from '../utils'
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 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}
))}
)}
)
}
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) => (
-
P{r.position}
{r.name_acronym || `#${r.driver_number}`}
{r.position === 1
? formatDuration(r.duration)
: formatGap(r.gap_to_leader)}
))}
)
}
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 : '') || '',
}
}