197 lines
6.1 KiB
TypeScript
197 lines
6.1 KiB
TypeScript
'use client';
|
|
|
|
import { PenaltyFAB } from '@/ui/PenaltyFAB';
|
|
import { QuickPenaltyModal } from '@/components/leagues/QuickPenaltyModal';
|
|
import { ReviewProtestModal } from '@/components/leagues/ReviewProtestModal';
|
|
import { StewardingStats } from '@/components/leagues/StewardingStats';
|
|
import { Button } from '@/ui/Button';
|
|
import { Card } from '@/ui/Card';
|
|
import { useLeagueStewardingMutations } from "@/hooks/league/useLeagueStewardingMutations";
|
|
import {
|
|
AlertCircle,
|
|
AlertTriangle,
|
|
Calendar,
|
|
ChevronRight,
|
|
Flag,
|
|
Gavel,
|
|
MapPin,
|
|
Video
|
|
} from 'lucide-react';
|
|
import Link from 'next/link';
|
|
import { useMemo, useState } from 'react';
|
|
import { PendingProtestsList } from '@/ui/PendingProtestsList';
|
|
import { PenaltyHistoryList } from '@/components/leagues/PenaltyHistoryList';
|
|
|
|
interface StewardingData {
|
|
totalPending: number;
|
|
totalResolved: number;
|
|
totalPenalties: number;
|
|
racesWithData: Array<{
|
|
race: { id: string; track: string; scheduledAt: Date; car?: string };
|
|
pendingProtests: any[];
|
|
resolvedProtests: any[];
|
|
penalties: any[];
|
|
}>;
|
|
allDrivers: any[];
|
|
driverMap: Record<string, any>;
|
|
}
|
|
|
|
interface StewardingTemplateProps {
|
|
data: StewardingData;
|
|
leagueId: string;
|
|
currentDriverId: string;
|
|
onRefetch: () => void;
|
|
}
|
|
|
|
export function StewardingTemplate({ data, leagueId, currentDriverId, onRefetch }: StewardingTemplateProps) {
|
|
const [activeTab, setActiveTab] = useState<'pending' | 'history'>('pending');
|
|
const [selectedProtest, setSelectedProtest] = useState<any | 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 data.racesWithData.flatMap(r => r.pendingProtests);
|
|
}, [data]);
|
|
|
|
const allResolvedProtests = useMemo(() => {
|
|
return data.racesWithData.flatMap(r => r.resolvedProtests);
|
|
}, [data]);
|
|
|
|
const racesMap = useMemo(() => {
|
|
const map: Record<string, any> = {};
|
|
data.racesWithData.forEach(r => {
|
|
map[r.race.id] = r.race;
|
|
});
|
|
return map;
|
|
}, [data]);
|
|
|
|
const handleAcceptProtest = async (
|
|
protestId: string,
|
|
penaltyType: string,
|
|
penaltyValue: number,
|
|
stewardNotes: string
|
|
) => {
|
|
// Find the protest to get details for penalty
|
|
let foundProtest: any | undefined;
|
|
data.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) {
|
|
acceptProtestMutation.mutate({
|
|
protestId,
|
|
penaltyType,
|
|
penaltyValue,
|
|
stewardNotes,
|
|
raceId: foundProtest.raceId,
|
|
accusedDriverId: foundProtest.accusedDriverId,
|
|
reason: foundProtest.incident.description,
|
|
});
|
|
}
|
|
};
|
|
|
|
const handleRejectProtest = async (protestId: string, stewardNotes: string) => {
|
|
rejectProtestMutation.mutate({
|
|
protestId,
|
|
stewardNotes,
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<Card>
|
|
<div className="flex items-center justify-between mb-6">
|
|
<div>
|
|
<h2 className="text-xl font-semibold text-white">Stewarding</h2>
|
|
<p className="text-sm text-gray-400 mt-1">
|
|
Quick overview of protests and penalties across all races
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stats summary */}
|
|
<StewardingStats
|
|
totalPending={data.totalPending}
|
|
totalResolved={data.totalResolved}
|
|
totalPenalties={data.totalPenalties}
|
|
/>
|
|
|
|
{/* Tab navigation */}
|
|
<div className="border-b border-charcoal-outline mb-6">
|
|
<div className="flex gap-4">
|
|
<button
|
|
onClick={() => setActiveTab('pending')}
|
|
className={`pb-3 px-1 font-medium transition-colors ${
|
|
activeTab === 'pending'
|
|
? 'text-primary-blue border-b-2 border-primary-blue'
|
|
: 'text-gray-400 hover:text-white'
|
|
}`}
|
|
>
|
|
Pending Protests
|
|
{data.totalPending > 0 && (
|
|
<span className="ml-2 px-2 py-0.5 text-xs bg-warning-amber/20 text-warning-amber rounded-full">
|
|
{data.totalPending}
|
|
</span>
|
|
)}
|
|
</button>
|
|
<button
|
|
onClick={() => setActiveTab('history')}
|
|
className={`pb-3 px-1 font-medium transition-colors ${
|
|
activeTab === 'history'
|
|
? 'text-primary-blue border-b-2 border-primary-blue'
|
|
: 'text-gray-400 hover:text-white'
|
|
}`}
|
|
>
|
|
History
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
{activeTab === 'pending' ? (
|
|
<PendingProtestsList
|
|
protests={allPendingProtests}
|
|
races={racesMap}
|
|
drivers={data.driverMap}
|
|
leagueId={leagueId}
|
|
onReviewProtest={setSelectedProtest}
|
|
onProtestReviewed={onRefetch}
|
|
/>
|
|
) : (
|
|
<PenaltyHistoryList
|
|
protests={allResolvedProtests}
|
|
races={racesMap}
|
|
drivers={data.driverMap}
|
|
/>
|
|
)}
|
|
</Card>
|
|
|
|
{activeTab === 'history' && (
|
|
<PenaltyFAB onClick={() => setShowQuickPenaltyModal(true)} />
|
|
)}
|
|
|
|
{selectedProtest && (
|
|
<ReviewProtestModal
|
|
protest={selectedProtest}
|
|
onClose={() => setSelectedProtest(null)}
|
|
onAccept={handleAcceptProtest}
|
|
onReject={handleRejectProtest}
|
|
/>
|
|
)}
|
|
|
|
{showQuickPenaltyModal && (
|
|
<QuickPenaltyModal
|
|
drivers={data.allDrivers}
|
|
onClose={() => setShowQuickPenaltyModal(false)}
|
|
adminId={currentDriverId || ''}
|
|
races={data.racesWithData.map(r => ({ id: r.race.id, track: r.race.track, scheduledAt: r.race.scheduledAt }))}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
} |