663 lines
26 KiB
TypeScript
663 lines
26 KiB
TypeScript
'use client';
|
|
|
|
import { useMemo } from 'react';
|
|
import Link from 'next/link';
|
|
import Card from '@/components/ui/Card';
|
|
import Heading from '@/components/ui/Heading';
|
|
import {
|
|
Calendar,
|
|
Clock,
|
|
Flag,
|
|
ChevronRight,
|
|
MapPin,
|
|
Car,
|
|
Trophy,
|
|
Users,
|
|
Zap,
|
|
PlayCircle,
|
|
CheckCircle2,
|
|
XCircle,
|
|
CalendarDays,
|
|
ArrowRight,
|
|
} from 'lucide-react';
|
|
import { RaceFilterModal } from '@/components/races/RaceFilterModal';
|
|
import { RaceJoinButton } from '@/components/races/RaceJoinButton';
|
|
|
|
export type TimeFilter = 'all' | 'upcoming' | 'live' | 'past';
|
|
export type RaceStatusFilter = 'scheduled' | 'running' | 'completed' | 'cancelled' | 'all';
|
|
|
|
export interface Race {
|
|
id: string;
|
|
track: string;
|
|
car: string;
|
|
scheduledAt: string;
|
|
status: 'scheduled' | 'running' | 'completed' | 'cancelled';
|
|
sessionType: string;
|
|
leagueId?: string;
|
|
leagueName?: string;
|
|
strengthOfField?: number | null;
|
|
isUpcoming: boolean;
|
|
isLive: boolean;
|
|
isPast: boolean;
|
|
}
|
|
|
|
export interface RacesTemplateProps {
|
|
races: Race[];
|
|
totalCount: number;
|
|
scheduledRaces: Race[];
|
|
runningRaces: Race[];
|
|
completedRaces: Race[];
|
|
isLoading: boolean;
|
|
// Filters
|
|
statusFilter: RaceStatusFilter;
|
|
setStatusFilter: (filter: RaceStatusFilter) => void;
|
|
leagueFilter: string;
|
|
setLeagueFilter: (filter: string) => void;
|
|
timeFilter: TimeFilter;
|
|
setTimeFilter: (filter: TimeFilter) => void;
|
|
// Actions
|
|
onRaceClick: (raceId: string) => void;
|
|
onLeagueClick: (leagueId: string) => void;
|
|
onRegister: (raceId: string, leagueId: string) => void;
|
|
onWithdraw: (raceId: string) => void;
|
|
onCancel: (raceId: string) => void;
|
|
// UI State
|
|
showFilterModal: boolean;
|
|
setShowFilterModal: (show: boolean) => void;
|
|
// User state
|
|
currentDriverId?: string;
|
|
userMemberships?: Array<{ leagueId: string; role: string }>;
|
|
}
|
|
|
|
export function RacesTemplate({
|
|
races,
|
|
totalCount,
|
|
scheduledRaces,
|
|
runningRaces,
|
|
completedRaces,
|
|
isLoading,
|
|
statusFilter,
|
|
setStatusFilter,
|
|
leagueFilter,
|
|
setLeagueFilter,
|
|
timeFilter,
|
|
setTimeFilter,
|
|
onRaceClick,
|
|
onLeagueClick,
|
|
onRegister,
|
|
onWithdraw,
|
|
onCancel,
|
|
showFilterModal,
|
|
setShowFilterModal,
|
|
currentDriverId,
|
|
userMemberships,
|
|
}: RacesTemplateProps) {
|
|
// Filter races
|
|
const filteredRaces = useMemo(() => {
|
|
return races.filter((race) => {
|
|
// Status filter
|
|
if (statusFilter !== 'all' && race.status !== statusFilter) {
|
|
return false;
|
|
}
|
|
|
|
// League filter
|
|
if (leagueFilter !== 'all' && race.leagueId !== leagueFilter) {
|
|
return false;
|
|
}
|
|
|
|
// Time filter
|
|
if (timeFilter === 'upcoming' && !race.isUpcoming) {
|
|
return false;
|
|
}
|
|
if (timeFilter === 'live' && !race.isLive) {
|
|
return false;
|
|
}
|
|
if (timeFilter === 'past' && !race.isPast) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}, [races, statusFilter, leagueFilter, timeFilter]);
|
|
|
|
// Group races by date for calendar view
|
|
const racesByDate = useMemo(() => {
|
|
const grouped = new Map<string, typeof filteredRaces[0][]>();
|
|
filteredRaces.forEach((race) => {
|
|
const dateKey = race.scheduledAt.split('T')[0]!;
|
|
if (!grouped.has(dateKey)) {
|
|
grouped.set(dateKey, []);
|
|
}
|
|
grouped.get(dateKey)!.push(race);
|
|
});
|
|
return grouped;
|
|
}, [filteredRaces]);
|
|
|
|
const upcomingRaces = filteredRaces.filter(r => r.isUpcoming).slice(0, 5);
|
|
const liveRaces = filteredRaces.filter(r => r.isLive);
|
|
const recentResults = filteredRaces.filter(r => r.isPast).slice(0, 5);
|
|
const stats = {
|
|
total: totalCount,
|
|
scheduled: scheduledRaces.length,
|
|
running: runningRaces.length,
|
|
completed: completedRaces.length,
|
|
};
|
|
|
|
const formatDate = (date: Date | string) => {
|
|
const d = typeof date === 'string' ? new Date(date) : date;
|
|
return d.toLocaleDateString('en-US', {
|
|
weekday: 'short',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
});
|
|
};
|
|
|
|
const formatTime = (date: Date | string) => {
|
|
const d = typeof date === 'string' ? new Date(date) : date;
|
|
return d.toLocaleTimeString('en-US', {
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
});
|
|
};
|
|
|
|
const formatFullDate = (date: Date | string) => {
|
|
const d = typeof date === 'string' ? new Date(date) : date;
|
|
return d.toLocaleDateString('en-US', {
|
|
weekday: 'long',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
year: 'numeric',
|
|
});
|
|
};
|
|
|
|
const getRelativeTime = (date?: Date | string) => {
|
|
if (!date) return '';
|
|
const now = new Date();
|
|
const targetDate = typeof date === 'string' ? new Date(date) : date;
|
|
const diffMs = targetDate.getTime() - now.getTime();
|
|
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
|
|
if (diffMs < 0) return 'Past';
|
|
if (diffHours < 1) return 'Starting soon';
|
|
if (diffHours < 24) return `In ${diffHours}h`;
|
|
if (diffDays === 1) return 'Tomorrow';
|
|
if (diffDays < 7) return `In ${diffDays} days`;
|
|
return formatDate(targetDate);
|
|
};
|
|
|
|
const statusConfig = {
|
|
scheduled: {
|
|
icon: Clock,
|
|
color: 'text-primary-blue',
|
|
bg: 'bg-primary-blue/10',
|
|
border: 'border-primary-blue/30',
|
|
label: 'Scheduled',
|
|
},
|
|
running: {
|
|
icon: PlayCircle,
|
|
color: 'text-performance-green',
|
|
bg: 'bg-performance-green/10',
|
|
border: 'border-performance-green/30',
|
|
label: 'LIVE',
|
|
},
|
|
completed: {
|
|
icon: CheckCircle2,
|
|
color: 'text-gray-400',
|
|
bg: 'bg-gray-500/10',
|
|
border: 'border-gray-500/30',
|
|
label: 'Completed',
|
|
},
|
|
cancelled: {
|
|
icon: XCircle,
|
|
color: 'text-warning-amber',
|
|
bg: 'bg-warning-amber/10',
|
|
border: 'border-warning-amber/30',
|
|
label: 'Cancelled',
|
|
},
|
|
};
|
|
|
|
const isUserRegistered = (race: Race) => {
|
|
// This would need actual registration data
|
|
return false;
|
|
};
|
|
|
|
const canRegister = (race: Race) => {
|
|
// This would need actual registration rules
|
|
return race.status === 'scheduled';
|
|
};
|
|
|
|
const isOwnerOrAdmin = (leagueId?: string) => {
|
|
if (!leagueId || !userMemberships) return false;
|
|
const membership = userMemberships.find(m => m.leagueId === leagueId);
|
|
return membership?.role === 'owner' || membership?.role === 'admin';
|
|
};
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="min-h-screen bg-deep-graphite py-8 px-4 sm:px-6 lg:px-8">
|
|
<div className="max-w-7xl mx-auto">
|
|
<div className="animate-pulse space-y-6">
|
|
<div className="h-10 bg-iron-gray rounded w-1/4" />
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
{[1, 2, 3, 4].map(i => (
|
|
<div key={i} className="h-24 bg-iron-gray rounded-lg" />
|
|
))}
|
|
</div>
|
|
<div className="h-64 bg-iron-gray rounded-lg" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-deep-graphite py-8 px-4 sm:px-6 lg:px-8">
|
|
<div className="max-w-7xl mx-auto space-y-8">
|
|
{/* Hero Header */}
|
|
<div className="relative overflow-hidden rounded-2xl bg-gradient-to-br from-iron-gray via-iron-gray to-charcoal-outline border border-charcoal-outline p-8">
|
|
<div className="absolute top-0 right-0 w-64 h-64 bg-primary-blue/5 rounded-full blur-3xl" />
|
|
<div className="absolute bottom-0 left-0 w-48 h-48 bg-performance-green/5 rounded-full blur-3xl" />
|
|
|
|
<div className="relative z-10">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<div className="p-2 bg-primary-blue/10 rounded-lg">
|
|
<Flag className="w-6 h-6 text-primary-blue" />
|
|
</div>
|
|
<Heading level={1} className="text-3xl font-bold text-white">
|
|
Race Calendar
|
|
</Heading>
|
|
</div>
|
|
<p className="text-gray-400 max-w-2xl">
|
|
Track upcoming races, view live events, and explore results across all your leagues.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Quick Stats */}
|
|
<div className="relative z-10 grid grid-cols-2 md:grid-cols-4 gap-4 mt-6">
|
|
<div className="bg-deep-graphite/60 backdrop-blur rounded-xl p-4 border border-charcoal-outline/50">
|
|
<div className="flex items-center gap-2 text-gray-400 text-sm mb-1">
|
|
<CalendarDays className="w-4 h-4" />
|
|
<span>Total</span>
|
|
</div>
|
|
<p className="text-2xl font-bold text-white">{stats.total}</p>
|
|
</div>
|
|
<div className="bg-deep-graphite/60 backdrop-blur rounded-xl p-4 border border-charcoal-outline/50">
|
|
<div className="flex items-center gap-2 text-primary-blue text-sm mb-1">
|
|
<Clock className="w-4 h-4" />
|
|
<span>Scheduled</span>
|
|
</div>
|
|
<p className="text-2xl font-bold text-white">{stats.scheduled}</p>
|
|
</div>
|
|
<div className="bg-deep-graphite/60 backdrop-blur rounded-xl p-4 border border-charcoal-outline/50">
|
|
<div className="flex items-center gap-2 text-performance-green text-sm mb-1">
|
|
<Zap className="w-4 h-4" />
|
|
<span>Live Now</span>
|
|
</div>
|
|
<p className="text-2xl font-bold text-white">{stats.running}</p>
|
|
</div>
|
|
<div className="bg-deep-graphite/60 backdrop-blur rounded-xl p-4 border border-charcoal-outline/50">
|
|
<div className="flex items-center gap-2 text-gray-400 text-sm mb-1">
|
|
<Trophy className="w-4 h-4" />
|
|
<span>Completed</span>
|
|
</div>
|
|
<p className="text-2xl font-bold text-white">{stats.completed}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Live Races Banner */}
|
|
{liveRaces.length > 0 && (
|
|
<div className="relative overflow-hidden rounded-xl bg-gradient-to-r from-performance-green/20 via-performance-green/10 to-transparent border border-performance-green/30 p-6">
|
|
<div className="absolute top-0 right-0 w-32 h-32 bg-performance-green/20 rounded-full blur-2xl animate-pulse" />
|
|
|
|
<div className="relative z-10">
|
|
<div className="flex items-center gap-2 mb-4">
|
|
<div className="flex items-center gap-2 px-3 py-1 bg-performance-green/20 rounded-full">
|
|
<span className="w-2 h-2 bg-performance-green rounded-full animate-pulse" />
|
|
<span className="text-performance-green font-semibold text-sm">LIVE NOW</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-3">
|
|
{liveRaces.map((race) => (
|
|
<div
|
|
key={race.id}
|
|
onClick={() => onRaceClick(race.id)}
|
|
className="flex items-center justify-between p-4 bg-deep-graphite/80 rounded-lg border border-performance-green/20 cursor-pointer hover:border-performance-green/40 transition-all"
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<div className="p-2 bg-performance-green/20 rounded-lg">
|
|
<PlayCircle className="w-5 h-5 text-performance-green" />
|
|
</div>
|
|
<div>
|
|
<h3 className="font-semibold text-white">{race.track}</h3>
|
|
<p className="text-sm text-gray-400">{race.leagueName}</p>
|
|
</div>
|
|
</div>
|
|
<ChevronRight className="w-5 h-5 text-gray-400" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
{/* Main Content - Race List */}
|
|
<div className="lg:col-span-2 space-y-6">
|
|
{/* Filters */}
|
|
<Card className="!p-4">
|
|
<div className="flex flex-wrap gap-4">
|
|
{/* Time Filter Tabs */}
|
|
<div className="flex items-center gap-1 p-1 bg-deep-graphite rounded-lg">
|
|
{(['upcoming', 'live', 'past', 'all'] as TimeFilter[]).map(filter => (
|
|
<button
|
|
key={filter}
|
|
onClick={() => setTimeFilter(filter)}
|
|
className={`px-4 py-2 rounded-md text-sm font-medium transition-all ${
|
|
timeFilter === filter
|
|
? 'bg-primary-blue text-white'
|
|
: 'text-gray-400 hover:text-white'
|
|
}`}
|
|
>
|
|
{filter === 'live' && <span className="inline-block w-2 h-2 bg-performance-green rounded-full mr-2 animate-pulse" />}
|
|
{filter.charAt(0).toUpperCase() + filter.slice(1)}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* League Filter */}
|
|
<select
|
|
value={leagueFilter}
|
|
onChange={(e) => setLeagueFilter(e.target.value)}
|
|
className="px-4 py-2 bg-deep-graphite border border-charcoal-outline rounded-lg text-white text-sm focus:outline-none focus:ring-2 focus:ring-primary-blue"
|
|
>
|
|
<option value="all">All Leagues</option>
|
|
{races && [...new Set(races.map(r => r.leagueId))].filter(Boolean).map(leagueId => {
|
|
const item = races.find(r => r.leagueId === leagueId);
|
|
return item ? (
|
|
<option key={leagueId} value={leagueId}>
|
|
{item.leagueName}
|
|
</option>
|
|
) : null;
|
|
})}
|
|
</select>
|
|
|
|
{/* Filter Button */}
|
|
<button
|
|
onClick={() => setShowFilterModal(true)}
|
|
className="px-4 py-2 bg-iron-gray border border-charcoal-outline rounded-lg text-white text-sm hover:border-primary-blue transition-colors"
|
|
>
|
|
More Filters
|
|
</button>
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Race List by Date */}
|
|
{filteredRaces.length === 0 ? (
|
|
<Card className="text-center py-12">
|
|
<div className="flex flex-col items-center gap-4">
|
|
<div className="p-4 bg-iron-gray rounded-full">
|
|
<Calendar className="w-8 h-8 text-gray-500" />
|
|
</div>
|
|
<div>
|
|
<p className="text-white font-medium mb-1">No races found</p>
|
|
<p className="text-sm text-gray-500">
|
|
{totalCount === 0
|
|
? 'No races have been scheduled yet'
|
|
: 'Try adjusting your filters'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
) : (
|
|
<div className="space-y-4">
|
|
{Array.from(racesByDate.entries()).map(([dateKey, dayRaces]) => (
|
|
<div key={dateKey} className="space-y-3">
|
|
{/* Date Header */}
|
|
<div className="flex items-center gap-3 px-2">
|
|
<div className="p-2 bg-primary-blue/10 rounded-lg">
|
|
<Calendar className="w-4 h-4 text-primary-blue" />
|
|
</div>
|
|
<span className="text-sm font-semibold text-white">
|
|
{formatFullDate(new Date(dateKey))}
|
|
</span>
|
|
<span className="text-xs text-gray-500">
|
|
{dayRaces.length} race{dayRaces.length !== 1 ? 's' : ''}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Races for this date */}
|
|
<div className="space-y-2">
|
|
{dayRaces.map((race) => {
|
|
const config = statusConfig[race.status as keyof typeof statusConfig];
|
|
const StatusIcon = config.icon;
|
|
|
|
return (
|
|
<div
|
|
key={race.id}
|
|
className={`group relative overflow-hidden rounded-xl bg-iron-gray border ${config.border} p-4 cursor-pointer transition-all duration-200 hover:scale-[1.01] hover:border-primary-blue`}
|
|
onClick={() => onRaceClick(race.id)}
|
|
>
|
|
{/* Live indicator */}
|
|
{race.status === 'running' && (
|
|
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-performance-green via-performance-green/50 to-performance-green animate-pulse" />
|
|
)}
|
|
|
|
<div className="flex items-start gap-4">
|
|
{/* Time Column */}
|
|
<div className="flex-shrink-0 text-center min-w-[60px]">
|
|
<p className="text-lg font-bold text-white">
|
|
{formatTime(race.scheduledAt)}
|
|
</p>
|
|
<p className={`text-xs ${config.color}`}>
|
|
{race.status === 'running'
|
|
? 'LIVE'
|
|
: getRelativeTime(race.scheduledAt)}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Divider */}
|
|
<div className={`w-px self-stretch ${config.bg}`} />
|
|
|
|
{/* Main Content */}
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-start justify-between gap-4">
|
|
<div className="min-w-0">
|
|
<h3 className="font-semibold text-white truncate group-hover:text-primary-blue transition-colors">
|
|
{race.track}
|
|
</h3>
|
|
<div className="flex items-center gap-3 mt-1">
|
|
<span className="flex items-center gap-1 text-sm text-gray-400">
|
|
<Car className="w-3.5 h-3.5" />
|
|
{race.car}
|
|
</span>
|
|
{race.strengthOfField && (
|
|
<span className="flex items-center gap-1 text-sm text-gray-400">
|
|
<Zap className="w-3.5 h-3.5 text-warning-amber" />
|
|
SOF {race.strengthOfField}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Status Badge */}
|
|
<div className={`flex items-center gap-1.5 px-2.5 py-1 rounded-full ${config.bg} ${config.border} border`}>
|
|
<StatusIcon className={`w-3.5 h-3.5 ${config.color}`} />
|
|
<span className={`text-xs font-medium ${config.color}`}>
|
|
{config.label}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* League Link */}
|
|
<div className="mt-3 pt-3 border-t border-charcoal-outline/50">
|
|
<Link
|
|
href={`/leagues/${race.leagueId ?? ''}`}
|
|
onClick={(e) => e.stopPropagation()}
|
|
className="inline-flex items-center gap-2 text-sm text-primary-blue hover:underline"
|
|
>
|
|
<Trophy className="w-3.5 h-3.5" />
|
|
{race.leagueName}
|
|
<ArrowRight className="w-3 h-3" />
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Arrow */}
|
|
<ChevronRight className="w-5 h-5 text-gray-500 group-hover:text-primary-blue transition-colors flex-shrink-0" />
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* View All Link */}
|
|
{filteredRaces.length > 0 && (
|
|
<div className="text-center">
|
|
<Link
|
|
href="/races/all"
|
|
className="inline-flex items-center gap-2 px-6 py-3 bg-iron-gray border border-charcoal-outline rounded-lg text-white hover:border-primary-blue transition-colors"
|
|
>
|
|
View All Races
|
|
<ArrowRight className="w-4 h-4" />
|
|
</Link>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Sidebar */}
|
|
<div className="space-y-6">
|
|
{/* Upcoming This Week */}
|
|
<Card>
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h3 className="font-semibold text-white flex items-center gap-2">
|
|
<Clock className="w-4 h-4 text-primary-blue" />
|
|
Next Up
|
|
</h3>
|
|
<span className="text-xs text-gray-500">This week</span>
|
|
</div>
|
|
|
|
{upcomingRaces.length === 0 ? (
|
|
<p className="text-sm text-gray-500 text-center py-4">
|
|
No races scheduled this week
|
|
</p>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{upcomingRaces.map((race) => {
|
|
if (!race.scheduledAt) {
|
|
return null;
|
|
}
|
|
const scheduledAtDate = new Date(race.scheduledAt);
|
|
return (
|
|
<div
|
|
key={race.id}
|
|
onClick={() => onRaceClick(race.id)}
|
|
className="flex items-center gap-3 p-2 rounded-lg hover:bg-deep-graphite cursor-pointer transition-colors"
|
|
>
|
|
<div className="flex-shrink-0 w-10 h-10 bg-primary-blue/10 rounded-lg flex items-center justify-center">
|
|
<span className="text-sm font-bold text-primary-blue">
|
|
{scheduledAtDate.getDate()}
|
|
</span>
|
|
</div>
|
|
<div className="min-w-0 flex-1">
|
|
<p className="text-sm font-medium text-white truncate">{race.track}</p>
|
|
<p className="text-xs text-gray-500">{formatTime(scheduledAtDate)}</p>
|
|
</div>
|
|
<ChevronRight className="w-4 h-4 text-gray-500" />
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</Card>
|
|
|
|
{/* Recent Results */}
|
|
<Card>
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h3 className="font-semibold text-white flex items-center gap-2">
|
|
<Trophy className="w-4 h-4 text-warning-amber" />
|
|
Recent Results
|
|
</h3>
|
|
</div>
|
|
|
|
{recentResults.length === 0 ? (
|
|
<p className="text-sm text-gray-500 text-center py-4">
|
|
No completed races yet
|
|
</p>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{recentResults.map((race) => (
|
|
<div
|
|
key={race.id}
|
|
onClick={() => onRaceClick(race.id)}
|
|
className="flex items-center gap-3 p-2 rounded-lg hover:bg-deep-graphite cursor-pointer transition-colors"
|
|
>
|
|
<div className="flex-shrink-0 w-10 h-10 bg-gray-500/10 rounded-lg flex items-center justify-center">
|
|
<CheckCircle2 className="w-5 h-5 text-gray-400" />
|
|
</div>
|
|
<div className="min-w-0 flex-1">
|
|
<p className="text-sm font-medium text-white truncate">{race.track}</p>
|
|
<p className="text-xs text-gray-500">{formatDate(new Date(race.scheduledAt))}</p>
|
|
</div>
|
|
<ChevronRight className="w-4 h-4 text-gray-500" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</Card>
|
|
|
|
{/* Quick Actions */}
|
|
<Card>
|
|
<h3 className="font-semibold text-white mb-4">Quick Actions</h3>
|
|
<div className="space-y-2">
|
|
<Link
|
|
href="/leagues"
|
|
className="flex items-center gap-3 p-3 rounded-lg bg-deep-graphite hover:bg-charcoal-outline/50 transition-colors"
|
|
>
|
|
<div className="p-2 bg-primary-blue/10 rounded-lg">
|
|
<Users className="w-4 h-4 text-primary-blue" />
|
|
</div>
|
|
<span className="text-sm text-white">Browse Leagues</span>
|
|
<ChevronRight className="w-4 h-4 text-gray-500 ml-auto" />
|
|
</Link>
|
|
<Link
|
|
href="/leaderboards"
|
|
className="flex items-center gap-3 p-3 rounded-lg bg-deep-graphite hover:bg-charcoal-outline/50 transition-colors"
|
|
>
|
|
<div className="p-2 bg-warning-amber/10 rounded-lg">
|
|
<Trophy className="w-4 h-4 text-warning-amber" />
|
|
</div>
|
|
<span className="text-sm text-white">View Leaderboards</span>
|
|
<ChevronRight className="w-4 h-4 text-gray-500 ml-auto" />
|
|
</Link>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Filter Modal */}
|
|
<RaceFilterModal
|
|
isOpen={showFilterModal}
|
|
onClose={() => setShowFilterModal(false)}
|
|
statusFilter={statusFilter}
|
|
setStatusFilter={setStatusFilter}
|
|
leagueFilter={leagueFilter}
|
|
setLeagueFilter={setLeagueFilter}
|
|
timeFilter={timeFilter}
|
|
setTimeFilter={setTimeFilter}
|
|
searchQuery=""
|
|
setSearchQuery={() => {}}
|
|
leagues={[...new Set(races.map(r => ({ id: r.leagueId || '', name: r.leagueName || '' })))]}
|
|
showSearch={false}
|
|
showTimeFilter={false}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |