92 lines
3.1 KiB
TypeScript
92 lines
3.1 KiB
TypeScript
import { SeasonScheduleGenerator } from '../../domain/services/SeasonScheduleGenerator';
|
|
import type { LeagueSchedulePreviewDTO, LeagueScheduleDTO } from '../dto/LeagueScheduleDTO';
|
|
import { scheduleDTOToSeasonSchedule } from '../dto/LeagueScheduleDTO';
|
|
import type { ILeagueSchedulePreviewPresenter } from '../presenters/ILeagueSchedulePreviewPresenter';
|
|
|
|
interface PreviewLeagueScheduleQueryParams {
|
|
schedule: LeagueScheduleDTO;
|
|
maxRounds?: number;
|
|
}
|
|
|
|
export class PreviewLeagueScheduleUseCase {
|
|
constructor(
|
|
private readonly scheduleGenerator: typeof SeasonScheduleGenerator = SeasonScheduleGenerator,
|
|
private readonly presenter: ILeagueSchedulePreviewPresenter,
|
|
) {}
|
|
|
|
execute(params: PreviewLeagueScheduleQueryParams): void {
|
|
const seasonSchedule = scheduleDTOToSeasonSchedule(params.schedule);
|
|
|
|
const maxRounds =
|
|
params.maxRounds && params.maxRounds > 0
|
|
? Math.min(params.maxRounds, seasonSchedule.plannedRounds)
|
|
: seasonSchedule.plannedRounds;
|
|
|
|
const slots = this.scheduleGenerator.generateSlotsUpTo(seasonSchedule, maxRounds);
|
|
|
|
const rounds = slots.map((slot) => ({
|
|
roundNumber: slot.roundNumber,
|
|
scheduledAt: slot.scheduledAt.toISOString(),
|
|
timezoneId: slot.timezone.getId(),
|
|
}));
|
|
|
|
const summary = this.buildSummary(params.schedule, rounds);
|
|
|
|
this.presenter.present({
|
|
rounds,
|
|
summary,
|
|
});
|
|
}
|
|
|
|
private buildSummary(
|
|
schedule: LeagueScheduleDTO,
|
|
rounds: Array<{ roundNumber: number; scheduledAt: string; timezoneId: string }>,
|
|
): string {
|
|
if (rounds.length === 0) {
|
|
return 'No rounds scheduled.';
|
|
}
|
|
|
|
const first = new Date(rounds[0].scheduledAt);
|
|
const last = new Date(rounds[rounds.length - 1].scheduledAt);
|
|
|
|
const firstDate = first.toISOString().slice(0, 10);
|
|
const lastDate = last.toISOString().slice(0, 10);
|
|
|
|
const timePart = schedule.raceStartTime;
|
|
const tz = schedule.timezoneId;
|
|
|
|
let recurrenceDescription: string;
|
|
|
|
if (schedule.recurrenceStrategy === 'weekly') {
|
|
const days = (schedule.weekdays ?? []).join(', ');
|
|
recurrenceDescription = `Every ${days}`;
|
|
} else if (schedule.recurrenceStrategy === 'everyNWeeks') {
|
|
const interval = schedule.intervalWeeks ?? 1;
|
|
const days = (schedule.weekdays ?? []).join(', ');
|
|
recurrenceDescription = `Every ${interval} week(s) on ${days}`;
|
|
} else if (schedule.recurrenceStrategy === 'monthlyNthWeekday') {
|
|
const ordinalLabel = this.ordinalToLabel(schedule.monthlyOrdinal ?? 1);
|
|
const weekday = schedule.monthlyWeekday ?? 'Mon';
|
|
recurrenceDescription = `Every ${ordinalLabel} ${weekday}`;
|
|
} else {
|
|
recurrenceDescription = 'Custom recurrence';
|
|
}
|
|
|
|
return `${recurrenceDescription} at ${timePart} ${tz}, starting ${firstDate} — ${rounds.length} rounds from ${firstDate} to ${lastDate}.`;
|
|
}
|
|
|
|
private ordinalToLabel(ordinal: 1 | 2 | 3 | 4): string {
|
|
switch (ordinal) {
|
|
case 1:
|
|
return '1st';
|
|
case 2:
|
|
return '2nd';
|
|
case 3:
|
|
return '3rd';
|
|
case 4:
|
|
return '4th';
|
|
default:
|
|
return `${ordinal}th`;
|
|
}
|
|
}
|
|
} |