/** * Détermine l'ordre d'affichage des séquences sur le lien d'inscription public. * Les sessions ouvertes avec des places disponibles sont toujours proposées en premier. */ export type RegistrationSequence = { id: number; nom: string; statut: string; dateBlocage?: Date | string | null; capaciteMax: number; nbInscrits?: number | null; dates?: ReadonlyArray<{ dateDebut: Date | string }>; }; export type RegistrationSequenceWithAvailability = T & { isBloquee: boolean; placesRestantes: number; }; function firstDateTimestamp(sequence: RegistrationSequence): number { const firstDate = sequence.dates?.[0]?.dateDebut; if (!firstDate) return Number.MAX_SAFE_INTEGER; const timestamp = new Date(firstDate).getTime(); return Number.isFinite(timestamp) ? timestamp : Number.MAX_SAFE_INTEGER; } function displayPriority(sequence: RegistrationSequenceWithAvailability): number { if (sequence.isBloquee) return 2; return sequence.placesRestantes > 0 ? 0 : 1; } export function sortSequencesForRegistration( sequences: readonly T[], now = new Date(), ): RegistrationSequenceWithAvailability[] { return sequences .map((sequence) => { const dateBlocage = sequence.dateBlocage ? new Date(sequence.dateBlocage) : null; const hasReachedBlockingDate = dateBlocage !== null && now >= dateBlocage; const isBloquee = sequence.statut !== "ouverte" || hasReachedBlockingDate; const placesRestantes = Math.max(0, sequence.capaciteMax - (sequence.nbInscrits ?? 0)); return { ...sequence, isBloquee, placesRestantes }; }) .sort((left, right) => { const priorityDifference = displayPriority(left) - displayPriority(right); if (priorityDifference !== 0) return priorityDifference; const dateDifference = firstDateTimestamp(left) - firstDateTimestamp(right); if (dateDifference !== 0) return dateDifference; return left.nom.localeCompare(right.nom, "fr", { sensitivity: "base" }); }); }