137 lines
4.0 KiB
TypeScript
137 lines
4.0 KiB
TypeScript
'use client';
|
|
|
|
import type { LeagueConfigFormModel } from '@gridpilot/racing/application';
|
|
import Input from '@/components/ui/Input';
|
|
import SegmentedControl from '@/components/ui/SegmentedControl';
|
|
|
|
interface LeagueDropSectionProps {
|
|
form: LeagueConfigFormModel;
|
|
onChange?: (form: LeagueConfigFormModel) => void;
|
|
readOnly?: boolean;
|
|
}
|
|
|
|
export function LeagueDropSection({
|
|
form,
|
|
onChange,
|
|
readOnly,
|
|
}: LeagueDropSectionProps) {
|
|
const disabled = readOnly || !onChange;
|
|
const dropPolicy = form.dropPolicy;
|
|
|
|
const updateDropPolicy = (
|
|
patch: Partial<LeagueConfigFormModel['dropPolicy']>,
|
|
) => {
|
|
if (!onChange) return;
|
|
onChange({
|
|
...form,
|
|
dropPolicy: {
|
|
...dropPolicy,
|
|
...patch,
|
|
},
|
|
});
|
|
};
|
|
|
|
const handleStrategyChange = (
|
|
strategy: LeagueConfigFormModel['dropPolicy']['strategy'],
|
|
) => {
|
|
if (strategy === 'none') {
|
|
updateDropPolicy({ strategy: 'none', n: undefined });
|
|
} else if (strategy === 'bestNResults') {
|
|
const n = dropPolicy.n ?? 6;
|
|
updateDropPolicy({ strategy: 'bestNResults', n });
|
|
} else if (strategy === 'dropWorstN') {
|
|
const n = dropPolicy.n ?? 2;
|
|
updateDropPolicy({ strategy: 'dropWorstN', n });
|
|
}
|
|
};
|
|
|
|
const handleNChange = (value: string) => {
|
|
const parsed = parseInt(value, 10);
|
|
updateDropPolicy({
|
|
n: Number.isNaN(parsed) || parsed <= 0 ? undefined : parsed,
|
|
});
|
|
};
|
|
|
|
const computeSummary = () => {
|
|
if (dropPolicy.strategy === 'none') {
|
|
return 'All results will count towards the championship.';
|
|
}
|
|
if (dropPolicy.strategy === 'bestNResults') {
|
|
const n = dropPolicy.n;
|
|
if (typeof n === 'number' && n > 0) {
|
|
return `Best ${n} results will count; others are ignored.`;
|
|
}
|
|
return 'Best N results will count; others are ignored.';
|
|
}
|
|
if (dropPolicy.strategy === 'dropWorstN') {
|
|
const n = dropPolicy.n;
|
|
if (typeof n === 'number' && n > 0) {
|
|
return `Worst ${n} results will be dropped from the standings.`;
|
|
}
|
|
return 'Worst N results will be dropped from the standings.';
|
|
}
|
|
return 'All results will count towards the championship.';
|
|
};
|
|
|
|
const currentStrategyValue =
|
|
dropPolicy.strategy === 'none'
|
|
? 'all'
|
|
: dropPolicy.strategy === 'bestNResults'
|
|
? 'bestN'
|
|
: 'dropWorstN';
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<h3 className="text-sm font-semibold text-white">Drop rule</h3>
|
|
<p className="text-xs text-gray-400">
|
|
Decide whether to count every round or ignore a few worst results.
|
|
</p>
|
|
|
|
<SegmentedControl
|
|
options={[
|
|
{ value: 'all', label: 'All count' },
|
|
{ value: 'bestN', label: 'Best N' },
|
|
{ value: 'dropWorstN', label: 'Drop worst N' },
|
|
]}
|
|
value={currentStrategyValue}
|
|
onChange={(value) => {
|
|
if (disabled) return;
|
|
if (value === 'all') {
|
|
handleStrategyChange('none');
|
|
} else if (value === 'bestN') {
|
|
handleStrategyChange('bestNResults');
|
|
} else if (value === 'dropWorstN') {
|
|
handleStrategyChange('dropWorstN');
|
|
}
|
|
}}
|
|
/>
|
|
|
|
{(dropPolicy.strategy === 'bestNResults' ||
|
|
dropPolicy.strategy === 'dropWorstN') && (
|
|
<div className="mt-2 max-w-[140px]">
|
|
<label className="mb-1 block text-xs font-medium text-gray-300">
|
|
N
|
|
</label>
|
|
<Input
|
|
type="number"
|
|
value={
|
|
typeof dropPolicy.n === 'number' && dropPolicy.n > 0
|
|
? String(dropPolicy.n)
|
|
: ''
|
|
}
|
|
onChange={(e) => handleNChange(e.target.value)}
|
|
disabled={disabled}
|
|
min={1}
|
|
/>
|
|
<p className="mt-1 text-[11px] text-gray-500">
|
|
{dropPolicy.strategy === 'bestNResults'
|
|
? 'For example, best 6 of 10 rounds count.'
|
|
: 'For example, drop the worst 2 results.'}
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
<p className="mt-3 text-xs text-gray-400">{computeSummary()}</p>
|
|
</div>
|
|
);
|
|
} |