website refactor

This commit is contained in:
2026-01-14 13:27:26 +01:00
parent e7887f054f
commit faa4c3309e
24 changed files with 964 additions and 401 deletions

View File

@@ -0,0 +1,75 @@
import type { LeagueStandingsViewData, StandingEntryData, DriverData, LeagueMembershipData } from '@/lib/view-data/LeagueStandingsViewData';
import type { LeagueStandingDTO } from '@/lib/types/generated/LeagueStandingDTO';
import type { LeagueMemberDTO } from '@/lib/types/generated/LeagueMemberDTO';
interface LeagueStandingsApiDto {
standings: LeagueStandingDTO[];
}
interface LeagueMembershipsApiDto {
members: LeagueMemberDTO[];
}
/**
* LeagueStandingsViewDataBuilder
*
* Transforms API DTOs into LeagueStandingsViewData for server-side rendering.
* Deterministic; side-effect free; no HTTP calls.
*/
export class LeagueStandingsViewDataBuilder {
static build(
standingsDto: LeagueStandingsApiDto,
membershipsDto: LeagueMembershipsApiDto,
leagueId: string
): LeagueStandingsViewData {
const standings = standingsDto.standings || [];
const members = membershipsDto.members || [];
// Convert LeagueStandingDTO to StandingEntryData
const standingData: StandingEntryData[] = standings.map(standing => ({
driverId: standing.driverId,
position: standing.position,
totalPoints: standing.points,
racesFinished: standing.races,
racesStarted: standing.races,
avgFinish: null, // Not in DTO
penaltyPoints: 0, // Not in DTO
bonusPoints: 0, // Not in DTO
}));
// Extract unique drivers from standings
const driverMap = new Map<string, DriverData>();
standings.forEach(standing => {
if (standing.driver && !driverMap.has(standing.driver.id)) {
const driver = standing.driver;
driverMap.set(driver.id, {
id: driver.id,
name: driver.name,
avatarUrl: null, // DTO may not have this
iracingId: driver.iracingId,
rating: undefined,
country: driver.country,
});
}
});
const driverData: DriverData[] = Array.from(driverMap.values());
// Convert LeagueMemberDTO to LeagueMembershipData
const membershipData: LeagueMembershipData[] = members.map(member => ({
driverId: member.driverId,
leagueId: leagueId,
role: (member.role as LeagueMembershipData['role']) || 'member',
joinedAt: member.joinedAt,
status: 'active' as const,
}));
return {
standings: standingData,
drivers: driverData,
memberships: membershipData,
leagueId,
currentDriverId: null, // Would need to get from auth
isAdmin: false, // Would need to check permissions
};
}
}

View File

@@ -0,0 +1,46 @@
import { ProtestDetailViewData } from '@/lib/view-data/leagues/ProtestDetailViewData';
interface ProtestDetailApiDto {
id: string;
leagueId: string;
status: string;
submittedAt: string;
incident: {
lap: number;
description: string;
};
protestingDriver: {
id: string;
name: string;
};
accusedDriver: {
id: string;
name: string;
};
race: {
id: string;
name: string;
scheduledAt: string;
};
penaltyTypes: Array<{
type: string;
label: string;
description: string;
}>;
}
export class ProtestDetailViewDataBuilder {
static build(apiDto: ProtestDetailApiDto): ProtestDetailViewData {
return {
protestId: apiDto.id,
leagueId: apiDto.leagueId,
status: apiDto.status,
submittedAt: apiDto.submittedAt,
incident: apiDto.incident,
protestingDriver: apiDto.protestingDriver,
accusedDriver: apiDto.accusedDriver,
race: apiDto.race,
penaltyTypes: apiDto.penaltyTypes,
};
}
}

View File

@@ -0,0 +1,25 @@
import { RulebookViewData } from '@/lib/view-data/leagues/RulebookViewData';
import { RulebookApiDto } from '@/lib/types/tbd/RulebookApiDto';
export class RulebookViewDataBuilder {
static build(apiDto: RulebookApiDto): RulebookViewData {
const primaryChampionship = apiDto.scoringConfig.championships.find(c => c.type === 'driver') ?? apiDto.scoringConfig.championships[0];
const positionPoints: { position: number; points: number }[] = primaryChampionship?.pointsPreview
.filter((p): p is { sessionType: string; position: number; points: number } => p.sessionType === primaryChampionship.sessionTypes[0])
.map(p => ({ position: p.position, points: p.points }))
.sort((a, b) => a.position - b.position) || [];
return {
leagueId: apiDto.leagueId,
gameName: apiDto.scoringConfig.gameName,
scoringPresetName: apiDto.scoringConfig.scoringPresetName,
championshipsCount: apiDto.scoringConfig.championships.length,
sessionTypes: primaryChampionship?.sessionTypes.join(', ') || 'Main',
dropPolicySummary: apiDto.scoringConfig.dropPolicySummary,
hasActiveDropPolicy: !apiDto.scoringConfig.dropPolicySummary.includes('All'),
positionPoints,
bonusPoints: primaryChampionship?.bonusSummary || [],
hasBonusPoints: (primaryChampionship?.bonusSummary.length || 0) > 0,
};
}
}

View File

@@ -0,0 +1,26 @@
import { StewardingApiDto } from '@/lib/types/tbd/StewardingApiDto';
import { StewardingViewData } from '@/lib/view-data/leagues/StewardingViewData';
export class StewardingViewDataBuilder {
static build(apiDto: StewardingApiDto): StewardingViewData {
return {
leagueId: apiDto.leagueId,
totalPending: apiDto.totalPending || 0,
totalResolved: apiDto.totalResolved || 0,
totalPenalties: apiDto.totalPenalties || 0,
races: (apiDto.races || []).map((race) => ({
id: race.id,
track: race.track,
scheduledAt: race.scheduledAt,
pendingProtests: race.pendingProtests || [],
resolvedProtests: race.resolvedProtests || [],
penalties: race.penalties || [],
})),
drivers: (apiDto.drivers || []).map((driver) => ({
id: driver.id,
name: driver.name,
})),
};
}
}