'use client'; import { useState, useEffect } from 'react'; import { useRouter, useParams } from 'next/navigation'; import Button from '@/components/ui/Button'; import Card from '@/components/ui/Card'; import Breadcrumbs from '@/components/layout/Breadcrumbs'; import ResultsTable from '@/components/races/ResultsTable'; import ImportResultsForm from '@/components/races/ImportResultsForm'; import { Race } from '@gridpilot/racing/domain/entities/Race'; import { League } from '@gridpilot/racing/domain/entities/League'; import { Result } from '@gridpilot/racing/domain/entities/Result'; import { Driver } from '@gridpilot/racing/domain/entities/Driver'; import type { PenaltyType } from '@gridpilot/racing/domain/entities/Penalty'; import { getRaceRepository, getLeagueRepository, getResultRepository, getStandingRepository, getDriverRepository, getGetRaceWithSOFQuery, getGetRacePenaltiesQuery, } from '@/lib/di-container'; interface PenaltyData { driverId: string; type: PenaltyType; value?: number; } import { ArrowLeft, Zap, Trophy, Users, Clock, Calendar } from 'lucide-react'; export default function RaceResultsPage() { const router = useRouter(); const params = useParams(); const raceId = params.id as string; const [race, setRace] = useState(null); const [league, setLeague] = useState(null); const [results, setResults] = useState([]); const [drivers, setDrivers] = useState([]); const [currentDriverId, setCurrentDriverId] = useState(undefined); const [raceSOF, setRaceSOF] = useState(null); const [penalties, setPenalties] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [importing, setImporting] = useState(false); const [importSuccess, setImportSuccess] = useState(false); const loadData = async () => { try { const raceRepo = getRaceRepository(); const leagueRepo = getLeagueRepository(); const resultRepo = getResultRepository(); const driverRepo = getDriverRepository(); const raceWithSOFQuery = getGetRaceWithSOFQuery(); const raceData = await raceRepo.findById(raceId); if (!raceData) { setError('Race not found'); setLoading(false); return; } setRace(raceData); // Load race with SOF from application query const raceWithSOF = await raceWithSOFQuery.execute({ raceId }); if (raceWithSOF) { setRaceSOF(raceWithSOF.strengthOfField); } // Load league data const leagueData = await leagueRepo.findById(raceData.leagueId); setLeague(leagueData); // Load results const resultsData = await resultRepo.findByRaceId(raceId); setResults(resultsData); // Load drivers const driversData = await driverRepo.findAll(); setDrivers(driversData); // Get current driver (first driver in demo mode) if (driversData.length > 0) { setCurrentDriverId(driversData[0].id); } // Load penalties for this race try { const penaltiesQuery = getGetRacePenaltiesQuery(); const penaltiesData = await penaltiesQuery.execute(raceId); // Map the DTO to the PenaltyData interface expected by ResultsTable setPenalties(penaltiesData.map(p => ({ driverId: p.driverId, type: p.type, value: p.value, }))); } catch (penaltyErr) { console.error('Failed to load penalties:', penaltyErr); // Don't fail the whole page if penalties fail to load } } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load race data'); } finally { setLoading(false); } }; useEffect(() => { loadData(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [raceId]); const handleImportSuccess = async (importedResults: Result[]) => { setImporting(true); setError(null); try { const resultRepo = getResultRepository(); const standingRepo = getStandingRepository(); // Check if results already exist const existingResults = await resultRepo.existsByRaceId(raceId); if (existingResults) { throw new Error('Results already exist for this race'); } // Create all results await resultRepo.createMany(importedResults); // Recalculate standings for the league if (league) { await standingRepo.recalculate(league.id); } // Reload results const resultsData = await resultRepo.findByRaceId(raceId); setResults(resultsData); setImportSuccess(true); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to import results'); } finally { setImporting(false); } }; const handleImportError = (errorMessage: string) => { setError(errorMessage); }; const getPointsSystem = (): Record => { if (!league) return {}; const pointsSystems: Record> = { 'f1-2024': { 1: 25, 2: 18, 3: 15, 4: 12, 5: 10, 6: 8, 7: 6, 8: 4, 9: 2, 10: 1 }, 'indycar': { 1: 50, 2: 40, 3: 35, 4: 32, 5: 30, 6: 28, 7: 26, 8: 24, 9: 22, 10: 20, 11: 19, 12: 18, 13: 17, 14: 16, 15: 15 } }; return league.settings.customPoints || pointsSystems[league.settings.pointsSystem] || pointsSystems['f1-2024']; }; const getFastestLapTime = (): number | undefined => { if (results.length === 0) return undefined; return Math.min(...results.map(r => r.fastestLap)); }; if (loading) { return (
Loading results...
); } if (error && !race) { return (
{error || 'Race not found'}
); } const hasResults = results.length > 0; const breadcrumbItems = [ { label: 'Races', href: '/races' }, ...(league ? [{ label: league.name, href: `/leagues/${league.id}` }] : []), ...(race ? [{ label: race.track, href: `/races/${raceId}` }] : []), { label: 'Results' }, ]; return (
{/* Navigation Row: Breadcrumbs left, Back button right */}
{/* Hero Header */}
Final Results
{raceSOF && ( SOF {raceSOF} )}

{race?.track ?? 'Race'} Results

{race && ( <> {new Date(race.scheduledAt).toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', })} {results.length} drivers classified )} {league && ( {league.name} )}
{/* Success Message */} {importSuccess && (
Success! Results imported and standings updated.
)} {/* Error Message */} {error && (
Error: {error}
)} {/* Content */} {hasResults ? ( ) : ( <>

Import Results

No results imported. Upload CSV to test the standings system.

{importing ? (
Importing results and updating standings...
) : ( )} )}
); }