/** * League Stewarding View Model * Represents all data needed for league stewarding across all races */ export class LeagueStewardingViewModel { constructor( public readonly racesWithData: RaceWithProtests[], public readonly driverMap: Record ) {} /** UI-specific: Total pending protests count */ get totalPending(): number { return this.racesWithData.reduce((sum, r) => sum + r.pendingProtests.length, 0); } /** UI-specific: Total resolved protests count */ get totalResolved(): number { return this.racesWithData.reduce((sum, r) => sum + r.resolvedProtests.length, 0); } /** UI-specific: Total penalties count */ get totalPenalties(): number { return this.racesWithData.reduce((sum, r) => sum + r.penalties.length, 0); } /** UI-specific: Filtered races for pending tab */ get pendingRaces(): RaceWithProtests[] { return this.racesWithData.filter(r => r.pendingProtests.length > 0); } /** UI-specific: Filtered races for history tab */ get historyRaces(): RaceWithProtests[] { return this.racesWithData.filter(r => r.resolvedProtests.length > 0 || r.penalties.length > 0); } /** UI-specific: All drivers for quick penalty modal */ get allDrivers(): Array<{ id: string; name: string; avatarUrl?: string; iracingId?: string; rating?: number }> { return Object.values(this.driverMap); } } export interface RaceWithProtests { race: { id: string; track: string; scheduledAt: Date; }; pendingProtests: Protest[]; resolvedProtests: Protest[]; penalties: Penalty[]; } export interface Protest { id: string; protestingDriverId: string; accusedDriverId: string; incident: { lap: number; description: string; }; filedAt: string; status: string; decisionNotes?: string; proofVideoUrl?: string; } export interface Penalty { id: string; driverId: string; type: string; value: number; reason: string; notes?: string; }