251 lines
10 KiB
TypeScript
251 lines
10 KiB
TypeScript
'use client';
|
|
|
|
import { FileText, Users, Calendar, Trophy, Award, Info } from 'lucide-react';
|
|
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 (
|
|
<div className="space-y-6">
|
|
<div className="rounded-lg bg-primary-blue/5 border border-primary-blue/20 p-4">
|
|
<div className="flex items-start gap-3">
|
|
<Info className="w-5 h-5 text-primary-blue shrink-0 mt-0.5" />
|
|
<div>
|
|
<p className="text-sm font-medium text-white mb-1">Review your league configuration</p>
|
|
<p className="text-xs text-gray-400">
|
|
Double-check all settings before creating your league. You can modify most of these later.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<Card className="bg-iron-gray/80">
|
|
<div className="space-y-6 text-sm text-gray-200">
|
|
{/* 1. Basics & visibility */}
|
|
<section className="space-y-3">
|
|
<div className="flex items-center gap-2 pb-2 border-b border-charcoal-outline/40">
|
|
<FileText className="w-4 h-4 text-primary-blue" />
|
|
<h3 className="text-sm font-semibold text-white">
|
|
Basics & visibility
|
|
</h3>
|
|
</div>
|
|
<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">
|
|
<div className="flex items-center gap-2 pb-2 border-b border-charcoal-outline/40">
|
|
<Users className="w-4 h-4 text-primary-blue" />
|
|
<h3 className="text-sm font-semibold text-white">
|
|
Structure & capacity
|
|
</h3>
|
|
</div>
|
|
<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">
|
|
<div className="flex items-center gap-2 pb-2 border-b border-charcoal-outline/40">
|
|
<Calendar className="w-4 h-4 text-primary-blue" />
|
|
<h3 className="text-sm font-semibold text-white">
|
|
Schedule & timings
|
|
</h3>
|
|
</div>
|
|
<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">
|
|
<div className="flex items-center gap-2 pb-2 border-b border-charcoal-outline/40">
|
|
<Trophy className="w-4 h-4 text-primary-blue" />
|
|
<h3 className="text-sm font-semibold text-white">
|
|
Scoring & drops
|
|
</h3>
|
|
</div>
|
|
<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">
|
|
<div className="flex items-center gap-2 pb-2 border-b border-charcoal-outline/40">
|
|
<Award className="w-4 h-4 text-primary-blue" />
|
|
<h3 className="text-sm font-semibold text-white">
|
|
Championships
|
|
</h3>
|
|
</div>
|
|
<dl className="grid grid-cols-1 gap-4">
|
|
<div className="space-y-1">
|
|
<dt className="text-xs text-gray-500">Enabled championships</dt>
|
|
<dd className="flex flex-wrap gap-2">
|
|
{enabledChampionshipsLabels.length > 0 ? (
|
|
enabledChampionshipsLabels.map((label) => (
|
|
<span key={label} className="inline-flex items-center gap-1 rounded-full bg-primary-blue/10 px-3 py-1 text-xs font-medium text-primary-blue">
|
|
<Award className="w-3 h-3" />
|
|
{label}
|
|
</span>
|
|
))
|
|
) : (
|
|
<span className="text-gray-400">None enabled yet</span>
|
|
)}
|
|
</dd>
|
|
</div>
|
|
</dl>
|
|
</section>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
);
|
|
} |