website refactor

This commit is contained in:
2026-01-15 17:12:24 +01:00
parent c3b308e960
commit f035cfe7ce
468 changed files with 24378 additions and 17324 deletions

View File

@@ -1,38 +1,54 @@
'use client';
import { useState, useRef, useEffect } from 'react';
import Link from 'next/link';
import Image from 'next/image';
import CountryFlag from '@/ui/CountryFlag';
import PlaceholderImage from '@/ui/PlaceholderImage';
import { Link } from '@/ui/Link';
import { Image } from '@/ui/Image';
import { CountryFlag } from '@/ui/CountryFlag';
import { PlaceholderImage } from '@/ui/PlaceholderImage';
import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
import { Table, TableHead, TableBody, TableRow, TableHeader, TableCell } from '@/ui/Table';
import { Badge } from '@/ui/Badge';
import { Stack } from '@/ui/Stack';
import { routes } from '@/lib/routing/RouteConfig';
import { Icon } from '@/ui/Icon';
import { User, Edit } from 'lucide-react';
// League role display data
const leagueRoleDisplay = {
owner: {
text: 'Owner',
badgeClasses: 'bg-yellow-500/10 text-yellow-500 border-yellow-500/30',
bg: 'bg-yellow-500/10',
color: 'text-yellow-500',
borderColor: 'border-yellow-500/30',
},
admin: {
text: 'Admin',
badgeClasses: 'bg-purple-500/10 text-purple-400 border-purple-500/30',
bg: 'bg-purple-500/10',
color: 'text-purple-400',
borderColor: 'border-purple-500/30',
},
steward: {
text: 'Steward',
badgeClasses: 'bg-blue-500/10 text-blue-400 border-blue-500/30',
bg: 'bg-blue-500/10',
color: 'text-blue-400',
borderColor: 'border-blue-500/30',
},
member: {
text: 'Member',
badgeClasses: 'bg-primary-blue/10 text-primary-blue border-primary-blue/30',
bg: 'bg-primary-blue/10',
color: 'text-primary-blue',
borderColor: 'border-primary-blue/30',
},
} as const;
// Position background colors
const getPositionBgColor = (position: number): string => {
const getPositionBgColor = (position: number) => {
switch (position) {
case 1: return 'bg-yellow-500/10 border-l-4 border-l-yellow-500';
case 2: return 'bg-gray-300/10 border-l-4 border-l-gray-400';
case 3: return 'bg-amber-600/10 border-l-4 border-l-amber-600';
default: return 'border-l-4 border-l-transparent';
case 1: return { bg: 'bg-yellow-500/10', borderLeft: true, borderColor: 'border-l-yellow-500' };
case 2: return { bg: 'bg-gray-300/10', borderLeft: true, borderColor: 'border-l-gray-400' };
case 3: return { bg: 'bg-amber-600/10', borderLeft: true, borderColor: 'border-l-amber-600' };
default: return { borderLeft: true, borderColor: 'border-l-transparent' };
}
};
@@ -156,151 +172,293 @@ export function StandingsTable({
const hasMembership = !!membership;
return (
<div
<Box
ref={menuRef}
className="absolute right-0 top-full mt-1 z-50 bg-deep-graphite border border-charcoal-outline rounded-lg shadow-xl p-2 min-w-[200px]"
onClick={(e) => e.stopPropagation()}
position="absolute"
right="0"
top="full"
mt={1}
zIndex={50}
bg="bg-deep-graphite"
border
borderColor="border-charcoal-outline"
rounded="lg"
shadow="xl"
p={2}
minWidth="200px"
onClick={(e: React.MouseEvent) => e.stopPropagation()}
>
<div className="text-xs text-gray-400 px-2 py-1 mb-1">
<Text size="xs" color="text-gray-400" px={2} py={1} mb={1} block>
Member Management
</div>
<div className="space-y-1">
</Text>
<Stack gap={1}>
{hasMembership ? (
<>
{/* Role Management for existing members */}
{membership!.role !== 'admin' && membership!.role !== 'owner' && (
<button
onClick={(e) => { e.stopPropagation(); handleRoleChange(driverId, 'admin'); }}
className="w-full text-left px-3 py-2 text-sm text-white hover:bg-purple-500/10 rounded flex items-center gap-2 transition-colors"
<Box
as="button"
onClick={(e: React.MouseEvent) => { e.stopPropagation(); handleRoleChange(driverId, 'admin'); }}
display="flex"
alignItems="center"
gap={2}
w="full"
textAlign="left"
px={3}
py={2}
rounded="md"
fontSize="14px"
color="text-white"
hoverBg="bg-purple-500/10"
transition
>
<span>🛡</span>
<span>Promote to Admin</span>
</button>
<Text>🛡</Text>
<Text>Promote to Admin</Text>
</Box>
)}
{membership!.role === 'admin' && (
<button
onClick={(e) => { e.stopPropagation(); handleRoleChange(driverId, 'member'); }}
className="w-full text-left px-3 py-2 text-sm text-white hover:bg-iron-gray/20 rounded flex items-center gap-2 transition-colors"
<Box
as="button"
onClick={(e: React.MouseEvent) => { e.stopPropagation(); handleRoleChange(driverId, 'member'); }}
display="flex"
alignItems="center"
gap={2}
w="full"
textAlign="left"
px={3}
py={2}
rounded="md"
fontSize="14px"
color="text-white"
hoverBg="bg-iron-gray/20"
transition
>
<span></span>
<span>Demote to Member</span>
</button>
<Text></Text>
<Text>Demote to Member</Text>
</Box>
)}
{membership!.role === 'member' && (
<button
onClick={(e) => { e.stopPropagation(); handleRoleChange(driverId, 'steward'); }}
className="w-full text-left px-3 py-2 text-sm text-white hover:bg-blue-500/10 rounded flex items-center gap-2 transition-colors"
<Box
as="button"
onClick={(e: React.MouseEvent) => { e.stopPropagation(); handleRoleChange(driverId, 'steward'); }}
display="flex"
alignItems="center"
gap={2}
w="full"
textAlign="left"
px={3}
py={2}
rounded="md"
fontSize="14px"
color="text-white"
hoverBg="bg-blue-500/10"
transition
>
<span>🏁</span>
<span>Make Steward</span>
</button>
<Text>🏁</Text>
<Text>Make Steward</Text>
</Box>
)}
{membership!.role === 'steward' && (
<button
onClick={(e) => { e.stopPropagation(); handleRoleChange(driverId, 'member'); }}
className="w-full text-left px-3 py-2 text-sm text-white hover:bg-iron-gray/20 rounded flex items-center gap-2 transition-colors"
<Box
as="button"
onClick={(e: React.MouseEvent) => { e.stopPropagation(); handleRoleChange(driverId, 'member'); }}
display="flex"
alignItems="center"
gap={2}
w="full"
textAlign="left"
px={3}
py={2}
rounded="md"
fontSize="14px"
color="text-white"
hoverBg="bg-iron-gray/20"
transition
>
<span>🏁</span>
<span>Remove Steward</span>
</button>
<Text>🏁</Text>
<Text>Remove Steward</Text>
</Box>
)}
<div className="border-t border-charcoal-outline my-1"></div>
<button
onClick={(e) => { e.stopPropagation(); handleRemove(driverId); }}
className="w-full text-left px-3 py-2 text-sm text-red-400 hover:bg-red-500/10 rounded flex items-center gap-2 transition-colors"
<Box borderTop borderColor="border-charcoal-outline" my={1} />
<Box
as="button"
onClick={(e: React.MouseEvent) => { e.stopPropagation(); handleRemove(driverId); }}
display="flex"
alignItems="center"
gap={2}
w="full"
textAlign="left"
px={3}
py={2}
rounded="md"
fontSize="14px"
color="text-red-400"
hoverBg="bg-red-500/10"
transition
>
<span>🚫</span>
<span>Remove from League</span>
</button>
<Text>🚫</Text>
<Text>Remove from League</Text>
</Box>
</>
) : (
<>
{/* Options for drivers without membership (participating but not formal members) */}
<div className="text-xs text-yellow-400/80 px-2 py-1 mb-1 bg-yellow-500/10 rounded">
Driver not a formal member
</div>
<button
onClick={(e) => { e.stopPropagation(); alert('Add as member - feature coming soon'); }}
className="w-full text-left px-3 py-2 text-sm text-white hover:bg-green-500/10 rounded flex items-center gap-2 transition-colors"
<Box bg="bg-yellow-500/10" rounded px={2} py={1} mb={1}>
<Text size="xs" color="text-yellow-400/80">Driver not a formal member</Text>
</Box>
<Box
as="button"
onClick={(e: React.MouseEvent) => { e.stopPropagation(); alert('Add as member - feature coming soon'); }}
display="flex"
alignItems="center"
gap={2}
w="full"
textAlign="left"
px={3}
py={2}
rounded="md"
fontSize="14px"
color="text-white"
hoverBg="bg-green-500/10"
transition
>
<span></span>
<span>Add as Member</span>
</button>
<button
onClick={(e) => { e.stopPropagation(); handleRemove(driverId); }}
className="w-full text-left px-3 py-2 text-sm text-red-400 hover:bg-red-500/10 rounded flex items-center gap-2 transition-colors"
<Text></Text>
<Text>Add as Member</Text>
</Box>
<Box
as="button"
onClick={(e: React.MouseEvent) => { e.stopPropagation(); handleRemove(driverId); }}
display="flex"
alignItems="center"
gap={2}
w="full"
textAlign="left"
px={3}
py={2}
rounded="md"
fontSize="14px"
color="text-red-400"
hoverBg="bg-red-500/10"
transition
>
<span>🚫</span>
<span>Remove from Standings</span>
</button>
<Text>🚫</Text>
<Text>Remove from Standings</Text>
</Box>
</>
)}
</div>
</div>
</Stack>
</Box>
);
};
const PointsActionMenu = () => {
return (
<div
<Box
ref={menuRef}
className="absolute right-0 top-full mt-1 z-50 bg-deep-graphite border border-charcoal-outline rounded-lg shadow-xl p-2 min-w-[180px]"
onClick={(e) => e.stopPropagation()}
position="absolute"
right="0"
top="full"
mt={1}
zIndex={50}
bg="bg-deep-graphite"
border
borderColor="border-charcoal-outline"
rounded="lg"
shadow="xl"
p={2}
minWidth="180px"
onClick={(e: React.MouseEvent) => e.stopPropagation()}
>
<div className="text-xs text-gray-400 px-2 py-1 mb-1">
<Text size="xs" color="text-gray-400" px={2} py={1} mb={1} block>
Score Actions
</div>
<div className="space-y-1">
<button
onClick={(e) => { e.stopPropagation(); alert('View detailed stats - feature coming soon'); }}
className="w-full text-left px-3 py-2 text-sm text-white hover:bg-iron-gray/20 rounded flex items-center gap-2 transition-colors"
</Text>
<Stack gap={1}>
<Box
as="button"
onClick={(e: React.MouseEvent) => { e.stopPropagation(); alert('View detailed stats - feature coming soon'); }}
display="flex"
alignItems="center"
gap={2}
w="full"
textAlign="left"
px={3}
py={2}
rounded="md"
color="text-white"
hoverBg="bg-iron-gray/20"
transition
fontSize="14px"
>
<span>📊</span>
<span>View Details</span>
</button>
<button
onClick={(e) => { e.stopPropagation(); alert('Manual adjustment - feature coming soon'); }}
className="w-full text-left px-3 py-2 text-sm text-white hover:bg-iron-gray/20 rounded flex items-center gap-2 transition-colors"
<Text>📊</Text>
<Text>View Details</Text>
</Box>
<Box
as="button"
onClick={(e: React.MouseEvent) => { e.stopPropagation(); alert('Manual adjustment - feature coming soon'); }}
display="flex"
alignItems="center"
gap={2}
w="full"
textAlign="left"
px={3}
py={2}
rounded="md"
color="text-white"
hoverBg="bg-iron-gray/20"
transition
fontSize="14px"
>
<span></span>
<span>Adjust Points</span>
</button>
<button
onClick={(e) => { e.stopPropagation(); alert('Race history - feature coming soon'); }}
className="w-full text-left px-3 py-2 text-sm text-white hover:bg-iron-gray/20 rounded flex items-center gap-2 transition-colors"
<Text></Text>
<Text>Adjust Points</Text>
</Box>
<Box
as="button"
onClick={(e: React.MouseEvent) => { e.stopPropagation(); alert('Race history - feature coming soon'); }}
display="flex"
alignItems="center"
gap={2}
w="full"
textAlign="left"
px={3}
py={2}
rounded="md"
color="text-white"
hoverBg="bg-iron-gray/20"
transition
fontSize="14px"
>
<span>📝</span>
<span>Race History</span>
</button>
</div>
</div>
<Text>📝</Text>
<Text>Race History</Text>
</Box>
</Stack>
</Box>
);
};
if (standings.length === 0) {
return (
<div className="text-center py-8 text-gray-400">
No standings available
</div>
<Box textAlign="center" py={8}>
<Text color="text-gray-400">No standings available</Text>
</Box>
);
}
return (
<div className="overflow-x-auto overflow-y-visible">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-charcoal-outline">
<th className="text-center py-3 px-3 font-semibold text-gray-400 w-14">Pos</th>
<th className="text-left py-3 px-4 font-semibold text-gray-400">Driver</th>
<th className="text-left py-3 px-4 font-semibold text-gray-400">Team</th>
<th className="text-right py-3 px-4 font-semibold text-gray-400">Points</th>
<th className="text-center py-3 px-4 font-semibold text-gray-400">Races</th>
<th className="text-right py-3 px-4 font-semibold text-gray-400">Avg Finish</th>
<th className="text-right py-3 px-4 font-semibold text-gray-400">Penalty</th>
<th className="text-right py-3 px-4 font-semibold text-gray-400">Bonus</th>
</tr>
</thead>
<tbody>
<Box overflow="auto">
<Table>
<TableHead>
<TableRow>
<TableHeader textAlign="center" w="14">Pos</TableHeader>
<TableHeader>Driver</TableHeader>
<TableHeader>Team</TableHeader>
<TableHeader textAlign="right">Points</TableHeader>
<TableHeader textAlign="center">Races</TableHeader>
<TableHeader textAlign="right">Avg Finish</TableHeader>
<TableHeader textAlign="right">Penalty</TableHeader>
<TableHeader textAlign="right">Bonus</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{standings.map((row) => {
const driver = getDriver(row.driverId);
const membership = getMembership(row.driverId);
@@ -311,11 +469,16 @@ export function StandingsTable({
const isPointsMenuOpen = activeMenu?.driverId === row.driverId && activeMenu?.type === 'points';
const isMe = isCurrentUser(row.driverId);
const posConfig = getPositionBgColor(row.position);
return (
<tr
<TableRow
key={row.driverId}
className={`border-b border-charcoal-outline/50 transition-all duration-200 ${getPositionBgColor(row.position)} ${isRowHovered ? 'bg-iron-gray/10' : ''} ${isMe ? 'ring-2 ring-primary-blue/50 ring-inset bg-primary-blue/5' : ''}`}
bg={isMe ? 'bg-primary-blue/5' : posConfig.bg}
borderLeft={posConfig.borderLeft}
borderColor={posConfig.borderColor}
hoverBg="bg-iron-gray/10"
ring={isMe ? 'ring-2 ring-primary-blue/50 ring-inset' : ''}
onMouseEnter={() => setHoveredRow(row.driverId)}
onMouseLeave={() => {
setHoveredRow(null);
@@ -325,150 +488,198 @@ export function StandingsTable({
}}
>
{/* Position */}
<td className="py-3 px-3 text-center">
<div className={`inline-flex items-center justify-center w-8 h-8 rounded-full font-bold ${
row.position === 1 ? 'bg-yellow-500 text-black' :
row.position === 2 ? 'bg-gray-400 text-black' :
row.position === 3 ? 'bg-amber-600 text-white' :
'bg-charcoal-outline text-white'
}`}>
<TableCell textAlign="center" w="14">
<Box
display="inline-flex"
alignItems="center"
justifyContent="center"
w="8"
h="8"
rounded="full"
weight="bold"
bg={
row.position === 1 ? 'bg-yellow-500' :
row.position === 2 ? 'bg-gray-400' :
row.position === 3 ? 'bg-amber-600' :
'bg-charcoal-outline'
}
color={
row.position === 1 ? 'text-black' :
row.position === 2 ? 'text-black' :
'text-white'
}
>
{row.position}
</div>
</td>
</Box>
</TableCell>
{/* Driver with Rating and Nationality */}
<td className="py-3 px-4 relative">
<div className="flex items-center gap-3">
{/* Avatar */}
<div className="relative">
<div className="w-10 h-10 rounded-full bg-primary-blue/20 overflow-hidden flex items-center justify-center shrink-0">
{driver && (
driver.avatarUrl ? (
<Image
src={driver.avatarUrl}
alt={driver.name}
width={40}
height={40}
className="w-full h-full object-cover"
/>
) : (
<PlaceholderImage size={40} />
)
)}
</div>
{/* Nationality flag */}
{driver && driver.country && (
<div className="absolute -bottom-1 -right-1">
<CountryFlag countryCode={driver.country} size="sm" />
</div>
)}
</div>
{/* Name and Rating */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<Link
href={`/drivers/${row.driverId}`}
className="font-medium text-white truncate hover:text-primary-blue transition-colors"
>
{driver?.name || 'Unknown Driver'}
</Link>
{isMe && (
<span className="px-1.5 py-0.5 text-[10px] font-medium rounded bg-primary-blue/20 text-primary-blue border border-primary-blue/30">
You
</span>
{/* Driver with Rating and Nationality */}
<TableCell position="relative">
<Box display="flex" alignItems="center" gap={3}>
{/* Avatar */}
<Box position="relative">
<Box
w="10"
h="10"
rounded="full"
bg="bg-primary-blue/20"
overflow="hidden"
display="flex"
alignItems="center"
justifyContent="center"
flexShrink={0}
>
{driver && (
driver.avatarUrl ? (
<Image
src={driver.avatarUrl}
alt={driver.name}
width={40}
height={40}
fullWidth
fullHeight
objectFit="cover"
/>
) : (
<PlaceholderImage size={40} />
)
)}
{roleDisplay && roleDisplay.text !== 'Member' && (
<span className={`px-2 py-0.5 text-xs font-medium rounded border ${roleDisplay.badgeClasses}`}>
{roleDisplay.text}
</span>
)}
</div>
<div className="text-xs flex items-center gap-1">
{/* Rating intentionally omitted until API provides driver stats */}
</div>
</div>
{/* Hover Actions for Member Management */}
{isAdmin && canModify && (
<div className="flex items-center gap-1" style={{ opacity: isRowHovered || isMemberMenuOpen ? 1 : 0, transition: 'opacity 0.2s', visibility: isRowHovered || isMemberMenuOpen ? 'visible' : 'hidden' }}>
<button
onClick={(e) => { e.stopPropagation(); setActiveMenu(isMemberMenuOpen ? null : { driverId: row.driverId, type: 'member' }); }}
className={`p-1.5 rounded transition-colors ${isMemberMenuOpen ? 'bg-primary-blue/20 text-primary-blue' : 'text-gray-400 hover:text-white hover:bg-iron-gray/30'}`}
title="Manage member"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
</button>
</div>
</Box>
{/* Nationality flag */}
{driver && driver.country && (
<Box position="absolute" bottom="-1" right="-1">
<CountryFlag countryCode={driver.country} size="sm" />
</Box>
)}
</div>
{isMemberMenuOpen && <MemberActionMenu driverId={row.driverId} />}
</td>
</Box>
{/* Name and Rating */}
<Box flexGrow={1} minWidth="0">
<Box display="flex" alignItems="center" gap={2}>
<Link
href={routes.driver.detail(row.driverId)}
weight="medium"
color="text-white"
truncate
hoverTextColor="text-primary-blue"
transition
>
{driver?.name || 'Unknown Driver'}
</Link>
{isMe && (
<Badge variant="primary">You</Badge>
)}
{roleDisplay && roleDisplay.text !== 'Member' && (
<Box
as="span"
px={2}
py={0.5}
fontSize="12px"
weight="medium"
rounded="md"
border
bg={roleDisplay.bg}
color={roleDisplay.color}
borderColor={roleDisplay.borderColor}
>
{roleDisplay.text}
</Box>
)}
</Box>
</Box>
{/* Team */}
<td className="py-3 px-4">
<span className="text-gray-300">{row.teamName ?? '—'}</span>
</td>
{/* Hover Actions for Member Management */}
{isAdmin && canModify && (
<Box
display="flex"
alignItems="center"
gap={1}
opacity={isRowHovered || isMemberMenuOpen ? 1 : 0}
transition
visibility={isRowHovered || isMemberMenuOpen ? 'visible' : 'hidden'}
>
<Box
as="button"
onClick={(e: React.MouseEvent) => { e.stopPropagation(); setActiveMenu(isMemberMenuOpen ? null : { driverId: row.driverId, type: 'member' }); }}
p={1.5}
rounded="md"
bg={isMemberMenuOpen ? 'bg-primary-blue/20' : ''}
color={isMemberMenuOpen ? 'text-primary-blue' : 'text-gray-400'}
hoverColor={!isMemberMenuOpen ? 'text-white' : ''}
hoverBg={!isMemberMenuOpen ? 'bg-iron-gray/30' : ''}
transition
title="Manage member"
>
<Icon icon={User} size={4} />
</Box>
</Box>
)}
</Box>
{isMemberMenuOpen && <MemberActionMenu driverId={row.driverId} />}
</TableCell>
{/* Total Points with Hover Action */}
<td className="py-3 px-4 text-right relative">
<div className="flex items-center justify-end gap-2">
<span className="text-white font-bold text-lg">{row.totalPoints}</span>
{isAdmin && canModify && (
<button
onClick={(e) => { e.stopPropagation(); setActiveMenu(isPointsMenuOpen ? null : { driverId: row.driverId, type: 'points' }); }}
className={`p-1 rounded transition-colors ${isPointsMenuOpen ? 'bg-primary-blue/20 text-primary-blue' : 'text-gray-400 hover:text-white hover:bg-iron-gray/30'}`}
style={{ opacity: isRowHovered || isPointsMenuOpen ? 1 : 0, transition: 'opacity 0.2s', visibility: isRowHovered || isPointsMenuOpen ? 'visible' : 'hidden' }}
{/* Team */}
<TableCell>
<Text color="text-gray-300">{row.teamName ?? '—'}</Text>
</TableCell>
{/* Total Points with Hover Action */}
<TableCell textAlign="right" position="relative">
<Box display="flex" alignItems="center" justifyContent="end" gap={2}>
<Text color="text-white" weight="bold" size="lg">{row.totalPoints}</Text>
{isAdmin && canModify && (
<Box
as="button"
onClick={(e: React.MouseEvent) => { e.stopPropagation(); setActiveMenu(isPointsMenuOpen ? null : { driverId: row.driverId, type: 'points' }); }}
p={1}
rounded="md"
bg={isPointsMenuOpen ? 'bg-primary-blue/20' : ''}
color={isPointsMenuOpen ? 'text-primary-blue' : 'text-gray-400'}
hoverColor={!isPointsMenuOpen ? 'text-white' : ''}
hoverBg={!isPointsMenuOpen ? 'bg-iron-gray/30' : ''}
transition
opacity={isRowHovered || isPointsMenuOpen ? 1 : 0}
visibility={isRowHovered || isPointsMenuOpen ? 'visible' : 'hidden'}
title="Score actions"
>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
</button>
)}
</div>
{isPointsMenuOpen && <PointsActionMenu />}
</td>
<Icon icon={Edit} size={3} />
</Box>
)}
</Box>
{isPointsMenuOpen && <PointsActionMenu />}
</TableCell>
{/* Races (Finished/Started) */}
<td className="py-3 px-4 text-center">
<span className="text-white">{row.racesFinished}</span>
<span className="text-gray-500">/{row.racesStarted}</span>
</td>
{/* Races (Finished/Started) */}
<TableCell textAlign="center">
<Text color="text-white">{row.racesFinished}</Text>
<Text color="text-gray-500">/{row.racesStarted}</Text>
</TableCell>
{/* Avg Finish */}
<td className="py-3 px-4 text-right">
<span className="text-gray-300">
{row.avgFinish !== null ? row.avgFinish.toFixed(1) : '—'}
</span>
</td>
{/* Avg Finish */}
<TableCell textAlign="right">
<Text color="text-gray-300">
{row.avgFinish !== null ? row.avgFinish.toFixed(1) : '—'}
</Text>
</TableCell>
{/* Penalty */}
<td className="py-3 px-4 text-right">
<span className={row.penaltyPoints > 0 ? 'text-red-400 font-medium' : 'text-gray-500'}>
{row.penaltyPoints > 0 ? `-${row.penaltyPoints}` : '—'}
</span>
</td>
{/* Penalty */}
<TableCell textAlign="right">
<Text color={row.penaltyPoints > 0 ? 'text-red-400' : 'text-gray-500'} weight={row.penaltyPoints > 0 ? 'medium' : 'normal'}>
{row.penaltyPoints > 0 ? `-${row.penaltyPoints}` : '—'}
</Text>
</TableCell>
{/* Bonus */}
<td className="py-3 px-4 text-right">
<span className={row.bonusPoints !== 0 ? 'text-green-400 font-medium' : 'text-gray-500'}>
{row.bonusPoints !== 0 ? `+${row.bonusPoints}` : '—'}
</span>
</td>
</tr>
);
})}
</tbody>
</table>
<style jsx>{`
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
`}</style>
</div>
{/* Bonus */}
<TableCell textAlign="right">
<Text color={row.bonusPoints !== 0 ? 'text-green-400' : 'text-gray-500'} weight={row.bonusPoints !== 0 ? 'medium' : 'normal'}>
{row.bonusPoints !== 0 ? `+${row.bonusPoints}` : '—'}
</Text>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</Box>
);
}