210 lines
8.0 KiB
TypeScript
210 lines
8.0 KiB
TypeScript
'use client';
|
|
|
|
import Card from '@/components/ui/Card';
|
|
import type { LeagueConfigFormModel } from '@gridpilot/racing/application';
|
|
import type { LeagueScoringPresetDTO } from '@gridpilot/racing/application/ports/LeagueScoringPresetProvider';
|
|
|
|
interface LeagueReviewSummaryProps {
|
|
form: LeagueConfigFormModel;
|
|
presets: LeagueScoringPresetDTO[];
|
|
}
|
|
|
|
export default function LeagueReviewSummary({ form, presets }: LeagueReviewSummaryProps) {
|
|
const { basics, structure, timings, scoring, championships, dropPolicy } = form;
|
|
|
|
const modeLabel =
|
|
structure.mode === 'solo'
|
|
? 'Drivers only (solo)'
|
|
: 'Teams with fixed drivers per team';
|
|
|
|
const capacitySentence = (() => {
|
|
if (structure.mode === 'solo') {
|
|
if (typeof structure.maxDrivers === 'number') {
|
|
return `Up to ${structure.maxDrivers} drivers`;
|
|
}
|
|
return 'Capacity not fully specified';
|
|
}
|
|
const parts: string[] = [];
|
|
if (typeof structure.maxTeams === 'number') {
|
|
parts.push(`Teams: ${structure.maxTeams}`);
|
|
}
|
|
if (typeof structure.driversPerTeam === 'number') {
|
|
parts.push(`Drivers per team: ${structure.driversPerTeam}`);
|
|
}
|
|
if (typeof structure.maxDrivers === 'number') {
|
|
parts.push(`Max grid: ${structure.maxDrivers}`);
|
|
}
|
|
if (parts.length === 0) {
|
|
return '—';
|
|
}
|
|
return parts.join(', ');
|
|
})();
|
|
|
|
const formatMinutes = (value: number | undefined) => {
|
|
if (typeof value !== 'number' || value <= 0) return '—';
|
|
return `${value} min`;
|
|
};
|
|
|
|
const dropRuleSentence = (() => {
|
|
if (dropPolicy.strategy === 'none') {
|
|
return 'All results will count towards the championship.';
|
|
}
|
|
if (dropPolicy.strategy === 'bestNResults') {
|
|
if (typeof dropPolicy.n === 'number' && dropPolicy.n > 0) {
|
|
return `Best ${dropPolicy.n} results will count; others are ignored.`;
|
|
}
|
|
return 'Best N results will count; others are ignored.';
|
|
}
|
|
if (dropPolicy.strategy === 'dropWorstN') {
|
|
if (typeof dropPolicy.n === 'number' && dropPolicy.n > 0) {
|
|
return `Worst ${dropPolicy.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 preset =
|
|
presets.find((p) => p.id === scoring.patternId) ?? null;
|
|
|
|
const scoringPresetName = preset ? preset.name : scoring.patternId ? 'Preset not found' : '—';
|
|
const scoringPatternSummary = preset?.sessionSummary ?? '—';
|
|
const dropPolicySummary = dropRuleSentence;
|
|
|
|
const enabledChampionshipsLabels: string[] = [];
|
|
if (championships.enableDriverChampionship) enabledChampionshipsLabels.push('Driver');
|
|
if (championships.enableTeamChampionship) enabledChampionshipsLabels.push('Team');
|
|
if (championships.enableNationsChampionship) enabledChampionshipsLabels.push('Nations Cup');
|
|
if (championships.enableTrophyChampionship) enabledChampionshipsLabels.push('Trophy');
|
|
|
|
const championshipsSummary =
|
|
enabledChampionshipsLabels.length === 0
|
|
? 'None enabled yet.'
|
|
: enabledChampionshipsLabels.join(', ');
|
|
|
|
const visibilityLabel = basics.visibility === 'public' ? 'Public' : 'Private';
|
|
const gameLabel = 'iRacing';
|
|
|
|
return (
|
|
<Card className="bg-iron-gray/80">
|
|
<div className="space-y-6 text-sm text-gray-200">
|
|
{/* 1. Basics & visibility */}
|
|
<section className="space-y-3">
|
|
<h3 className="text-[11px] font-semibold text-gray-400 uppercase tracking-wide">
|
|
Basics & visibility
|
|
</h3>
|
|
<dl className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
<div className="space-y-1">
|
|
<dt className="text-xs text-gray-500">Name</dt>
|
|
<dd className="font-medium text-white">{basics.name || '—'}</dd>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<dt className="text-xs text-gray-500">Visibility</dt>
|
|
<dd>{visibilityLabel}</dd>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<dt className="text-xs text-gray-500">Game</dt>
|
|
<dd>{gameLabel}</dd>
|
|
</div>
|
|
{basics.description && (
|
|
<div className="space-y-1 md:col-span-2">
|
|
<dt className="text-xs text-gray-500">Description</dt>
|
|
<dd className="text-gray-300">{basics.description}</dd>
|
|
</div>
|
|
)}
|
|
</dl>
|
|
</section>
|
|
|
|
{/* 2. Structure & capacity */}
|
|
<section className="space-y-3">
|
|
<h3 className="text-[11px] font-semibold text-gray-400 uppercase tracking-wide">
|
|
Structure & capacity
|
|
</h3>
|
|
<dl className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
<div className="space-y-1">
|
|
<dt className="text-xs text-gray-500">Mode</dt>
|
|
<dd>{modeLabel}</dd>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<dt className="text-xs text-gray-500">Capacity</dt>
|
|
<dd>{capacitySentence}</dd>
|
|
</div>
|
|
</dl>
|
|
</section>
|
|
|
|
{/* 3. Schedule & timings */}
|
|
<section className="space-y-3">
|
|
<h3 className="text-[11px] font-semibold text-gray-400 uppercase tracking-wide">
|
|
Schedule & timings
|
|
</h3>
|
|
<dl className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
<div className="space-y-1">
|
|
<dt className="text-xs text-gray-500">Planned rounds</dt>
|
|
<dd>{typeof timings.roundsPlanned === 'number' ? timings.roundsPlanned : '—'}</dd>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<dt className="text-xs text-gray-500">Sessions per weekend</dt>
|
|
<dd>{timings.sessionCount ?? '—'}</dd>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<dt className="text-xs text-gray-500">Practice</dt>
|
|
<dd>{formatMinutes(timings.practiceMinutes)}</dd>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<dt className="text-xs text-gray-500">Qualifying</dt>
|
|
<dd>{formatMinutes(timings.qualifyingMinutes)}</dd>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<dt className="text-xs text-gray-500">Sprint</dt>
|
|
<dd>{formatMinutes(timings.sprintRaceMinutes)}</dd>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<dt className="text-xs text-gray-500">Main race</dt>
|
|
<dd>{formatMinutes(timings.mainRaceMinutes)}</dd>
|
|
</div>
|
|
</dl>
|
|
</section>
|
|
|
|
{/* 4. Scoring & drops */}
|
|
<section className="space-y-3">
|
|
<h3 className="text-[11px] font-semibold text-gray-400 uppercase tracking-wide">
|
|
Scoring & drops
|
|
</h3>
|
|
<dl className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
<div className="space-y-1">
|
|
<dt className="text-xs text-gray-500">Scoring pattern</dt>
|
|
<dd>{scoringPresetName}</dd>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<dt className="text-xs text-gray-500">Pattern summary</dt>
|
|
<dd>{scoringPatternSummary}</dd>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<dt className="text-xs text-gray-500">Drop rule</dt>
|
|
<dd>{dropPolicySummary}</dd>
|
|
</div>
|
|
{scoring.customScoringEnabled && (
|
|
<div className="space-y-1">
|
|
<dt className="text-xs text-gray-500">Custom scoring</dt>
|
|
<dd>Custom scoring flagged</dd>
|
|
</div>
|
|
)}
|
|
</dl>
|
|
</section>
|
|
|
|
{/* 5. Championships */}
|
|
<section className="space-y-3">
|
|
<h3 className="text-[11px] font-semibold text-gray-400 uppercase tracking-wide">
|
|
Championships
|
|
</h3>
|
|
<dl className="grid grid-cols-1 gap-4">
|
|
<div className="space-y-1">
|
|
<dt className="text-xs text-gray-500">Enabled championships</dt>
|
|
<dd>{championshipsSummary}</dd>
|
|
</div>
|
|
</dl>
|
|
</section>
|
|
</div>
|
|
</Card>
|
|
);
|
|
} |