'use client'; import PenaltyFAB from '@/components/leagues/PenaltyFAB'; import QuickPenaltyModal from '@/components/leagues/QuickPenaltyModal'; import { ReviewProtestModal } from '@/components/leagues/ReviewProtestModal'; import StewardingStats from '@/components/leagues/StewardingStats'; import Button from '@/components/ui/Button'; import Card from '@/components/ui/Card'; import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId'; import { useServices } from '@/lib/services/ServiceProvider'; import { LeagueStewardingViewModel } from '@/lib/view-models/LeagueStewardingViewModel'; import { LeagueRoleUtility } from '@/lib/utilities/LeagueRoleUtility'; import { AlertCircle, AlertTriangle, Calendar, ChevronRight, Flag, Gavel, MapPin, Video } from 'lucide-react'; import Link from 'next/link'; import { useParams } from 'next/navigation'; import { useMemo, useState } from 'react'; // Shared state components import { useDataFetching } from '@/components/shared/hooks/useDataFetching'; import { StateContainer } from '@/components/shared/state/StateContainer'; import { LoadingWrapper } from '@/components/shared/state/LoadingWrapper'; export default function LeagueStewardingPage() { const params = useParams(); const leagueId = params.id as string; const currentDriverId = useEffectiveDriverId(); const { leagueStewardingService, leagueMembershipService } = useServices(); const [activeTab, setActiveTab] = useState<'pending' | 'history'>('pending'); const [selectedProtest, setSelectedProtest] = useState(null); const [expandedRaces, setExpandedRaces] = useState>(new Set()); const [showQuickPenaltyModal, setShowQuickPenaltyModal] = useState(false); // Check admin status const { data: isAdmin, isLoading: adminLoading } = useDataFetching({ queryKey: ['leagueMembership', leagueId, currentDriverId], queryFn: async () => { const membership = await leagueMembershipService.getMembership(leagueId, currentDriverId); return membership ? LeagueRoleUtility.isLeagueAdminOrHigherRole(membership.role) : false; }, }); // Load stewarding data (only if admin) const { data: stewardingData, isLoading: dataLoading, error, retry } = useDataFetching({ queryKey: ['leagueStewarding', leagueId], queryFn: () => leagueStewardingService.getLeagueStewardingData(leagueId), enabled: !!isAdmin, onSuccess: (data) => { // Auto-expand races with pending protests const racesWithPending = new Set(); data.pendingRaces.forEach(race => { racesWithPending.add(race.race.id); }); setExpandedRaces(racesWithPending); }, }); // Filter races based on active tab const filteredRaces = useMemo(() => { return activeTab === 'pending' ? stewardingData?.pendingRaces ?? [] : stewardingData?.historyRaces ?? []; }, [stewardingData, activeTab]); const handleAcceptProtest = async ( protestId: string, penaltyType: string, penaltyValue: number, stewardNotes: string ) => { await leagueStewardingService.reviewProtest({ protestId, stewardId: currentDriverId, decision: 'uphold', decisionNotes: stewardNotes, }); // Find the protest to get details for penalty let foundProtest: any | undefined; stewardingData?.racesWithData.forEach(raceData => { const p = raceData.pendingProtests.find(pr => pr.id === protestId) || raceData.resolvedProtests.find(pr => pr.id === protestId); if (p) foundProtest = { ...p, raceId: raceData.race.id }; }); if (foundProtest) { await leagueStewardingService.applyPenalty({ raceId: foundProtest.raceId, driverId: foundProtest.accusedDriverId, stewardId: currentDriverId, type: penaltyType, value: penaltyValue, reason: foundProtest.incident.description, protestId, notes: stewardNotes, }); } // Retry to refresh data await retry(); }; const handleRejectProtest = async (protestId: string, stewardNotes: string) => { await leagueStewardingService.reviewProtest({ protestId, stewardId: currentDriverId, decision: 'dismiss', decisionNotes: stewardNotes, }); // Retry to refresh data await retry(); }; const toggleRaceExpanded = (raceId: string) => { setExpandedRaces(prev => { const next = new Set(prev); if (next.has(raceId)) { next.delete(raceId); } else { next.add(raceId); } return next; }); }; const getStatusBadge = (status: string) => { switch (status) { case 'pending': case 'under_review': return Pending; case 'upheld': return Upheld; case 'dismissed': return Dismissed; case 'withdrawn': return Withdrawn; default: return null; } }; // Show loading for admin check if (adminLoading) { return ; } // Show access denied if not admin if (!isAdmin) { return (

Admin Access Required

Only league admins can access stewarding functions.

); } return ( {(data) => (

Stewarding

Quick overview of protests and penalties across all races

{/* Stats summary */} {/* Tab navigation */}
{/* Content */} {filteredRaces.length === 0 ? (

{activeTab === 'pending' ? 'All Clear!' : 'No History Yet'}

{activeTab === 'pending' ? 'No pending protests to review' : 'No resolved protests or penalties'}

) : (
{filteredRaces.map(({ race, pendingProtests, resolvedProtests, penalties }) => { const isExpanded = expandedRaces.has(race.id); const displayProtests = activeTab === 'pending' ? pendingProtests : resolvedProtests; return (
{/* Race Header */} {/* Expanded Content */} {isExpanded && (
{displayProtests.length === 0 && penalties.length === 0 ? (

No items to display

) : ( <> {displayProtests.map((protest) => { const protester = data.driverMap[protest.protestingDriverId]; const accused = data.driverMap[protest.accusedDriverId]; const daysSinceFiled = Math.floor((Date.now() - new Date(protest.filedAt).getTime()) / (1000 * 60 * 60 * 24)); const isUrgent = daysSinceFiled > 2 && (protest.status === 'pending' || protest.status === 'under_review'); return (
{protester?.name || 'Unknown'} vs {accused?.name || 'Unknown'} {getStatusBadge(protest.status)} {isUrgent && ( {daysSinceFiled}d old )}
Lap {protest.incident.lap} Filed {new Date(protest.filedAt).toLocaleDateString()} {protest.proofVideoUrl && ( <> )}

{protest.incident.description}

{protest.decisionNotes && (

Steward: {protest.decisionNotes}

)}
{(protest.status === 'pending' || protest.status === 'under_review') && ( )}
); })} {activeTab === 'history' && penalties.map((penalty) => { const driver = data.driverMap[penalty.driverId]; return (
{driver?.name || 'Unknown'} {penalty.type.replace('_', ' ')}

{penalty.reason}

{penalty.type === 'time_penalty' && `+${penalty.value}s`} {penalty.type === 'grid_penalty' && `+${penalty.value} grid`} {penalty.type === 'points_deduction' && `-${penalty.value} pts`} {penalty.type === 'disqualification' && 'DSQ'} {penalty.type === 'warning' && 'Warning'} {penalty.type === 'license_points' && `${penalty.value} LP`}
); })} )}
)}
); })}
)}
{activeTab === 'history' && ( setShowQuickPenaltyModal(true)} /> )} {selectedProtest && ( setSelectedProtest(null)} onAccept={handleAcceptProtest} onReject={handleRejectProtest} /> )} {showQuickPenaltyModal && stewardingData && ( setShowQuickPenaltyModal(false)} adminId={currentDriverId} races={stewardingData.racesWithData.map(r => ({ id: r.race.id, track: r.race.track, scheduledAt: r.race.scheduledAt }))} /> )}
)}
); }