website refactor

This commit is contained in:
2026-01-18 16:18:18 +01:00
parent 0b301feb61
commit 13567d51af
329 changed files with 4701 additions and 4750 deletions

View File

@@ -22,9 +22,8 @@ import type { LeagueConfigFormModel } from '@/lib/types/LeagueConfigFormModel';
import type { Weekday } from '@/lib/types/Weekday';
import { Input } from '@/ui/Input';
import { RangeField } from '@/components/shared/RangeField';
import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Heading } from '@/ui/Heading';
import { Icon } from '@/ui/Icon';
@@ -119,7 +118,7 @@ function RaceDayPreview({
return (
<Stack gap={4}>
<Box display="flex" alignItems="center" gap={2}>
<Stack display="flex" alignItems="center" gap={2}>
<Icon icon={Flag} size={3.5} color="text-primary-blue" />
<Text size="xs" weight="medium" color="text-white">Race Day Schedule</Text>
<Text size="xs" color="text-gray-600"></Text>
@@ -127,7 +126,7 @@ function RaceDayPreview({
<Text size="xs" color="text-gray-400">
Starts {effectiveRaceTime}{!raceTime && <Text as="span" color="text-gray-600" ml={1}>(default)</Text>}
</Text>
</Box>
</Stack>
{/* Timeline visualization - show ALL sessions */}
<Stack gap={2}>
@@ -138,7 +137,7 @@ function RaceDayPreview({
const startTime = isActive ? getStartTime(activeIndex) : null;
return (
<Box
<Stack
key={session.name}
position="relative"
display="flex"
@@ -157,7 +156,7 @@ function RaceDayPreview({
>
{/* Status badge */}
{!isActive && (
<Box position="absolute" top="-1" right="-1" px={1.5} py={0.5} rounded="sm" bg="bg-charcoal-outline">
<Stack position="absolute" top="-1" right="-1" px={1.5} py={0.5} rounded="sm" bg="bg-charcoal-outline">
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '8px' }}
@@ -166,21 +165,21 @@ function RaceDayPreview({
>
Not included
</Text>
</Box>
</Stack>
)}
{/* Time marker */}
<Box w="12" textAlign="right" flexShrink={0}>
<Stack w="12" textAlign="right" flexShrink={0}>
<Text size="xs" font="mono" color={!isActive ? 'text-gray-600' : isRace ? 'text-primary-blue' : 'text-gray-400'}>
{startTime ?? '—'}
</Text>
</Box>
</Stack>
{/* Session indicator */}
<Box w="1" h="8" rounded="full" transition bg={!isActive ? 'bg-charcoal-outline/30' : isRace ? 'bg-primary-blue' : 'bg-charcoal-outline'} />
<Stack w="1" h="8" rounded="full" transition bg={!isActive ? 'bg-charcoal-outline/30' : isRace ? 'bg-primary-blue' : 'bg-charcoal-outline'} />
{/* Session info */}
<Box flexGrow={1}>
<Stack flexGrow={1}>
<Text size="sm" weight="medium" color={!isActive ? 'text-gray-600' : isRace ? 'text-white' : 'text-gray-300'} block>
{session.name}
</Text>
@@ -192,29 +191,29 @@ function RaceDayPreview({
>
{isActive ? `${session.duration} minutes` : 'Disabled'}
</Text>
</Box>
</Stack>
{/* Duration bar */}
{isActive && (
<Box w="16" h="2" bg="bg-charcoal-outline/50" rounded="full" overflow="hidden">
<Box
<Stack w="16" h="2" bg="bg-charcoal-outline/50" rounded="full" overflow="hidden">
<Stack
h="full"
rounded="full"
bg={isRace ? 'bg-primary-blue' : 'bg-gray-600'}
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ width: `${(session.duration / Math.max(...activeSessions.map(s => s.duration))) * 100}%` }}
/>
</Box>
</Stack>
)}
</Box>
</Stack>
);
})}
</Stack>
{/* Legend */}
<Box display="flex" alignItems="center" justifyContent="center" gap={4} pt={2} borderTop borderColor="border-charcoal-outline/30">
<Box display="flex" alignItems="center" gap={1.5}>
<Box w="2" h="2" rounded="sm" bg="bg-primary-blue" />
<Stack display="flex" alignItems="center" justifyContent="center" gap={4} pt={2} borderTop borderColor="border-charcoal-outline/30">
<Stack display="flex" alignItems="center" gap={1.5}>
<Stack w="2" h="2" rounded="sm" bg="bg-primary-blue" />
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
@@ -222,9 +221,9 @@ function RaceDayPreview({
>
Active race
</Text>
</Box>
<Box display="flex" alignItems="center" gap={1.5}>
<Box w="2" h="2" rounded="sm" bg="bg-gray-600" />
</Stack>
<Stack display="flex" alignItems="center" gap={1.5}>
<Stack w="2" h="2" rounded="sm" bg="bg-gray-600" />
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
@@ -232,9 +231,9 @@ function RaceDayPreview({
>
Active session
</Text>
</Box>
<Box display="flex" alignItems="center" gap={1.5}>
<Box w="2" h="2" rounded="sm" border borderStyle="dashed" borderColor="border-charcoal-outline/50" bg="transparent" />
</Stack>
<Stack display="flex" alignItems="center" gap={1.5}>
<Stack w="2" h="2" rounded="sm" border borderStyle="dashed" borderColor="border-charcoal-outline/50" bg="transparent" />
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
@@ -242,12 +241,12 @@ function RaceDayPreview({
>
Not included
</Text>
</Box>
</Box>
</Stack>
</Stack>
{/* Summary */}
<Box display="flex" alignItems="center" justifyContent="between">
<Box display="flex" alignItems="center" gap={3}>
<Stack display="flex" alignItems="center" justifyContent="between">
<Stack display="flex" alignItems="center" gap={3}>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
@@ -269,14 +268,14 @@ function RaceDayPreview({
>
{activeSessions.filter(s => s.type === 'race' || s.type === 'sprint').length} race{activeSessions.filter(s => s.type === 'race' || s.type === 'sprint').length > 1 ? 's' : ''}
</Text>
</Box>
<Box display="flex" alignItems="center" gap={1.5} px={2} py={1} rounded="full" bg="bg-iron-gray/60">
</Stack>
<Stack display="flex" alignItems="center" gap={1.5} px={2} py={1} rounded="full" bg="bg-iron-gray/60">
<Icon icon={Timer} size={3} color="text-primary-blue" />
<Text size="xs" weight="semibold" color="text-white">
{Math.floor(totalDuration / 60)}h {totalDuration % 60}m
</Text>
</Box>
</Box>
</Stack>
</Stack>
</Stack>
);
}
@@ -565,11 +564,11 @@ function YearCalendarPreview({
return (
<Stack gap={4}>
<Box display="flex" alignItems="center" justifyContent="between">
<Box display="flex" alignItems="center" gap={2}>
<Stack display="flex" alignItems="center" justifyContent="between">
<Stack display="flex" alignItems="center" gap={2}>
<Icon icon={CalendarRange} size={3.5} color="text-primary-blue" />
<Text size="xs" weight="medium" color="text-white">Season Calendar</Text>
</Box>
</Stack>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
@@ -577,17 +576,17 @@ function YearCalendarPreview({
>
{raceDates.length} race{raceDates.length !== 1 ? 's' : ''} scheduled
</Text>
</Box>
</Stack>
{/* Year grid - 3 columns x 4 rows */}
<Box display="grid" gridCols={3} gap={2}>
<Stack display="grid" gridCols={3} gap={2}>
{yearView.map(({ month, monthIndex, year, days }) => {
const hasRaces = days.some(d => d.isRace);
const raceCount = days.filter(d => d.isRace).length;
const uniqueKey = `${year}-${monthIndex}`;
return (
<Box
<Stack
key={uniqueKey}
rounded="lg"
p={2}
@@ -596,7 +595,7 @@ function YearCalendarPreview({
borderColor={hasRaces ? 'border-primary-blue/30' : 'border-charcoal-outline/30'}
bg={hasRaces ? 'bg-primary-blue/5' : 'bg-iron-gray/20'}
>
<Box display="flex" alignItems="center" justifyContent="between" mb={1}>
<Stack display="flex" alignItems="center" justifyContent="between" mb={1}>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
@@ -615,17 +614,17 @@ function YearCalendarPreview({
{raceCount}
</Text>
)}
</Box>
</Stack>
{/* Mini calendar grid */}
<Box display="grid" gridCols={7} gap="1px">
<Stack display="grid" gridCols={7} gap="1px">
{/* Fill empty days at start - getDay() returns 0 for Sunday, we want Monday first */}
{Array.from({ length: (new Date(year, monthIndex, 1).getDay() + 6) % 7 }).map((_, i) => (
<Box key={`empty-${uniqueKey}-${i}`} w="2" h="2" />
<Stack key={`empty-${uniqueKey}-${i}`} w="2" h="2" />
))}
{days.map(({ dayOfMonth, isRace, isStart, isEnd, raceNumber }) => (
<Box
<Stack
key={`${uniqueKey}-${dayOfMonth}`}
w="2"
h="2"
@@ -651,15 +650,15 @@ function YearCalendarPreview({
}
/>
))}
</Box>
</Box>
</Stack>
</Stack>
);
})}
</Box>
</Stack>
{/* Season summary */}
<Box display="grid" gridCols={3} gap={2} pt={2} borderTop borderColor="border-charcoal-outline/30">
<Box textAlign="center">
<Stack display="grid" gridCols={3} gap={2} pt={2} borderTop borderColor="border-charcoal-outline/30">
<Stack textAlign="center">
<Text size="lg" weight="bold" color="text-white" block>{rounds}</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
@@ -669,8 +668,8 @@ function YearCalendarPreview({
>
Rounds
</Text>
</Box>
<Box textAlign="center">
</Stack>
<Stack textAlign="center">
<Text size="lg" weight="bold" color="text-white" block>
{seasonDurationWeeks || '—'}
</Text>
@@ -682,8 +681,8 @@ function YearCalendarPreview({
>
Weeks
</Text>
</Box>
<Box textAlign="center">
</Stack>
<Stack textAlign="center">
<Text size="lg" weight="bold" color="text-primary-blue" block>
{firstRace && lastRace
? `${getMonthLabel(firstRace.getMonth())}${getMonthLabel(
@@ -699,13 +698,13 @@ function YearCalendarPreview({
>
Duration
</Text>
</Box>
</Box>
</Stack>
</Stack>
{/* Legend */}
<Box display="flex" alignItems="center" justifyContent="center" gap={3} flexWrap="wrap">
<Box display="flex" alignItems="center" gap={1}>
<Box w="2" h="2" rounded="sm" bg="bg-performance-green"
<Stack display="flex" alignItems="center" justifyContent="center" gap={3} flexWrap="wrap">
<Stack display="flex" alignItems="center" gap={1}>
<Stack w="2" h="2" rounded="sm" bg="bg-performance-green"
// eslint-disable-next-line gridpilot-rules/component-classification
className="ring-1 ring-performance-green"
/>
@@ -716,9 +715,9 @@ function YearCalendarPreview({
>
Start
</Text>
</Box>
<Box display="flex" alignItems="center" gap={1}>
<Box w="2" h="2" rounded="sm" bg="bg-primary-blue" />
</Stack>
<Stack display="flex" alignItems="center" gap={1}>
<Stack w="2" h="2" rounded="sm" bg="bg-primary-blue" />
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
@@ -726,10 +725,10 @@ function YearCalendarPreview({
>
Race
</Text>
</Box>
</Stack>
{seasonEnd && (
<Box display="flex" alignItems="center" gap={1}>
<Box w="2" h="2" rounded="sm" bg="bg-warning-amber"
<Stack display="flex" alignItems="center" gap={1}>
<Stack w="2" h="2" rounded="sm" bg="bg-warning-amber"
// eslint-disable-next-line gridpilot-rules/component-classification
className="ring-1 ring-warning-amber"
/>
@@ -740,10 +739,10 @@ function YearCalendarPreview({
>
End
</Text>
</Box>
</Stack>
)}
<Box display="flex" alignItems="center" gap={1}>
<Box w="2" h="2" rounded="sm" bg="bg-charcoal-outline/30" />
<Stack display="flex" alignItems="center" gap={1}>
<Stack w="2" h="2" rounded="sm" bg="bg-charcoal-outline/30" />
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
@@ -751,8 +750,8 @@ function YearCalendarPreview({
>
No race
</Text>
</Box>
</Box>
</Stack>
</Stack>
</Stack>
);
}
@@ -790,16 +789,16 @@ function SeasonStatsPreview({
return (
<Stack gap={4}>
<Box display="flex" alignItems="center" gap={2}>
<Stack display="flex" alignItems="center" gap={2}>
<Icon icon={Trophy} size={3.5} color="text-primary-blue" />
<Text size="xs" weight="medium" color="text-white">Season Statistics</Text>
</Box>
</Stack>
{/* Visual rounds */}
<Stack gap={2}>
<Box display="flex" alignItems="center" gap={1} flexWrap="wrap">
<Stack display="flex" alignItems="center" gap={1} flexWrap="wrap">
{Array.from({ length: Math.min(rounds, 20) }).map((_, i) => (
<Box
<Stack
key={i}
w="5"
h="5"
@@ -830,7 +829,7 @@ function SeasonStatsPreview({
>
{i + 1}
</Text>
</Box>
</Stack>
))}
{rounds > 20 && (
<Text
@@ -842,10 +841,10 @@ function SeasonStatsPreview({
+{rounds - 20}
</Text>
)}
</Box>
<Box display="flex" alignItems="center" gap={3}>
<Box display="flex" alignItems="center" gap={1}>
<Box w="2" h="2" rounded="sm" bg="bg-performance-green" />
</Stack>
<Stack display="flex" alignItems="center" gap={3}>
<Stack display="flex" alignItems="center" gap={1}>
<Stack w="2" h="2" rounded="sm" bg="bg-performance-green" />
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
@@ -853,9 +852,9 @@ function SeasonStatsPreview({
>
Season start
</Text>
</Box>
<Box display="flex" alignItems="center" gap={1}>
<Box w="2" h="2" rounded="sm" bg="bg-primary-blue" />
</Stack>
<Stack display="flex" alignItems="center" gap={1}>
<Stack w="2" h="2" rounded="sm" bg="bg-primary-blue" />
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
@@ -863,13 +862,13 @@ function SeasonStatsPreview({
>
Finale
</Text>
</Box>
</Box>
</Stack>
</Stack>
</Stack>
{/* Stats grid */}
<Box display="grid" gridCols={2} gap={2}>
<Box rounded="lg" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline/50" p={3}>
<Stack display="grid" gridCols={2} gap={2}>
<Stack rounded="lg" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline/50" p={3}>
<Text size="2xl" weight="bold" color="text-white" block>{totalSessions}</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
@@ -879,8 +878,8 @@ function SeasonStatsPreview({
>
Total sessions
</Text>
</Box>
<Box rounded="lg" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline/50" p={3}>
</Stack>
<Stack rounded="lg" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline/50" p={3}>
<Text size="2xl" weight="bold" color="text-white" block>{Math.round(totalRaceMinutes / 60)}h</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
@@ -890,8 +889,8 @@ function SeasonStatsPreview({
>
Racing time
</Text>
</Box>
<Box rounded="lg" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline/50" p={3}>
</Stack>
<Stack rounded="lg" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline/50" p={3}>
<Text size="2xl" weight="bold" color="text-white" block>~{weeksNeeded}</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
@@ -901,8 +900,8 @@ function SeasonStatsPreview({
>
Weeks duration
</Text>
</Box>
<Box rounded="lg" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline/50" p={3}>
</Stack>
<Stack rounded="lg" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline/50" p={3}>
<Text size="2xl" weight="bold" color="text-white" block>{totalMinutesPerRound}</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
@@ -912,8 +911,8 @@ function SeasonStatsPreview({
>
min/race day
</Text>
</Box>
</Box>
</Stack>
</Stack>
</Stack>
);
}
@@ -964,12 +963,12 @@ function InlineEditableRounds({
};
return (
<Box display="flex" alignItems="center" gap={4} p={4} rounded="xl"
<Stack display="flex" alignItems="center" gap={4} p={4} rounded="xl"
// eslint-disable-next-line gridpilot-rules/component-classification
className="bg-gradient-to-br from-iron-gray/60 to-iron-gray/30 border border-charcoal-outline"
>
{isEditing ? (
<Box
<Stack
as="input"
ref={inputRef}
type="number"
@@ -991,7 +990,7 @@ function InlineEditableRounds({
className="text-4xl outline-none"
/>
) : (
<Box
<Stack
as="button"
type="button"
onClick={() => {
@@ -1013,9 +1012,9 @@ function InlineEditableRounds({
// eslint-disable-next-line gridpilot-rules/component-classification
className="group-hover:opacity-100 text-primary-blue transition-opacity"
/>
</Box>
</Stack>
)}
<Box flexGrow={1}>
<Stack flexGrow={1}>
<Text size="sm" weight="medium" color="text-gray-300" block>rounds</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
@@ -1025,9 +1024,9 @@ function InlineEditableRounds({
>
Click to edit
</Text>
</Box>
<Box display="flex" flexDirection="col" gap={1}>
<Box
</Stack>
<Stack display="flex" flexDirection="col" gap={1}>
<Stack
as="button"
type="button"
onClick={() => onChange(Math.min(value + 1, max))}
@@ -1043,8 +1042,8 @@ function InlineEditableRounds({
cursor={value >= max ? 'not-allowed' : 'pointer'}
>
<Icon icon={ChevronUp} size={4} color="text-gray-400" />
</Box>
<Box
</Stack>
<Stack
as="button"
type="button"
onClick={() => onChange(Math.max(value - 1, min))}
@@ -1060,9 +1059,9 @@ function InlineEditableRounds({
cursor={value <= min ? 'not-allowed' : 'pointer'}
>
<Icon icon={ChevronDown} size={4} color="text-gray-400" />
</Box>
</Box>
</Box>
</Stack>
</Stack>
</Stack>
);
}
@@ -1088,8 +1087,8 @@ function CollapsibleSection({
const [isOpen, setIsOpen] = useState(defaultOpen);
return (
<Box rounded="xl" border borderColor="border-charcoal-outline/50" bg="bg-iron-gray/20" overflow="hidden" transition>
<Box
<Stack rounded="xl" border borderColor="border-charcoal-outline/50" bg="bg-iron-gray/20" overflow="hidden" transition>
<Stack
as="button"
type="button"
onClick={() => setIsOpen(!isOpen)}
@@ -1101,36 +1100,36 @@ function CollapsibleSection({
transition
hoverBg="bg-iron-gray/30"
>
<Box display="flex" alignItems="center" gap={3}>
<Box display="flex" h="8" w="8" alignItems="center" justifyContent="center" rounded="lg" bg="bg-primary-blue/10" flexShrink={0}>
<Stack display="flex" alignItems="center" gap={3}>
<Stack display="flex" h="8" w="8" alignItems="center" justifyContent="center" rounded="lg" bg="bg-primary-blue/10" flexShrink={0}>
{icon}
</Box>
<Box textAlign="left">
</Stack>
<Stack textAlign="left">
<Heading level={3} fontSize="sm" weight="semibold" color="text-white">{title}</Heading>
{description && (
<Text size="xs" color="text-gray-500" mt={0.5} block>{description}</Text>
)}
</Box>
</Box>
<Box transform transition
</Stack>
</Stack>
<Stack transform transition
// eslint-disable-next-line gridpilot-rules/component-classification
className={isOpen ? 'rotate-180' : ''}
>
<Icon icon={ChevronDown} size={4} color="text-gray-400" />
</Box>
</Box>
<Box
</Stack>
</Stack>
<Stack
transition
// eslint-disable-next-line gridpilot-rules/component-classification
className={`ease-in-out ${
isOpen ? 'max-h-[2000px] opacity-100' : 'max-h-0 opacity-0 overflow-hidden'
}`}
>
<Box px={4} pb={4} pt={2} borderTop borderColor="border-charcoal-outline/30">
<Stack px={4} pb={4} pt={2} borderTop borderColor="border-charcoal-outline/30">
{children}
</Box>
</Box>
</Box>
</Stack>
</Stack>
</Stack>
);
}
@@ -1193,10 +1192,10 @@ export function LeagueTimingsSection({
<Stack gap={4}>
<Heading level={3} fontSize="lg" weight="semibold" color="text-white">Schedule & timings</Heading>
<Stack gap={3}>
<Box>
<Stack>
<Text weight="medium" color="text-gray-200">Planned rounds:</Text>{' '}
<Text color="text-gray-300">{timings.roundsPlanned ?? '—'}</Text>
</Box>
</Stack>
</Stack>
</Stack>
);
@@ -1213,7 +1212,7 @@ export function LeagueTimingsSection({
];
return (
<Box display="grid" responsiveGridCols={{ base: 1, lg: 2 }} gap={6}>
<Stack display="grid" responsiveGridCols={{ base: 1, lg: 2 }} gap={6}>
{/* LEFT COLUMN: Configuration */}
<Stack gap={4}>
{/* Session Durations - Collapsible */}
@@ -1223,7 +1222,7 @@ export function LeagueTimingsSection({
description="Configure practice, qualifying, and race lengths"
defaultOpen={false}
>
<Box display="grid" gridCols={2} gap={3}>
<Stack display="grid" gridCols={2} gap={3}>
<RangeField
label="Practice"
value={timings.practiceMinutes ?? 20}
@@ -1264,7 +1263,7 @@ export function LeagueTimingsSection({
compact
error={errors?.mainRaceMinutes}
/>
</Box>
</Stack>
</CollapsibleSection>
{/* Season Length - Collapsible */}
@@ -1301,7 +1300,7 @@ export function LeagueTimingsSection({
{/* Frequency */}
<Stack gap={2} mb={4}>
<Text as="label" size="xs" color="text-gray-400" block>How often?</Text>
<Box display="flex" gap={2}>
<Stack display="flex" gap={2}>
{[
{ id: 'weekly', label: 'Weekly' },
{ id: 'everyNWeeks', label: 'Every 2 weeks' },
@@ -1310,7 +1309,7 @@ export function LeagueTimingsSection({
(opt.id === 'weekly' && recurrenceStrategy === 'weekly') ||
(opt.id === 'everyNWeeks' && recurrenceStrategy === 'everyNWeeks');
return (
<Box
<Stack
key={opt.id}
as="button"
type="button"
@@ -1336,15 +1335,15 @@ export function LeagueTimingsSection({
style={{ fontSize: '12px', fontWeight: 500 }}
>
{opt.label}
</Box>
</Stack>
);
})}
</Box>
</Stack>
</Stack>
{/* Day selection */}
<Stack gap={2}>
<Box display="flex" alignItems="center" justifyContent="between">
<Stack display="flex" alignItems="center" justifyContent="between">
<Text as="label" size="xs" color="text-gray-400">Which days?</Text>
{weekdays.length === 0 && (
<Text
@@ -1355,13 +1354,13 @@ export function LeagueTimingsSection({
Select at least one
</Text>
)}
</Box>
</Stack>
<Box display="flex" gap={1}>
<Stack display="flex" gap={1}>
{allWeekdays.map(({ day, short }) => {
const isSelected = weekdays.includes(day);
return (
<Box
<Stack
key={day}
as="button"
type="button"
@@ -1380,10 +1379,10 @@ export function LeagueTimingsSection({
style={{ fontSize: '10px', fontWeight: 500 }}
>
{short}
</Box>
</Stack>
);
})}
</Box>
</Stack>
</Stack>
</CollapsibleSection>
@@ -1395,7 +1394,7 @@ export function LeagueTimingsSection({
defaultOpen={false}
>
<Stack gap={4}>
<Box display="grid" gridCols={2} gap={3}>
<Stack display="grid" gridCols={2} gap={3}>
<Stack gap={1.5}>
<Text as="label"
// eslint-disable-next-line gridpilot-rules/component-classification
@@ -1406,7 +1405,7 @@ export function LeagueTimingsSection({
alignItems="center"
gap={1}
>
<Box w="2" h="2" rounded="sm" bg="bg-performance-green" />
<Stack w="2" h="2" rounded="sm" bg="bg-performance-green" />
Season Start
</Text>
<Input
@@ -1427,7 +1426,7 @@ export function LeagueTimingsSection({
alignItems="center"
gap={1}
>
<Box w="2" h="2" rounded="sm" bg="bg-warning-amber" />
<Stack w="2" h="2" rounded="sm" bg="bg-warning-amber" />
Season End
</Text>
<Input
@@ -1438,10 +1437,10 @@ export function LeagueTimingsSection({
className="bg-iron-gray/30"
/>
</Stack>
</Box>
</Stack>
{timings.seasonStartDate && timings.seasonEndDate && (
<Box display="flex" alignItems="start" gap={2} p={2} rounded="lg" bg="bg-primary-blue/5" border borderColor="border-primary-blue/20">
<Stack display="flex" alignItems="start" gap={2} p={2} rounded="lg" bg="bg-primary-blue/5" border borderColor="border-primary-blue/20">
<Icon icon={Info} size={3.5} color="text-primary-blue" mt={0.5} flexShrink={0} />
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
@@ -1450,7 +1449,7 @@ export function LeagueTimingsSection({
>
Races will be <Text as="span" color="text-white" weight="medium">evenly distributed</Text> between start and end dates on your selected race days.
</Text>
</Box>
</Stack>
)}
<Stack gap={1.5}>
@@ -1481,12 +1480,12 @@ export function LeagueTimingsSection({
>
Time Zone
</Text>
<Box display="grid" gridCols={2} gap={2}>
<Stack display="grid" gridCols={2} gap={2}>
{TIME_ZONES.slice(0, 2).map((tz) => {
const isSelected = (timings.timezoneId ?? 'UTC') === tz.value;
const TzIcon = tz.icon;
return (
<Box
<Stack
key={tz.value}
as="button"
type="button"
@@ -1506,7 +1505,7 @@ export function LeagueTimingsSection({
textAlign="left"
>
<Icon icon={TzIcon} size={4} color={isSelected ? (tz.value === 'track' ? 'text-performance-green' : 'text-primary-blue') : 'text-gray-400'} />
<Box flexGrow={1}>
<Stack flexGrow={1}>
<Text size="xs" weight="medium" color={isSelected ? 'text-white' : 'text-gray-300'} block>
{tz.label}
</Text>
@@ -1520,14 +1519,14 @@ export function LeagueTimingsSection({
Adjusts per track
</Text>
)}
</Box>
</Box>
</Stack>
</Stack>
);
})}
</Box>
</Stack>
{/* More time zones - expandable */}
<Box
<Stack
as="button"
type="button"
onClick={() => setShowAdvanced(!showAdvanced)}
@@ -1545,15 +1544,15 @@ export function LeagueTimingsSection({
>
{showAdvanced ? 'Hide' : 'Show'} more time zones
</Text>
</Box>
</Stack>
{showAdvanced && (
<Box display="grid" gridCols={2} gap={2} mt={2}>
<Stack display="grid" gridCols={2} gap={2} mt={2}>
{TIME_ZONES.slice(2).map((tz) => {
const isSelected = (timings.timezoneId ?? 'UTC') === tz.value;
const TzIcon = tz.icon;
return (
<Box
<Stack
key={tz.value}
as="button"
type="button"
@@ -1573,14 +1572,14 @@ export function LeagueTimingsSection({
>
<Icon icon={TzIcon} size={3.5} color={isSelected ? 'text-primary-blue' : 'text-gray-500'} />
<Text size="xs" color={isSelected ? 'text-white' : 'text-gray-400'}>{tz.label}</Text>
</Box>
</Stack>
);
})}
</Box>
</Stack>
)}
</Stack>
<Box display="flex" alignItems="start" gap={2} p={2} rounded="lg" bg="bg-performance-green/5" border borderColor="border-performance-green/20">
<Stack display="flex" alignItems="start" gap={2} p={2} rounded="lg" bg="bg-performance-green/5" border borderColor="border-performance-green/20">
<Icon icon={Info} size={3.5} color="text-performance-green" mt={0.5} flexShrink={0} />
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
@@ -1592,35 +1591,35 @@ export function LeagueTimingsSection({
: 'All race times will be displayed in the selected time zone for consistency.'
}
</Text>
</Box>
</Stack>
</Stack>
</CollapsibleSection>
</Stack>
{/* RIGHT COLUMN: Live Preview */}
<Stack gap={4}>
<Box
<Stack
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ position: 'sticky', top: '1rem' }}
>
<Box rounded="xl" border borderColor="border-charcoal-outline"
<Stack rounded="xl" border borderColor="border-charcoal-outline"
// eslint-disable-next-line gridpilot-rules/component-classification
className="bg-gradient-to-br from-iron-gray/80 to-deep-graphite"
overflow="hidden"
>
{/* Preview header with tabs */}
<Box display="flex" alignItems="center" justifyContent="between" p={3} borderBottom borderColor="border-charcoal-outline/50" bg="bg-deep-graphite/50">
<Box display="flex" alignItems="center" gap={2}>
<Stack display="flex" alignItems="center" justifyContent="between" p={3} borderBottom borderColor="border-charcoal-outline/50" bg="bg-deep-graphite/50">
<Stack display="flex" alignItems="center" gap={2}>
<Icon icon={Eye} size={4} color="text-primary-blue" />
<Text size="xs" weight="semibold" color="text-white">Preview</Text>
</Box>
<Box display="flex" gap={1} p={0.5} rounded="lg" bg="bg-iron-gray/60">
</Stack>
<Stack display="flex" gap={1} p={0.5} rounded="lg" bg="bg-iron-gray/60">
{[
{ id: 'day', label: 'Race Day', icon: Play },
{ id: 'year', label: 'Calendar', icon: CalendarRange },
{ id: 'stats', label: 'Stats', icon: Trophy },
].map((tab) => (
<Box
<Stack
key={tab.id}
as="button"
type="button"
@@ -1639,19 +1638,19 @@ export function LeagueTimingsSection({
style={{ fontSize: '10px', fontWeight: 500 }}
>
<Icon icon={tab.icon} size={3} />
<Box as="span"
<Stack as="span"
// eslint-disable-next-line gridpilot-rules/component-classification
className="hidden sm:inline"
>
{tab.label}
</Box>
</Box>
</Stack>
</Stack>
))}
</Box>
</Box>
</Stack>
</Stack>
{/* Preview content */}
<Box p={4}
<Stack p={4}
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ minHeight: '300px' }}
>
@@ -1708,11 +1707,11 @@ export function LeagueTimingsSection({
/>
);
})()}
</Box>
</Box>
</Stack>
</Stack>
{/* Helper tip */}
<Box mt={3} display="flex" alignItems="start" gap={2} p={3} rounded="lg" bg="bg-primary-blue/5" border borderColor="border-primary-blue/20">
<Stack mt={3} display="flex" alignItems="start" gap={2} p={3} rounded="lg" bg="bg-primary-blue/5" border borderColor="border-primary-blue/20">
<Icon icon={Info} size={4} color="text-primary-blue" mt={0.5} flexShrink={0} />
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
@@ -1722,9 +1721,9 @@ export function LeagueTimingsSection({
>
Preview updates live as you configure. Check <Text as="span" color="text-white" weight="medium">Race Day</Text> for session timing, <Text as="span" color="text-white" weight="medium">Calendar</Text> for the full year view, and <Text as="span" color="text-white" weight="medium">Stats</Text> for season totals.
</Text>
</Box>
</Box>
</Stack>
</Stack>
</Stack>
</Box>
</Stack>
);
}