108 lines
3.6 KiB
TypeScript
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>;
|