website refactor
This commit is contained in:
283
apps/website/components/leagues/EnhancedLeagueSchedulePanel.tsx
Normal file
283
apps/website/components/leagues/EnhancedLeagueSchedulePanel.tsx
Normal file
@@ -0,0 +1,283 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user