Files
gridpilot.gg/apps/website/app/drivers/page.tsx
2025-12-04 23:31:55 +01:00

263 lines
8.7 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
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 { getDriverRepository, getDriverStats, getAllDriverRankings } from '@/lib/di-container';
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<DriverListItem[]>([]);
const [loading, setLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
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(drivers.map((d) => d.nationality).filter(Boolean)),
).sort();
const filteredDrivers = drivers.filter((driver) => {
const matchesSearch = driver.name
.toLowerCase()
.includes(searchQuery.toLowerCase());
const matchesSkill =
selectedSkill === 'all' || driver.skillLevel === selectedSkill;
const matchesNationality =
selectedNationality === 'all' || driver.nationality === selectedNationality;
const matchesActive = !activeOnly || driver.isActive;
return matchesSearch && matchesSkill && matchesNationality && matchesActive;
});
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 rankA - rankB || b.rating - a.rating || a.name.localeCompare(b.name);
case 'rating':
return b.rating - a.rating;
case 'wins':
return b.wins - a.wins;
case 'podiums':
return b.podiums - a.podiums;
default:
return 0;
}
});
const handleDriverClick = (driverId: string) => {
router.push(`/drivers/${driverId}`);
};
if (loading) {
return (
<div className="max-w-6xl mx-auto">
<div className="text-center text-gray-400">Loading drivers...</div>
</div>
);
}
return (
<div className="max-w-6xl mx-auto">
<div className="mb-8">
<h1 className="text-3xl font-bold text-white mb-2">Drivers</h1>
<p className="text-gray-400">
Browse driver profiles and stats
</p>
</div>
<Card className="mb-8">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4 mb-4">
<div>
<label className="block text-sm font-medium text-gray-400 mb-2">
Search Drivers
</label>
<Input
type="text"
placeholder="Search by name..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-400 mb-2">
Skill Level
</label>
<select
className="w-full px-3 py-3 bg-iron-gray border-0 rounded-md text-white ring-1 ring-inset ring-charcoal-outline focus:ring-2 focus:ring-primary-blue transition-all duration-150 text-sm"
value={selectedSkill}
onChange={(e) => setSelectedSkill(e.target.value as SkillLevel | 'all')}
>
<option value="all">All Levels</option>
<option value="beginner">Beginner</option>
<option value="intermediate">Intermediate</option>
<option value="advanced">Advanced</option>
<option value="pro">Pro</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-400 mb-2">
Nationality
</label>
<select
className="w-full px-3 py-3 bg-iron-gray border-0 rounded-md text-white ring-1 ring-inset ring-charcoal-outline focus:ring-2 focus:ring-primary-blue transition-all duration-150 text-sm"
value={selectedNationality}
onChange={(e) => setSelectedNationality(e.target.value)}
>
<option value="all">All Countries</option>
{nationalities.map((nat) => (
<option key={nat} value={nat}>
{nat}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-400 mb-2">
Status
</label>
<label className="flex items-center pt-3">
<input
type="checkbox"
className="w-4 h-4 text-primary-blue bg-iron-gray border-charcoal-outline rounded focus:ring-primary-blue focus:ring-2"
checked={activeOnly}
onChange={(e) => setActiveOnly(e.target.checked)}
/>
<span className="ml-2 text-sm text-gray-400">Active only</span>
</label>
</div>
<div>
<label className="block text-sm font-medium text-gray-400 mb-2">
Sort By
</label>
<select
className="w-full px-3 py-3 bg-iron-gray border-0 rounded-md text-white ring-1 ring-inset ring-charcoal-outline focus:ring-2 focus:ring-primary-blue transition-all duration-150 text-sm"
value={sortBy}
onChange={(e) => setSortBy(e.target.value as any)}
>
<option value="rank">Overall Rank</option>
<option value="rating">Rating</option>
<option value="wins">Wins</option>
<option value="podiums">Podiums</option>
</select>
</div>
</div>
</Card>
<div className="mb-4 flex items-center justify-between">
<p className="text-sm text-gray-400">
{sortedDrivers.length} {sortedDrivers.length === 1 ? 'driver' : 'drivers'} found
</p>
</div>
<div className="space-y-4">
{sortedDrivers.map((driver) => (
<DriverCard
key={driver.id}
id={driver.id}
name={driver.name}
rating={driver.rating}
skillLevel={driver.skillLevel}
nationality={driver.nationality}
racesCompleted={driver.racesCompleted}
wins={driver.wins}
podiums={driver.podiums}
rank={driver.rank}
onClick={() => handleDriverClick(driver.id)}
/>
))}
</div>
{sortedDrivers.length === 0 && (
<div className="text-center py-12">
<p className="text-gray-400">No drivers found matching your filters.</p>
</div>
)}
</div>
);
}