163 lines
5.5 KiB
TypeScript
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}
|
|
/>
|
|
);
|
|
}
|