This commit is contained in:
2025-12-05 12:24:38 +01:00
parent fb509607c1
commit 5a9cd28d5b
47 changed files with 5456 additions and 228 deletions

View File

@@ -9,18 +9,21 @@ import LeagueMembers from '@/components/leagues/LeagueMembers';
import LeagueSchedule from '@/components/leagues/LeagueSchedule';
import LeagueAdmin from '@/components/leagues/LeagueAdmin';
import StandingsTable from '@/components/leagues/StandingsTable';
import LeagueScoringTab from '@/components/leagues/LeagueScoringTab';
import DriverIdentity from '@/components/drivers/DriverIdentity';
import DriverSummaryPill from '@/components/profile/DriverSummaryPill';
import { League } from '@gridpilot/racing/domain/entities/League';
import { Driver } from '@gridpilot/racing/domain/entities/Driver';
import type { DriverDTO } from '@gridpilot/racing/application/dto/DriverDTO';
import type { LeagueDriverSeasonStatsDTO } from '@gridpilot/racing/application/dto/LeagueDriverSeasonStatsDTO';
import type { LeagueScoringConfigDTO } from '@gridpilot/racing/application/dto/LeagueScoringConfigDTO';
import { EntityMappers } from '@gridpilot/racing/application/mappers/EntityMappers';
import {
getLeagueRepository,
getRaceRepository,
getDriverRepository,
getGetLeagueDriverSeasonStatsQuery,
getGetLeagueScoringConfigQuery,
getDriverStats,
getAllDriverRankings,
} from '@/lib/di-container';
@@ -36,9 +39,12 @@ export default function LeagueDetailPage() {
const [owner, setOwner] = useState<Driver | null>(null);
const [standings, setStandings] = useState<LeagueDriverSeasonStatsDTO[]>([]);
const [drivers, setDrivers] = useState<DriverDTO[]>([]);
const [scoringConfig, setScoringConfig] = useState<LeagueScoringConfigDTO | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [activeTab, setActiveTab] = useState<'overview' | 'schedule' | 'standings' | 'admin'>('overview');
const [activeTab, setActiveTab] = useState<
'overview' | 'schedule' | 'standings' | 'scoring' | 'admin'
>('overview');
const [refreshKey, setRefreshKey] = useState(0);
const currentDriverId = useEffectiveDriverId();
@@ -71,6 +77,11 @@ export default function LeagueDetailPage() {
const leagueStandings = await getLeagueDriverSeasonStatsQuery.execute({ leagueId });
setStandings(leagueStandings);
// Load scoring configuration for the active season
const getLeagueScoringConfigQuery = getGetLeagueScoringConfigQuery();
const scoring = await getLeagueScoringConfigQuery.execute({ leagueId });
setScoringConfig(scoring);
// Load all drivers for standings and map to DTOs for UI components
const allDrivers = await driverRepo.findAll();
const driverDtos: DriverDTO[] = allDrivers
@@ -100,9 +111,10 @@ export default function LeagueDetailPage() {
initialTab === 'overview' ||
initialTab === 'schedule' ||
initialTab === 'standings' ||
initialTab === 'scoring' ||
initialTab === 'admin'
) {
setActiveTab(initialTab);
setActiveTab(initialTab as typeof activeTab);
}
}, [searchParams]);
@@ -231,6 +243,16 @@ export default function LeagueDetailPage() {
>
Standings
</button>
<button
onClick={() => setActiveTab('scoring')}
className={`px-3 py-1 text-xs font-medium rounded-full transition-colors ${
activeTab === 'scoring'
? 'bg-primary-blue text-white'
: 'text-gray-300 hover:text-white hover:bg-charcoal-outline/80'
}`}
>
Scoring
</button>
{isAdmin && (
<button
onClick={() => setActiveTab('admin')}
@@ -266,22 +288,36 @@ export default function LeagueDetailPage() {
</div>
<div className="pt-4 border-t border-charcoal-outline">
<h3 className="text-white font-medium mb-3">League Settings</h3>
<h3 className="text-white font-medium mb-3">At a glance</h3>
<div className="grid grid-cols-2 gap-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm">
<div>
<label className="text-sm text-gray-500">Points System</label>
<p className="text-white">{league.settings.pointsSystem.toUpperCase()}</p>
<h4 className="text-xs font-semibold text-gray-400 uppercase tracking-wide mb-1">
Structure
</h4>
<p className="text-gray-200">
Solo {league.settings.maxDrivers ?? 32} drivers
</p>
</div>
<div>
<label className="text-sm text-gray-500">Session Duration</label>
<p className="text-white">{league.settings.sessionDuration} minutes</p>
<h4 className="text-xs font-semibold text-gray-400 uppercase tracking-wide mb-1">
Schedule
</h4>
<p className="text-gray-200">
{`? rounds • 30 min Qualifying • ${
typeof league.settings.sessionDuration === 'number'
? league.settings.sessionDuration
: 40
} min Races`}
</p>
</div>
<div>
<label className="text-sm text-gray-500">Qualifying Format</label>
<p className="text-white capitalize">{league.settings.qualifyingFormat}</p>
<h4 className="text-xs font-semibold text-gray-400 uppercase tracking-wide mb-1">
Scoring & drops
</h4>
<p className="text-gray-200">
{league.settings.pointsSystem.toUpperCase()}
</p>
</div>
</div>
</div>
@@ -439,6 +475,23 @@ export default function LeagueDetailPage() {
</Card>
)}
{activeTab === 'scoring' && (
<Card>
<h2 className="text-xl font-semibold text-white mb-4">Scoring</h2>
<LeagueScoringTab
scoringConfig={scoringConfig}
practiceMinutes={20}
qualifyingMinutes={30}
sprintRaceMinutes={20}
mainRaceMinutes={
typeof league.settings.sessionDuration === 'number'
? league.settings.sessionDuration
: 40
}
/>
</Card>
)}
{activeTab === 'admin' && isAdmin && (
<LeagueAdmin
league={league}