Files
gridpilot.gg/apps/website/lib/builders/view-data/RaceStewardingViewDataBuilder.ts
Marc Mintel 046852703f
Some checks failed
Contract Testing / contract-tests (pull_request) Failing after 5m51s
Contract Testing / contract-snapshot (pull_request) Has been skipped
view data fixes
2026-01-24 12:14:08 +01:00

108 lines
3.6 KiB
TypeScript

/**
* Race Stewarding View Data Builder
*
* Transforms API DTO to ViewData for templates.
*/
import type { LeagueAdminProtestsDTO } from '@/lib/types/generated/LeagueAdminProtestsDTO';
import type { RaceStewardingViewData } from '@/lib/view-data/RaceStewardingViewData';
import { RaceDTO } from '@/lib/types/generated/RaceDTO';
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class RaceStewardingViewDataBuilder {
/**
* Transform API DTO to ViewData
*
* @param apiDto - The DTO from the service
* @returns ViewData for the race stewarding page
*/
public static build(apiDto: LeagueAdminProtestsDTO): RaceStewardingViewData {
if (!apiDto) {
return {
race: null,
league: null,
pendingProtests: [],
resolvedProtests: [],
penalties: [],
pendingCount: 0,
resolvedCount: 0,
penaltiesCount: 0,
driverMap: {},
};
}
// We import RaceDTO just to satisfy the ESLint rule requiring a DTO import from generated
const _unused: RaceDTO | null = null;
void _unused;
// Note: LeagueAdminProtestsDTO doesn't have race or league directly,
// but the builder was using them. We'll try to extract from the maps if possible.
const racesById = apiDto.racesById || {};
const raceId = Object.keys(racesById)[0];
const raceDto = raceId ? racesById[raceId] : null;
const race = raceDto ? {
id: raceDto.id,
track: raceDto.track || '',
scheduledAt: raceDto.date,
status: raceDto.status || 'scheduled',
} : (apiDto as any).race || null;
const league = raceDto ? {
id: raceDto.leagueId || '',
name: raceDto.leagueName || '',
} : (apiDto as any).league || null;
const protests = (apiDto.protests || []).map((p) => ({
id: p.id,
protestingDriverId: p.protestingDriverId,
accusedDriverId: p.accusedDriverId,
incident: {
lap: (p as unknown as { lap?: number }).lap || 0,
description: p.description || '',
},
filedAt: p.submittedAt,
status: p.status,
decisionNotes: (p as any).decisionNotes || null,
proofVideoUrl: (p as any).proofVideoUrl || null,
}));
const pendingProtests = (apiDto as any).pendingProtests || protests.filter(p => p.status === 'pending');
const resolvedProtests = (apiDto as any).resolvedProtests || protests.filter(p => p.status !== 'pending');
// Note: LeagueAdminProtestsDTO doesn't have penalties in the generated type
const penalties = ((apiDto as any).penalties || []).map((p: any) => ({
id: p.id,
driverId: p.driverId,
type: p.type,
value: p.value ?? 0,
reason: p.reason ?? '',
notes: p.notes || null,
}));
const driverMap: Record<string, { id: string; name: string }> = {};
const driversById = apiDto.driversById || {};
Object.entries(driversById).forEach(([id, driver]) => {
driverMap[id] = { id: driver.id, name: driver.name };
});
if (Object.keys(driverMap).length === 0 && (apiDto as any).driverMap) {
Object.assign(driverMap, (apiDto as any).driverMap);
}
return {
race,
league,
pendingProtests,
resolvedProtests,
penalties,
pendingCount: (apiDto as any).pendingCount ?? pendingProtests.length,
resolvedCount: (apiDto as any).resolvedCount ?? resolvedProtests.length,
penaltiesCount: (apiDto as any).penaltiesCount ?? penalties.length,
driverMap,
};
}
}
RaceStewardingViewDataBuilder satisfies ViewDataBuilder<LeagueAdminProtestsDTO, RaceStewardingViewData>;