This commit is contained in:
2025-12-12 01:11:36 +01:00
parent ec3ddc3a5c
commit 6a88fe93ab
125 changed files with 1513 additions and 803 deletions

View File

@@ -1,4 +1,3 @@
import type { League } from '@gridpilot/racing/domain/entities/League';
import type {
IAllLeaguesWithCapacityAndScoringPresenter,
LeagueEnrichedData,
@@ -9,7 +8,11 @@ import type {
export class AllLeaguesWithCapacityAndScoringPresenter implements IAllLeaguesWithCapacityAndScoringPresenter {
private viewModel: AllLeaguesWithCapacityAndScoringViewModel | null = null;
present(enrichedLeagues: LeagueEnrichedData[]): AllLeaguesWithCapacityAndScoringViewModel {
reset(): void {
this.viewModel = null;
}
present(enrichedLeagues: LeagueEnrichedData[]): void {
const leagueItems: LeagueSummaryViewModel[] = enrichedLeagues.map((data) => {
const { league, usedDriverSlots, season, scoringConfig, game, preset } = data;
@@ -68,7 +71,7 @@ export class AllLeaguesWithCapacityAndScoringPresenter implements IAllLeaguesWit
name: league.name,
description: league.description,
ownerId: league.ownerId,
createdAt: league.createdAt,
createdAt: league.createdAt.toISOString(),
maxDrivers: safeMaxDrivers,
usedDriverSlots,
// Team capacity is not yet modeled here; use zero for now to satisfy strict typing.
@@ -87,14 +90,9 @@ export class AllLeaguesWithCapacityAndScoringPresenter implements IAllLeaguesWit
leagues: leagueItems,
totalCount: leagueItems.length,
};
return this.viewModel;
}
getViewModel(): AllLeaguesWithCapacityAndScoringViewModel {
if (!this.viewModel) {
throw new Error('Presenter has not been called yet');
}
getViewModel(): AllLeaguesWithCapacityAndScoringViewModel | null {
return this.viewModel;
}

View File

@@ -1,17 +1,19 @@
import type { League } from '@gridpilot/racing/domain/entities/League';
import type {
IAllLeaguesWithCapacityPresenter,
LeagueWithCapacityViewModel,
AllLeaguesWithCapacityViewModel,
AllLeaguesWithCapacityResultDTO,
} from '@gridpilot/racing/application/presenters/IAllLeaguesWithCapacityPresenter';
export class AllLeaguesWithCapacityPresenter implements IAllLeaguesWithCapacityPresenter {
private viewModel: AllLeaguesWithCapacityViewModel | null = null;
present(
leagues: League[],
memberCounts: Map<string, number>
): AllLeaguesWithCapacityViewModel {
reset(): void {
this.viewModel = null;
}
present(input: AllLeaguesWithCapacityResultDTO): void {
const { leagues, memberCounts } = input;
const leagueItems: LeagueWithCapacityViewModel[] = leagues.map((league) => {
const usedSlots = memberCounts.get(league.id) ?? 0;
@@ -63,14 +65,9 @@ export class AllLeaguesWithCapacityPresenter implements IAllLeaguesWithCapacityP
leagues: leagueItems,
totalCount: leagueItems.length,
};
return this.viewModel;
}
getViewModel(): AllLeaguesWithCapacityViewModel {
if (!this.viewModel) {
throw new Error('Presenter has not been called yet');
}
getViewModel(): AllLeaguesWithCapacityViewModel | null {
return this.viewModel;
}
}

View File

@@ -1,13 +1,18 @@
import type {
IAllRacesPagePresenter,
AllRacesPageResultDTO,
AllRacesPageViewModel,
} from '@gridpilot/racing/application/presenters/IAllRacesPagePresenter';
export class AllRacesPagePresenter implements IAllRacesPagePresenter {
private viewModel: AllRacesPageViewModel | null = null;
present(viewModel: AllRacesPageViewModel): void {
this.viewModel = viewModel;
reset(): void {
this.viewModel = null;
}
present(dto: AllRacesPageResultDTO): void {
this.viewModel = dto;
}
getViewModel(): AllRacesPageViewModel | null {

View File

@@ -1,13 +1,18 @@
import type {
IDashboardOverviewPresenter,
DashboardOverviewResultDTO,
DashboardOverviewViewModel,
} from '@gridpilot/racing/application/presenters/IDashboardOverviewPresenter';
export class DashboardOverviewPresenter implements IDashboardOverviewPresenter {
private viewModel: DashboardOverviewViewModel | null = null;
present(viewModel: DashboardOverviewViewModel): void {
this.viewModel = viewModel;
reset(): void {
this.viewModel = null;
}
present(dto: DashboardOverviewResultDTO): void {
this.viewModel = dto;
}
getViewModel(): DashboardOverviewViewModel | null {

View File

@@ -1,21 +1,20 @@
import type { Driver } from '@gridpilot/racing/domain/entities/Driver';
import type { SkillLevel } from '@gridpilot/racing/domain/services/SkillLevelService';
import { SkillLevelService } from '@gridpilot/racing/domain/services/SkillLevelService';
import type {
IDriversLeaderboardPresenter,
DriverLeaderboardItemViewModel,
DriversLeaderboardViewModel,
DriversLeaderboardResultDTO,
} from '@gridpilot/racing/application/presenters/IDriversLeaderboardPresenter';
export class DriversLeaderboardPresenter implements IDriversLeaderboardPresenter {
private viewModel: DriversLeaderboardViewModel | null = null;
present(
drivers: Driver[],
rankings: Array<{ driverId: string; rating: number; overallRank: number }>,
stats: Record<string, { rating: number; wins: number; podiums: number; totalRaces: number; overallRank: number }>,
avatarUrls: Record<string, string>
): DriversLeaderboardViewModel {
reset(): void {
this.viewModel = null;
}
present(input: DriversLeaderboardResultDTO): void {
const { drivers, rankings, stats, avatarUrls } = input;
const items: DriverLeaderboardItemViewModel[] = drivers.map((driver) => {
const driverStats = stats[driver.id];
const rating = driverStats?.rating ?? 0;
@@ -68,14 +67,9 @@ export class DriversLeaderboardPresenter implements IDriversLeaderboardPresenter
totalWins,
activeCount,
};
return this.viewModel;
}
getViewModel(): DriversLeaderboardViewModel {
if (!this.viewModel) {
throw new Error('Presenter has not been called yet');
}
getViewModel(): DriversLeaderboardViewModel | null {
return this.viewModel;
}
}

View File

@@ -2,23 +2,19 @@ import type {
ILeagueDriverSeasonStatsPresenter,
LeagueDriverSeasonStatsItemViewModel,
LeagueDriverSeasonStatsViewModel,
LeagueDriverSeasonStatsResultDTO,
} from '@gridpilot/racing/application/presenters/ILeagueDriverSeasonStatsPresenter';
export class LeagueDriverSeasonStatsPresenter implements ILeagueDriverSeasonStatsPresenter {
private viewModel: LeagueDriverSeasonStatsViewModel | null = null;
present(
leagueId: string,
standings: Array<{
driverId: string;
position: number;
points: number;
racesCompleted: number;
}>,
penalties: Map<string, { baseDelta: number; bonusDelta: number }>,
driverResults: Map<string, Array<{ position: number }>>,
driverRatings: Map<string, { rating: number | null; ratingChange: number | null }>
): LeagueDriverSeasonStatsViewModel {
reset(): void {
this.viewModel = null;
}
present(dto: LeagueDriverSeasonStatsResultDTO): void {
const { leagueId, standings, penalties, driverResults, driverRatings } = dto;
const stats: LeagueDriverSeasonStatsItemViewModel[] = standings.map((standing) => {
const penalty = penalties.get(standing.driverId) ?? { baseDelta: 0, bonusDelta: 0 };
const totalPenaltyPoints = penalty.baseDelta;
@@ -65,8 +61,6 @@ export class LeagueDriverSeasonStatsPresenter implements ILeagueDriverSeasonStat
leagueId,
stats,
};
return this.viewModel;
}
getViewModel(): LeagueDriverSeasonStatsViewModel {

View File

@@ -10,6 +10,10 @@ import type {
export class LeagueScoringConfigPresenter implements ILeagueScoringConfigPresenter {
private viewModel: LeagueScoringConfigViewModel | null = null;
reset(): void {
this.viewModel = null;
}
present(data: LeagueScoringConfigData): LeagueScoringConfigViewModel {
const championships: LeagueScoringChampionshipViewModel[] =
data.championships.map((champ) => this.mapChampionship(champ));

View File

@@ -6,9 +6,13 @@ import type {
export class RaceDetailPresenter implements IRaceDetailPresenter {
private viewModel: RaceDetailViewModel | null = null;
reset(): void {
this.viewModel = null;
}
present(viewModel: RaceDetailViewModel): RaceDetailViewModel {
this.viewModel = viewModel;
return this.viewModel;
return viewModel;
}
getViewModel(): RaceDetailViewModel | null {

View File

@@ -1,24 +1,25 @@
import type {
IRaceRegistrationsPresenter,
RaceRegistrationsViewModel,
RaceRegistrationsResultDTO,
} from '@gridpilot/racing/application/presenters/IRaceRegistrationsPresenter';
export class RaceRegistrationsPresenter implements IRaceRegistrationsPresenter {
private viewModel: RaceRegistrationsViewModel | null = null;
present(registeredDriverIds: string[]): RaceRegistrationsViewModel {
reset(): void {
this.viewModel = null;
}
present(input: RaceRegistrationsResultDTO): void {
const { registeredDriverIds } = input;
this.viewModel = {
registeredDriverIds,
count: registeredDriverIds.length,
};
return this.viewModel;
}
getViewModel(): RaceRegistrationsViewModel {
if (!this.viewModel) {
throw new Error('Presenter has not been called yet');
}
getViewModel(): RaceRegistrationsViewModel | null {
return this.viewModel;
}
}

View File

@@ -6,9 +6,12 @@ import type {
export class RaceResultsDetailPresenter implements IRaceResultsDetailPresenter {
private viewModel: RaceResultsDetailViewModel | null = null;
present(viewModel: RaceResultsDetailViewModel): RaceResultsDetailViewModel {
reset(): void {
this.viewModel = null;
}
present(viewModel: RaceResultsDetailViewModel): void {
this.viewModel = viewModel;
return this.viewModel;
}
getViewModel(): RaceResultsDetailViewModel | null {

View File

@@ -1,49 +1,35 @@
import type {
IRaceWithSOFPresenter,
RaceWithSOFResultDTO,
RaceWithSOFViewModel,
} from '@gridpilot/racing/application/presenters/IRaceWithSOFPresenter';
export class RaceWithSOFPresenter implements IRaceWithSOFPresenter {
private viewModel: RaceWithSOFViewModel | null = null;
present(
raceId: string,
leagueId: string,
scheduledAt: Date,
track: string,
trackId: string,
car: string,
carId: string,
sessionType: string,
status: string,
strengthOfField: number | null,
registeredCount: number,
maxParticipants: number,
participantCount: number
): RaceWithSOFViewModel {
present(dto: RaceWithSOFResultDTO): void {
this.viewModel = {
id: raceId,
leagueId,
scheduledAt: scheduledAt.toISOString(),
track,
trackId,
car,
carId,
sessionType,
status,
strengthOfField,
registeredCount,
maxParticipants,
participantCount,
id: dto.raceId,
leagueId: dto.leagueId,
scheduledAt: dto.scheduledAt.toISOString(),
track: dto.track,
trackId: dto.trackId,
car: dto.car,
carId: dto.carId,
sessionType: dto.sessionType,
status: dto.status,
strengthOfField: dto.strengthOfField,
registeredCount: dto.registeredCount,
maxParticipants: dto.maxParticipants,
participantCount: dto.participantCount,
};
}
getViewModel(): RaceWithSOFViewModel | null {
return this.viewModel;
}
getViewModel(): RaceWithSOFViewModel {
if (!this.viewModel) {
throw new Error('Presenter has not been called yet');
}
return this.viewModel;
reset(): void {
this.viewModel = null;
}
}

View File

@@ -2,26 +2,18 @@ import type {
IRacesPagePresenter,
RacesPageViewModel,
RaceListItemViewModel,
RacesPageResultDTO,
} from '@gridpilot/racing/application/presenters/IRacesPagePresenter';
interface RacesPageInput {
id: string;
track: string;
car: string;
scheduledAt: string | Date;
status: string;
leagueId: string;
leagueName: string;
strengthOfField: number | null;
isUpcoming: boolean;
isLive: boolean;
isPast: boolean;
}
export class RacesPagePresenter implements IRacesPagePresenter {
private viewModel: RacesPageViewModel | null = null;
present(races: RacesPageInput[]): void {
reset(): void {
this.viewModel = null;
}
present(input: RacesPageResultDTO): void {
const { races } = input;
const now = new Date();
const nextWeek = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);

View File

@@ -1,14 +1,25 @@
import type { ISponsorDashboardPresenter } from '@gridpilot/racing/application/presenters/ISponsorDashboardPresenter';
import type {
ISponsorDashboardPresenter,
SponsorDashboardViewModel,
} from '@gridpilot/racing/application/presenters/ISponsorDashboardPresenter';
import type { SponsorDashboardDTO } from '@gridpilot/racing/application/use-cases/GetSponsorDashboardUseCase';
export class SponsorDashboardPresenter implements ISponsorDashboardPresenter {
private data: SponsorDashboardDTO | null = null;
private viewModel: SponsorDashboardViewModel = null;
reset(): void {
this.viewModel = null;
}
present(data: SponsorDashboardDTO | null): void {
this.data = data;
this.viewModel = data;
}
getViewModel(): SponsorDashboardViewModel {
return this.viewModel;
}
getData(): SponsorDashboardDTO | null {
return this.data;
return this.viewModel;
}
}

View File

@@ -1,14 +1,25 @@
import type { ISponsorSponsorshipsPresenter } from '@gridpilot/racing/application/presenters/ISponsorSponsorshipsPresenter';
import type {
ISponsorSponsorshipsPresenter,
SponsorSponsorshipsViewModel,
} from '@gridpilot/racing/application/presenters/ISponsorSponsorshipsPresenter';
import type { SponsorSponsorshipsDTO } from '@gridpilot/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
export class SponsorSponsorshipsPresenter implements ISponsorSponsorshipsPresenter {
private data: SponsorSponsorshipsDTO | null = null;
private viewModel: SponsorSponsorshipsViewModel = null;
reset(): void {
this.viewModel = null;
}
present(data: SponsorSponsorshipsDTO | null): void {
this.data = data;
this.viewModel = data;
}
getViewModel(): SponsorSponsorshipsViewModel {
return this.viewModel;
}
getData(): SponsorSponsorshipsDTO | null {
return this.data;
return this.viewModel;
}
}

View File

@@ -1,18 +1,18 @@
import type { Team } from '@gridpilot/racing/domain/entities/Team';
import type { TeamMembership } from '@gridpilot/racing/domain/types/TeamMembership';
import type {
ITeamDetailsPresenter,
TeamDetailsViewModel,
TeamDetailsResultDTO,
} from '@gridpilot/racing/application/presenters/ITeamDetailsPresenter';
export class TeamDetailsPresenter implements ITeamDetailsPresenter {
private viewModel: TeamDetailsViewModel | null = null;
present(
team: Team,
membership: TeamMembership | null,
driverId: string
): TeamDetailsViewModel {
reset(): void {
this.viewModel = null;
}
present(input: TeamDetailsResultDTO): void {
const { team, membership } = input;
const canManage = membership?.role === 'owner' || membership?.role === 'manager';
const viewModel: TeamDetailsViewModel = {
@@ -23,6 +23,7 @@ export class TeamDetailsPresenter implements ITeamDetailsPresenter {
description: team.description,
ownerId: team.ownerId,
leagues: team.leagues,
createdAt: team.createdAt.toISOString(),
},
membership: membership
? {
@@ -35,14 +36,9 @@ export class TeamDetailsPresenter implements ITeamDetailsPresenter {
};
this.viewModel = viewModel;
return viewModel;
}
getViewModel(): TeamDetailsViewModel {
if (!this.viewModel) {
throw new Error('Presenter has not been called yet');
}
getViewModel(): TeamDetailsViewModel | null {
return this.viewModel;
}
}