Files
gridpilot.gg/apps/website/components/leagues/EnhancedLeagueSchedulePanel.tsx
2026-01-21 17:50:02 +01:00

284 lines
10 KiB
TypeScript

'use client';
import React, { useState } from 'react';
import { useRouter } from 'next/navigation';
import { routes } from '@/lib/routing/RouteConfig';
import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
import { Button } from '@/ui/Button';
import { Icon } from '@/ui/Icon';
import { Badge } from '@/ui/Badge';
import { Group } from '@/ui/Group';
import { Surface } from '@/ui/Surface';
import { ChevronDown, ChevronUp, Calendar, CheckCircle, Trophy, Edit, Clock } from 'lucide-react';
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
interface RaceEvent {
id: string;
name: string;
track?: string;
car?: string;
sessionType?: string;
scheduledAt: string;
status: 'scheduled' | 'completed';
strengthOfField?: number;
isUserRegistered?: boolean;
canRegister?: boolean;
canEdit?: boolean;
canReschedule?: boolean;
}
interface EnhancedLeagueSchedulePanelProps {
events: RaceEvent[];
leagueId: string;
currentDriverId?: string;
isAdmin: boolean;
onRegister: (raceId: string) => void;
onWithdraw: (raceId: string) => void;
onEdit: (raceId: string) => void;
onReschedule: (raceId: string) => void;
onRaceDetail: (raceId: string) => void;
onResultsClick: (raceId: string) => void;
}
interface MonthGroup {
month: string;
year: number;
races: RaceEvent[];
}
export function EnhancedLeagueSchedulePanel({
events,
leagueId,
currentDriverId,
isAdmin,
onRegister,
onWithdraw,
onEdit,
onReschedule,
onRaceDetail,
onResultsClick,
}: EnhancedLeagueSchedulePanelProps) {
const router = useRouter();
const [expandedMonths, setExpandedMonths] = useState<Set<string>>(new Set());
// Group races by month
const groupRacesByMonth = (): MonthGroup[] => {
const groups = new Map<string, MonthGroup>();
events.forEach(event => {
const date = new Date(event.scheduledAt);
const monthKey = `${date.getFullYear()}-${date.getMonth()}`;
const monthName = date.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
if (!groups.has(monthKey)) {
groups.set(monthKey, {
month: monthName,
year: date.getFullYear(),
races: [],
});
}
groups.get(monthKey)!.races.push(event);
});
return Array.from(groups.values()).sort((a, b) => {
if (a.year !== b.year) return b.year - a.year;
return b.month.localeCompare(a.month);
});
};
const toggleMonth = (monthKey: string) => {
setExpandedMonths(prev => {
const newSet = new Set(prev);
if (newSet.has(monthKey)) {
newSet.delete(monthKey);
} else {
newSet.add(monthKey);
}
return newSet;
});
};
const getRaceStatusBadge = (status: 'scheduled' | 'completed') => {
if (status === 'completed') {
return <Badge variant="success" size="sm">Completed</Badge>;
}
return <Badge variant="primary" size="sm">Scheduled</Badge>;
};
const formatTime = (scheduledAt: string) => {
return DateDisplay.formatDateTime(scheduledAt);
};
const groups = groupRacesByMonth();
if (events.length === 0) {
return (
<Box p={12} textAlign="center" border borderColor="zinc-800" bg="zinc-900/30">
<Text color="text-zinc-500" italic>No races scheduled for this season.</Text>
</Box>
);
}
return (
<Stack gap={4}>
{groups.map((group, groupIndex) => {
const monthKey = `${group.year}-${groupIndex}`;
const isExpanded = expandedMonths.has(monthKey);
return (
<Surface key={monthKey} border borderColor="border-outline-steel" overflow="hidden">
{/* Month Header */}
<Box
display="flex"
alignItems="center"
justifyContent="space-between"
p={4}
bg="bg-surface-charcoal"
borderBottom={isExpanded}
borderColor="border-outline-steel"
cursor="pointer"
onClick={() => toggleMonth(monthKey)}
>
<Group gap={3}>
<Icon icon={Calendar} size={4} color="text-primary-blue" />
<Text size="md" weight="bold" color="text-white">
{group.month}
</Text>
<Badge variant="outline" size="sm">
{group.races.length} {group.races.length === 1 ? 'Race' : 'Races'}
</Badge>
</Group>
<Icon icon={isExpanded ? ChevronUp : ChevronDown} size={4} color="text-zinc-400" />
</Box>
{/* Race List */}
{isExpanded && (
<Box p={4}>
<Stack gap={3}>
{group.races.map((race, raceIndex) => (
<Surface
key={race.id}
border
borderColor="border-outline-steel"
p={4}
bg="bg-base-black"
>
<Box display="flex" alignItems="center" justifyContent="space-between" gap={4}>
{/* Race Info */}
<Box flex={1}>
<Stack gap={2}>
<Group gap={2} align="center">
<Text size="sm" weight="bold" color="text-white">
{race.name || `Race ${race.id.substring(0, 4)}`}
</Text>
{getRaceStatusBadge(race.status)}
</Group>
<Group gap={3}>
<Text size="xs" color="text-zinc-400" uppercase letterSpacing="widest">
{race.track || 'TBA'}
</Text>
{race.car && (
<Text size="xs" color="text-zinc-500" uppercase letterSpacing="widest">
{race.car}
</Text>
)}
{race.sessionType && (
<Text size="xs" color="text-zinc-500" uppercase letterSpacing="widest">
{race.sessionType}
</Text>
)}
</Group>
<Group gap={2} align="center">
<Icon icon={Clock} size={3} color="text-zinc-500" />
<Text size="xs" color="text-zinc-400" font="mono">
{formatTime(race.scheduledAt)}
</Text>
</Group>
</Stack>
</Box>
{/* Action Buttons */}
<Box display="flex" gap={2} flexWrap="wrap">
{race.status === 'scheduled' && (
<>
{!race.isUserRegistered && race.canRegister && (
<Button
variant="primary"
size="sm"
onClick={() => onRegister(race.id)}
icon={<Icon icon={CheckCircle} size={3} />}
>
Register
</Button>
)}
{race.isUserRegistered && (
<Button
variant="secondary"
size="sm"
onClick={() => onWithdraw(race.id)}
icon={<Icon icon={ChevronDown} size={3} />}
>
Withdraw
</Button>
)}
{race.canEdit && (
<Button
variant="neutral"
size="sm"
onClick={() => onEdit(race.id)}
icon={<Icon icon={Edit} size={3} />}
>
Edit
</Button>
)}
{race.canReschedule && (
<Button
variant="neutral"
size="sm"
onClick={() => onReschedule(race.id)}
icon={<Icon icon={Clock} size={3} />}
>
Reschedule
</Button>
)}
</>
)}
{race.status === 'completed' && (
<>
<Button
variant="primary"
size="sm"
onClick={() => onResultsClick(race.id)}
icon={<Icon icon={Trophy} size={3} />}
>
View Results
</Button>
</>
)}
{/* Always show detail button */}
<Button
variant="secondary"
size="sm"
onClick={() => onRaceDetail(race.id)}
>
Details
</Button>
</Box>
</Box>
</Surface>
))}
</Stack>
</Box>
)}
</Surface>
);
})}
</Stack>
);
}