website refactor
This commit is contained in:
@@ -1,31 +1,21 @@
|
||||
'use client';
|
||||
|
||||
import { PenaltyHistoryList } from '@/components/leagues/PenaltyHistoryList';
|
||||
import { QuickPenaltyModal } from '@/components/leagues/QuickPenaltyModal';
|
||||
import { ReviewProtestModal } from '@/components/leagues/ReviewProtestModal';
|
||||
import { StewardingQueuePanel } from '@/components/leagues/StewardingQueuePanel';
|
||||
import { StewardingStats } from '@/components/leagues/StewardingStats';
|
||||
import { PenaltyFAB } from '@/components/races/PenaltyFAB';
|
||||
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 { Box } from '@/ui/Box';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { StewardingTemplate } from '@/templates/StewardingTemplate';
|
||||
import { ClientWrapperProps } from '@/lib/contracts/components/ComponentContracts';
|
||||
|
||||
interface StewardingTemplateProps {
|
||||
data: StewardingViewData;
|
||||
interface StewardingPageClientProps extends ClientWrapperProps<StewardingViewData> {
|
||||
leagueId: string;
|
||||
currentDriverId: string;
|
||||
onRefetch: () => void;
|
||||
}
|
||||
|
||||
export function StewardingPageClient({ data, currentDriverId, onRefetch }: StewardingTemplateProps) {
|
||||
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);
|
||||
@@ -35,19 +25,19 @@ export function StewardingPageClient({ data, currentDriverId, onRefetch }: Stewa
|
||||
|
||||
// Flatten protests for the specialized list components
|
||||
const allPendingProtests = useMemo(() => {
|
||||
return data.races.flatMap(r => r.pendingProtests.map(p => ({
|
||||
return viewData.races.flatMap(r => r.pendingProtests.map(p => ({
|
||||
id: p.id,
|
||||
raceName: r.track || 'Unknown Track',
|
||||
protestingDriver: data.drivers.find(d => d.id === p.protestingDriverId)?.name || 'Unknown',
|
||||
accusedDriver: data.drivers.find(d => d.id === p.accusedDriverId)?.name || 'Unknown',
|
||||
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',
|
||||
})));
|
||||
}, [data.races, data.drivers]);
|
||||
}, [viewData.races, viewData.drivers]);
|
||||
|
||||
const allResolvedProtests = useMemo(() => {
|
||||
return data.races.flatMap(r => r.resolvedProtests.map(p => new ProtestViewModel({
|
||||
return viewData.races.flatMap(r => r.resolvedProtests.map(p => new ProtestViewModel({
|
||||
id: p.id,
|
||||
protestingDriverId: p.protestingDriverId,
|
||||
accusedDriverId: p.accusedDriverId,
|
||||
@@ -59,11 +49,11 @@ export function StewardingPageClient({ data, currentDriverId, onRefetch }: Stewa
|
||||
proofVideoUrl: p.proofVideoUrl,
|
||||
decisionNotes: p.decisionNotes,
|
||||
} as never)));
|
||||
}, [data.races]);
|
||||
}, [viewData.races]);
|
||||
|
||||
const racesMap = useMemo(() => {
|
||||
const map: Record<string, RaceViewModel> = {};
|
||||
data.races.forEach(r => {
|
||||
viewData.races.forEach(r => {
|
||||
map[r.id] = new RaceViewModel({
|
||||
id: r.id,
|
||||
name: '',
|
||||
@@ -72,11 +62,11 @@ export function StewardingPageClient({ data, currentDriverId, onRefetch }: Stewa
|
||||
} as never);
|
||||
});
|
||||
return map;
|
||||
}, [data.races]);
|
||||
}, [viewData.races]);
|
||||
|
||||
const driverMap = useMemo(() => {
|
||||
const map: Record<string, DriverViewModel> = {};
|
||||
data.drivers.forEach(d => {
|
||||
viewData.drivers.forEach(d => {
|
||||
map[d.id] = new DriverViewModel({
|
||||
id: d.id,
|
||||
name: d.name,
|
||||
@@ -87,7 +77,7 @@ export function StewardingPageClient({ data, currentDriverId, onRefetch }: Stewa
|
||||
});
|
||||
});
|
||||
return map;
|
||||
}, [data.drivers]);
|
||||
}, [viewData.drivers]);
|
||||
|
||||
const handleAcceptProtest = async (
|
||||
protestId: string,
|
||||
@@ -97,7 +87,7 @@ export function StewardingPageClient({ data, currentDriverId, onRefetch }: Stewa
|
||||
) => {
|
||||
// Find the protest to get details for penalty
|
||||
let foundProtest: { raceId: string; accusedDriverId: string; incident: { description: string } } | undefined;
|
||||
data.races.forEach((raceData) => {
|
||||
viewData.races.forEach((raceData) => {
|
||||
const p = raceData.pendingProtests.find((pr) => pr.id === protestId) ||
|
||||
raceData.resolvedProtests.find((pr) => pr.id === protestId);
|
||||
if (p) foundProtest = {
|
||||
@@ -108,7 +98,7 @@ export function StewardingPageClient({ data, currentDriverId, onRefetch }: Stewa
|
||||
});
|
||||
|
||||
if (foundProtest) {
|
||||
acceptProtestMutation.mutate({
|
||||
await acceptProtestMutation.mutateAsync({
|
||||
protestId,
|
||||
penaltyType,
|
||||
penaltyValue,
|
||||
@@ -121,7 +111,7 @@ export function StewardingPageClient({ data, currentDriverId, onRefetch }: Stewa
|
||||
};
|
||||
|
||||
const handleRejectProtest = async (protestId: string, stewardNotes: string) => {
|
||||
rejectProtestMutation.mutate({
|
||||
await rejectProtestMutation.mutateAsync({
|
||||
protestId,
|
||||
stewardNotes,
|
||||
});
|
||||
@@ -130,7 +120,7 @@ export function StewardingPageClient({ data, currentDriverId, onRefetch }: Stewa
|
||||
const handleReviewProtest = (id: string) => {
|
||||
// Find the protest in the data
|
||||
let foundProtest: ProtestViewModel | null = null;
|
||||
data.races.forEach(r => {
|
||||
viewData.races.forEach(r => {
|
||||
const p = r.pendingProtests.find(p => p.id === id);
|
||||
if (p) {
|
||||
foundProtest = new ProtestViewModel({
|
||||
@@ -151,96 +141,22 @@ export function StewardingPageClient({ data, currentDriverId, onRefetch }: Stewa
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap={6}>
|
||||
<StewardingStats
|
||||
totalPending={data.totalPending}
|
||||
totalResolved={data.totalResolved}
|
||||
totalPenalties={data.totalPenalties}
|
||||
/>
|
||||
|
||||
{/* Tab navigation */}
|
||||
<Box borderBottom borderColor="border-charcoal-outline">
|
||||
<Stack direction="row" gap={4}>
|
||||
<Box
|
||||
borderBottom={activeTab === 'pending'}
|
||||
borderColor={activeTab === 'pending' ? 'border-primary-blue' : undefined}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => setActiveTab('pending')}
|
||||
rounded="none"
|
||||
>
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Text weight="medium" color={activeTab === 'pending' ? 'text-primary-blue' : undefined}>Pending Protests</Text>
|
||||
{data.totalPending > 0 && (
|
||||
<Box px={2} py={0.5} fontSize="0.75rem" bg="bg-warning-amber/20" color="text-warning-amber" rounded="full">
|
||||
{data.totalPending}
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Button>
|
||||
</Box>
|
||||
<Box
|
||||
borderBottom={activeTab === 'history'}
|
||||
borderColor={activeTab === 'history' ? 'border-primary-blue' : undefined}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => setActiveTab('history')}
|
||||
rounded="none"
|
||||
>
|
||||
<Text weight="medium" color={activeTab === 'history' ? 'text-primary-blue' : undefined}>History</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{/* Content */}
|
||||
{activeTab === 'pending' ? (
|
||||
<StewardingQueuePanel
|
||||
protests={allPendingProtests}
|
||||
onReview={handleReviewProtest}
|
||||
/>
|
||||
) : (
|
||||
<Card>
|
||||
<Box p={6}>
|
||||
<PenaltyHistoryList
|
||||
protests={allResolvedProtests}
|
||||
races={racesMap}
|
||||
drivers={driverMap}
|
||||
/>
|
||||
</Box>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{activeTab === 'history' && (
|
||||
<PenaltyFAB onClick={() => setShowQuickPenaltyModal(true)} />
|
||||
)}
|
||||
|
||||
{selectedProtest && (
|
||||
<ReviewProtestModal
|
||||
protest={selectedProtest}
|
||||
onClose={() => setSelectedProtest(null)}
|
||||
onAccept={handleAcceptProtest}
|
||||
onReject={handleRejectProtest}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showQuickPenaltyModal && (
|
||||
<QuickPenaltyModal
|
||||
drivers={data.drivers.map(d => new DriverViewModel({
|
||||
id: d.id,
|
||||
name: d.name,
|
||||
iracingId: '',
|
||||
country: '',
|
||||
joinedAt: '',
|
||||
avatarUrl: null,
|
||||
}))}
|
||||
onClose={() => setShowQuickPenaltyModal(false)}
|
||||
adminId={currentDriverId || ''}
|
||||
races={data.races.map(r => ({ id: r.id, track: r.track, scheduledAt: new Date(r.scheduledAt) }))}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
<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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user