import { ProtestDTO } from '@/lib/types/generated/ProtestDTO'; import { RaceProtestDTO } from '@/lib/types/generated/RaceProtestDTO'; import { DateDisplay } from '../display-objects/DateDisplay'; import { StatusDisplay } from '../display-objects/StatusDisplay'; /** * Protest view model * Represents a race protest */ import { ViewModel } from "../contracts/view-models/ViewModel"; export class ProtestViewModel extends ViewModel { id: string; raceId: string; protestingDriverId: string; accusedDriverId: string; description: string; submittedAt: string; filedAt?: string; status: string; reviewedAt?: string; decisionNotes?: string; incident?: { lap?: number; description?: string } | null; proofVideoUrl?: string | null; comment?: string | null; constructor(dto: ProtestDTO | RaceProtestDTO) { this.id = dto.id; // Type narrowing for raceId if ('raceId' in dto) { this.raceId = dto.raceId; } else { this.raceId = ''; } this.protestingDriverId = dto.protestingDriverId; this.accusedDriverId = dto.accusedDriverId; // Type narrowing for description if ('description' in dto && typeof dto.description === 'string') { this.description = dto.description; } else { this.description = ''; } // Type narrowing for submittedAt and filedAt if ('submittedAt' in dto && typeof dto.submittedAt === 'string') { this.submittedAt = dto.submittedAt; } else if ('filedAt' in dto && typeof dto.filedAt === 'string') { this.submittedAt = dto.filedAt; } else { this.submittedAt = ''; } if ('filedAt' in dto && typeof dto.filedAt === 'string') { this.filedAt = dto.filedAt; } else if ('submittedAt' in dto && typeof dto.submittedAt === 'string') { this.filedAt = dto.submittedAt; } // Handle different DTO structures if ('status' in dto && typeof dto.status === 'string') { this.status = dto.status; } else { this.status = 'pending'; } // Handle incident data if ('incident' in dto && dto.incident) { const incident = dto.incident as { lap?: number; description?: string }; this.incident = { lap: typeof incident.lap === 'number' ? incident.lap : undefined, description: typeof incident.description === 'string' ? incident.description : undefined }; } else if (('lap' in dto && typeof (dto as { lap?: number }).lap === 'number') || ('description' in dto && typeof (dto as { description?: string }).description === 'string')) { this.incident = { lap: 'lap' in dto ? (dto as { lap?: number }).lap : undefined, description: 'description' in dto ? (dto as { description?: string }).description : undefined }; } else { this.incident = null; } if ('proofVideoUrl' in dto) { this.proofVideoUrl = (dto as { proofVideoUrl?: string }).proofVideoUrl || null; } if ('comment' in dto) { this.comment = (dto as { comment?: string }).comment || null; } // Status and decision metadata are not part of the protest DTO in this build; they default to a pending, unreviewed protest if (!('status' in dto)) { this.status = 'pending'; } this.reviewedAt = undefined; this.decisionNotes = undefined; } /** UI-specific: Formatted submitted date */ get formattedSubmittedAt(): string { return DateDisplay.formatShort(this.submittedAt); } /** UI-specific: Status display */ get statusDisplay(): string { return StatusDisplay.protestStatus(this.status); } }