Files
gridpilot.gg/apps/website/client-wrapper/StewardingPageClient.tsx
2026-01-19 02:14:53 +01:00

163 lines
5.5 KiB
TypeScript

'use client';
import { useLeagueStewardingMutations } from "@/hooks/league/useLeagueStewardingMutations";
import type { StewardingViewData } from '@/lib/view-data/leagues/StewardingViewData';
import { DriverViewModel } from '@/lib/view-models/DriverViewModel';
import { ProtestViewModel } from '@/lib/view-models/ProtestViewModel';
import { RaceViewModel } from '@/lib/view-models/RaceViewModel';
import { useMemo, useState } from 'react';
import { StewardingTemplate } from '@/templates/StewardingTemplate';
import { ClientWrapperProps } from '@/lib/contracts/components/ComponentContracts';
interface StewardingPageClientProps extends ClientWrapperProps<StewardingViewData> {
leagueId: string;
currentDriverId: string;
onRefetch: () => void;
}
export function StewardingPageClient({ viewData, currentDriverId, onRefetch }: StewardingPageClientProps) {
const [activeTab, setActiveTab] = useState<'pending' | 'history'>('pending');
const [selectedProtest, setSelectedProtest] = useState<ProtestViewModel | null>(null);
const [showQuickPenaltyModal, setShowQuickPenaltyModal] = useState(false);
// Mutations using domain hook
const { acceptProtestMutation, rejectProtestMutation } = useLeagueStewardingMutations(onRefetch);
// Flatten protests for the specialized list components
const allPendingProtests = useMemo(() => {
return viewData.races.flatMap(r => r.pendingProtests.map(p => ({
id: p.id,
raceName: r.track || 'Unknown Track',
protestingDriver: viewData.drivers.find(d => d.id === p.protestingDriverId)?.name || 'Unknown',
accusedDriver: viewData.drivers.find(d => d.id === p.accusedDriverId)?.name || 'Unknown',
description: p.incident.description,
submittedAt: p.filedAt,
status: p.status as 'pending' | 'under_review' | 'resolved' | 'rejected',
})));
}, [viewData.races, viewData.drivers]);
const allResolvedProtests = useMemo(() => {
return viewData.races.flatMap(r => r.resolvedProtests.map(p => new ProtestViewModel({
id: p.id,
protestingDriverId: p.protestingDriverId,
accusedDriverId: p.accusedDriverId,
description: p.incident.description,
submittedAt: p.filedAt,
status: p.status,
raceId: r.id,
incident: p.incident,
proofVideoUrl: p.proofVideoUrl,
decisionNotes: p.decisionNotes,
} as never)));
}, [viewData.races]);
const racesMap = useMemo(() => {
const map: Record<string, RaceViewModel> = {};
viewData.races.forEach(r => {
map[r.id] = new RaceViewModel({
id: r.id,
name: '',
date: r.scheduledAt,
track: r.track,
} as never);
});
return map;
}, [viewData.races]);
const driverMap = useMemo(() => {
const map: Record<string, DriverViewModel> = {};
viewData.drivers.forEach(d => {
map[d.id] = new DriverViewModel({
id: d.id,
name: d.name,
iracingId: '',
country: '',
joinedAt: '',
avatarUrl: null,
});
});
return map;
}, [viewData.drivers]);
const handleAcceptProtest = async (
protestId: string,
penaltyType: string,
penaltyValue: number,
stewardNotes: string
) => {
// Find the protest to get details for penalty
let foundProtest: { raceId: string; accusedDriverId: string; incident: { description: string } } | undefined;
viewData.races.forEach((raceData) => {
const p = raceData.pendingProtests.find((pr) => pr.id === protestId) ||
raceData.resolvedProtests.find((pr) => pr.id === protestId);
if (p) foundProtest = {
raceId: raceData.id,
accusedDriverId: p.accusedDriverId,
incident: { description: p.incident.description }
};
});
if (foundProtest) {
await acceptProtestMutation.mutateAsync({
protestId,
penaltyType,
penaltyValue,
stewardNotes,
raceId: foundProtest.raceId,
accusedDriverId: foundProtest.accusedDriverId,
reason: foundProtest.incident.description,
});
}
};
const handleRejectProtest = async (protestId: string, stewardNotes: string) => {
await rejectProtestMutation.mutateAsync({
protestId,
stewardNotes,
});
};
const handleReviewProtest = (id: string) => {
// Find the protest in the data
let foundProtest: ProtestViewModel | null = null;
viewData.races.forEach(r => {
const p = r.pendingProtests.find(p => p.id === id);
if (p) {
foundProtest = new ProtestViewModel({
id: p.id,
protestingDriverId: p.protestingDriverId,
accusedDriverId: p.accusedDriverId,
description: p.incident.description,
submittedAt: p.filedAt,
status: p.status,
raceId: r.id,
incident: p.incident,
proofVideoUrl: p.proofVideoUrl,
decisionNotes: p.decisionNotes,
} as never);
}
});
if (foundProtest) setSelectedProtest(foundProtest);
};
return (
<StewardingTemplate
viewData={viewData}
activeTab={activeTab}
onTabChange={setActiveTab}
selectedProtest={selectedProtest}
onReviewProtest={handleReviewProtest}
onCloseProtestModal={() => setSelectedProtest(null)}
onAcceptProtest={handleAcceptProtest}
onRejectProtest={handleRejectProtest}
showQuickPenaltyModal={showQuickPenaltyModal}
setShowQuickPenaltyModal={setShowQuickPenaltyModal}
allPendingProtests={allPendingProtests}
allResolvedProtests={allResolvedProtests}
racesMap={racesMap}
driverMap={driverMap}
currentDriverId={currentDriverId}
/>
);
}