Files
sendou.ink/app/features/availability/core/RegistrationAvailability.server.ts
2026-09-09 21:46:09 +03:00

105 lines
2.7 KiB
TypeScript

import { addWeeks, subWeeks } from "date-fns";
import type { Tables } from "~/db/tables";
import { databaseTimestampToDate } from "~/utils/dates";
import { AVAILABILITY } from "../availability-constants";
import type { TimeRange } from "../availability-types";
import * as Availability from "./Availability";
import { estimatedEndsAt } from "./TournamentDuration.server";
import * as VisibleSchedules from "./VisibleSchedules.server";
export type RegistrationAvailability = Awaited<
ReturnType<typeof registrationAvailability>
>;
/**
* Availability of the users for the tournament's estimated window (start to
* {@link estimatedEndsAt}); its own registrations don't count as busy. Past the reportable
* horizon every schedule would be unknown, so the result is only when the event's week opens up.
*/
export async function registrationAvailability({
tournament,
userIds,
timezone,
viewerId,
}: {
tournament: {
id: number;
name: string;
organizationId: number | null;
startsAt: number;
minMembersPerTeam: number;
bracketTypes: Array<Tables["TournamentStage"]["type"]>;
teamCount: number;
};
userIds: Array<number>;
timezone: string;
viewerId: number;
}) {
const startDate = databaseTimestampToDate(tournament.startsAt);
const horizon = Availability.weekRange(
addWeeks(new Date(), AVAILABILITY.WEEK_HORIZON - 1),
timezone,
);
if (tournament.startsAt >= horizon.endsAt) {
return {
beyondHorizon: {
opensAt: Availability.weekStartsAt(subWeeks(startDate, 1), timezone),
},
window: null,
entries: null,
};
}
const window: TimeRange = {
startsAt: tournament.startsAt,
endsAt: await estimatedEndsAt(tournament),
};
const { reportedWeeks: weeks, busyByUserId } =
await VisibleSchedules.findByUserIds({
userIds,
viewerId,
...window,
excludeTournamentId: tournament.id,
});
const windowDates = [
Availability.dateInTimezone(window.startsAt, timezone),
Availability.dateInTimezone(window.endsAt - 1, timezone),
];
const entries = userIds.map((userId) => {
const userWeeks = weeks.filter((week) => week.userId === userId);
return {
userId,
availability: Availability.availabilityInWindow({
reported: userWeeks.some(
(week) =>
Availability.weekStartsAt(startDate, week.timezone) ===
week.weekStartsAt,
),
slots: userWeeks.flatMap((week) => week.slots),
busy: busyByUserId.get(userId) ?? [],
window,
}),
notes: userWeeks.flatMap((week) =>
week.dayNotes
.filter((note) =>
windowDates.includes(
Availability.dateAcrossTimezones({
date: note.date,
from: week.timezone,
to: timezone,
}),
),
)
.map((note) => note.text),
),
};
});
return { beyondHorizon: null, window, entries };
}