This commit is contained in:
2025-12-16 10:50:15 +01:00
parent 775d41e055
commit 8ed6ba1fd1
144 changed files with 5763 additions and 1985 deletions

View File

@@ -1,9 +1,89 @@
import type {
IAllLeaguesWithCapacityAndScoringPresenter,
LeagueEnrichedData,
LeagueSummaryViewModel,
AllLeaguesWithCapacityAndScoringViewModel,
} from '@gridpilot/racing/application/presenters/IAllLeaguesWithCapacityAndScoringPresenter';
/**
* AllLeaguesWithCapacityAndScoringPresenter - Pure data transformer
* Transforms API response to view model without DI dependencies.
*/
import { apiClient, type AllLeaguesWithCapacityViewModel } from '@/lib/apiClient';
export interface LeagueScoringViewModel {
gameId: string;
gameName: string;
primaryChampionshipType: string;
scoringPresetId: string;
scoringPresetName: string;
dropPolicySummary: string;
scoringPatternSummary: string;
}
export interface LeagueSummaryViewModel {
id: string;
name: string;
description?: string | undefined;
ownerId: string;
createdAt: string;
maxDrivers: number;
usedDriverSlots: number;
maxTeams: number;
usedTeamSlots: number;
structureSummary: string;
scoringPatternSummary: string;
timingSummary: string;
scoring: LeagueScoringViewModel;
}
export interface AllLeaguesWithCapacityAndScoringViewModel {
leagues: LeagueSummaryViewModel[];
totalCount: number;
}
export interface IAllLeaguesWithCapacityAndScoringPresenter {
reset(): void;
getViewModel(): AllLeaguesWithCapacityAndScoringViewModel | null;
}
/**
* Transform API response to view model
*/
function transformApiResponse(apiResponse: AllLeaguesWithCapacityViewModel): AllLeaguesWithCapacityAndScoringViewModel {
const leagueItems: LeagueSummaryViewModel[] = apiResponse.leagues.map((league) => {
const maxDrivers = league.maxMembers;
const usedDriverSlots = league.memberCount;
const structureSummary = `Solo • ${maxDrivers} drivers`;
const timingSummary = '30 min Quali • 40 min Race';
const scoringPatternSummary = 'Custom • All results count';
const scoringSummary: LeagueScoringViewModel = {
gameId: 'unknown',
gameName: 'Unknown',
primaryChampionshipType: 'driver',
scoringPresetId: 'custom',
scoringPresetName: 'Custom',
dropPolicySummary: 'All results count',
scoringPatternSummary,
};
return {
id: league.id,
name: league.name,
description: league.description,
ownerId: league.ownerId,
createdAt: new Date().toISOString(), // Would need from API
maxDrivers,
usedDriverSlots,
maxTeams: 0,
usedTeamSlots: 0,
structureSummary,
scoringPatternSummary,
timingSummary,
scoring: scoringSummary,
};
});
return {
leagues: leagueItems,
totalCount: leagueItems.length,
};
}
export class AllLeaguesWithCapacityAndScoringPresenter implements IAllLeaguesWithCapacityAndScoringPresenter {
private viewModel: AllLeaguesWithCapacityAndScoringViewModel | null = null;
@@ -12,116 +92,20 @@ export class AllLeaguesWithCapacityAndScoringPresenter implements IAllLeaguesWit
this.viewModel = null;
}
present(enrichedLeagues: LeagueEnrichedData[]): void {
const leagueItems: LeagueSummaryViewModel[] = enrichedLeagues.map((data) => {
const { league, usedDriverSlots, season, scoringConfig, game, preset } = data;
const configuredMaxDrivers = league.settings.maxDrivers ?? usedDriverSlots;
const safeMaxDrivers = Math.max(configuredMaxDrivers, usedDriverSlots);
const structureSummary = `Solo • ${safeMaxDrivers} drivers`;
const qualifyingMinutes = 30;
const mainRaceMinutes =
typeof league.settings.sessionDuration === 'number'
? league.settings.sessionDuration
: 40;
const timingSummary = `${qualifyingMinutes} min Quali • ${mainRaceMinutes} min Race`;
let scoringPatternSummary: string | null = null;
let scoringSummary: LeagueSummaryViewModel['scoring'];
if (season && scoringConfig && game) {
const dropPolicySummary =
preset?.dropPolicySummary ?? this.deriveDropPolicySummary(scoringConfig);
const primaryChampionshipType =
preset?.primaryChampionshipType ??
(scoringConfig.championships[0]?.type ?? 'driver');
const scoringPresetName = preset?.name ?? 'Custom';
scoringPatternSummary = `${scoringPresetName}${dropPolicySummary}`;
scoringSummary = {
gameId: game.id,
gameName: game.name,
primaryChampionshipType,
scoringPresetId: scoringConfig.scoringPresetId ?? 'custom',
scoringPresetName,
dropPolicySummary,
scoringPatternSummary,
};
} else {
const dropPolicySummary = 'All results count';
const scoringPresetName = 'Custom';
scoringPatternSummary = scoringPatternSummary ?? `${scoringPresetName}${dropPolicySummary}`;
scoringSummary = {
gameId: 'unknown',
gameName: 'Unknown',
primaryChampionshipType: 'driver',
scoringPresetId: 'custom',
scoringPresetName,
dropPolicySummary,
scoringPatternSummary,
};
}
const base: LeagueSummaryViewModel = {
id: league.id,
name: league.name,
description: league.description,
ownerId: league.ownerId,
createdAt: league.createdAt.toISOString(),
maxDrivers: safeMaxDrivers,
usedDriverSlots,
// Team capacity is not yet modeled here; use zero for now to satisfy strict typing.
maxTeams: 0,
usedTeamSlots: 0,
structureSummary,
scoringPatternSummary: scoringPatternSummary ?? '',
timingSummary,
scoring: scoringSummary,
};
return base;
});
this.viewModel = {
leagues: leagueItems,
totalCount: leagueItems.length,
};
async fetchAndPresent(): Promise<void> {
const apiResponse = await apiClient.leagues.getAllWithCapacity();
this.viewModel = transformApiResponse(apiResponse);
}
getViewModel(): AllLeaguesWithCapacityAndScoringViewModel | null {
return this.viewModel;
}
}
private deriveDropPolicySummary(config: {
championships: Array<{
dropScorePolicy: { strategy: string; count?: number; dropCount?: number };
}>;
}): string {
const championship = config.championships[0];
if (!championship) {
return 'All results count';
}
const policy = championship.dropScorePolicy;
if (!policy || policy.strategy === 'none') {
return 'All results count';
}
if (policy.strategy === 'bestNResults' && typeof policy.count === 'number') {
return `Best ${policy.count} results count`;
}
if (
policy.strategy === 'dropWorstN' &&
typeof policy.dropCount === 'number'
) {
return `Worst ${policy.dropCount} results are dropped`;
}
return 'Custom drop score rules';
}
/**
* Convenience function to fetch and transform all leagues
*/
export async function fetchAllLeaguesWithCapacityAndScoring(): Promise<AllLeaguesWithCapacityAndScoringViewModel> {
const apiResponse = await apiClient.leagues.getAllWithCapacity();
return transformApiResponse(apiResponse);
}