refactor
This commit is contained in:
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user