Files
gridpilot.gg/apps/website/app/leagues/[id]/stewarding/StewardingTemplate.tsx
2026-01-15 17:12:24 +01:00

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>
);
}