wip
This commit is contained in:
@@ -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<string | null>(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 */}
|
||||
<DriverProfile driver={driver} />
|
||||
<DriverProfile driver={driver} isOwnProfile={false} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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<DriverListItem[]>([]);
|
||||
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 (
|
||||
<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">
|
||||
@@ -183,7 +168,7 @@ export default function DriversPage() {
|
||||
<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)}
|
||||
onChange={(e) => setSelectedSkill(e.target.value as SkillLevel | 'all')}
|
||||
>
|
||||
<option value="all">All Levels</option>
|
||||
<option value="beginner">Beginner</option>
|
||||
@@ -251,56 +236,20 @@ export default function DriversPage() {
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{sortedDrivers.map((driver, index) => (
|
||||
<Card
|
||||
{sortedDrivers.map((driver) => (
|
||||
<DriverCard
|
||||
key={driver.id}
|
||||
className="hover:border-charcoal-outline/60 transition-colors cursor-pointer"
|
||||
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 className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4 flex-1">
|
||||
<RankBadge rank={driver.rank} size="lg" />
|
||||
|
||||
<div className="w-16 h-16 rounded-full bg-primary-blue/20 overflow-hidden flex items-center justify-center">
|
||||
<Image
|
||||
src={getDriverAvatarUrl(driver.id)}
|
||||
alt={driver.name}
|
||||
width={64}
|
||||
height={64}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<h3 className="text-xl font-semibold text-white mb-1">{driver.name}</h3>
|
||||
<p className="text-sm text-gray-400">
|
||||
{driver.nationality} • {driver.racesCompleted} races
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-8 text-center">
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-primary-blue">{driver.rating}</div>
|
||||
<div className="text-xs text-gray-400">Rating</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-green-400">{driver.wins}</div>
|
||||
<div className="text-xs text-gray-400">Wins</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-warning-amber">{driver.podiums}</div>
|
||||
<div className="text-xs text-gray-400">Podiums</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm text-gray-400">
|
||||
{((driver.wins / driver.racesCompleted) * 100).toFixed(0)}%
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">Win Rate</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user