website refactor
This commit is contained in:
@@ -3,9 +3,11 @@
|
||||
import { DriversViewData } from '@/lib/types/view-data/DriversViewData';
|
||||
import { DriverCard } from '@/components/drivers/DriverCard';
|
||||
import { DriverStatsHeader } from '@/components/drivers/DriverStatsHeader';
|
||||
import { DriverGrid } from '@/components/drivers/DriverGrid';
|
||||
import { PageHeader } from '@/ui/PageHeader';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Search, Users } from 'lucide-react';
|
||||
import { EmptyState } from '@/ui/EmptyState';
|
||||
|
||||
@@ -28,32 +30,29 @@ export function DriversTemplate({
|
||||
}: DriversTemplateProps) {
|
||||
return (
|
||||
<main>
|
||||
<Box marginBottom={8}>
|
||||
<PageHeader
|
||||
icon={Users}
|
||||
title="Drivers"
|
||||
description="Global driver roster and statistics."
|
||||
action={
|
||||
<Box
|
||||
as="button"
|
||||
onClick={onViewLeaderboard}
|
||||
className="px-4 py-2 rounded-md bg-[var(--ui-color-bg-surface)] border border-[var(--ui-color-border-default)] text-sm font-medium hover:bg-[var(--ui-color-bg-surface-muted)] transition-colors"
|
||||
>
|
||||
Leaderboard
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
<PageHeader
|
||||
icon={Users}
|
||||
title="Drivers"
|
||||
description="Global driver roster and statistics."
|
||||
action={
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onViewLeaderboard}
|
||||
>
|
||||
Leaderboard
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
<Box marginBottom={8}>
|
||||
<Container size="full" padding="none" py={8}>
|
||||
<DriverStatsHeader
|
||||
totalDrivers={viewData.totalDriversLabel}
|
||||
activeDrivers={viewData.activeCountLabel}
|
||||
totalRaces={viewData.totalRacesLabel}
|
||||
/>
|
||||
</Box>
|
||||
</Container>
|
||||
|
||||
<Box marginBottom={6} className="w-full">
|
||||
<Container size="full" padding="none" py={6}>
|
||||
<Input
|
||||
placeholder="Search drivers by name or nationality..."
|
||||
value={searchQuery}
|
||||
@@ -61,10 +60,10 @@ export function DriversTemplate({
|
||||
icon={Search}
|
||||
variant="search"
|
||||
/>
|
||||
</Box>
|
||||
</Container>
|
||||
|
||||
{filteredDrivers.length > 0 ? (
|
||||
<Box display="grid" gap={4} className="grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
<DriverGrid>
|
||||
{filteredDrivers.map(driver => (
|
||||
<DriverCard
|
||||
key={driver.id}
|
||||
@@ -72,7 +71,7 @@ export function DriversTemplate({
|
||||
onClick={onDriverClick}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</DriverGrid>
|
||||
) : (
|
||||
<EmptyState
|
||||
title="No drivers found"
|
||||
|
||||
@@ -52,10 +52,15 @@ export function HomeTemplate({ viewData }: HomeTemplateProps) {
|
||||
<ValuePillars />
|
||||
|
||||
{/* Stewarding Workflow Preview */}
|
||||
<StewardingPreview />
|
||||
<StewardingPreview
|
||||
race={viewData.upcomingRaces[0]}
|
||||
team={viewData.teams[0]}
|
||||
/>
|
||||
|
||||
{/* League Identity Showcase */}
|
||||
<LeagueIdentityPreview />
|
||||
<LeagueIdentityPreview
|
||||
league={viewData.topLeagues[0]}
|
||||
/>
|
||||
|
||||
{/* Migration Offer */}
|
||||
<MigrationSection />
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
import { DriverLeaderboardPreview } from '@/components/leaderboards/DriverLeaderboardPreview';
|
||||
import { TeamLeaderboardPreview } from '@/components/teams/TeamLeaderboardPreviewWrapper';
|
||||
import type { LeaderboardsViewData } from '@/lib/view-data/LeaderboardsViewData';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { GridItem } from '@/ui/GridItem';
|
||||
import { Section } from '@/ui/Section';
|
||||
import { PageHero } from '@/ui/PageHero';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { Trophy, Users } from 'lucide-react';
|
||||
import { FeatureGrid } from '@/ui/FeatureGrid';
|
||||
import { Trophy, Users, Activity } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
interface LeaderboardsTemplateProps {
|
||||
viewData: LeaderboardsViewData;
|
||||
@@ -25,11 +25,11 @@ export function LeaderboardsTemplate({
|
||||
onNavigateToTeams
|
||||
}: LeaderboardsTemplateProps) {
|
||||
return (
|
||||
<Container size="lg" py={8}>
|
||||
<Section variant="default" padding="lg">
|
||||
<PageHero
|
||||
title="Leaderboards"
|
||||
description="Track the best drivers and teams across all competitions. Every race counts. Every position matters. Analyze telemetry-grade rankings and performance metrics."
|
||||
icon={Trophy}
|
||||
title="Global Standings"
|
||||
description="Consolidated performance metrics for drivers and teams. Data-driven rankings based on competitive results and technical consistency."
|
||||
icon={Activity}
|
||||
actions={[
|
||||
{
|
||||
label: 'Driver Rankings',
|
||||
@@ -38,7 +38,7 @@ export function LeaderboardsTemplate({
|
||||
variant: 'primary'
|
||||
},
|
||||
{
|
||||
label: 'Team Rankings',
|
||||
label: 'Team Standings',
|
||||
onClick: onNavigateToTeams,
|
||||
icon: Users,
|
||||
variant: 'secondary'
|
||||
@@ -46,26 +46,18 @@ export function LeaderboardsTemplate({
|
||||
]}
|
||||
/>
|
||||
|
||||
<Grid cols={12} gap={6} mt={10}>
|
||||
<GridItem colSpan={12} lgSpan={6}>
|
||||
<DriverLeaderboardPreview
|
||||
drivers={viewData.drivers}
|
||||
onDriverClick={onDriverClick}
|
||||
onNavigateToDrivers={onNavigateToDrivers}
|
||||
/>
|
||||
</GridItem>
|
||||
<GridItem colSpan={12} lgSpan={6}>
|
||||
<TeamLeaderboardPreview
|
||||
topTeams={viewData.teams.map(team => ({
|
||||
...team,
|
||||
isRecruiting: false,
|
||||
performanceLevel: 'N/A'
|
||||
}))}
|
||||
onTeamClick={onTeamClick}
|
||||
onViewFullLeaderboard={onNavigateToTeams}
|
||||
/>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</Container>
|
||||
<FeatureGrid columns={{ base: 1, lg: 2 }} gap={8}>
|
||||
<DriverLeaderboardPreview
|
||||
drivers={viewData.drivers}
|
||||
onDriverClick={onDriverClick}
|
||||
onNavigateToDrivers={onNavigateToDrivers}
|
||||
/>
|
||||
<TeamLeaderboardPreview
|
||||
topTeams={viewData.teams}
|
||||
onTeamClick={onTeamClick}
|
||||
onViewFullLeaderboard={onNavigateToTeams}
|
||||
/>
|
||||
</FeatureGrid>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { PageHeader } from '@/ui/PageHeader';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Group } from '@/ui/Group';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
@@ -15,8 +14,7 @@ import { Section } from '@/ui/Section';
|
||||
import { ControlBar } from '@/ui/ControlBar';
|
||||
import { SegmentedControl } from '@/ui/SegmentedControl';
|
||||
import { MetricCard } from '@/ui/MetricCard';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { FeatureGrid } from '@/ui/FeatureGrid';
|
||||
import {
|
||||
Plus,
|
||||
Search,
|
||||
@@ -73,27 +71,27 @@ export function LeaguesTemplate({
|
||||
onClearFilters,
|
||||
}: LeaguesTemplateProps) {
|
||||
return (
|
||||
<Container size="xl" py={8}>
|
||||
<Stack gap={8}>
|
||||
{/* Header Section */}
|
||||
<PageHeader
|
||||
icon={Trophy}
|
||||
title="Leagues"
|
||||
description="Infrastructure for competitive sim racing."
|
||||
action={
|
||||
<Button
|
||||
onClick={onCreateLeague}
|
||||
variant="primary"
|
||||
size="lg"
|
||||
icon={<Plus size={16} />}
|
||||
>
|
||||
Create League
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Section variant="default" padding="lg">
|
||||
{/* Header Section */}
|
||||
<PageHeader
|
||||
icon={Trophy}
|
||||
title="Leagues"
|
||||
description="Infrastructure for competitive sim racing."
|
||||
action={
|
||||
<Button
|
||||
onClick={onCreateLeague}
|
||||
variant="primary"
|
||||
size="lg"
|
||||
icon={<Plus size={16} />}
|
||||
>
|
||||
Create League
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Stats Overview */}
|
||||
<Grid cols={{ base: 1, md: 3 }} gap={4}>
|
||||
{/* Stats Overview */}
|
||||
<Container size="full" padding="none" py={8}>
|
||||
<FeatureGrid columns={{ base: 1, md: 3 }} gap={4}>
|
||||
<MetricCard
|
||||
label="Active Leagues"
|
||||
value={viewData.leagues.length}
|
||||
@@ -112,68 +110,67 @@ export function LeaguesTemplate({
|
||||
icon={Trophy}
|
||||
intent="success"
|
||||
/>
|
||||
</Grid>
|
||||
</FeatureGrid>
|
||||
</Container>
|
||||
|
||||
{/* Control Bar */}
|
||||
<ControlBar
|
||||
leftContent={
|
||||
<Group gap={4} align="center">
|
||||
<Icon icon={Filter} size={4} intent="low" />
|
||||
<SegmentedControl
|
||||
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>
|
||||
}
|
||||
>
|
||||
<Box width="300px">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search infrastructure..."
|
||||
value={searchQuery}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onSearchChange(e.target.value)}
|
||||
icon={<Search size={16} />}
|
||||
size="sm"
|
||||
{/* Control Bar */}
|
||||
<ControlBar
|
||||
leftContent={
|
||||
<Group gap={4} align="center">
|
||||
<Icon icon={Filter} size={4} intent="low" />
|
||||
<SegmentedControl
|
||||
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)}
|
||||
/>
|
||||
</Box>
|
||||
</ControlBar>
|
||||
</Group>
|
||||
}
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search infrastructure..."
|
||||
value={searchQuery}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onSearchChange(e.target.value)}
|
||||
icon={<Search size={16} />}
|
||||
size="sm"
|
||||
width="300px"
|
||||
/>
|
||||
</ControlBar>
|
||||
|
||||
{/* Results */}
|
||||
<Stack gap={6}>
|
||||
{filteredLeagues.length > 0 ? (
|
||||
<Grid cols={{ 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)}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
) : (
|
||||
<Section variant="dark" padding="lg">
|
||||
<Stack align="center" justify="center" gap={4}>
|
||||
<Icon icon={Search} size={12} intent="low" />
|
||||
<Stack align="center" gap={1}>
|
||||
<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>
|
||||
</Section>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Container>
|
||||
{/* Results */}
|
||||
<Container size="full" padding="none" py={6}>
|
||||
{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>
|
||||
) : (
|
||||
<Section variant="dark" padding="lg">
|
||||
<Group direction="col" align="center" justify="center" gap={4}>
|
||||
<Icon icon={Search} size={12} intent="low" />
|
||||
<Group direction="col" align="center" gap={1}>
|
||||
<Text size="lg" weight="bold">No results found</Text>
|
||||
<Text variant="low" size="sm">Adjust filters to find matching infrastructure.</Text>
|
||||
</Group>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onClearFilters}
|
||||
>
|
||||
Reset Filters
|
||||
</Button>
|
||||
</Group>
|
||||
</Section>
|
||||
)}
|
||||
</Container>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Section } from '@/ui/Section';
|
||||
import { RacesLiveRail } from '@/components/races/RacesLiveRail';
|
||||
import { RacesCommandBar } from '@/components/races/RacesCommandBar';
|
||||
import { NextUpRacePanel } from '@/components/races/NextUpRacePanel';
|
||||
@@ -10,24 +10,19 @@ import { RacesDayGroup } from '@/components/races/RacesDayGroup';
|
||||
import { RacesEmptyState } from '@/components/races/RacesEmptyState';
|
||||
import { RaceFilterModal } from '@/components/races/RaceFilterModal';
|
||||
import { PageHeader } from '@/components/shared/PageHeader';
|
||||
import type { RacesViewData } from '@/lib/view-data/RacesViewData';
|
||||
import type { RacesViewData, RaceViewData } from '@/lib/view-data/RacesViewData';
|
||||
|
||||
export interface RacesIndexTemplateProps {
|
||||
viewData: RacesViewData & {
|
||||
racesByDate: Array<{
|
||||
dateKey: string;
|
||||
dateLabel: string;
|
||||
races: any[];
|
||||
}>;
|
||||
nextUpRace?: any;
|
||||
nextUpRace?: RaceViewData;
|
||||
};
|
||||
// Filters
|
||||
statusFilter: string;
|
||||
setStatusFilter: (filter: any) => void;
|
||||
setStatusFilter: (filter: string) => void;
|
||||
leagueFilter: string;
|
||||
setLeagueFilter: (filter: string) => void;
|
||||
timeFilter: string;
|
||||
setTimeFilter: (filter: any) => void;
|
||||
setTimeFilter: (filter: string) => void;
|
||||
// Actions
|
||||
onRaceClick: (raceId: string) => void;
|
||||
// UI State
|
||||
@@ -50,20 +45,22 @@ export function RacesIndexTemplate({
|
||||
const hasRaces = viewData.racesByDate.length > 0;
|
||||
|
||||
return (
|
||||
<Container size="lg">
|
||||
<Stack gap={8} paddingY={12}>
|
||||
<PageHeader
|
||||
title="Races"
|
||||
subtitle="Live Sessions & Upcoming Events"
|
||||
/>
|
||||
<Section variant="default" padding="lg">
|
||||
<PageHeader
|
||||
title="Races"
|
||||
subtitle="Live Sessions & Upcoming Events"
|
||||
/>
|
||||
|
||||
{/* 1. Status Rail: Live sessions first */}
|
||||
{/* 1. Status Rail: Live sessions first */}
|
||||
<Container size="full" padding="none" py={8}>
|
||||
<RacesLiveRail
|
||||
liveRaces={viewData.liveRaces}
|
||||
onRaceClick={onRaceClick}
|
||||
/>
|
||||
</Container>
|
||||
|
||||
{/* 2. Command Bar: Fast filters */}
|
||||
{/* 2. Command Bar: Fast filters */}
|
||||
<Container size="full" padding="none" py={4}>
|
||||
<RacesCommandBar
|
||||
timeFilter={timeFilter}
|
||||
setTimeFilter={setTimeFilter}
|
||||
@@ -72,47 +69,50 @@ export function RacesIndexTemplate({
|
||||
leagues={viewData.leagues}
|
||||
onShowMoreFilters={() => setShowFilterModal(true)}
|
||||
/>
|
||||
</Container>
|
||||
|
||||
{/* 3. Next Up: High signal panel */}
|
||||
{timeFilter === 'upcoming' && viewData.nextUpRace && (
|
||||
{/* 3. Next Up: High signal panel */}
|
||||
{timeFilter === 'upcoming' && viewData.nextUpRace && (
|
||||
<Container size="full" padding="none" py={8}>
|
||||
<NextUpRacePanel
|
||||
race={viewData.nextUpRace}
|
||||
onRaceClick={onRaceClick}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
)}
|
||||
|
||||
{/* 4. Browse by Day: Grouped schedule */}
|
||||
{hasRaces ? (
|
||||
<Stack gap={8}>
|
||||
{viewData.racesByDate.map((group) => (
|
||||
{/* 4. Browse by Day: Grouped schedule */}
|
||||
{hasRaces ? (
|
||||
<Container size="full" padding="none" py={8}>
|
||||
{viewData.racesByDate.map((group) => (
|
||||
<Container key={group.dateKey} size="full" padding="none" py={4}>
|
||||
<RacesDayGroup
|
||||
key={group.dateKey}
|
||||
dateLabel={group.dateLabel}
|
||||
races={group.races}
|
||||
onRaceClick={onRaceClick}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
) : (
|
||||
<RacesEmptyState />
|
||||
)}
|
||||
</Container>
|
||||
))}
|
||||
</Container>
|
||||
) : (
|
||||
<RacesEmptyState />
|
||||
)}
|
||||
|
||||
<RaceFilterModal
|
||||
isOpen={showFilterModal}
|
||||
onClose={() => setShowFilterModal(false)}
|
||||
statusFilter={statusFilter as any}
|
||||
setStatusFilter={setStatusFilter}
|
||||
leagueFilter={leagueFilter}
|
||||
setLeagueFilter={setLeagueFilter}
|
||||
timeFilter={timeFilter as any}
|
||||
setTimeFilter={setTimeFilter}
|
||||
searchQuery=""
|
||||
setSearchQuery={() => {}}
|
||||
leagues={viewData.leagues}
|
||||
showSearch={true}
|
||||
showTimeFilter={false}
|
||||
/>
|
||||
</Stack>
|
||||
</Container>
|
||||
<RaceFilterModal
|
||||
isOpen={showFilterModal}
|
||||
onClose={() => setShowFilterModal(false)}
|
||||
statusFilter={statusFilter as any}
|
||||
setStatusFilter={setStatusFilter}
|
||||
leagueFilter={leagueFilter}
|
||||
setLeagueFilter={setLeagueFilter}
|
||||
timeFilter={timeFilter as any}
|
||||
setTimeFilter={setTimeFilter}
|
||||
searchQuery=""
|
||||
setSearchQuery={() => {}}
|
||||
leagues={viewData.leagues}
|
||||
showSearch={true}
|
||||
showTimeFilter={false}
|
||||
/>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export function RacesTemplate({
|
||||
}: RacesTemplateProps) {
|
||||
return (
|
||||
<Container size="lg">
|
||||
<Stack gap={8}>
|
||||
<Stack gap={8} paddingY={12}>
|
||||
<RacePageHeader
|
||||
totalCount={viewData.totalCount}
|
||||
scheduledCount={viewData.scheduledCount}
|
||||
@@ -101,12 +101,12 @@ export function RacesTemplate({
|
||||
<RaceFilterModal
|
||||
isOpen={showFilterModal}
|
||||
onClose={() => setShowFilterModal(false)}
|
||||
statusFilter={statusFilter}
|
||||
setStatusFilter={setStatusFilter}
|
||||
statusFilter={statusFilter as any}
|
||||
setStatusFilter={setStatusFilter as any}
|
||||
leagueFilter={leagueFilter}
|
||||
setLeagueFilter={setLeagueFilter}
|
||||
timeFilter={timeFilter}
|
||||
setTimeFilter={setTimeFilter}
|
||||
timeFilter={timeFilter as any}
|
||||
setTimeFilter={setTimeFilter as any}
|
||||
searchQuery=""
|
||||
setSearchQuery={() => {}}
|
||||
leagues={viewData.leagues}
|
||||
|
||||
@@ -11,7 +11,8 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Panel } from '@/ui/Panel';
|
||||
import { Section } from '@/ui/Section';
|
||||
import { Award, ChevronLeft } from 'lucide-react';
|
||||
import { Select } from '@/ui/Select';
|
||||
import { Award, ChevronLeft, Users } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
interface TeamLeaderboardTemplateProps {
|
||||
@@ -26,13 +27,30 @@ interface TeamLeaderboardTemplateProps {
|
||||
export function TeamLeaderboardTemplate({
|
||||
viewData,
|
||||
onSearchChange,
|
||||
filterLevelChange,
|
||||
onSortChange,
|
||||
onTeamClick,
|
||||
onBackToTeams,
|
||||
}: TeamLeaderboardTemplateProps) {
|
||||
const { searchQuery, filteredAndSortedTeams } = viewData;
|
||||
const { searchQuery, filterLevel, sortBy, filteredAndSortedTeams } = viewData;
|
||||
|
||||
const levelOptions = [
|
||||
{ value: 'all', label: 'All Levels' },
|
||||
{ value: 'pro', label: 'Professional' },
|
||||
{ value: 'advanced', label: 'Advanced' },
|
||||
{ value: 'intermediate', label: 'Intermediate' },
|
||||
{ value: 'beginner', label: 'Beginner' },
|
||||
];
|
||||
|
||||
const sortOptions = [
|
||||
{ value: 'rating', label: 'Rating' },
|
||||
{ value: 'wins', label: 'Wins' },
|
||||
{ value: 'winRate', label: 'Win Rate' },
|
||||
{ value: 'races', label: 'Races' },
|
||||
];
|
||||
|
||||
return (
|
||||
<Container size="lg" py={12}>
|
||||
<Section variant="default" padding="lg">
|
||||
<Group direction="column" gap={8} fullWidth>
|
||||
{/* Header */}
|
||||
<Group direction="row" align="center" justify="between" fullWidth>
|
||||
@@ -41,28 +59,44 @@ export function TeamLeaderboardTemplate({
|
||||
Back
|
||||
</Button>
|
||||
<Group direction="column">
|
||||
<Heading level={1} weight="bold">Global Standings</Heading>
|
||||
<Text variant="low" size="sm" font="mono" uppercase letterSpacing="widest">Team Performance Index</Text>
|
||||
<Heading level={1} weight="bold">Team Standings</Heading>
|
||||
<Text variant="low" size="sm" font="mono" uppercase letterSpacing="widest">Global Performance Index</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
<Icon icon={Award} size={8} color="var(--ui-color-intent-warning)" />
|
||||
<Icon icon={Award} size={8} intent="warning" />
|
||||
</Group>
|
||||
|
||||
<LeaderboardFiltersBar
|
||||
searchQuery={searchQuery}
|
||||
onSearchChange={onSearchChange}
|
||||
placeholder="Search teams..."
|
||||
/>
|
||||
>
|
||||
<Group gap={4}>
|
||||
<Select
|
||||
size="sm"
|
||||
value={filterLevel}
|
||||
options={levelOptions}
|
||||
onChange={(e) => filterLevelChange(e.target.value as SkillLevel | 'all')}
|
||||
/>
|
||||
<Select
|
||||
size="sm"
|
||||
value={sortBy}
|
||||
options={sortOptions}
|
||||
onChange={(e) => onSortChange(e.target.value as SortBy)}
|
||||
/>
|
||||
</Group>
|
||||
</LeaderboardFiltersBar>
|
||||
|
||||
<Panel variant="dark" padding={0}>
|
||||
<Panel variant="dark" padding="none">
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeader w="20">Rank</TableHeader>
|
||||
<TableHeader>Team</TableHeader>
|
||||
<TableHeader textAlign="center">Personnel</TableHeader>
|
||||
<TableHeader textAlign="center">Races</TableHeader>
|
||||
<TableHeader textAlign="right">Rating</TableHeader>
|
||||
<TableCell w="80px">Rank</TableCell>
|
||||
<TableCell>Team</TableCell>
|
||||
<TableCell textAlign="center">Personnel</TableCell>
|
||||
<TableCell textAlign="center">Races</TableCell>
|
||||
<TableCell textAlign="center">Wins</TableCell>
|
||||
<TableCell textAlign="right">Rating</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
@@ -80,10 +114,13 @@ export function TeamLeaderboardTemplate({
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Group direction="row" align="center" gap={3}>
|
||||
<Panel variant="muted" padding={2}>
|
||||
<Text size="xs" weight="bold" color="text-primary-accent">{team.name.substring(0, 2).toUpperCase()}</Text>
|
||||
<Panel variant="muted" padding="sm">
|
||||
<Icon icon={Users} size={4} intent="low" />
|
||||
</Panel>
|
||||
<Text weight="bold" size="sm">{team.name}</Text>
|
||||
<Group direction="column" gap={0}>
|
||||
<Text weight="bold" size="sm">{team.name}</Text>
|
||||
<Text size="xs" variant="low" uppercase font="mono">{team.performanceLevel}</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</TableCell>
|
||||
<TableCell textAlign="center">
|
||||
@@ -92,14 +129,19 @@ export function TeamLeaderboardTemplate({
|
||||
<TableCell textAlign="center">
|
||||
<Text size="xs" variant="low" font="mono">{team.totalRaces}</Text>
|
||||
</TableCell>
|
||||
<TableCell textAlign="center">
|
||||
<Text size="xs" variant="low" font="mono">{team.totalWins}</Text>
|
||||
</TableCell>
|
||||
<TableCell textAlign="right">
|
||||
<Text font="mono" weight="bold" color="text-primary-accent">1450</Text>
|
||||
<Text font="mono" weight="bold" variant="primary">
|
||||
{team.rating?.toFixed(0) || '1000'}
|
||||
</Text>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5} textAlign="center">
|
||||
<TableCell colSpan={6} textAlign="center">
|
||||
<Section variant="dark" padding="lg">
|
||||
<Group align="center" justify="center" fullWidth>
|
||||
<Text variant="low" font="mono" size="xs" uppercase letterSpacing="widest">
|
||||
@@ -114,6 +156,6 @@ export function TeamLeaderboardTemplate({
|
||||
</Table>
|
||||
</Panel>
|
||||
</Group>
|
||||
</Container>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,9 +10,7 @@ import { TeamCard } from '@/components/teams/TeamCard';
|
||||
import { TeamSearchBar } from '@/components/teams/TeamSearchBar';
|
||||
import { EmptyState } from '@/ui/EmptyState';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Section } from '@/ui/Section';
|
||||
import { Carousel } from '@/components/shared/Carousel';
|
||||
|
||||
interface TeamsTemplateProps extends TemplateProps<TeamsViewData> {
|
||||
@@ -65,22 +63,21 @@ export function TeamsTemplate({
|
||||
}, [teams, filteredTeams, searchQuery]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[var(--ui-color-bg-base)] py-12">
|
||||
<Container size="xl">
|
||||
<TeamsDirectoryHeader onCreateTeam={onCreateTeam} />
|
||||
|
||||
<Box marginBottom={12}>
|
||||
<TeamSearchBar
|
||||
searchQuery={searchQuery}
|
||||
onSearchChange={onSearchChange}
|
||||
/>
|
||||
</Box>
|
||||
<Section variant="default" padding="lg">
|
||||
<TeamsDirectoryHeader onCreateTeam={onCreateTeam} />
|
||||
|
||||
<Container size="full" padding="none" py={12}>
|
||||
<TeamSearchBar
|
||||
searchQuery={searchQuery}
|
||||
onSearchChange={onSearchChange}
|
||||
/>
|
||||
</Container>
|
||||
|
||||
{clusters.length > 0 ? (
|
||||
<div className="space-y-20">
|
||||
{clusters.map((cluster) => (
|
||||
{clusters.length > 0 ? (
|
||||
<Container size="full" padding="none">
|
||||
{clusters.map((cluster, index) => (
|
||||
<Container key={cluster.title} size="full" padding="none" py={index === 0 ? 0 : 10}>
|
||||
<Carousel
|
||||
key={cluster.title}
|
||||
title={cluster.title}
|
||||
count={cluster.teams.length}
|
||||
>
|
||||
@@ -92,23 +89,21 @@ export function TeamsTemplate({
|
||||
/>
|
||||
))}
|
||||
</Carousel>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-20 border border-dashed border-[var(--ui-color-border-muted)] flex flex-col items-center justify-center text-center">
|
||||
<EmptyState
|
||||
icon={Users}
|
||||
title={searchQuery ? "No matching teams" : "No teams yet"}
|
||||
description={searchQuery ? "Try adjusting your search filters" : "Get started by creating your first racing team"}
|
||||
action={{
|
||||
label: 'Create Team',
|
||||
onClick: onCreateTeam,
|
||||
variant: 'primary'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Container>
|
||||
</div>
|
||||
</Container>
|
||||
))}
|
||||
</Container>
|
||||
) : (
|
||||
<EmptyState
|
||||
icon={Users}
|
||||
title={searchQuery ? "No matching teams" : "No teams yet"}
|
||||
description={searchQuery ? "Try adjusting your search filters" : "Get started by creating your first racing team"}
|
||||
action={{
|
||||
label: 'Create Team',
|
||||
onClick: onCreateTeam,
|
||||
variant: 'primary'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user