This commit is contained in:
2025-12-11 00:57:32 +01:00
parent 1303a14493
commit 6a427eab57
112 changed files with 6148 additions and 2272 deletions

View File

@@ -7,9 +7,11 @@ import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import Heading from '@/components/ui/Heading';
import Breadcrumbs from '@/components/layout/Breadcrumbs';
import { Race, RaceStatus } from '@gridpilot/racing/domain/entities/Race';
import { League } from '@gridpilot/racing/domain/entities/League';
import { getRaceRepository, getLeagueRepository } from '@/lib/di-container';
import { getGetAllRacesPageDataUseCase } from '@/lib/di-container';
import type {
AllRacesPageViewModel,
AllRacesListItemViewModel,
} from '@gridpilot/racing/application/presenters/IAllRacesPagePresenter';
import {
Calendar,
Clock,
@@ -30,38 +32,30 @@ import {
const ITEMS_PER_PAGE = 10;
type StatusFilter = 'scheduled' | 'running' | 'completed' | 'cancelled' | 'all';
export default function AllRacesPage() {
const router = useRouter();
const searchParams = useSearchParams();
const [races, setRaces] = useState<Race[]>([]);
const [leagues, setLeagues] = useState<Map<string, League>>(new Map());
const [pageData, setPageData] = useState<AllRacesPageViewModel | null>(null);
const [loading, setLoading] = useState(true);
// Pagination
const [currentPage, setCurrentPage] = useState(1);
// Filters
const [statusFilter, setStatusFilter] = useState<RaceStatus | 'all'>('all');
const [statusFilter, setStatusFilter] = useState<StatusFilter>('all');
const [leagueFilter, setLeagueFilter] = useState<string>('all');
const [searchQuery, setSearchQuery] = useState('');
const [showFilters, setShowFilters] = useState(false);
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);
const useCase = getGetAllRacesPageDataUseCase();
await useCase.execute();
const viewModel = useCase.presenter.getViewModel();
setPageData(viewModel);
} catch (err) {
console.error('Failed to load races:', err);
} finally {
@@ -70,29 +64,26 @@ export default function AllRacesPage() {
};
useEffect(() => {
loadRaces();
void loadRaces();
}, []);
// Filter races
const races: AllRacesListItemViewModel[] = pageData?.races ?? [];
const filteredRaces = useMemo(() => {
return races.filter(race => {
// Status filter
if (statusFilter !== 'all' && race.status !== statusFilter) {
return false;
}
// League filter
if (leagueFilter !== 'all' && race.leagueId !== leagueFilter) {
return false;
}
// Search filter
if (searchQuery) {
const query = searchQuery.toLowerCase();
const league = leagues.get(race.leagueId);
const matchesTrack = race.track.toLowerCase().includes(query);
const matchesCar = race.car.toLowerCase().includes(query);
const matchesLeague = league?.name.toLowerCase().includes(query);
const matchesLeague = race.leagueName.toLowerCase().includes(query);
if (!matchesTrack && !matchesCar && !matchesLeague) {
return false;
}
@@ -100,7 +91,7 @@ export default function AllRacesPage() {
return true;
});
}, [races, statusFilter, leagueFilter, searchQuery, leagues]);
}, [races, statusFilter, leagueFilter, searchQuery]);
// Paginate
const totalPages = Math.ceil(filteredRaces.length / ITEMS_PER_PAGE);
@@ -232,7 +223,7 @@ export default function AllRacesPage() {
{/* Status Filter */}
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value as RaceStatus | 'all')}
onChange={(e) => setStatusFilter(e.target.value as StatusFilter)}
className="px-4 py-2 bg-deep-graphite border border-charcoal-outline rounded-lg text-white text-sm focus:outline-none focus:ring-2 focus:ring-primary-blue"
>
<option value="all">All Statuses</option>
@@ -249,7 +240,7 @@ export default function AllRacesPage() {
className="px-4 py-2 bg-deep-graphite border border-charcoal-outline rounded-lg text-white text-sm focus:outline-none focus:ring-2 focus:ring-primary-blue"
>
<option value="all">All Leagues</option>
{Array.from(leagues.values()).map(league => (
{pageData?.filters.leagues.map((league) => (
<option key={league.id} value={league.id}>
{league.name}
</option>
@@ -295,7 +286,6 @@ export default function AllRacesPage() {
{paginatedRaces.map(race => {
const config = statusConfig[race.status];
const StatusIcon = config.icon;
const league = leagues.get(race.leagueId);
return (
<div
@@ -347,16 +337,14 @@ export default function AllRacesPage() {
{formatDate(race.scheduledAt)}
</span>
</div>
{league && (
<Link
href={`/leagues/${league.id}`}
onClick={(e) => e.stopPropagation()}
className="inline-flex items-center gap-1.5 mt-2 text-sm text-primary-blue hover:underline"
>
<Trophy className="w-3.5 h-3.5" />
{league.name}
</Link>
)}
<Link
href={`/leagues/${race.leagueId}`}
onClick={(e) => e.stopPropagation()}
className="inline-flex items-center gap-1.5 mt-2 text-sm text-primary-blue hover:underline"
>
<Trophy className="w-3.5 h-3.5" />
{race.leagueName}
</Link>
</div>
{/* Status Badge */}