website refactor
This commit is contained in:
@@ -1,22 +1,17 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Breadcrumbs } from '@/ui/Breadcrumbs';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { GridItem } from '@/ui/GridItem';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
import { ArrowLeft, Trophy, Zap, type LucideIcon } from 'lucide-react';
|
||||
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Trophy, Zap, AlertTriangle, type LucideIcon } from 'lucide-react';
|
||||
import type { RaceResultsViewData } from '@/lib/view-data/races/RaceResultsViewData';
|
||||
import { RaceResultRow } from '@/components/races/RaceResultRow';
|
||||
import { RacePenaltyRow } from '@/ui/RacePenaltyRowWrapper';
|
||||
import { RaceResultsTable } from '@/ui/RaceResultsTable';
|
||||
import { RaceDetailsHeader } from '@/components/races/RaceDetailsHeader';
|
||||
|
||||
export interface RaceResultsTemplateProps {
|
||||
viewData: RaceResultsViewData;
|
||||
@@ -40,15 +35,9 @@ export function RaceResultsTemplate({
|
||||
isLoading,
|
||||
error,
|
||||
onBack,
|
||||
onImportResults,
|
||||
importing,
|
||||
importSuccess,
|
||||
importError,
|
||||
}: RaceResultsTemplateProps) {
|
||||
const formatDate = (date: string) => {
|
||||
return DateDisplay.formatFull(date);
|
||||
};
|
||||
|
||||
const formatTime = (ms: number) => {
|
||||
const minutes = Math.floor(ms / 60000);
|
||||
const seconds = Math.floor((ms % 60000) / 1000);
|
||||
@@ -56,17 +45,10 @@ export function RaceResultsTemplate({
|
||||
return `${minutes}:${seconds.toString().padStart(2, '0')}.${milliseconds.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
const breadcrumbItems = [
|
||||
{ label: 'Races', href: '/races' },
|
||||
...(viewData.leagueName ? [{ label: viewData.leagueName, href: `/leagues/${viewData.leagueName}` }] : []),
|
||||
...(viewData.raceTrack ? [{ label: viewData.raceTrack, href: `/races/${viewData.raceTrack}` }] : []),
|
||||
{ label: 'Results' },
|
||||
];
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Container size="lg" py={12}>
|
||||
<Stack align="center">
|
||||
<Stack alignItems="center">
|
||||
<Text color="text-gray-400">Loading results...</Text>
|
||||
</Stack>
|
||||
</Container>
|
||||
@@ -76,147 +58,123 @@ export function RaceResultsTemplate({
|
||||
if (error && !viewData.raceTrack) {
|
||||
return (
|
||||
<Container size="md" py={12}>
|
||||
<Card>
|
||||
<Stack align="center" py={12} gap={4}>
|
||||
<Box bg="bg-surface-charcoal" border borderColor="border-outline-steel" p={12} textAlign="center" rounded="xl">
|
||||
<Stack alignItems="center" gap={4}>
|
||||
<Text color="text-warning-amber">{error?.message || 'Race not found'}</Text>
|
||||
<Button
|
||||
variant="secondary"
|
||||
<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 Races
|
||||
</Button>
|
||||
Back to Schedule
|
||||
</Box>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const hasResults = viewData.results.length > 0;
|
||||
|
||||
return (
|
||||
<Container size="lg" py={8}>
|
||||
<Stack gap={6}>
|
||||
<Stack direction="row" align="center" justify="between">
|
||||
<Breadcrumbs items={breadcrumbItems} />
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onBack}
|
||||
icon={<Icon icon={ArrowLeft} size={4} />}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Stack>
|
||||
<Box as="main" minHeight="screen" bg="bg-base-black">
|
||||
<RaceDetailsHeader
|
||||
title={viewData.raceTrack || 'Unknown Track'}
|
||||
leagueName={viewData.leagueName || 'Official'}
|
||||
trackName={viewData.raceTrack || 'Unknown Track'}
|
||||
scheduledAt={viewData.raceScheduledAt || ''}
|
||||
status="completed"
|
||||
onBack={onBack}
|
||||
/>
|
||||
|
||||
{/* Header */}
|
||||
<Surface variant="muted" rounded="xl" border padding={6} bg="bg-neutral-800/50" borderColor="border-neutral-800">
|
||||
<Stack direction="row" align="center" gap={4} mb={6}>
|
||||
<Surface variant="muted" rounded="xl" padding={3} bg="bg-blue-500/20">
|
||||
<Icon icon={Trophy} size={6} color="#3b82f6" />
|
||||
</Surface>
|
||||
<Box>
|
||||
<Heading level={1}>Race Results</Heading>
|
||||
<Text size="sm" color="text-gray-400" block mt={1}>
|
||||
{viewData.raceTrack} • {viewData.raceScheduledAt ? formatDate(viewData.raceScheduledAt) : ''}
|
||||
</Text>
|
||||
<Container size="lg" py={8}>
|
||||
<Stack gap={8}>
|
||||
{importSuccess && (
|
||||
<Box p={4} bg="bg-success-green" bgOpacity={0.1} border borderColor="border-success-green" rounded>
|
||||
<Text color="text-success-green" weight="bold">Success!</Text>
|
||||
<Text color="text-success-green" size="sm" block mt={1}>Results imported and standings updated.</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{/* Stats */}
|
||||
<Grid cols={4} gap={4}>
|
||||
<StatItem label="Drivers" value={viewData.totalDrivers ?? 0} />
|
||||
<StatItem label="League" value={viewData.leagueName ?? '—'} />
|
||||
<StatItem label="SOF" value={viewData.raceSOF ?? '—'} icon={Zap} color="text-warning-amber" />
|
||||
<StatItem label="Fastest Lap" value={viewData.fastestLapTime ? formatTime(viewData.fastestLapTime) : '—'} color="text-performance-green" />
|
||||
</Grid>
|
||||
</Surface>
|
||||
{importError && (
|
||||
<Box p={4} bg="bg-critical-red" bgOpacity={0.1} border borderColor="border-critical-red" rounded>
|
||||
<Text color="text-critical-red" weight="bold">Error:</Text>
|
||||
<Text color="text-critical-red" size="sm" block mt={1}>{importError}</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{importSuccess && (
|
||||
<Surface variant="muted" rounded="lg" border padding={4} bg="bg-green-500/10" borderColor="border-green-500/30">
|
||||
<Text color="text-performance-green" weight="bold">Success!</Text>
|
||||
<Text color="text-performance-green" size="sm" block mt={1}>Results imported and standings updated.</Text>
|
||||
</Surface>
|
||||
)}
|
||||
<Grid cols={12} gap={6}>
|
||||
<GridItem colSpan={12} lgSpan={8}>
|
||||
<Box as="section" bg="bg-surface-charcoal" border borderColor="border-outline-steel" overflow="hidden">
|
||||
<Box p={4} borderBottom borderColor="border-outline-steel" bg="bg-base-black" bgOpacity={0.2} display="flex" alignItems="center" gap={2}>
|
||||
<Icon icon={Trophy} size={4} color="var(--color-primary)" />
|
||||
<Text as="h2" size="xs" weight="bold" color="text-gray-500" uppercase letterSpacing="widest">Final Standings</Text>
|
||||
</Box>
|
||||
<RaceResultsTable
|
||||
results={viewData.results as unknown as never[]}
|
||||
drivers={[]}
|
||||
pointsSystem={viewData.pointsSystem as unknown as Record<number, number>}
|
||||
fastestLapTime={viewData.fastestLapTime}
|
||||
penalties={viewData.penalties as unknown as never[]}
|
||||
/>
|
||||
</Box>
|
||||
</GridItem>
|
||||
|
||||
{importError && (
|
||||
<Surface variant="muted" rounded="lg" border padding={4} bg="bg-red-500/10" borderColor="border-red-500/30">
|
||||
<Text color="text-error-red" weight="bold">Error:</Text>
|
||||
<Text color="text-error-red" size="sm" block mt={1}>{importError}</Text>
|
||||
</Surface>
|
||||
)}
|
||||
|
||||
<Card>
|
||||
{hasResults ? (
|
||||
<Stack gap={6}>
|
||||
{/* Results Table */}
|
||||
<Stack gap={2}>
|
||||
{viewData.results.map((result) => (
|
||||
<RaceResultRow
|
||||
key={result.driverId}
|
||||
result={result as unknown as never}
|
||||
points={viewData.pointsSystem[result.position.toString()] ?? 0}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
{/* Penalties Section */}
|
||||
{viewData.penalties.length > 0 && (
|
||||
<Box pt={6} borderTop="1px solid" borderColor="border-neutral-800">
|
||||
<Box mb={4}>
|
||||
<Heading level={2}>Penalties</Heading>
|
||||
</Box>
|
||||
<Stack gap={2}>
|
||||
{viewData.penalties.map((penalty, index) => (
|
||||
<RacePenaltyRow key={index} penalty={penalty as unknown as never} />
|
||||
))}
|
||||
<GridItem colSpan={12} lgSpan={4}>
|
||||
<Stack gap={6}>
|
||||
<Box as="section" bg="bg-surface-charcoal" border borderColor="border-outline-steel" p={4}>
|
||||
<Text as="h3" size="xs" weight="bold" color="text-gray-500" uppercase letterSpacing="widest" mb={4}>Session Stats</Text>
|
||||
<Stack gap={4}>
|
||||
<StatItem label="DRIVERS" value={viewData.totalDrivers ?? 0} />
|
||||
<StatItem label="SOF" value={viewData.raceSOF ?? '—'} icon={Zap} color="text-warning-amber" />
|
||||
<StatItem label="FASTEST LAP" value={viewData.fastestLapTime ? formatTime(viewData.fastestLapTime) : '—'} color="text-success-green" />
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
) : (
|
||||
<Stack gap={6}>
|
||||
<Box>
|
||||
<Heading level={2}>Import Results</Heading>
|
||||
<Text size="sm" color="text-gray-400" block mt={2}>
|
||||
No results imported. Upload CSV to test the standings system.
|
||||
</Text>
|
||||
</Box>
|
||||
{importing ? (
|
||||
<Stack align="center" py={8}>
|
||||
<Text color="text-gray-400">Importing results and updating standings...</Text>
|
||||
</Stack>
|
||||
) : (
|
||||
<Stack gap={4}>
|
||||
<Text size="sm" color="text-gray-400">
|
||||
This is a placeholder for the import form. In the actual implementation,
|
||||
this would render the ImportResultsForm component.
|
||||
</Text>
|
||||
<Box>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => onImportResults([])}
|
||||
disabled={importing}
|
||||
>
|
||||
Import Results (Demo)
|
||||
</Button>
|
||||
|
||||
{viewData.penalties.length > 0 && (
|
||||
<Box as="section" bg="bg-surface-charcoal" border borderColor="border-outline-steel" p={4}>
|
||||
<Text as="h3" size="xs" weight="bold" color="text-gray-500" uppercase letterSpacing="widest" mb={4}>Penalties</Text>
|
||||
<Stack gap={2}>
|
||||
{viewData.penalties.map((penalty, index) => (
|
||||
<Box key={index} p={3} bg="bg-base-black" bgOpacity={0.5} border borderColor="border-outline-steel">
|
||||
<Stack direction="row" alignItems="center" gap={3}>
|
||||
<Icon icon={AlertTriangle} size={4} color="var(--color-critical)" />
|
||||
<Box>
|
||||
<Text size="sm" weight="bold" color="text-white" block>{penalty.driverName || 'Unknown Driver'}</Text>
|
||||
<Text size="xs" color="text-gray-500">{penalty.reason || penalty.type}</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</Card>
|
||||
</Stack>
|
||||
</Container>
|
||||
)}
|
||||
</Stack>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function StatItem({ label, value, icon, color = 'text-white' }: { label: string, value: string | number, icon?: LucideIcon, color?: string }) {
|
||||
return (
|
||||
<Surface variant="muted" rounded="lg" padding={3} bg="bg-neutral-900/60">
|
||||
<Text size="xs" color="text-gray-500" block mb={1}>{label}</Text>
|
||||
<Stack direction="row" align="center" gap={1.5}>
|
||||
{icon && <Icon icon={icon} size={4} color={color === 'text-white' ? '#9ca3af' : color} />}
|
||||
<Text weight="bold" color={color} size="lg">{value}</Text>
|
||||
<Box p={4} bg="bg-base-black" bgOpacity={0.5} border borderColor="border-outline-steel">
|
||||
<Stack gap={1}>
|
||||
<Stack direction="row" alignItems="center" gap={2}>
|
||||
{icon && <Icon icon={icon} size={3} color={color === 'text-white' ? '#9ca3af' : color} />}
|
||||
<Text size="xs" color="text-gray-500" weight="bold" uppercase>{label}</Text>
|
||||
</Stack>
|
||||
<Text size="xl" weight="bold" color={color}>{value}</Text>
|
||||
</Stack>
|
||||
</Surface>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user