wip
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Calendar, Clock, MapPin, Zap, Info, Loader2 } from 'lucide-react';
|
||||
import type {
|
||||
LeagueConfigFormModel,
|
||||
LeagueSchedulePreviewDTO,
|
||||
@@ -312,66 +313,110 @@ export function LeagueTimingsSection({
|
||||
const weekendTemplateValue = weekendTemplate ?? '';
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Heading level={3} className="text-lg font-semibold text-white">
|
||||
{title ?? 'Schedule & timings'}
|
||||
</Heading>
|
||||
<div className="space-y-8">
|
||||
{/* Step intro */}
|
||||
<div className="rounded-lg bg-primary-blue/5 border border-primary-blue/20 p-4">
|
||||
<div className="flex items-start gap-2">
|
||||
<Info className="w-4 h-4 text-primary-blue shrink-0 mt-0.5" />
|
||||
<p className="text-sm text-gray-300">
|
||||
<span className="font-medium text-primary-blue">Quick setup:</span> Pick a weekend template and season length. The detailed schedule configuration is optional—you can set it now or schedule races manually later.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* Season length block */}
|
||||
<section className="space-y-3">
|
||||
<h4 className="text-xs font-semibold uppercase tracking-wide text-gray-400">
|
||||
Season length
|
||||
{/* 1. Weekend template - FIRST */}
|
||||
<section className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Zap className="w-4 h-4 text-primary-blue" />
|
||||
<h4 className="text-sm font-semibold text-white">
|
||||
1. Choose your race weekend format
|
||||
</h4>
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-300">
|
||||
Planned rounds
|
||||
</label>
|
||||
<div className="w-24">
|
||||
<Input
|
||||
type="number"
|
||||
value={
|
||||
typeof timings.roundsPlanned === 'number'
|
||||
? String(timings.roundsPlanned)
|
||||
: ''
|
||||
}
|
||||
onChange={(e) => handleRoundsChange(e.target.value)}
|
||||
min={1}
|
||||
error={!!errors?.roundsPlanned}
|
||||
errorMessage={errors?.roundsPlanned}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">
|
||||
This determines session counts and sets sensible duration defaults
|
||||
</p>
|
||||
<SegmentedControl
|
||||
options={[
|
||||
{ value: 'feature', label: 'Feature only', description: '1 race per weekend' },
|
||||
{ value: 'sprintFeature', label: 'Sprint + Feature', description: '2 races per weekend' },
|
||||
{ value: 'endurance', label: 'Endurance', description: 'Longer races' },
|
||||
]}
|
||||
value={weekendTemplateValue}
|
||||
onChange={onWeekendTemplateChange}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* 2. Season length */}
|
||||
<section className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="w-4 h-4 text-primary-blue" />
|
||||
<h4 className="text-sm font-semibold text-white">
|
||||
2. How many race rounds?
|
||||
</h4>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Input
|
||||
type="number"
|
||||
value={
|
||||
typeof timings.roundsPlanned === 'number'
|
||||
? String(timings.roundsPlanned)
|
||||
: ''
|
||||
}
|
||||
onChange={(e) => handleRoundsChange(e.target.value)}
|
||||
min={1}
|
||||
error={!!errors?.roundsPlanned}
|
||||
errorMessage={errors?.roundsPlanned}
|
||||
className="w-32"
|
||||
placeholder="e.g., 10"
|
||||
/>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRoundsChange('6')}
|
||||
className="px-3 py-1.5 rounded text-xs bg-iron-gray border border-charcoal-outline text-gray-300 hover:border-primary-blue hover:text-primary-blue transition-colors"
|
||||
>
|
||||
Short season (6)
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRoundsChange('10')}
|
||||
className="px-3 py-1.5 rounded text-xs bg-iron-gray border border-charcoal-outline text-gray-300 hover:border-primary-blue hover:text-primary-blue transition-colors"
|
||||
>
|
||||
Medium season (10)
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRoundsChange('16')}
|
||||
className="px-3 py-1.5 rounded text-xs bg-iron-gray border border-charcoal-outline text-gray-300 hover:border-primary-blue hover:text-primary-blue transition-colors"
|
||||
>
|
||||
Long season (16)
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">
|
||||
Used for drop rule suggestions. Can be approximate—you can always add or remove rounds.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 3. Optional: Detailed schedule */}
|
||||
<section className="space-y-4">
|
||||
<div className="rounded-lg border-2 border-dashed border-charcoal-outline p-4 space-y-4">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Clock className="w-4 h-4 text-gray-400" />
|
||||
<h4 className="text-sm font-semibold text-white">
|
||||
3. Automatic schedule (optional)
|
||||
</h4>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-gray-500">
|
||||
Used for planning and drop hints; can be approximate.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-300">
|
||||
Sessions per weekend
|
||||
</label>
|
||||
<div className="w-24">
|
||||
<Input
|
||||
type="number"
|
||||
value={String(timings.sessionCount)}
|
||||
onChange={(e) => handleSessionCountChange(e.target.value)}
|
||||
min={1}
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-gray-500">
|
||||
Typically 1 for feature-only; 2 for sprint + feature.
|
||||
<p className="text-xs text-gray-500">
|
||||
Configure when races happen automatically, or skip this and schedule rounds manually later
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Race schedule block */}
|
||||
<section className="space-y-4">
|
||||
<h4 className="text-xs font-semibold uppercase tracking-wide text-gray-400">
|
||||
Race schedule
|
||||
</h4>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-300">
|
||||
Season start date
|
||||
@@ -428,56 +473,47 @@ export function LeagueTimingsSection({
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-[minmax(0,2fr)_minmax(0,3fr)] items-start">
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-300">
|
||||
Cadence
|
||||
</label>
|
||||
<SegmentedControl
|
||||
options={[
|
||||
{ value: 'weekly', label: 'Weekly' },
|
||||
{ value: 'everyNWeeks', label: 'Every N weeks' },
|
||||
{
|
||||
value: 'monthlyNthWeekday',
|
||||
label: 'Monthly (beta)',
|
||||
disabled: true,
|
||||
},
|
||||
]}
|
||||
value={recurrenceStrategy}
|
||||
onChange={handleRecurrenceChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block text-sm font-medium text-gray-300">
|
||||
How often do races occur?
|
||||
</label>
|
||||
<SegmentedControl
|
||||
options={[
|
||||
{ value: 'weekly', label: 'Weekly' },
|
||||
{ value: 'everyNWeeks', label: 'Bi-weekly' },
|
||||
]}
|
||||
value={recurrenceStrategy}
|
||||
onChange={handleRecurrenceChange}
|
||||
/>
|
||||
|
||||
{recurrenceStrategy === 'everyNWeeks' && (
|
||||
<div className="flex items-center gap-2 text-sm text-gray-300">
|
||||
<span>Every</span>
|
||||
<div className="w-20">
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={
|
||||
typeof timings.intervalWeeks === 'number'
|
||||
? String(timings.intervalWeeks)
|
||||
: '2'
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<span className="text-gray-300">Every</span>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={
|
||||
typeof timings.intervalWeeks === 'number'
|
||||
? String(timings.intervalWeeks)
|
||||
: '2'
|
||||
}
|
||||
onChange={(e) => {
|
||||
const raw = e.target.value.trim();
|
||||
if (raw === '') {
|
||||
updateTimings({ intervalWeeks: undefined });
|
||||
return;
|
||||
}
|
||||
onChange={(e) => {
|
||||
const raw = e.target.value.trim();
|
||||
if (raw === '') {
|
||||
updateTimings({ intervalWeeks: undefined });
|
||||
return;
|
||||
}
|
||||
const parsed = parseInt(raw, 10);
|
||||
updateTimings({
|
||||
intervalWeeks:
|
||||
Number.isNaN(parsed) || parsed <= 0 ? 2 : parsed,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span>weeks</span>
|
||||
const parsed = parseInt(raw, 10);
|
||||
updateTimings({
|
||||
intervalWeeks:
|
||||
Number.isNaN(parsed) || parsed <= 0 ? 2 : parsed,
|
||||
});
|
||||
}}
|
||||
className="w-20"
|
||||
/>
|
||||
<span className="text-gray-300">weeks</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -485,11 +521,11 @@ export function LeagueTimingsSection({
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<label className="block text-sm font-medium text-gray-300">
|
||||
Race days in a week
|
||||
Which day(s)?
|
||||
</label>
|
||||
{requiresWeekdaySelection && (
|
||||
<span className="text-[11px] text-warning-amber">
|
||||
Select at least one weekday.
|
||||
<span className="text-xs text-warning-amber">
|
||||
Pick at least one
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -502,10 +538,10 @@ export function LeagueTimingsSection({
|
||||
key={day}
|
||||
type="button"
|
||||
onClick={() => handleWeekdayToggle(day)}
|
||||
className={`px-3 py-1 text-xs font-medium rounded-full border transition-colors ${
|
||||
className={`px-3 py-1.5 text-xs font-medium rounded-full border transition-all duration-200 ${
|
||||
isActive
|
||||
? 'bg-primary-blue text-white border-primary-blue'
|
||||
: 'bg-iron-gray/80 text-gray-300 border-charcoal-outline hover:bg-charcoal-outline/80 hover:text-white'
|
||||
? 'bg-primary-blue text-white border-primary-blue shadow-[0_0_10px_rgba(25,140,255,0.3)]'
|
||||
: 'bg-iron-gray/80 text-gray-300 border-charcoal-outline hover:bg-charcoal-outline hover:text-white hover:border-gray-500'
|
||||
}`}
|
||||
>
|
||||
{day}
|
||||
@@ -517,235 +553,196 @@ export function LeagueTimingsSection({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 rounded-md border border-charcoal-outline bg-iron-gray/40 p-3">
|
||||
{/* Schedule preview */}
|
||||
{(timings.seasonStartDate && timings.raceStartTime && weekdays.length > 0) && (
|
||||
<div className="space-y-3 rounded-lg border border-charcoal-outline bg-iron-gray/40 p-4">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div>
|
||||
<p className="text-xs font-medium text-gray-200">
|
||||
Schedule summary
|
||||
</p>
|
||||
<p className="text-xs text-gray-400">
|
||||
{schedulePreview?.summary ??
|
||||
'Set a start date, time, and at least one weekday to preview the schedule.'}
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="w-4 h-4 text-primary-blue" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-200">
|
||||
Schedule preview
|
||||
</p>
|
||||
<p className="text-xs text-gray-400">
|
||||
{schedulePreview?.summary ??
|
||||
'Set a start date, time, and at least one weekday to preview the schedule.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{isSchedulePreviewLoading && (
|
||||
<span className="text-[11px] text-gray-400">Updating…</span>
|
||||
<Loader2 className="w-4 h-4 text-primary-blue animate-spin" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-2 space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-xs font-medium text-gray-300">
|
||||
Schedule preview
|
||||
</p>
|
||||
<p className="text-[11px] text-gray-500">
|
||||
First few rounds with your current settings.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{schedulePreviewError && (
|
||||
<p className="text-[11px] text-warning-amber">
|
||||
{schedulePreviewError}
|
||||
</p>
|
||||
<div className="rounded-md bg-warning-amber/10 p-3 border border-warning-amber/20 text-xs text-warning-amber flex items-start gap-2">
|
||||
<span className="shrink-0">⚠️</span>
|
||||
<span>{schedulePreviewError}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!schedulePreview && !schedulePreviewError && (
|
||||
<p className="text-[11px] text-gray-500">
|
||||
Adjust the fields above to see a preview of your calendar.
|
||||
<p className="text-xs text-gray-500 flex items-start gap-1.5">
|
||||
<Info className="w-3 h-3 mt-0.5 shrink-0" />
|
||||
<span>Adjust the fields above to see a preview of your calendar.</span>
|
||||
</p>
|
||||
)}
|
||||
|
||||
{schedulePreview && (
|
||||
<div className="mt-1 space-y-1.5 text-xs text-gray-200">
|
||||
{schedulePreview.rounds.map((round) => {
|
||||
const date = new Date(round.scheduledAt);
|
||||
const dateStr = date.toLocaleDateString(undefined, {
|
||||
weekday: 'short',
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
});
|
||||
const timeStr = date.toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs text-gray-500">
|
||||
First few rounds with your current settings:
|
||||
</p>
|
||||
<div className="space-y-1.5">
|
||||
{schedulePreview.rounds.map((round) => {
|
||||
const date = new Date(round.scheduledAt);
|
||||
const dateStr = date.toLocaleDateString(undefined, {
|
||||
weekday: 'short',
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
});
|
||||
const timeStr = date.toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
key={round.roundNumber}
|
||||
className="flex items-center justify-between gap-2"
|
||||
>
|
||||
<span className="text-gray-300">
|
||||
Round {round.roundNumber}
|
||||
</span>
|
||||
<span className="text-gray-200">
|
||||
{dateStr}, {timeStr}{' '}
|
||||
<span className="text-gray-500">
|
||||
{round.timezoneId}
|
||||
return (
|
||||
<div
|
||||
key={round.roundNumber}
|
||||
className="flex items-center justify-between gap-2 px-3 py-2 rounded-md bg-deep-graphite/40 text-xs"
|
||||
>
|
||||
<span className="font-medium text-gray-300">
|
||||
Round {round.roundNumber}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<span className="text-gray-200">
|
||||
{dateStr}, {timeStr}{' '}
|
||||
<span className="text-gray-500">
|
||||
{round.timezoneId}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{typeof timings.roundsPlanned === 'number' &&
|
||||
timings.roundsPlanned > schedulePreview.rounds.length && (
|
||||
<p className="pt-1 text-[11px] text-gray-500">
|
||||
+
|
||||
{timings.roundsPlanned - schedulePreview.rounds.length}{' '}
|
||||
more rounds scheduled.
|
||||
</p>
|
||||
)}
|
||||
{typeof timings.roundsPlanned === 'number' &&
|
||||
timings.roundsPlanned > schedulePreview.rounds.length && (
|
||||
<p className="pt-1 text-xs text-gray-500 text-center">
|
||||
+ {timings.roundsPlanned - schedulePreview.rounds.length} more rounds scheduled
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Weekend template block */}
|
||||
<section className="space-y-3">
|
||||
<h4 className="text-xs font-semibold uppercase tracking-wide text-gray-400">
|
||||
Weekend template
|
||||
</h4>
|
||||
{/* 4. Optional: Session duration overrides */}
|
||||
<details className="group">
|
||||
<summary className="cursor-pointer list-none">
|
||||
<div className="flex items-center justify-between p-4 rounded-lg border border-charcoal-outline bg-iron-gray/20 hover:border-primary-blue/50 transition-colors">
|
||||
<div className="flex items-center gap-2">
|
||||
<Clock className="w-4 h-4 text-gray-400" />
|
||||
<h4 className="text-sm font-semibold text-white">
|
||||
4. Customize session durations (optional)
|
||||
</h4>
|
||||
</div>
|
||||
<span className="text-xs text-gray-500 group-open:hidden">
|
||||
Click to customize
|
||||
</span>
|
||||
</div>
|
||||
</summary>
|
||||
|
||||
<div className="mt-4 pl-6 space-y-4">
|
||||
<p className="text-xs text-gray-500">
|
||||
Pick a typical weekend; you can fine-tune durations below.
|
||||
Your weekend template already set reasonable defaults. Only change these if you need specific timings.
|
||||
</p>
|
||||
<SegmentedControl
|
||||
options={[
|
||||
{ value: 'feature', label: 'Feature only' },
|
||||
{ value: 'sprintFeature', label: 'Sprint + feature' },
|
||||
{ value: 'endurance', label: 'Endurance' },
|
||||
]}
|
||||
value={weekendTemplateValue}
|
||||
onChange={onWeekendTemplateChange}
|
||||
/>
|
||||
<p className="text-[11px] text-gray-500">
|
||||
Templates set starting values only; you can override any number.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Session durations block */}
|
||||
<section className="space-y-3">
|
||||
<h4 className="text-xs font-semibold uppercase tracking-wide text-gray-400">
|
||||
Session durations
|
||||
</h4>
|
||||
<p className="text-xs text-gray-500">
|
||||
Rough lengths for each session type; used for planning only.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-300">
|
||||
Practice duration (optional)
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4">
|
||||
<div className="space-y-2">
|
||||
<label className="block text-xs font-medium text-gray-300">
|
||||
Practice (min)
|
||||
</label>
|
||||
<div className="w-24">
|
||||
<Input
|
||||
type="number"
|
||||
value={
|
||||
typeof timings.practiceMinutes === 'number' &&
|
||||
timings.practiceMinutes > 0
|
||||
? String(timings.practiceMinutes)
|
||||
: ''
|
||||
}
|
||||
onChange={(e) =>
|
||||
handleNumericMinutesChange(
|
||||
'practiceMinutes',
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
min={0}
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-gray-500">
|
||||
Set to 0 or leave empty if you don’t plan dedicated practice.
|
||||
</p>
|
||||
<Input
|
||||
type="number"
|
||||
value={
|
||||
typeof timings.practiceMinutes === 'number' && timings.practiceMinutes > 0
|
||||
? String(timings.practiceMinutes)
|
||||
: ''
|
||||
}
|
||||
onChange={(e) => handleNumericMinutesChange('practiceMinutes', e.target.value)}
|
||||
min={0}
|
||||
className="w-24"
|
||||
placeholder="20"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-300">
|
||||
Qualifying duration
|
||||
<div className="space-y-2">
|
||||
<label className="block text-xs font-medium text-gray-300">
|
||||
Qualifying (min)
|
||||
</label>
|
||||
<div className="w-24">
|
||||
<Input
|
||||
type="number"
|
||||
value={
|
||||
typeof timings.qualifyingMinutes === 'number' &&
|
||||
timings.qualifyingMinutes > 0
|
||||
? String(timings.qualifyingMinutes)
|
||||
: ''
|
||||
}
|
||||
onChange={(e) =>
|
||||
handleNumericMinutesChange(
|
||||
'qualifyingMinutes',
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
min={5}
|
||||
error={!!errors?.qualifyingMinutes}
|
||||
errorMessage={errors?.qualifyingMinutes}
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
type="number"
|
||||
value={
|
||||
typeof timings.qualifyingMinutes === 'number' && timings.qualifyingMinutes > 0
|
||||
? String(timings.qualifyingMinutes)
|
||||
: ''
|
||||
}
|
||||
onChange={(e) => handleNumericMinutesChange('qualifyingMinutes', e.target.value)}
|
||||
min={5}
|
||||
error={!!errors?.qualifyingMinutes}
|
||||
errorMessage={errors?.qualifyingMinutes}
|
||||
className="w-24"
|
||||
placeholder="30"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{showSprint && (
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-300">
|
||||
Sprint duration
|
||||
<div className="space-y-2">
|
||||
<label className="block text-xs font-medium text-gray-300">
|
||||
Sprint (min)
|
||||
</label>
|
||||
<div className="w-24">
|
||||
<Input
|
||||
type="number"
|
||||
value={
|
||||
typeof timings.sprintRaceMinutes === 'number' &&
|
||||
timings.sprintRaceMinutes > 0
|
||||
? String(timings.sprintRaceMinutes)
|
||||
: ''
|
||||
}
|
||||
onChange={(e) =>
|
||||
handleNumericMinutesChange(
|
||||
'sprintRaceMinutes',
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
min={0}
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-gray-500">
|
||||
Only shown when your scoring pattern includes a sprint race.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-300">
|
||||
Main race duration
|
||||
</label>
|
||||
<div className="w-24">
|
||||
<Input
|
||||
type="number"
|
||||
value={
|
||||
typeof timings.mainRaceMinutes === 'number' &&
|
||||
timings.mainRaceMinutes > 0
|
||||
? String(timings.mainRaceMinutes)
|
||||
typeof timings.sprintRaceMinutes === 'number' && timings.sprintRaceMinutes > 0
|
||||
? String(timings.sprintRaceMinutes)
|
||||
: ''
|
||||
}
|
||||
onChange={(e) =>
|
||||
handleNumericMinutesChange(
|
||||
'mainRaceMinutes',
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
min={10}
|
||||
error={!!errors?.mainRaceMinutes}
|
||||
errorMessage={errors?.mainRaceMinutes}
|
||||
onChange={(e) => handleNumericMinutesChange('sprintRaceMinutes', e.target.value)}
|
||||
min={0}
|
||||
className="w-24"
|
||||
placeholder="20"
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-gray-500">
|
||||
Approximate length of your main race.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="block text-xs font-medium text-gray-300">
|
||||
Main race (min)
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
value={
|
||||
typeof timings.mainRaceMinutes === 'number' && timings.mainRaceMinutes > 0
|
||||
? String(timings.mainRaceMinutes)
|
||||
: ''
|
||||
}
|
||||
onChange={(e) => handleNumericMinutesChange('mainRaceMinutes', e.target.value)}
|
||||
min={10}
|
||||
error={!!errors?.mainRaceMinutes}
|
||||
errorMessage={errors?.mainRaceMinutes}
|
||||
className="w-24"
|
||||
placeholder="40"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user