// Cœur métier du pré-remplissage : traduit le payload déposé par la liste en // valeurs prêtes à poser dans le formulaire mairie. Fonctions PURES, aucun DOM // (c'est cette partie-là qui est testée ; le poseur, lui, est fin et impur). // // ⚠️ Ni `import` ni `export`, comme config.js (cf. DECISIONS.md 2026-07-17). const LOCALE = "fr-FR"; const FUSEAU = "Europe/Paris"; // Dupliquée depuis transfert.js (ESM, inatteignable depuis un content script // classique). Un test de dérive garantit l'égalité — cf. DECISIONS.md 2026-07-17. const CLE_EVENEMENT_EN_ATTENTE = "evenement-en-attente"; // Un événement horodaté porte un vrai instant : la mairie attend l'heure de // Cugnaux, quel que soit le fuseau de la machine du bénévole. const FORMAT_DATE_PARIS = new Intl.DateTimeFormat(LOCALE, { timeZone: FUSEAU, day: "2-digit", month: "2-digit", year: "numeric", }); const FORMAT_HEURE_PARIS = new Intl.DateTimeFormat(LOCALE, { timeZone: FUSEAU, hour: "2-digit", minute: "2-digit", hourCycle: "h23", }); globalThis.EchoFormulaire = { CLE_EVENEMENT_EN_ATTENTE, // payload (transfert.js) + config (config.js) → objet plat, prêt à poser. // Ce qu'on ignore reste vide et part dans `aCompleter` : jamais d'invention. calculerValeurs(payload, config) { const debut = new Date(payload.debut); const fin = new Date(payload.fin); const valeurs = { organisateur: config.organisateur, email: config.email, titre: payload.titre, description: payload.description, theme: config.themeDefaut, tarifs: config.tarifs, ...(payload.journeeEntiere ? datesFlottantes(debut, fin) : datesHorodatees(debut, fin)), ...lieuEtCoordonnees(payload.lieu, config.cafe), }; return { ...valeurs, aCompleter: listerACompleter(valeurs) }; }, }; // Journée entière = date FLOTTANTE (RFC 5545) : composantes locales // uniquement, jamais l'heure, jamais un fuseau — sinon la date glisse d'un jour // hors de +02:00 (cf. DECISIONS.md 2026-07-14). function datesFlottantes(debut, fin) { const dateDebut = formaterDateLocale(debut); const dernierJour = veilleDe(fin); return { dateDebut, // Un seul jour, ou DTEND absent (le parseur pose alors fin === debut). dateFin: dernierJour.getTime() <= debut.getTime() ? dateDebut : formaterDateLocale(dernierJour), // Poser « 00:00 » serait exactement le mensonge que la liste refuse déjà. heureDebut: null, heureFin: null, }; } function datesHorodatees(debut, fin) { const dateDebut = FORMAT_DATE_PARIS.format(debut); const heureDebut = heureDe(debut); // DTEND absent : le parseur pose fin === debut. Pas d'heure de fin à // soumettre — mais l'événement a bien lieu ce jour-là. if (fin.getTime() <= debut.getTime()) { return { dateDebut, dateFin: dateDebut, heureDebut, heureFin: null }; } // Jour de fin calculé à Paris : un 22:00 → 02:00 change de jour. return { dateDebut, dateFin: FORMAT_DATE_PARIS.format(fin), heureDebut, heureFin: heureDe(fin), }; } // Le formulaire attend deux selects [HH, MM], zéro-paddés. function heureDe(date) { return FORMAT_HEURE_PARIS.format(date).split(":"); } function formaterDateLocale(date) { const jour = String(date.getDate()).padStart(2, "0"); const mois = String(date.getMonth() + 1).padStart(2, "0"); return `${jour}/${mois}/${date.getFullYear()}`; } // 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 soumet à une mairie // qui modère une date de fin FAUSSE. Composantes locales, jamais « -24h ». function veilleDe(date) { return new Date(date.getFullYear(), date.getMonth(), date.getDate() - 1); } // La majorité des événements n'a pas de LOCATION : ils se tiennent au café. function lieuEtCoordonnees(lieu, cafe) { const saisi = lieu.trim(); if (!saisi) { return { adresse: cafe.adresse, coordonnees: `${cafe.coords}|${cafe.adresse}` }; } // Lieu ailleurs : on ne sait pas géocoder. Inventer un lat|lng serait pire // qu'un champ vide → l'humain place le point sur la carte. return { adresse: saisi, coordonnees: "" }; } // Toujours honnête : ce que la machine ne sait pas est dit, pas masqué. function listerACompleter({ heureDebut, heureFin, coordonnees, organisateur, email, titre }) { const aCompleter = [ "l'image (obligatoire)", // un script ne peut pas remplir un "le thème", // le calendrier n'en a pas : le défaut est à confirmer ]; if (heureDebut === null || heureFin === null) aCompleter.push("l'heure"); if (coordonnees === "") aCompleter.push("le lieu exact sur la carte"); if (!organisateur || !email) aCompleter.push("l'organisateur et l'email (config.local.json)"); if (titre === "") aCompleter.push("le titre"); return aCompleter; }