From 55a2f8a7306342b3fa0e4cfbaf57af6e5888749f Mon Sep 17 00:00:00 2001 From: "fefe.clochette" Date: Wed, 6 May 2026 17:44:43 +0200 Subject: [PATCH] smart selector --- family-calendar.php | 8 +- modules/family-calendar/family-calendar.css | 39 ++++++ modules/family-calendar/family-calendar.js | 72 ++++++---- pachafamily_source.md | 139 ++++++++++++-------- 4 files changed, 176 insertions(+), 82 deletions(-) diff --git a/family-calendar.php b/family-calendar.php index 4943c4c..b448fc1 100644 --- a/family-calendar.php +++ b/family-calendar.php @@ -110,7 +110,13 @@ require __DIR__ . '/header.php';
-

+ +
+ + + +
+
diff --git a/modules/family-calendar/family-calendar.css b/modules/family-calendar/family-calendar.css index 59c0651..9153e27 100644 --- a/modules/family-calendar/family-calendar.css +++ b/modules/family-calendar/family-calendar.css @@ -795,6 +795,45 @@ td.col-laia-sub { color: #ef4444; /* Devient rouge au survol */ } +/* ========================================================= + SMART DATE SELECTOR (Comportement natif calqué sur .pf-input) + ========================================================= */ +.fc-smart-select { + background: #ffffff; + border: 1px solid #cbd5e1; + border-radius: 8px; + + font-family: inherit; + font-size: 1.1rem; + font-weight: 600; + color: var(--text-main); + + /* Padding standard */ + padding: 8px 12px; + cursor: pointer; + transition: all 0.2s ease; + text-transform: capitalize; + box-shadow: var(--pf-shadow-sm); +} + +.fc-smart-select:hover { + border-color: #94a3b8; +} + +.fc-smart-select:focus { + background: #ffffff; + border-color: var(--primary); + outline: none; + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15); +} + +@media (max-width: 768px) { + .fc-smart-select { + font-size: 1rem; + padding: 6px 8px; + } +} + /* Mobile */ @media (max-width: 768px) { .pf-family-calendar .pf-container { diff --git a/modules/family-calendar/family-calendar.js b/modules/family-calendar/family-calendar.js index 68fd83c..a28e879 100644 --- a/modules/family-calendar/family-calendar.js +++ b/modules/family-calendar/family-calendar.js @@ -96,7 +96,8 @@ document.addEventListener("DOMContentLoaded", () => { now.getMonth() >= 8 ? now.getFullYear() : now.getFullYear() - 1; this.modalSelectedYear = this.currentSchoolYearStart; - this.setupModalUI(); // Prépare le selecteur d'année dans la modale + this.setupModalUI(); + this.initSmartSelectors(); await this.refreshAllData(); this.updateSchoolYearLabel(); @@ -132,6 +133,40 @@ document.addEventListener("DOMContentLoaded", () => { } } + initSmartSelectors() { + const selectMonth = document.getElementById("fc-select-month"); + const selectYear = document.getElementById("fc-select-year"); + if (!selectMonth || !selectYear) return; + + const lang = window.appLang || "fr-FR"; + + // 1. Remplir les mois en tenant compte de la langue locale + for (let i = 0; i < 12; i++) { + const d = new Date(2000, i, 1); + const monthName = new Intl.DateTimeFormat(lang, { + month: "long", + }).format(d); + selectMonth.add(new Option(monthName, i)); + } + + // 2. Remplir les années (de l'année actuelle -2 à +5) + const currentY = new Date().getFullYear(); + for (let y = currentY - 2; y <= currentY + 5; y++) { + selectYear.add(new Option(y, y)); + } + + // 3. Écouteurs d'événements pour le rechargement en direct + const handleChange = () => { + const m = parseInt(selectMonth.value); + const y = parseInt(selectYear.value); + this.currentMonth = new Date(y, m, 1); + this.renderMonthCalendar(); + }; + + selectMonth.addEventListener("change", handleChange); + selectYear.addEventListener("change", handleChange); + } + async refreshAllData() { try { const weeksData = await this.fetchApi( @@ -714,37 +749,22 @@ document.addEventListener("DOMContentLoaded", () => { const y = this.currentMonth.getFullYear(); const m = this.currentMonth.getMonth(); - const lang = window.I18N_LANG || "fr-FR"; - const titleEl = document.querySelector("#fc-current-month-year"); - if (titleEl) { + const selectMonth = document.getElementById("fc-select-month"); + const selectYear = document.getElementById("fc-select-year"); + // On masque définitivement le suffixe encombrant + const suffixEl = document.getElementById("fc-multi-month-suffix"); + + if (selectMonth && selectYear) { + selectMonth.value = m; + selectYear.value = y; + if (suffixEl) suffixEl.style.display = "none"; + if (this.viewMode === "3months") { - const nextM2 = new Date(y, m + 2, 1); - const m1 = new Intl.DateTimeFormat(lang, { month: "short" }).format( - this.currentMonth, - ); - const m2 = new Intl.DateTimeFormat(lang, { - month: "short", - year: "numeric", - }).format(nextM2); - titleEl.textContent = `${m1} - ${m2}`; this.renderThreeMonthsView(); } else if (this.viewMode === "2months") { - const nextM = new Date(y, m + 1, 1); - const m1 = new Intl.DateTimeFormat(lang, { month: "short" }).format( - this.currentMonth, - ); - const m2 = new Intl.DateTimeFormat(lang, { - month: "short", - year: "numeric", - }).format(nextM); - titleEl.textContent = `${m1} - ${m2}`; this.renderTwoMonthsView(); } else { - titleEl.textContent = new Intl.DateTimeFormat(lang, { - month: "long", - year: "numeric", - }).format(this.currentMonth); this.monthCalendar.innerHTML = this.generateMonthHTML(y, m); } } diff --git a/pachafamily_source.md b/pachafamily_source.md index a58d594..f7a73bc 100644 --- a/pachafamily_source.md +++ b/pachafamily_source.md @@ -1,6 +1,6 @@ # 🦙 Source Code PachaFamily -> *Généré le 2026-05-06 13:00:43* +> *Généré le 2026-05-06 17:19:12* ### 📄 Fichier : `budget.php` ```php @@ -7400,10 +7400,11 @@ $monthName = $monthNames[(int)$viewM] . ' ' . $viewY;
-
-

🏦

- +
+

🏦

+
+
@@ -7415,9 +7416,10 @@ $monthName = $monthNames[(int)$viewM] . ' ' . $viewY;
-
@@ -7527,7 +7529,9 @@ window.I18N = { 'bud_confirm_delete': , 'bud_to_define_js': , 'error_occured': , - 'bud_err_tech': + 'bud_err_tech': , + 'btn_cancel': , + 'btn_delete': , }; const activeViewMonth = ''; @@ -7853,11 +7857,17 @@ try { :root { /* Couleurs sémantiques Calendrier */ - --c-school-holiday: #e5d9f2; - --c-public-holiday: #e2e8f0; /* Plus marqué que l'original */ - --c-off-carole: #ffedd5; + --c-school-holiday: #ede9fe; + --c-public-holiday: repeating-linear-gradient( + 45deg, + #f8fafc, + #f8fafc 8px, + #f1f5f9 8px, + #f1f5f9 16px + ); + --c-off-carole: #fef3c7; --c-extra-off: #fee2e2; - --c-selected: #bfdbfe; + --c-selected: #dbeafe; /* Thèmes Parents (Sync Budget) */ --bg-alex: #ecfeff; @@ -8811,14 +8821,22 @@ document.addEventListener("DOMContentLoaded", () => { // Modifie dynamiquement le titre de la modale pour y insérer le selecteur d'année setupModalUI() { - const headerH2 = document.querySelector(".fc-modal-header h2"); - if (headerH2 && !document.getElementById("holidayYearSelect")) { - headerH2.innerHTML = `${tr("fc_modal_holidays_title")} - + ${options} `; document @@ -8908,7 +8926,7 @@ document.addEventListener("DOMContentLoaded", () => { renderModalHolidays() { if (!this.schoolHolidaysTableBody) return; - // Filtrer les événements de type VACANCES_SCOLAIRES pour l'année scolaire sélectionnée + // On utilise bien l'année de la modale, indépendamment du planning de fond const startDate = `${this.modalSelectedYear}-09-01`; const endDate = `${this.modalSelectedYear + 1}-08-31`; @@ -8919,61 +8937,50 @@ document.addEventListener("DOMContentLoaded", () => { e.date <= endDate, ); - // S'il n'y a pas de vacances en base pour cette année, on affiche le bouton "Générer" if (yearHolidays.length === 0) { this.schoolHolidaysTableBody.innerHTML = ` -

Les vacances de cette année ne sont pas encore enregistrées.

- +

${tr("fc_err_no_data_gov")}

+ `; - document .getElementById("btnFetchGovHolidays") - .addEventListener("click", (e) => { - e.target.innerText = "Téléchargement en cours..."; + ?.addEventListener("click", (e) => { + e.target.innerText = "..."; e.target.disabled = true; this.fetchAndSaveGovHolidays(this.modalSelectedYear); }); return; } - // 1. Tri chronologique strict des jours yearHolidays.sort((a, b) => new Date(a.date) - new Date(b.date)); - // 2. Regroupement par blocs de jours consécutifs const blocks = []; let currentBlock = null; yearHolidays.forEach((e) => { - const d = new Date(e.date + "T00:00:00"); // Force locale - + const d = new Date(e.date + "T00:00:00"); if (!currentBlock) { currentBlock = { start: d, end: d }; blocks.push(currentBlock); } else { - // Calcul de l'écart en jours entre la date actuelle et la fin du bloc en cours - const diffTime = d.getTime() - currentBlock.end.getTime(); - const diffDays = Math.round(diffTime / (1000 * 60 * 60 * 24)); - - // Si l'écart est minime (<= 4 jours, pour absorber un éventuel week-end non stocké) - // on considère qu'on est toujours dans la même période de vacances. + const diffDays = Math.round( + (d.getTime() - currentBlock.end.getTime()) / (1000 * 60 * 60 * 24), + ); if (diffDays <= 4) { currentBlock.end = d; } else { - // Sinon, l'écart est grand : c'est une NOUVELLE période de vacances currentBlock = { start: d, end: d }; blocks.push(currentBlock); } } }); - // 3. Rendu HTML et déduction des noms let html = ""; blocks.forEach((block) => { - // Déduction intelligente du nom selon le mois de départ ET la durée const m = block.start.getMonth() + 1; const durationDays = Math.round( @@ -8982,26 +8989,19 @@ document.addEventListener("DOMContentLoaded", () => { ) + 1; let name = tr("leg_school_holidays"); - - if (m === 10 || m === 11) { - name = tr("vac_toussaint"); - } else if (m === 12 || m === 1) { - name = tr("vac_noel"); - } else if (m === 2 || m === 3) { - name = tr("vac_hiver"); - } else if (m === 4 || (m === 5 && durationDays > 6)) { + if (m === 10 || m === 11) name = tr("vac_toussaint"); + else if (m === 12 || m === 1) name = tr("vac_noel"); + else if (m === 2 || m === 3) name = tr("vac_hiver"); + else if (m === 4 || (m === 5 && durationDays > 6)) name = tr("vac_printemps"); - } else if (m === 5 && durationDays <= 6) { - name = tr("vac_ascension"); - } else if (m === 7 || m === 8) { - name = tr("vac_ete"); - } + else if (m === 5 && durationDays <= 6) name = tr("vac_ascension"); + else if (m === 7 || m === 8) name = tr("vac_ete"); html += ` ${name} - ${block.start.toLocaleDateString("fr-FR")} - ${block.end.toLocaleDateString("fr-FR")} + ${block.start.toLocaleDateString(window.appLang || "fr-FR")} + ${block.end.toLocaleDateString(window.appLang || "fr-FR")} `; }); @@ -9894,6 +9894,35 @@ document.addEventListener("DOMContentLoaded", () => { }); document.addEventListener("touchend", (e) => this.handleTouchEnd(e)); } + const scrollWrapper = document.getElementById("planningTable-wrapper"); + if (scrollWrapper) { + scrollWrapper.addEventListener("scroll", async () => { + // Bloquer si on est déjà en train de charger + if (this._isAutoLoading) return; + + // Détection du bas (On descend dans le temps : passage à l'année suivante) + // On met une tolérance de 5px pour les calculs de pixels décimaux + if ( + scrollWrapper.scrollTop + scrollWrapper.clientHeight >= + scrollWrapper.scrollHeight - 5 + ) { + this._isAutoLoading = true; + await this.changeSchoolYear(1); + // On replace le scroll tout en haut pour la continuité visuelle + scrollWrapper.scrollTop = 5; + setTimeout(() => (this._isAutoLoading = false), 500); + } + // Détection du haut (On remonte le temps : passage à l'année précédente) + else if (scrollWrapper.scrollTop === 0) { + this._isAutoLoading = true; + await this.changeSchoolYear(-1); + // On replace le scroll tout en bas pour la continuité visuelle + scrollWrapper.scrollTop = + scrollWrapper.scrollHeight - scrollWrapper.clientHeight - 5; + setTimeout(() => (this._isAutoLoading = false), 500); + } + }); + } // --- GESTION MODALE VACANCES SCOLAIRES --- const btnOpen = document.getElementById("btnOpenHolidays");