diff --git a/apps/website/app/drivers/[id]/page.tsx b/apps/website/app/drivers/[id]/page.tsx index 4dc85d13b..f3dae6479 100644 --- a/apps/website/app/drivers/[id]/page.tsx +++ b/apps/website/app/drivers/[id]/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, use } from 'react'; import Link from 'next/link'; import { useRouter, useParams } from 'next/navigation'; import { getDriverRepository } from '@/lib/di-container'; @@ -14,7 +14,7 @@ import type { DriverDTO } from '@gridpilot/racing/application/dto/DriverDTO'; export default function DriverDetailPage({ searchParams, }: { - searchParams?: { [key: string]: string | string[] | undefined }; + searchParams: any; }) { const router = useRouter(); const params = useParams(); @@ -24,14 +24,36 @@ export default function DriverDetailPage({ const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const unwrappedSearchParams = use(searchParams) as URLSearchParams | undefined; + const from = - typeof searchParams?.from === 'string' ? searchParams.from : undefined; - const leagueId = - typeof searchParams?.leagueId === 'string' - ? searchParams.leagueId + typeof unwrappedSearchParams?.get === 'function' + ? unwrappedSearchParams.get('from') ?? undefined : undefined; - const backLink = - from === 'league' && leagueId ? `/leagues/${leagueId}` : null; + + const leagueId = + typeof unwrappedSearchParams?.get === 'function' + ? unwrappedSearchParams.get('leagueId') ?? undefined + : undefined; + + const raceId = + typeof unwrappedSearchParams?.get === 'function' + ? unwrappedSearchParams.get('raceId') ?? undefined + : undefined; + + let backLink: string | null = null; + + if (from === 'league-standings' && leagueId) { + backLink = `/leagues/${leagueId}/standings`; + } else if (from === 'league' && leagueId) { + backLink = `/leagues/${leagueId}`; + } else if (from === 'league-members' && leagueId) { + backLink = `/leagues/${leagueId}`; + } else if (from === 'league-race' && leagueId && raceId) { + backLink = `/leagues/${leagueId}/races/${raceId}`; + } else { + backLink = null; + } useEffect(() => { loadDriver(); @@ -119,7 +141,7 @@ export default function DriverDetailPage({ /> {/* Driver Profile Component */} - + ); diff --git a/apps/website/app/drivers/page.tsx b/apps/website/app/drivers/page.tsx index 285d134c6..1796ef595 100644 --- a/apps/website/app/drivers/page.tsx +++ b/apps/website/app/drivers/page.tsx @@ -1,127 +1,101 @@ 'use client'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; -import Image from 'next/image'; import DriverCard from '@/components/drivers/DriverCard'; import RankBadge from '@/components/drivers/RankBadge'; import Input from '@/components/ui/Input'; import Card from '@/components/ui/Card'; -import { getDriverAvatarUrl } from '@/lib/racingLegacyFacade'; +import { getDriverRepository, getDriverStats, getAllDriverRankings } from '@/lib/di-container'; -// Mock data (fictional demo drivers only) -const MOCK_DRIVERS = [ - { - id: 'driver-1', - name: 'Alex Vermeer', - rating: 3245, - skillLevel: 'pro' as const, - nationality: 'Netherlands', - racesCompleted: 156, - wins: 45, - podiums: 89, - isActive: true, - rank: 1, - }, - { - id: 'driver-2', - name: 'Liam Hartmann', - rating: 3198, - skillLevel: 'pro' as const, - nationality: 'United Kingdom', - racesCompleted: 234, - wins: 78, - podiums: 145, - isActive: true, - rank: 2, - }, - { - id: 'driver-3', - name: 'Michael Schmidt', - rating: 2912, - skillLevel: 'advanced' as const, - nationality: 'Germany', - racesCompleted: 145, - wins: 34, - podiums: 67, - isActive: true, - rank: 3, - }, - { - id: 'driver-4', - name: 'Emma Thompson', - rating: 2789, - skillLevel: 'advanced' as const, - nationality: 'Australia', - racesCompleted: 112, - wins: 23, - podiums: 56, - isActive: true, - rank: 5, - }, - { - id: 'driver-5', - name: 'Sarah Chen', - rating: 2456, - skillLevel: 'advanced' as const, - nationality: 'Singapore', - racesCompleted: 89, - wins: 12, - podiums: 34, - isActive: true, - rank: 8, - }, - { - id: 'driver-6', - name: 'Isabella Rossi', - rating: 2145, - skillLevel: 'intermediate' as const, - nationality: 'Italy', - racesCompleted: 67, - wins: 8, - podiums: 23, - isActive: true, - rank: 12, - }, - { - id: 'driver-7', - name: 'Carlos Rodriguez', - rating: 1876, - skillLevel: 'intermediate' as const, - nationality: 'Spain', - racesCompleted: 45, - wins: 3, - podiums: 12, - isActive: false, - rank: 18, - }, - { - id: 'driver-8', - name: 'Yuki Tanaka', - rating: 1234, - skillLevel: 'beginner' as const, - nationality: 'Japan', - racesCompleted: 12, - wins: 0, - podiums: 2, - isActive: true, - rank: 45, - }, -]; +type SkillLevel = 'beginner' | 'intermediate' | 'advanced' | 'pro'; + +type DriverListItem = { + id: string; + name: string; + rating: number; + skillLevel: SkillLevel; + nationality: string; + racesCompleted: number; + wins: number; + podiums: number; + isActive: boolean; + rank: number; +}; export default function DriversPage() { const router = useRouter(); + const [drivers, setDrivers] = useState([]); + const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(''); - const [selectedSkill, setSelectedSkill] = useState('all'); + const [selectedSkill, setSelectedSkill] = useState<'all' | SkillLevel>('all'); const [selectedNationality, setSelectedNationality] = useState('all'); const [activeOnly, setActiveOnly] = useState(false); const [sortBy, setSortBy] = useState<'rank' | 'rating' | 'wins' | 'podiums'>('rank'); + useEffect(() => { + const load = async () => { + const driverRepo = getDriverRepository(); + const allDrivers = await driverRepo.findAll(); + const rankings = getAllDriverRankings(); + + const items: DriverListItem[] = allDrivers.map((driver) => { + const stats = getDriverStats(driver.id); + const rating = stats?.rating ?? 0; + const wins = stats?.wins ?? 0; + const podiums = stats?.podiums ?? 0; + const totalRaces = stats?.totalRaces ?? 0; + + let effectiveRank = Number.POSITIVE_INFINITY; + + if (typeof stats?.overallRank === 'number' && stats.overallRank > 0) { + effectiveRank = stats.overallRank; + } else { + const indexInGlobal = rankings.findIndex( + (entry) => entry.driverId === driver.id, + ); + if (indexInGlobal !== -1) { + effectiveRank = indexInGlobal + 1; + } + } + + const skillLevel: SkillLevel = + rating >= 3000 + ? 'pro' + : rating >= 2500 + ? 'advanced' + : rating >= 1800 + ? 'intermediate' + : 'beginner'; + + const isActive = rankings.some((r) => r.driverId === driver.id); + + return { + id: driver.id, + name: driver.name, + rating, + skillLevel, + nationality: driver.country, + racesCompleted: totalRaces, + wins, + podiums, + isActive, + rank: effectiveRank, + }; + }); + + setDrivers(items); + setLoading(false); + }; + + void load(); + }, []); + const nationalities = Array.from( - new Set(MOCK_DRIVERS.map((d) => d.nationality).filter(Boolean)) + new Set(drivers.map((d) => d.nationality).filter(Boolean)), ).sort(); - const filteredDrivers = MOCK_DRIVERS.filter((driver) => { + const filteredDrivers = drivers.filter((driver) => { const matchesSearch = driver.name .toLowerCase() .includes(searchQuery.toLowerCase()); @@ -135,9 +109,12 @@ export default function DriversPage() { }); const sortedDrivers = [...filteredDrivers].sort((a, b) => { + const rankA = Number.isFinite(a.rank) && a.rank > 0 ? a.rank : Number.POSITIVE_INFINITY; + const rankB = Number.isFinite(b.rank) && b.rank > 0 ? b.rank : Number.POSITIVE_INFINITY; + switch (sortBy) { case 'rank': - return a.rank - b.rank; + return rankA - rankB || b.rating - a.rating || a.name.localeCompare(b.name); case 'rating': return b.rating - a.rating; case 'wins': @@ -153,6 +130,14 @@ export default function DriversPage() { router.push(`/drivers/${driverId}`); }; + if (loading) { + return ( +
+
Loading drivers...
+
+ ); + } + return (
@@ -183,7 +168,7 @@ export default function DriversPage() {