'use client'; import { useState, useEffect } from 'react'; import { useParams, useRouter } from 'next/navigation'; import { PageWrapper } from '@/components/shared/state/PageWrapper'; import { RaceStewardingTemplate, StewardingTab } from '@/templates/RaceStewardingTemplate'; import { RacesApiClient } from '@/lib/api/races/RacesApiClient'; import { ProtestsApiClient } from '@/lib/api/protests/ProtestsApiClient'; import { PenaltiesApiClient } from '@/lib/api/penalties/PenaltiesApiClient'; import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter'; import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger'; import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl'; import { useLeagueMemberships } from "@/lib/hooks/league/useLeagueMemberships"; import { useEffectiveDriverId } from "@/lib/hooks/useEffectiveDriverId"; import { LeagueRoleUtility } from '@/lib/utilities/LeagueRoleUtility'; import { Gavel } from 'lucide-react'; // Define the view model structure locally to avoid type issues interface RaceStewardingViewModel { race: any; league: any; protests: any[]; penalties: any[]; driverMap: Record; pendingProtests: any[]; resolvedProtests: any[]; pendingCount: number; resolvedCount: number; penaltiesCount: number; } export default function RaceStewardingPage() { const router = useRouter(); const params = useParams(); const raceId = params.id as string; const currentDriverId = useEffectiveDriverId() || ''; // Data state const [pageData, setPageData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); // UI State const [activeTab, setActiveTab] = useState('pending'); // Fetch data on mount and when raceId/currentDriverId changes useEffect(() => { async function fetchData() { if (!raceId) return; try { setIsLoading(true); setError(null); // Manual wiring: create dependencies const baseUrl = getWebsiteApiBaseUrl(); const logger = new ConsoleLogger(); const errorReporter = new EnhancedErrorReporter(logger, { showUserNotifications: true, logToConsole: true, reportToExternal: process.env.NODE_ENV === 'production', }); // Create API clients const racesApiClient = new RacesApiClient(baseUrl, errorReporter, logger); const protestsApiClient = new ProtestsApiClient(baseUrl, errorReporter, logger); const penaltiesApiClient = new PenaltiesApiClient(baseUrl, errorReporter, logger); // Fetch data in parallel const [raceDetail, protests, penalties] = await Promise.all([ racesApiClient.getDetail(raceId, currentDriverId), protestsApiClient.getRaceProtests(raceId), penaltiesApiClient.getRacePenalties(raceId), ]); // Transform data to match view model structure const data: RaceStewardingViewModel = { race: raceDetail.race, league: raceDetail.league, protests: protests.protests.map(p => ({ id: p.id, protestingDriverId: p.protestingDriverId, accusedDriverId: p.accusedDriverId, incident: { lap: p.lap, description: p.description, }, filedAt: p.filedAt, status: p.status, })), penalties: penalties.penalties, driverMap: { ...protests.driverMap, ...penalties.driverMap }, pendingProtests: [], resolvedProtests: [], pendingCount: 0, resolvedCount: 0, penaltiesCount: 0, }; // Calculate derived properties data.pendingProtests = data.protests.filter(p => p.status === 'pending' || p.status === 'under_review'); data.resolvedProtests = data.protests.filter(p => p.status === 'upheld' || p.status === 'dismissed' || p.status === 'withdrawn' ); data.pendingCount = data.pendingProtests.length; data.resolvedCount = data.resolvedProtests.length; data.penaltiesCount = data.penalties.length; if (data) { setPageData(data); } else { setPageData(null); } } catch (err) { setError(err instanceof Error ? err : new Error('Failed to fetch stewarding data')); setPageData(null); } finally { setIsLoading(false); } } fetchData(); }, [raceId, currentDriverId]); // Fetch membership const { data: membershipsData } = useLeagueMemberships(pageData?.league?.id || '', currentDriverId || ''); const currentMembership = membershipsData?.members.find(m => m.driverId === currentDriverId); const isAdmin = currentMembership ? LeagueRoleUtility.isLeagueAdminOrHigherRole(currentMembership.role) : false; // Actions const handleBack = () => { router.push(`/races/${raceId}`); }; const handleReviewProtest = (protestId: string) => { // Navigate to protest review page router.push(`/leagues/${pageData?.league?.id}/stewarding/protests/${protestId}`); }; // Transform data for template const templateData = pageData ? { race: pageData.race, league: pageData.league, pendingProtests: pageData.pendingProtests, resolvedProtests: pageData.resolvedProtests, penalties: pageData.penalties, driverMap: pageData.driverMap, pendingCount: pageData.pendingCount, resolvedCount: pageData.resolvedCount, penaltiesCount: pageData.penaltiesCount, } : undefined; const retry = async () => { try { setIsLoading(true); setError(null); // Manual wiring: create dependencies const baseUrl = getWebsiteApiBaseUrl(); const logger = new ConsoleLogger(); const errorReporter = new EnhancedErrorReporter(logger, { showUserNotifications: true, logToConsole: true, reportToExternal: process.env.NODE_ENV === 'production', }); // Create API clients const racesApiClient = new RacesApiClient(baseUrl, errorReporter, logger); const protestsApiClient = new ProtestsApiClient(baseUrl, errorReporter, logger); const penaltiesApiClient = new PenaltiesApiClient(baseUrl, errorReporter, logger); // Fetch data in parallel const [raceDetail, protests, penalties] = await Promise.all([ racesApiClient.getDetail(raceId, currentDriverId), protestsApiClient.getRaceProtests(raceId), penaltiesApiClient.getRacePenalties(raceId), ]); // Transform data to match view model structure const data: RaceStewardingViewModel = { race: raceDetail.race, league: raceDetail.league, protests: protests.protests.map(p => ({ id: p.id, protestingDriverId: p.protestingDriverId, accusedDriverId: p.accusedDriverId, incident: { lap: p.lap, description: p.description, }, filedAt: p.filedAt, status: p.status, })), penalties: penalties.penalties, driverMap: { ...protests.driverMap, ...penalties.driverMap }, pendingProtests: [], resolvedProtests: [], pendingCount: 0, resolvedCount: 0, penaltiesCount: 0, }; // Calculate derived properties data.pendingProtests = data.protests.filter(p => p.status === 'pending' || p.status === 'under_review'); data.resolvedProtests = data.protests.filter(p => p.status === 'upheld' || p.status === 'dismissed' || p.status === 'withdrawn' ); data.pendingCount = data.pendingProtests.length; data.resolvedCount = data.resolvedProtests.length; data.penaltiesCount = data.penalties.length; if (data) { setPageData(data); } } catch (err) { setError(err instanceof Error ? err : new Error('Failed to fetch stewarding data')); } finally { setIsLoading(false); } }; return ( ( )} loading={{ variant: 'skeleton', message: 'Loading stewarding data...' }} errorConfig={{ variant: 'full-screen' }} empty={{ icon: Gavel, title: 'No stewarding data', description: 'No protests or penalties for this race', action: { label: 'Back to Race', onClick: handleBack } }} /> ); }