97 lines
2.7 KiB
TypeScript
97 lines
2.7 KiB
TypeScript
import type {
|
|
IRacesPagePresenter,
|
|
RacesPageViewModel,
|
|
RaceListItemViewModel,
|
|
} from '@gridpilot/racing/application/presenters/IRacesPagePresenter';
|
|
|
|
interface RacesPageInput {
|
|
id: string;
|
|
track: string;
|
|
car: string;
|
|
scheduledAt: string | Date;
|
|
status: string;
|
|
leagueId: string;
|
|
leagueName: string;
|
|
strengthOfField: number | null;
|
|
isUpcoming: boolean;
|
|
isLive: boolean;
|
|
isPast: boolean;
|
|
}
|
|
|
|
export class RacesPagePresenter implements IRacesPagePresenter {
|
|
private viewModel: RacesPageViewModel | null = null;
|
|
|
|
present(races: RacesPageInput[]): void {
|
|
const now = new Date();
|
|
const nextWeek = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
|
|
|
|
const raceViewModels: RaceListItemViewModel[] = races.map((race) => {
|
|
const scheduledAt =
|
|
typeof race.scheduledAt === 'string'
|
|
? race.scheduledAt
|
|
: race.scheduledAt.toISOString();
|
|
|
|
const allowedStatuses: RaceListItemViewModel['status'][] = [
|
|
'scheduled',
|
|
'running',
|
|
'completed',
|
|
'cancelled',
|
|
];
|
|
|
|
const status: RaceListItemViewModel['status'] =
|
|
allowedStatuses.includes(race.status as RaceListItemViewModel['status'])
|
|
? (race.status as RaceListItemViewModel['status'])
|
|
: 'scheduled';
|
|
|
|
return {
|
|
id: race.id,
|
|
track: race.track,
|
|
car: race.car,
|
|
scheduledAt,
|
|
status,
|
|
leagueId: race.leagueId,
|
|
leagueName: race.leagueName,
|
|
strengthOfField: race.strengthOfField,
|
|
isUpcoming: race.isUpcoming,
|
|
isLive: race.isLive,
|
|
isPast: race.isPast,
|
|
};
|
|
});
|
|
|
|
const stats = {
|
|
total: raceViewModels.length,
|
|
scheduled: raceViewModels.filter(r => r.status === 'scheduled').length,
|
|
running: raceViewModels.filter(r => r.status === 'running').length,
|
|
completed: raceViewModels.filter(r => r.status === 'completed').length,
|
|
};
|
|
|
|
const liveRaces = raceViewModels.filter(r => r.isLive);
|
|
|
|
const upcomingThisWeek = raceViewModels
|
|
.filter(r => {
|
|
const scheduledDate = new Date(r.scheduledAt);
|
|
return r.isUpcoming && scheduledDate >= now && scheduledDate <= nextWeek;
|
|
})
|
|
.slice(0, 5);
|
|
|
|
const recentResults = raceViewModels
|
|
.filter(r => r.status === 'completed')
|
|
.sort((a, b) => new Date(b.scheduledAt).getTime() - new Date(a.scheduledAt).getTime())
|
|
.slice(0, 3);
|
|
|
|
this.viewModel = {
|
|
races: raceViewModels,
|
|
stats,
|
|
liveRaces,
|
|
upcomingThisWeek,
|
|
recentResults,
|
|
};
|
|
}
|
|
|
|
getViewModel(): RacesPageViewModel {
|
|
if (!this.viewModel) {
|
|
throw new Error('ViewModel not yet generated. Call present() first.');
|
|
}
|
|
return this.viewModel;
|
|
}
|
|
} |