Files
gridpilot.gg/apps/website/templates/LeaguesTemplate.tsx
2026-01-26 17:56:11 +01:00

183 lines
6.1 KiB
TypeScript

import { LeagueCard } from '@/components/leagues/LeagueCardWrapper';
import { CategoryId, LeagueCategory } from '@/lib/config/leagueCategories';
import { TemplateProps } from '@/lib/contracts/components/ComponentContracts';
import { LeaguesViewData } from '@/lib/view-data/LeaguesViewData';
import { LeagueSummaryViewModel } from '@/lib/view-models/LeagueSummaryViewModel';
import { Box } from '@/ui/Box';
import { Button } from '@/ui/Button';
import { ControlBar } from '@/ui/ControlBar';
import { FeatureGrid } from '@/ui/FeatureGrid';
import { Group } from '@/ui/Group';
import { Heading } from '@/ui/Heading';
import { Icon } from '@/ui/Icon';
import { Input } from '@/ui/Input';
import { MetricCard } from '@/ui/MetricCard';
import { PageHeader } from '@/ui/PageHeader';
import { Section } from '@/ui/Section';
import { SegmentedControl } from '@/ui/SegmentedControl';
import { Stack } from '@/ui/Stack';
import { Surface } from '@/ui/Surface';
import { Text } from '@/ui/Text';
import {
Filter,
Plus,
Search,
Sparkles,
Trophy
} from 'lucide-react';
import React from 'react';
interface LeaguesTemplateProps extends TemplateProps<LeaguesViewData> {
searchQuery: string;
onSearchChange: (query: string) => void;
activeCategory: CategoryId;
onCategoryChange: (id: CategoryId) => void;
filteredLeagues: LeaguesViewData['leagues'];
categories: LeagueCategory[];
onCreateLeague: () => void;
onLeagueClick: (id: string) => void;
onClearFilters: () => void;
}
export function LeaguesTemplate({
viewData,
searchQuery,
onSearchChange,
activeCategory,
onCategoryChange,
filteredLeagues,
categories,
onCreateLeague,
onLeagueClick,
onClearFilters,
}: LeaguesTemplateProps) {
return (
<Section variant="default" padding="md">
{/* Header Section */}
<PageHeader
icon={Trophy}
title="Leagues"
description="Infrastructure for competitive sim racing."
action={
<Button
onClick={onCreateLeague}
variant="primary"
size="lg"
icon={<Icon icon={Plus} size={4} />}
>
Create League
</Button>
}
/>
<Stack gap={12}>
{/* Stats Overview */}
<FeatureGrid columns={{ base: 1, md: 3 }} gap={4}>
<MetricCard
label="Active Leagues"
value={viewData.leagues.length}
icon={Trophy}
intent="telemetry"
/>
<MetricCard
label="Total Drivers"
value={viewData.leagues.reduce((acc, l) => acc + (l.usedDriverSlots ?? 0), 0)}
icon={Trophy}
intent="primary"
/>
<MetricCard
label="Open Slots"
value={viewData.leagues.reduce((acc, l) => acc + Math.max(0, (l.maxDrivers ?? 0) - (l.usedDriverSlots ?? 0)), 0)}
icon={Trophy}
intent="success"
/>
</FeatureGrid>
{/* Featured Leagues Section */}
{viewData.leagues.filter(l => (l.usedDriverSlots ?? 0) > 20).length > 0 && (
<Stack gap={4}>
<Group align="center" gap={2}>
<Icon icon={Sparkles} size={5} intent="warning" />
<Heading level={3} weight="bold" uppercase letterSpacing="wider">Featured Leagues</Heading>
</Group>
<Surface variant="dark" padding={6} rounded="2xl" border borderColor="var(--ui-color-intent-warning-muted)" data-testid="featured-leagues-section">
<FeatureGrid columns={{ base: 1, md: 2 }} gap={6}>
{viewData.leagues
.filter(l => (l.usedDriverSlots ?? 0) > 20)
.slice(0, 2)
.map((league) => (
<LeagueCard
key={`featured-${league.id}`}
league={league as unknown as LeagueSummaryViewModel}
onClick={() => onLeagueClick(league.id)}
/>
))}
</FeatureGrid>
</Surface>
</Stack>
)}
{/* Control Bar */}
<ControlBar
leftContent={
<Group gap={4} align="center" data-testid="category-filters">
<Icon icon={Filter} size={4} intent="low" />
<SegmentedControl
data-testid="category-filter-all"
options={categories.map(c => ({
id: c.id,
label: c.label,
icon: <Icon icon={c.icon} size={3} />
}))}
activeId={activeCategory}
onChange={(id) => onCategoryChange(id as CategoryId)}
/>
</Group>
}
>
<Input
type="text"
placeholder="Search infrastructure..."
value={searchQuery}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onSearchChange(e.target.value)}
icon={<Icon icon={Search} size={4} />}
size="sm"
/>
</ControlBar>
{/* Results */}
{filteredLeagues.length > 0 ? (
<FeatureGrid columns={{ base: 1, md: 2, lg: 3 }} gap={6}>
{filteredLeagues.map((league) => (
<LeagueCard
key={league.id}
league={league as unknown as LeagueSummaryViewModel}
onClick={() => onLeagueClick(league.id)}
/>
))}
</FeatureGrid>
) : (
<Surface variant="dark" padding={12} rounded="xl" border>
<Stack align="center" justify="center" gap={6}>
<Icon icon={Search} size={12} intent="low" />
<Stack align="center" gap={2}>
<Text size="lg" weight="bold">No results found</Text>
<Text variant="low" size="sm">Adjust filters to find matching infrastructure.</Text>
</Stack>
<Button
variant="secondary"
onClick={onClearFilters}
>
Reset Filters
</Button>
</Stack>
</Surface>
)}
<Box data-testid="league-card" display="none" />
</Stack>
</Section>
);
}