Files
gridpilot.gg/apps/website/templates/RacesAllTemplate.tsx
2026-01-14 23:31:57 +01:00

226 lines
6.6 KiB
TypeScript

'use client';
import React, { useMemo, useEffect } from 'react';
import { Card } from '@/ui/Card';
import { Button } from '@/ui/Button';
import { Heading } from '@/ui/Heading';
import Breadcrumbs from '@/components/layout/Breadcrumbs';
import {
Flag,
SlidersHorizontal,
Calendar,
} from 'lucide-react';
import { RaceFilterModal } from '@/components/races/RaceFilterModal';
import { RacePagination } from '@/components/races/RacePagination';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Container } from '@/ui/Container';
import { Icon } from '@/ui/Icon';
import { Surface } from '@/ui/Surface';
import { Skeleton } from '@/ui/Skeleton';
import { RaceListItem } from '@/components/races/RaceListItem';
import type { RacesViewData } from '@/lib/view-data/RacesViewData';
export type StatusFilter = 'scheduled' | 'running' | 'completed' | 'cancelled' | 'all';
interface RacesAllTemplateProps {
viewData: RacesViewData;
isLoading: boolean;
// Pagination
currentPage: number;
totalPages: number;
itemsPerPage: number;
onPageChange: (page: number) => void;
// Filters
statusFilter: StatusFilter;
setStatusFilter: (filter: StatusFilter) => void;
leagueFilter: string;
setLeagueFilter: (filter: string) => void;
searchQuery: string;
setSearchQuery: (query: string) => void;
// UI State
showFilters: boolean;
setShowFilters: (show: boolean) => void;
showFilterModal: boolean;
setShowFilterModal: (show: boolean) => void;
// Actions
onRaceClick: (raceId: string) => void;
onLeagueClick: (leagueId: string) => void;
}
export function RacesAllTemplate({
viewData,
isLoading,
currentPage,
totalPages,
itemsPerPage,
onPageChange,
statusFilter,
setStatusFilter,
leagueFilter,
setLeagueFilter,
searchQuery,
setSearchQuery,
showFilters,
setShowFilters,
showFilterModal,
setShowFilterModal,
onRaceClick,
}: RacesAllTemplateProps) {
const { races } = viewData;
// Filter races
const filteredRaces = useMemo(() => {
return races.filter(race => {
if (statusFilter !== 'all' && race.status !== statusFilter) {
return false;
}
if (leagueFilter !== 'all' && race.leagueId !== leagueFilter) {
return false;
}
if (searchQuery) {
const query = searchQuery.toLowerCase();
const matchesTrack = race.track.toLowerCase().includes(query);
const matchesCar = race.car.toLowerCase().includes(query);
const matchesLeague = race.leagueName?.toLowerCase().includes(query);
if (!matchesTrack && !matchesCar && !matchesLeague) {
return false;
}
}
return true;
});
}, [races, statusFilter, leagueFilter, searchQuery]);
// Paginate
const paginatedRaces = useMemo(() => {
const start = (currentPage - 1) * itemsPerPage;
return filteredRaces.slice(start, start + itemsPerPage);
}, [filteredRaces, currentPage, itemsPerPage]);
// Reset page when filters change
useEffect(() => {
onPageChange(1);
}, [statusFilter, leagueFilter, searchQuery]);
const breadcrumbItems = [
{ label: 'Races', href: '/races' },
{ label: 'All Races' },
];
if (isLoading) {
return (
<Container size="lg" py={8}>
<Stack gap={6}>
<Skeleton width="8rem" height="1.5rem" />
<Skeleton width="12rem" height="2.5rem" />
<Stack gap={4}>
{[1, 2, 3, 4, 5].map(i => (
<Skeleton key={i} width="100%" height="6rem" />
))}
</Stack>
</Stack>
</Container>
);
}
return (
<Container size="lg" py={8}>
<Stack gap={6}>
{/* Breadcrumbs */}
<Breadcrumbs items={breadcrumbItems} />
{/* Header */}
<Stack direction="row" align="center" justify="between" wrap gap={4}>
<Box>
<Heading level={1} icon={<Icon icon={Flag} size={6} color="#3b82f6" />}>
All Races
</Heading>
<Text size="sm" color="text-gray-400" block mt={1}>
{filteredRaces.length} race{filteredRaces.length !== 1 ? 's' : ''} found
</Text>
</Box>
<Button
variant="secondary"
onClick={() => setShowFilters(!showFilters)}
icon={<Icon icon={SlidersHorizontal} size={4} />}
>
Filters
</Button>
</Stack>
{/* Search & Filters (Simplified for template) */}
{showFilters && (
<Card>
<Stack gap={4}>
<Text size="sm" color="text-gray-400">
Use the filter button to open advanced search and filtering options.
</Text>
<Box>
<Button variant="primary" onClick={() => setShowFilterModal(true)}>
Open Filters
</Button>
</Box>
</Stack>
</Card>
)}
{/* Race List */}
{paginatedRaces.length === 0 ? (
<Card>
<Stack align="center" py={12} gap={4}>
<Surface variant="muted" rounded="full" padding={4}>
<Icon icon={Calendar} size={8} color="#525252" />
</Surface>
<Box style={{ textAlign: 'center' }}>
<Text weight="medium" color="text-white" block mb={1}>No races found</Text>
<Text size="sm" color="text-gray-500">
{races.length === 0
? 'No races have been scheduled yet'
: 'Try adjusting your search or filters'}
</Text>
</Box>
</Stack>
</Card>
) : (
<Stack gap={3}>
{paginatedRaces.map(race => (
<RaceListItem key={race.id} race={race as any} onClick={onRaceClick} />
))}
</Stack>
)}
{/* Pagination */}
<RacePagination
currentPage={currentPage}
totalPages={totalPages}
totalItems={filteredRaces.length}
itemsPerPage={itemsPerPage}
onPageChange={onPageChange}
/>
{/* Filter Modal */}
<RaceFilterModal
isOpen={showFilterModal}
onClose={() => setShowFilterModal(false)}
statusFilter={statusFilter}
setStatusFilter={setStatusFilter}
leagueFilter={leagueFilter}
setLeagueFilter={setLeagueFilter}
timeFilter="all"
setTimeFilter={() => {}}
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
leagues={viewData.leagues}
showSearch={true}
showTimeFilter={false}
/>
</Stack>
</Container>
);
}