109 lines
3.5 KiB
TypeScript
109 lines
3.5 KiB
TypeScript
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
|
|
*/
|
|
export class ProtestViewModel {
|
|
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);
|
|
}
|
|
}
|