Each row now shows its shared status and carries the matching gesture. Nothing is displayed before Nextcloud confirms it: on failure the status stays put and the reason is spelled out. Ignored events are greyed and pushed to the end, never hidden.
108 lines
4.4 KiB
JavaScript
108 lines
4.4 KiB
JavaScript
// Présentation des Event pour la liste : ordre, formatage date, défaut lieu.
|
|
// Fonctions pures, sans DOM (testables). Locale et fuseau sont des constantes :
|
|
// l'appli est mono-locale (bénévoles de Cugnaux).
|
|
|
|
// config.js n'exporte rien : il pose globalThis.EchoConfig (cf. DECISIONS.md
|
|
// 2026-07-17). L'import ne sert qu'à son effet de bord.
|
|
import "./config.js";
|
|
import { lireStatut } from "./statut-mairie.js";
|
|
|
|
const LOCALE = "fr-FR";
|
|
const FUSEAU = "Europe/Paris";
|
|
|
|
// Source unique : le formulaire mairie a besoin de la même adresse (et de ses
|
|
// coordonnées) que la liste.
|
|
export const LIEU_PAR_DEFAUT = globalThis.EchoConfig.DEFAUTS.cafe.adresse;
|
|
|
|
// Un événement horodaté porte un vrai instant : on l'affiche à l'heure de
|
|
// Cugnaux, quel que soit le fuseau de la machine.
|
|
const FORMAT_JOUR_PARIS = new Intl.DateTimeFormat(LOCALE, {
|
|
timeZone: FUSEAU,
|
|
dateStyle: "full",
|
|
});
|
|
const FORMAT_HEURE_PARIS = new Intl.DateTimeFormat(LOCALE, {
|
|
timeZone: FUSEAU,
|
|
timeStyle: "short",
|
|
});
|
|
|
|
// Sans timeZone, DÉLIBÉRÉMENT (ne pas « rétablir » FUSEAU ici) : une journée
|
|
// entière est une date *flottante* (RFC 5545), sans fuseau. ical.js la
|
|
// matérialise à minuit local ; la relire en local restitue le bon jour partout,
|
|
// alors que la convertir vers Paris la ferait glisser au 9 depuis Tokyo.
|
|
const FORMAT_JOUR_FLOTTANT = new Intl.DateTimeFormat(LOCALE, {
|
|
dateStyle: "full",
|
|
});
|
|
|
|
// Le parseur rend l'ordre du flux : l'ordre chronologique se décide ici.
|
|
export function trierParDebut(evenements) {
|
|
return [...evenements].sort((a, b) => a.debut.getTime() - b.debut.getTime());
|
|
}
|
|
|
|
// Une journée entière n'a pas d'heure : afficher « 00:00 » serait un mensonge.
|
|
export function formaterDate(evenement) {
|
|
return evenement.journeeEntiere
|
|
? formaterJourneeEntiere(evenement)
|
|
: formaterHorodate(evenement);
|
|
}
|
|
|
|
function formaterJourneeEntiere({ debut, fin }) {
|
|
const premierJour = FORMAT_JOUR_FLOTTANT.format(debut);
|
|
const dernierJour = veilleDe(fin);
|
|
|
|
// Un seul jour, ou pas de DTEND exploitable (le parseur pose alors fin === debut).
|
|
if (dernierJour.getTime() <= debut.getTime()) return premierJour;
|
|
|
|
return `du ${premierJour} au ${FORMAT_JOUR_FLOTTANT.format(dernierJour)}`;
|
|
}
|
|
|
|
// DTEND est EXCLUSIF (RFC 5545) : un événement du 18 porte DTEND=19, un festival
|
|
// du 18 au 20 porte DTEND=21. Sans ce recul d'un jour, on annoncerait un jour de
|
|
// trop — une date FAUSSE, pas seulement laide.
|
|
// Composantes locales (jamais « -24h ») : la date est flottante, et un
|
|
// changement d'heure ferait basculer le jour.
|
|
function veilleDe(date) {
|
|
return new Date(date.getFullYear(), date.getMonth(), date.getDate() - 1);
|
|
}
|
|
|
|
function formaterHorodate({ debut, fin }) {
|
|
const jourDebut = FORMAT_JOUR_PARIS.format(debut);
|
|
const heureDebut = FORMAT_HEURE_PARIS.format(debut);
|
|
|
|
// DTEND absent : le parseur pose fin === debut. « de 10:30 à 10:30 » serait absurde.
|
|
if (fin.getTime() <= debut.getTime()) return `${jourDebut} à ${heureDebut}`;
|
|
|
|
const jourFin = FORMAT_JOUR_PARIS.format(fin);
|
|
const heureFin = FORMAT_HEURE_PARIS.format(fin);
|
|
|
|
// Comparaison des jours tels qu'AFFICHÉS (donc à Paris) : un événement de
|
|
// 22:00 à 02:00 change de jour, l'annoncer sur un seul serait un mensonge.
|
|
if (jourFin === jourDebut) return `${jourDebut} de ${heureDebut} à ${heureFin}`;
|
|
|
|
return `du ${jourDebut} à ${heureDebut} au ${jourFin} à ${heureFin}`;
|
|
}
|
|
|
|
// La plupart des événements se tiennent au café et n'ont pas de LOCATION :
|
|
// afficher l'adresse par défaut plutôt qu'une ligne vide.
|
|
export function formaterLieu(evenement) {
|
|
return evenement.lieu.trim() || LIEU_PAR_DEFAUT;
|
|
}
|
|
|
|
// Statut mairie en clair. Le jour du tag est une date NUE (« AAAA-MM-JJ »),
|
|
// jamais un instant : on la découpe, on ne la convertit pas — un Date la ferait
|
|
// glisser d'un jour selon le fuseau de la machine.
|
|
export function formaterStatut(statut) {
|
|
if (statut.type === "ignoré") return "Ignoré";
|
|
if (statut.type !== "soumis") return "";
|
|
|
|
if (!statut.jour) return "Soumis";
|
|
const [, mois, jour] = statut.jour.split("-");
|
|
return `Soumis le ${jour}/${mois}`;
|
|
}
|
|
|
|
// Les ignorés restent VISIBLES (on doit pouvoir les dé-ignorer) mais cessent
|
|
// d'encombrer : ils passent en fin de liste, chaque groupe gardant son ordre.
|
|
export function repousserIgnores(evenements) {
|
|
const ignore = (evenement) => lireStatut(evenement.categories).type === "ignoré";
|
|
return [...evenements.filter((e) => !ignore(e)), ...evenements.filter(ignore)];
|
|
}
|