Files
gridpilot.gg/apps/website/templates/RaceStewardingTemplate.tsx
Marc Mintel 09632d004d
Some checks failed
CI / lint-typecheck (pull_request) Failing after 12s
CI / tests (pull_request) Has been skipped
CI / contract-tests (pull_request) Has been skipped
CI / e2e-tests (pull_request) Has been skipped
CI / comment-pr (pull_request) Has been skipped
CI / commit-types (pull_request) Has been skipped
code quality
2026-01-26 22:16:33 +01:00

215 lines
8.5 KiB
TypeScript

import { ProtestCard } from '@/components/leagues/ProtestCardWrapper';
import { StewardingTabs } from '@/components/leagues/StewardingTabs';
import { RaceDetailsHeader } from '@/components/races/RaceDetailsHeader';
import { RacePenaltyRow } from '@/components/races/RacePenaltyRowWrapper';
import { RaceStewardingStats } from '@/components/races/RaceStewardingStats';
import type { RaceStewardingViewData } from '@/lib/view-data/RaceStewardingViewData';
import { Box } from '@/ui/Box';
import { Container } from '@/ui/Container';
import { Grid } from '@/ui/Grid';
import { GridItem } from '@/ui/GridItem';
import { Icon } from '@/ui/Icon';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { CheckCircle, Flag, Gavel, Info } from 'lucide-react';
export type StewardingTab = 'pending' | 'resolved' | 'penalties';
interface RaceStewardingTemplateProps {
viewData: RaceStewardingViewData;
isLoading: boolean;
error?: Error | null;
// Actions
onBack: () => void;
onReviewProtest: (protestId: string) => void;
// User state
isAdmin: boolean;
// UI State
activeTab: StewardingTab;
setActiveTab: (tab: StewardingTab) => void;
}
export function RaceStewardingTemplate({
viewData,
isLoading,
onBack,
onReviewProtest,
isAdmin,
activeTab,
setActiveTab,
}: RaceStewardingTemplateProps) {
const formatDate = (date: string) => {
return date; // Simplified for template
};
if (isLoading) {
return (
<Container size="lg" spacing="lg">
<Stack alignItems="center">
<Text color="text-gray-400">Loading stewarding data...</Text>
</Stack>
</Container>
);
}
if (!viewData?.race) {
return (
<Container size="md" spacing="lg">
<Box bg="bg-surface-charcoal" border borderColor="border-outline-steel" p={12} textAlign="center" rounded="xl">
<Stack alignItems="center" gap={4}>
<Text as="h2" size="xl" weight="bold" color="text-white">Race Not Found</Text>
<Text color="text-gray-400">{`The race you're looking for doesn't exist.`}</Text>
<Box
as="button"
onClick={onBack}
mt={4}
px={6}
py={2}
bg="bg-primary-accent"
color="text-white"
weight="bold"
rounded="md"
hoverBg="bg-primary-accent"
bgOpacity={0.8}
transition
>
Back to Schedule
</Box>
</Stack>
</Box>
</Container>
);
}
return (
<Box as="main" minHeight="screen" bg="bg-base-black">
<RaceDetailsHeader
title="Stewarding Dashboard"
leagueName={viewData.race.track}
trackName={viewData.race.track}
scheduledAt={viewData.race.scheduledAt}
status="completed"
onBack={onBack}
/>
<Container size="lg" spacing="md">
<Stack gap={8}>
<Grid cols={12} gap={6}>
<GridItem colSpan={12} lgSpan={8}>
<Stack gap={6}>
<StewardingTabs
activeTab={activeTab}
onTabChange={setActiveTab}
pendingCount={viewData.pendingProtests.length}
/>
{activeTab === 'pending' && (
<Stack gap={4}>
{viewData.pendingProtests.length === 0 ? (
<Box bg="bg-surface-charcoal" border borderColor="border-outline-steel" p={12} textAlign="center">
<Stack alignItems="center" gap={4}>
<Icon icon={Flag} size={8} color="var(--color-success)" />
<Box>
<Text weight="semibold" size="lg" color="text-white" block mb={1}>All Clear!</Text>
<Text size="sm" color="text-gray-400">No pending protests to review</Text>
</Box>
</Stack>
</Box>
) : (
viewData.pendingProtests.map((protest) => (
<ProtestCard
key={protest.id}
protest={{ ...protest, proofVideoUrl: protest.proofVideoUrl ?? undefined, decisionNotes: protest.decisionNotes ?? undefined }}
protester={viewData.driverMap[protest.protestingDriverId]}
accused={viewData.driverMap[protest.accusedDriverId]}
isAdmin={isAdmin}
onReview={onReviewProtest}
formatDate={formatDate}
/>
))
)}
</Stack>
)}
{activeTab === 'resolved' && (
<Stack gap={4}>
{viewData.resolvedProtests.length === 0 ? (
<Box bg="bg-surface-charcoal" border borderColor="border-outline-steel" p={12} textAlign="center">
<Stack alignItems="center" gap={4}>
<Icon icon={CheckCircle} size={8} color="#525252" />
<Box>
<Text weight="semibold" size="lg" color="text-white" block mb={1}>No Resolved Protests</Text>
<Text size="sm" color="text-gray-400">Resolved protests will appear here</Text>
</Box>
</Stack>
</Box>
) : (
viewData.resolvedProtests.map((protest) => (
<ProtestCard
key={protest.id}
protest={{ ...protest, proofVideoUrl: protest.proofVideoUrl ?? undefined, decisionNotes: protest.decisionNotes ?? undefined }}
protester={viewData.driverMap[protest.protestingDriverId]}
accused={viewData.driverMap[protest.accusedDriverId]}
isAdmin={isAdmin}
onReview={onReviewProtest}
formatDate={formatDate}
/>
))
)}
</Stack>
)}
{activeTab === 'penalties' && (
<Stack gap={4}>
{viewData.penalties.length === 0 ? (
<Box bg="bg-surface-charcoal" border borderColor="border-outline-steel" p={12} textAlign="center">
<Stack alignItems="center" gap={4}>
<Icon icon={Gavel} size={8} color="#525252" />
<Box>
<Text weight="semibold" size="lg" color="text-white" block mb={1}>No Penalties</Text>
<Text size="sm" color="text-gray-400">Penalties issued for this race will appear here</Text>
</Box>
</Stack>
</Box>
) : (
viewData.penalties.map((penalty) => (
<RacePenaltyRow
key={penalty.id}
penalty={{
...penalty,
driverName: viewData.driverMap[penalty.driverId]?.name || 'Unknown',
notes: penalty.notes ?? undefined,
type: penalty.type as 'time_penalty' | 'grid_penalty' | 'points_deduction' | 'disqualification' | 'warning' | 'license_points'
}}
/>
))
)}
</Stack>
)}
</Stack>
</GridItem>
<GridItem colSpan={12} lgSpan={4}>
<Stack gap={6}>
<Box as="section" bg="bg-surface-charcoal" border borderColor="border-outline-steel" p={4}>
<Box display="flex" alignItems="center" gap={2} mb={4}>
<Icon icon={Info} size={4} color="var(--color-primary)" />
<Text as="h3" size="xs" weight="bold" color="text-gray-500" uppercase letterSpacing="widest">Stewarding Stats</Text>
</Box>
<RaceStewardingStats
pendingCount={viewData.pendingCount}
resolvedCount={viewData.resolvedCount}
penaltiesCount={viewData.penaltiesCount}
/>
</Box>
</Stack>
</GridItem>
</Grid>
</Stack>
</Container>
</Box>
);
}