website refactor
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user