284 lines
10 KiB
TypeScript
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>
|
|
);
|
|
}
|