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

183 lines
6.2 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import Card from '@/components/ui/Card';
import RaceCard from '@/components/races/RaceCard';
import { Race, RaceStatus } from '@gridpilot/racing/domain/entities/Race';
import { League } from '@gridpilot/racing/domain/entities/League';
import { getRaceRepository, getLeagueRepository } from '@/lib/di-container';
export default function RacesPage() {
const router = useRouter();
const [races, setRaces] = useState<Race[]>([]);
const [leagues, setLeagues] = useState<Map<string, League>>(new Map());
const [loading, setLoading] = useState(true);
// Filters
const [statusFilter, setStatusFilter] = useState<RaceStatus | 'all'>('all');
const [leagueFilter, setLeagueFilter] = useState<string>('all');
const [timeFilter, setTimeFilter] = useState<'all' | 'upcoming' | 'past'>('all');
const loadRaces = async () => {
try {
const raceRepo = getRaceRepository();
const leagueRepo = getLeagueRepository();
const [allRaces, allLeagues] = await Promise.all([
raceRepo.findAll(),
leagueRepo.findAll()
]);
setRaces(allRaces.sort((a, b) => b.scheduledAt.getTime() - a.scheduledAt.getTime()));
const leagueMap = new Map<string, League>();
allLeagues.forEach(league => leagueMap.set(league.id, league));
setLeagues(leagueMap);
} catch (err) {
console.error('Failed to load races:', err);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadRaces();
}, []);
const filteredRaces = races.filter(race => {
// Status filter
if (statusFilter !== 'all' && race.status !== statusFilter) {
return false;
}
// League filter
if (leagueFilter !== 'all' && race.leagueId !== leagueFilter) {
return false;
}
// Time filter
if (timeFilter === 'upcoming' && !race.isUpcoming()) {
return false;
}
if (timeFilter === 'past' && !race.isPast()) {
return false;
}
return true;
});
if (loading) {
return (
<div className="min-h-screen bg-deep-graphite py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-6xl mx-auto">
<div className="text-center text-gray-400">Loading races...</div>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-deep-graphite py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-6xl mx-auto">
{/* Header */}
<div className="mb-8">
<div className="flex items-center justify-between mb-4">
<h1 className="text-3xl font-bold text-white">Races</h1>
</div>
<p className="text-gray-400">
Manage and view all scheduled races across your leagues
</p>
</div>
{/* Filters */}
<Card className="mb-6">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{/* Time Filter */}
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Time
</label>
<select
value={timeFilter}
onChange={(e) => setTimeFilter(e.target.value as typeof timeFilter)}
className="w-full px-4 py-2 bg-deep-graphite border border-charcoal-outline rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-primary-blue"
>
<option value="all">All Races</option>
<option value="upcoming">Upcoming</option>
<option value="past">Past</option>
</select>
</div>
{/* Status Filter */}
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Status
</label>
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value as typeof statusFilter)}
className="w-full px-4 py-2 bg-deep-graphite border border-charcoal-outline rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-primary-blue"
>
<option value="all">All Statuses</option>
<option value="scheduled">Scheduled</option>
<option value="completed">Completed</option>
<option value="cancelled">Cancelled</option>
</select>
</div>
{/* League Filter */}
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
League
</label>
<select
value={leagueFilter}
onChange={(e) => setLeagueFilter(e.target.value)}
className="w-full px-4 py-2 bg-deep-graphite border border-charcoal-outline rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-primary-blue"
>
<option value="all">All Leagues</option>
{Array.from(leagues.values()).map(league => (
<option key={league.id} value={league.id}>
{league.name}
</option>
))}
</select>
</div>
</div>
</Card>
{/* Race List */}
{filteredRaces.length === 0 ? (
<Card className="text-center py-12">
<div className="text-gray-400 mb-4">
{races.length === 0 ? (
<>
<p className="mb-2">No races scheduled</p>
<p className="text-sm text-gray-500">Try the full workflow in alpha mode</p>
</>
) : (
<>
<p className="mb-2">No races match your filters</p>
<p className="text-sm text-gray-500">Try adjusting your filter criteria</p>
</>
)}
</div>
</Card>
) : (
<div className="space-y-4">
{filteredRaces.map(race => (
<RaceCard
key={race.id}
race={race}
leagueName={leagues.get(race.leagueId)?.name}
onClick={() => router.push(`/races/${race.id}`)}
/>
))}
</div>
)}
</div>
</div>
);
}