/** * family-calendar.js (Version Optimisée - API BDD, Décalage Vendredi & Paramètres Dynamiques) */ // ============================================================================ // 1. VARIABLES GLOBALES & LOGIQUE DE CONFIGURATION (SETTINGS) // ============================================================================ let calGlobalData = null; let currentSelectedMemberId = null; let localCareModes = []; // Ouvre et initialise la modale avec les données fraîches de la BDD async function openCalendarSettings() { try { const res = await pachaFetch( "/modules/family-calendar/includes/api/settings.php?action=get_all", ); if (!res.success) throw new Error(res.error); calGlobalData = res.data; const zoneInput = document.getElementById("setZoneScolaire"); if (zoneInput) zoneInput.value = calGlobalData.foyer.zone_scolaire; localCareModes = calGlobalData.foyer.care_modes || []; renderCareModeTags(); const selectMember = document.getElementById("selectCalMember"); if (selectMember) { selectMember.innerHTML = calGlobalData.people .map( (p) => ``, ) .join(""); } switchCalendarTab("foyer"); const modalSettings = document.getElementById("modalCalendarSettings"); if (modalSettings) { modalSettings.classList.add("open"); document.body.classList.add("no-scroll"); } } catch (err) { alert("Erreur : " + err.message); } } function closeCalendarSettings() { document.getElementById("modalCalendarSettings").classList.remove("open"); document.body.classList.remove("no-scroll"); } function switchCalendarTab(tabName) { document .querySelectorAll(".cal-settings-pane") .forEach((p) => (p.style.display = "none")); document .querySelectorAll(".bs-tab-btn") .forEach((b) => b.classList.remove("active")); if (tabName === "foyer") { document.getElementById("cal-pane-foyer").style.display = "block"; document.getElementById("tab-btn-foyer").classList.add("active"); } else { document.getElementById("cal-pane-membres").style.display = "block"; document.getElementById("tab-btn-membres").classList.add("active"); loadMemberConfigView(); } } function renderCareModeTags() { const container = document.getElementById("careModesContainer"); container.innerHTML = localCareModes .map( (mode, i) => ` ${mode} × `, ) .join(""); } function addCareModeTag() { const input = document.getElementById("inputNewCareMode"); const val = input.value.trim(); if (val && !localCareModes.includes(val)) { localCareModes.push(val); renderCareModeTags(); input.value = ""; } } function removeCareModeTag(index) { localCareModes.splice(index, 1); renderCareModeTags(); } async function submitCalFoyer(e) { e.preventDefault(); try { const formData = new FormData(); formData.append("action", "save_foyer"); formData.append( "zone_scolaire", document.getElementById("setZoneScolaire").value, ); formData.append("care_modes", JSON.stringify(localCareModes)); const res = await pachaFetch( "/modules/family-calendar/includes/api/settings.php", { method: "POST", body: formData }, ); if (!res.success) throw new Error(res.error); alert( window.FAMILY_CONFIG?.i18n?.settings_updated || "Configuration enregistrée ! ✨", ); window.location.reload(); // On recharge pour appliquer la zone et les colonnes de garde } catch (err) { alert("Erreur : " + err.message); } } function loadMemberConfigView() { const select = document.getElementById("selectCalMember"); const zone = document.getElementById("memberConfigZone"); if (!select || !select.value || !zone) return; currentSelectedMemberId = parseInt(select.value); const role = select.options[select.selectedIndex].dataset.role; if (role === "enfant") { zone.innerHTML = `
${tr("fc_care_modes_title")}

${tr("fc_care_modes_desc")}

${ localCareModes .map( (m) => ` `, ) .join("") || `${tr("fc_no_care_mode")}` }
`; } else { // 1. Récupération STRICTE des congés existants en BDD (Aucun forçage) let userLeaves = calGlobalData.leaves[currentSelectedMemberId] || []; // 2. Génération des lignes du tableau dynamiquement (Version compactée) const tableRows = userLeaves .map( (l) => ` `, ) .join(""); zone.innerHTML = `
🗓️ Matrice des Congés

Définissez les quotas et méthodes d'acquisition.

${tableRows || ``}
Code Méthode Quota Renouv. / Fin
Aucun compteur défini.
`; } } window.addLeaveRowToMatrix = function () { const tbody = document.getElementById("tbodyLeaveMatrix"); const empty = document.getElementById("emptyMatrixRow"); if (empty) empty.remove(); if (!tbody) return; const tr = document.createElement("tr"); tr.className = "js-leave-row"; tr.style.borderBottom = "1px solid var(--border-light)"; tr.innerHTML = ` `; tbody.appendChild(tr); }; async function submitMemberLeaves() { // 🟢 Sécurité : on s'assure qu'un membre est bien sélectionné if (!currentSelectedMemberId || isNaN(currentSelectedMemberId)) { alert("Erreur technique : Aucun membre valide sélectionné."); return; } try { const rows = document.querySelectorAll(".js-leave-row"); // 🟢 Récupération enrichie avec "method" et "allowance" const leavesPayload = Array.from(rows) .map((row) => ({ type: row.querySelector(".js-leave-type").value.trim().toUpperCase(), method: row.querySelector(".js-leave-method").value, // <-- NOUVEAU allowance: row.querySelector(".js-leave-allowance").value, // <-- NOUVEAU date: row.querySelector(".js-leave-date").value, })) .filter((l) => l.type && l.date); // Filtre de sécurité const formData = new FormData(); formData.append("action", "save_member_leaves"); formData.append("person_id", currentSelectedMemberId); formData.append("leaves", JSON.stringify(leavesPayload)); const res = await pachaFetch( "/modules/family-calendar/includes/api/settings.php", { method: "POST", body: formData }, ); if (!res.success) throw new Error(res.error); if (window.showToast) { showToast(tr("fc_matrix_saved"), "success"); } else { alert(tr("fc_matrix_saved")); } closeCalendarSettings(); setTimeout(openCalendarSettings, 300); } catch (err) { alert("Erreur : " + err.message); } } // ============================================================================ // 2. MOTEUR PRINCIPAL DU CALENDRIER (DOM CONTENT LOADED) // ============================================================================ document.addEventListener("DOMContentLoaded", () => { // Récupération sécurisée depuis le nouveau format PHP const parents = window.FAMILY_CONFIG?.parents || []; const kids = window.FAMILY_CONFIG?.kids || []; const activeCareModes = window.FAMILY_CONFIG?.activeCareModes || []; const CONGE_TYPES = ["OFF_CAROLE", "EXTRA_OFF_CAROLE"]; const GUARDE_TYPES = ["CENTRE", "AVIS"]; const PEP_TYPES = ["PEP_SICK"]; // (Note: La logique LEAVES_CONFIG restera à basculer vers BDD dans un 2nd temps) const LEAVES_CONFIG = { CP: { startMonth: 8, defaultBalance: 25 }, JRA: { yearlyTotals: { 2024: 10, 2025: 10, 2026: 11 }, defaultBalance: 10, toleranceMonths: 2, maxReport: 2, }, JA: {}, }; parents.forEach((parent) => { LEAVES_CONFIG.JA[parent.id] = { startMonth: 4, startDay: 29, defaultBalance: 4, }; }); class FamilyCalendar { constructor() { this.planningBody = document.getElementById("planningBody"); this.selectionMenu = document.getElementById("selectionMenu"); this.schoolHolidaysTableBody = document.querySelector( "#schoolHolidaysTable tbody", ); this.monthCalendar = document.getElementById("fc-month-calendar"); this.monthSelectionMenu = document.getElementById( "fc-month-selectionMenu", ); if ( this.selectionMenu && this.selectionMenu.parentElement !== document.body ) document.body.appendChild(this.selectionMenu); if ( this.monthSelectionMenu && this.monthSelectionMenu.parentElement !== document.body ) document.body.appendChild(this.monthSelectionMenu); this.currentMonth = new Date(); this.currentMonth.setDate(1); this.viewMode = "1month"; this.currentSchoolYearStart = null; this.modalSelectedYear = null; this.isSelecting = false; this.selectedCells = []; this.monthSelectedCells = []; this._currentBulkInfo = null; this.dbEvents = []; this.fixedEvents = []; this.events = []; this.leaves = []; this.weeks = []; this.monthlyLeaveBalances = { 2: { CP: {}, JRA: {}, JA: {} }, 3: { CP: {}, JRA: {}, JA: {} }, }; if (!this.planningBody) return; this.init(); } getLocalIsoDate(dateObj) { const y = dateObj.getFullYear(); const m = String(dateObj.getMonth() + 1).padStart(2, "0"); const d = String(dateObj.getDate()).padStart(2, "0"); return `${y}-${m}-${d}`; } async init() { this.setupEventListeners(); const now = new Date(); this.currentSchoolYearStart = now.getMonth() >= 8 ? now.getFullYear() : now.getFullYear() - 1; this.modalSelectedYear = this.currentSchoolYearStart; this.setupModalUI(); this.initSmartSelectors(); await this.refreshAllData(); this.updateSchoolYearLabel(); setTimeout(() => this.scrollToCurrentMonth(), 100); } setupModalUI() { const headerTitle = document.querySelector( "#modalHolidays .pf-modal-title", ); if (headerTitle && !document.getElementById("holidayYearSelect")) { let options = ""; const currentY = new Date().getFullYear(); for (let y = currentY - 2; y <= currentY + 3; y++) { options += ``; } // Utilise la globale config ou fallback si non définie const zoneText = window.CONFIG?.ZONE_SCOLAIRE && window.CONFIG.ZONE_SCOLAIRE !== "Autre" ? `(Zone ${window.CONFIG.ZONE_SCOLAIRE})` : ""; headerTitle.innerHTML = `🏖️ ${window.I18N?.fc_modal_holidays_title || "Vacances"} ${zoneText} `; document .getElementById("holidayYearSelect") .addEventListener("change", (e) => { this.modalSelectedYear = parseInt(e.target.value); this.renderModalHolidays(); }); } } 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"; for (let i = 0; i < 12; i++) { selectMonth.add( new Option( new Intl.DateTimeFormat(lang, { month: "long" }).format( new Date(2000, i, 1), ), i, ), ); } const currentY = new Date().getFullYear(); for (let y = currentY - 2; y <= currentY + 5; y++) { selectYear.add(new Option(y, y)); } const handleChange = () => { this.currentMonth = new Date( parseInt(selectYear.value), parseInt(selectMonth.value), 1, ); this.renderMonthCalendar(); }; selectMonth.addEventListener("change", handleChange); selectYear.addEventListener("change", handleChange); } async refreshAllData() { try { const [ weeksData, eventsData, fixedEventsData, leavesData, balancesData, snapshotsData, ] = await Promise.all([ this.fetchApi( `/modules/family-calendar/includes/api/get-calendar-weeks-scolaire.php?school_year_start=${this.currentSchoolYearStart}`, ), this.fetchApi("/modules/family-calendar/includes/api/get-events.php"), this.fetchPublicHolidays(), this.fetchApi("/modules/family-calendar/includes/api/get-leaves.php"), this.fetchApi( "/modules/family-calendar/includes/api/get-leave-balances.php", ), this.fetchApi( "/modules/family-calendar/includes/api/get-leave-snapshots.php", ), ]); this.weeks = this.processWeeks(weeksData.weeks || []); this.dbEvents = (eventsData.events || []).map((e) => ({ ...e, duration: parseFloat(e.duration), })); this.fixedEvents = fixedEventsData; this.leaves = leavesData.leaves || []; this.leaveBalances = balancesData.balances || []; this.leaveSnapshots = snapshotsData.snapshots || []; this.events = [...this.dbEvents, ...this.fixedEvents]; this.publicHolidayDates = new Set( this.fixedEvents .filter((e) => e.type === "PUBLIC_HOLIDAY") .map((e) => e.date), ); this.reprocessAndRender(); this.renderModalHolidays(); } catch (e) { console.error("Erreur chargement données:", e); } } async fetchPublicHolidays() { try { const res = await fetch( "https://calendrier.api.gouv.fr/jours-feries/metropole.json", ); const holidays = await res.json(); return Object.keys(holidays).map((date, idx) => ({ id: `ph-${idx}`, date: date, name: holidays[date], type: "PUBLIC_HOLIDAY", duration: 1, })); } catch (error) { return []; } } renderModalHolidays() { if (!this.schoolHolidaysTableBody) return; const startDate = `${this.modalSelectedYear}-09-01`; const endDate = `${this.modalSelectedYear + 1}-08-31`; const yearHolidays = this.events.filter( (e) => e.type === "VACANCES_SCOLAIRES" && e.date >= startDate && e.date <= endDate, ); if (yearHolidays.length === 0) { this.schoolHolidaysTableBody.innerHTML = `

${window.I18N?.fc_err_no_data_gov || "Aucune donnée"}

`; document .getElementById("btnFetchGovHolidays") ?.addEventListener("click", (e) => { e.target.innerText = "..."; e.target.disabled = true; this.fetchAndSaveGovHolidays(this.modalSelectedYear); }); return; } yearHolidays.sort((a, b) => new Date(a.date) - new Date(b.date)); const blocks = []; let currentBlock = null; yearHolidays.forEach((e) => { const d = new Date(e.date + "T00:00:00"); if (!currentBlock) { currentBlock = { start: d, end: d }; blocks.push(currentBlock); } else { const diffDays = Math.round( (d.getTime() - currentBlock.end.getTime()) / 86400000, ); if (diffDays <= 4) currentBlock.end = d; else { currentBlock = { start: d, end: d }; blocks.push(currentBlock); } } }); this.schoolHolidaysTableBody.innerHTML = blocks .map((block) => { const m = block.start.getMonth() + 1; const dur = Math.round( (block.end.getTime() - block.start.getTime()) / 86400000, ) + 1; let name = window.I18N?.leg_school_holidays || "Vacances"; if (m === 10 || m === 11) name = window.I18N?.vac_toussaint || "Toussaint"; else if (m === 12 || m === 1) name = window.I18N?.vac_noel || "Noël"; else if (m === 2 || m === 3) name = window.I18N?.vac_hiver || "Hiver"; else if (m === 4 || (m === 5 && dur > 6)) name = window.I18N?.vac_printemps || "Printemps"; else if (m === 5 && dur <= 6) name = window.I18N?.vac_ascension || "Ascension"; else if (m === 7 || m === 8) name = window.I18N?.vac_ete || "Eté"; return ` ${name} ${block.start.toLocaleDateString(window.appLang || "fr-FR")} ${block.end.toLocaleDateString(window.appLang || "fr-FR")} `; }) .join(""); } async fetchAndSaveGovHolidays(yearStart) { try { const yearStr = `${yearStart}-${yearStart + 1}`; const zone = window.CONFIG?.ZONE_SCOLAIRE || "C"; if (zone === "Autre") { alert("Import auto dispo que pour Zones A, B ou C (France)."); return; } const url = `https://data.education.gouv.fr/api/explore/v2.1/catalog/datasets/fr-en-calendrier-scolaire/records?where=annee_scolaire='${yearStr}' AND zones LIKE '%Zone ${zone}%'&limit=100`; const res = await fetch(url); const data = await res.json(); const uniqueMap = new Map(); (data.results || []).forEach((r) => uniqueMap.set(`${r.description}|${r.start_date}`, r), ); const payload = []; Array.from(uniqueMap.values()).forEach((r) => { let curr = new Date(r.start_date.split("T")[0] + "T00:00:00"); const end = new Date(r.end_date.split("T")[0] + "T00:00:00"); if (curr.getDay() === 5) curr.setDate(curr.getDate() + 1); // Règle: Décale vendredi -> samedi while (curr < end) { payload.push({ date: this.getLocalIsoDate(curr), type: "VACANCES_SCOLAIRES", duration: 1, person: r.description, }); curr.setDate(curr.getDate() + 1); } }); if (payload.length > 0) { await this.postApi( "/modules/family-calendar/includes/api/save-events.php", payload, ); await this.refreshAllData(); } else { alert("Aucune donnée trouvée."); } } catch (e) { alert("Erreur API Gouv."); } } reprocessAndRender() { this.reprocessEvents(); this.calculateMonthlyBalances(); this.initSummaryControls(); this.renderTable(); this.renderMonthCalendar(); } reprocessEvents() { this.weeks.forEach((w) => { Object.keys(w.totals).forEach((k) => (w.totals[k] = 0)); Object.values(w.dayFlags).forEach((f) => (f.events = [])); this.events.forEach((e) => { const d = new Date(e.date + "T00:00:00"); if (d >= w.dayDates.mon && d <= w.dayDates.fri) { const dayKey = Object.keys(w.dayDates).find( (k) => w.dayDates[k].getTime() === d.getTime(), ); if (dayKey) w.dayFlags[dayKey].events.push(e); const dur = parseFloat(e.duration) || 1; const typeMap = { OFF_CAROLE: "offCarole", EXTRA_OFF_CAROLE: "extraOffCarole", CENTRE: "centre", AVIS: "avis", PEP_SICK: "pepSick", }; if (typeMap[e.type]) w.totals[typeMap[e.type]] += dur; } }); this.leaves.forEach((l) => { const d = new Date(l.leave_date + "T00:00:00"); if (d >= w.dayDates.mon && d <= w.dayDates.fri) { const dur = parseFloat(l.duration) || 1; let prefix = null; if (parents[0] && parseInt(l.person_id) === parseInt(parents[0].id)) prefix = "alex"; else if ( parents[1] && parseInt(l.person_id) === parseInt(parents[1].id) ) prefix = "laia"; if (prefix) w.totals[`${prefix}${l.leave_type}`] += dur; } }); let workingDays = 0; Object.values(w.dayDates).forEach((d) => { if (!this.publicHolidayDates.has(this.getLocalIsoDate(d))) workingDays++; }); w.totals.presencePep = Math.max( 0, workingDays - (w.totals.offCarole + w.totals.extraOffCarole + w.totals.pepSick), ); }); } calculateMonthlyBalances() { const balances = {}; parents.forEach((p) => { balances[p.id] = { CP: {}, JRA: {}, JA: {} }; }); const ymSet = new Set(); this.weeks.forEach((w) => ymSet.add(w.monthKey)); const ymList = Array.from(ymSet).sort(); const usageByMonth = {}; this.leaves.forEach((l) => { const pid = l.person_id, type = l.leave_type, ym = l.leave_date.substring(0, 7); if (!usageByMonth[pid]) usageByMonth[pid] = {}; if (!usageByMonth[pid][type]) usageByMonth[pid][type] = {}; usageByMonth[pid][type][ym] = (usageByMonth[pid][type][ym] || 0) + parseFloat(l.duration); }); parents.forEach((parent) => { const pid = parent.id; ["CP", "JRA", "JA"].forEach((type) => { ymList.forEach((ym) => { const [currYear, currMonth] = ym.split("-").map(Number); let cycleStartStr = "", initialBalance = 0; const latestSnapshot = (this.leaveSnapshots || []) .filter( (s) => s.person_id == pid && s.leave_type == type && s.snapshot_date.substring(0, 7) <= ym, ) .sort((a, b) => b.snapshot_date.localeCompare(a.snapshot_date), )[0]; if (latestSnapshot) { cycleStartStr = latestSnapshot.snapshot_date.substring(0, 7); initialBalance = parseFloat(latestSnapshot.remaining_balance); } else { if (type === "CP") { const refYear = currMonth >= LEAVES_CONFIG.CP.startMonth ? currYear : currYear - 1; cycleStartStr = `${refYear}-${String(LEAVES_CONFIG.CP.startMonth).padStart(2, "0")}`; const dbBal = this.leaveBalances.find( (b) => b.person_id == pid && b.leave_type == "CP" && b.balance_year == refYear, ); initialBalance = dbBal ? parseFloat(dbBal.initial_balance) : LEAVES_CONFIG.CP.defaultBalance; } else if (type === "JRA") { cycleStartStr = `${currYear}-01`; initialBalance = LEAVES_CONFIG.JRA.yearlyTotals[currYear] || LEAVES_CONFIG.JRA.defaultBalance; if (currMonth <= LEAVES_CONFIG.JRA.toleranceMonths) { const prevYear = currYear - 1; const prevInitial = LEAVES_CONFIG.JRA.yearlyTotals[prevYear] || LEAVES_CONFIG.JRA.defaultBalance; let usedPrevYear = 0; for (let m = 1; m <= 12; m++) usedPrevYear += usageByMonth[pid]?.[type]?.[ `${prevYear}-${String(m).padStart(2, "0")}` ] || 0; initialBalance += Math.min( Math.max(0, prevInitial - usedPrevYear), LEAVES_CONFIG.JRA.maxReport, ); } } else if (type === "JA") { const configJA = LEAVES_CONFIG.JA[pid]; const isPastAnniversary = currMonth > configJA.startMonth || (currMonth === configJA.startMonth && configJA.startDay === 1); const refYear = isPastAnniversary ? currYear : currYear - 1; cycleStartStr = `${refYear}-${String(configJA.startMonth).padStart(2, "0")}`; const dbBal = this.leaveBalances.find( (b) => b.person_id == pid && b.leave_type == "JA" && b.balance_year == refYear, ); initialBalance = dbBal ? parseFloat(dbBal.initial_balance) : configJA.defaultBalance; } } let usedBeforeCurrentMonth = 0; Object.keys(usageByMonth[pid]?.[type] || {}).forEach((usedYm) => { if (usedYm >= cycleStartStr && usedYm < ym) usedBeforeCurrentMonth += usageByMonth[pid][type][usedYm]; }); balances[pid][type][ym] = { availableAtMonthStart: Math.max( 0, initialBalance - usedBeforeCurrentMonth, ), usedInMonth: usageByMonth[pid]?.[type]?.[ym] || 0, }; }); }); }); this.monthlyLeaveBalances = balances; } renderTable() { if (!this.planningBody) return; this.planningBody.innerHTML = ""; const monthSpans = this.weeks.reduce((acc, w) => { acc[w.monthKey] = (acc[w.monthKey] || 0) + 1; return acc; }, {}); const processedMonths = {}; const processedLeavesCols = {}; const fmt = (n) => n > 0 ? (Number.isInteger(n) ? n : n.toFixed(1)) : ""; this.weeks.forEach((w, idx) => { const tr = document.createElement("tr"); tr.setAttribute("data-month", w.monthKey); if (idx === 0 || this.weeks[idx - 1].monthKey !== w.monthKey) tr.classList.add("fc-month-first-week-row"); if ( idx === this.weeks.length - 1 || this.weeks[idx + 1].monthKey !== w.monthKey ) tr.classList.add("fc-month-last-week-row"); if (!processedMonths[w.monthKey]) { processedMonths[w.monthKey] = true; const td = document.createElement("td"); td.className = "col-month col-sticky-mois"; td.innerHTML = `${w.monthName}`; td.rowSpan = monthSpans[w.monthKey]; tr.appendChild(td); } const tdW = document.createElement("td"); tdW.className = "col-month col-sticky-sem"; tdW.textContent = w.weekLabel; tr.appendChild(tdW); ["mon", "tue", "wed", "thu", "fri"].forEach((d) => { const td = document.createElement("td"); const dateObj = w.dayDates[d]; const iso = this.getLocalIsoDate(dateObj); td.dataset.date = iso; td.textContent = String(dateObj.getDate()).padStart(2, "0"); td.className = "col-day"; w.dayFlags[d].events.forEach((evt) => { if (evt.type === "OFF_CAROLE") td.classList.add("fc-day--off-carole"); if (evt.type === "EXTRA_OFF_CAROLE") td.classList.add("fc-day--extra-off-carole"); if (evt.type === "PUBLIC_HOLIDAY") td.classList.add("fc-day--public-holiday"); if (evt.type === "VACANCES_SCOLAIRES") td.classList.add("fc-day--school-holiday"); if (evt.type === "CENTRE") td.classList.add("fc-day--centre"); if (evt.type === "AVIS") td.classList.add("fc-day--avis"); if (evt.type === "PEP_SICK") td.innerHTML += `🤒`; }); const dayLeaves = this.leaves.filter((l) => l.leave_date === iso); if (dayLeaves.length) { let html = `
`; const colors = [ "#0f766e", "#b45309", "#047857", "#4338ca", "#b91c1c", ]; parents.forEach((person, index) => { if ( dayLeaves.some( (l) => parseInt(l.person_id) === parseInt(person.id), ) ) { html += `${person.name.charAt(0).toUpperCase()}`; } }); td.innerHTML += html + `
`; } tr.appendChild(td); }); // --- 🟢 CORRECTION 1 : Colonnes du milieu (Modes de garde & Maladie) --- const activeCareModes = window.FAMILY_CONFIG?.activeCareModes || []; const kids = window.FAMILY_CONFIG?.kids || []; // 1A. Modes de garde activeCareModes.forEach((mode) => { const td = document.createElement("td"); td.className = "col-total"; // On normalise le nom (ex: "Centre" -> "centre") pour matcher ton objet w.totals const key = mode.toLowerCase(); td.textContent = fmt(w.totals[key] || 0); tr.appendChild(td); }); // 1B. Enfants (Maladie) kids.forEach((kid) => { const td = document.createElement("td"); td.className = "col-total"; // Rétrocompatibilité avec ton ancien code "pepSick" const key = kid.name.toLowerCase() === "pep" ? "pepSick" : `${kid.name.toLowerCase()}Sick`; td.textContent = fmt(w.totals[key] || 0); tr.appendChild(td); }); // --- 🟢 CORRECTION 2 : Colonnes des congés Parents (Dynamique !) --- if (!processedLeavesCols[w.monthKey]) { processedLeavesCols[w.monthKey] = true; const parentsList = window.FAMILY_CONFIG?.parents || []; parentsList.forEach((parent, index) => { const cssPrefix = index % 2 === 0 ? "col-alex" : "col-laia"; // On récupère les compteurs réels du parent envoyés par le PHP const parentLeaveTypes = parent.leave_types && parent.leave_types.length > 0 ? parent.leave_types : ["CP", "JRA", "JA"]; parentLeaveTypes.forEach((type) => { const info = this.monthlyLeaveBalances[parent.id]?.[type]?.[w.monthKey]; tr.innerHTML += `${info ? fmt(info.availableAtMonthStart) : "-"}`; tr.innerHTML += `${info ? fmt(info.usedInMonth) : ""}`; }); }); } this.planningBody.appendChild(tr); }); } scrollToCurrentMonth() { const wrapper = document.getElementById("planningTable-wrapper"); const targetRow = document.querySelector( `#planningTable tbody tr[data-month="${this.getLocalIsoDate(new Date()).slice(0, 7)}"]`, ); if (wrapper && targetRow) setTimeout( () => wrapper.scrollTo({ top: Math.max(0, targetRow.offsetTop - 50), behavior: "smooth", }), 50, ); } renderMonthCalendar() { if (!this.monthCalendar) return; const y = this.currentMonth.getFullYear(), m = this.currentMonth.getMonth(); const selectMonth = document.getElementById("fc-select-month"); const selectYear = document.getElementById("fc-select-year"); if (selectMonth && selectYear) { selectMonth.value = m; selectYear.value = y; if (this.viewMode === "3months") this.renderThreeMonthsView(); else if (this.viewMode === "2months") this.renderTwoMonthsView(); else this.monthCalendar.innerHTML = this.generateMonthHTML(y, m); } this.renderMonthBalances(); this.syncSummaryWithMonth(); } renderTwoMonthsView() { const y = this.currentMonth.getFullYear(), m = this.currentMonth.getMonth(); const nextDate = new Date(y, m + 1, 1); const lang = window.I18N_LANG || "fr-FR"; this.monthCalendar.innerHTML = `
${new Intl.DateTimeFormat(lang, { month: "long" }).format(this.currentMonth)}
${this.generateMonthHTML(y, m)}
${new Intl.DateTimeFormat(lang, { month: "long" }).format(nextDate)}
${this.generateMonthHTML(nextDate.getFullYear(), nextDate.getMonth())}
`; } renderThreeMonthsView() { const y = this.currentMonth.getFullYear(), m = this.currentMonth.getMonth(); const [d1, d2, d3] = [ this.currentMonth, new Date(y, m + 1, 1), new Date(y, m + 2, 1), ]; let html = `
`; [d1, d2, d3].forEach( (d) => (html += `
${new Intl.DateTimeFormat("fr-FR", { month: "long" }).format(d)}
${this.generateMonthHTML(d.getFullYear(), d.getMonth())}
`), ); this.monthCalendar.innerHTML = html + `
`; } renderMonthBalances() { const container = document.getElementById("fc-month-balances"); if (!container) return; const monthsToDisplay = [], y = this.currentMonth.getFullYear(), m = this.currentMonth.getMonth(); let numMonths = this.viewMode === "2months" ? 2 : this.viewMode === "3months" ? 3 : 1; for (let i = 0; i < numMonths; i++) monthsToDisplay.push(`${y}-${String(m + i + 1).padStart(2, "0")}`); container.style.display = "flex"; container.innerHTML = parents .map((person) => { let cards = `
${person.name.toUpperCase()}
`; ["CP", "JRA", "JA"].forEach((type) => { const startBal = this.monthlyLeaveBalances[person.id]?.[type]?.[monthsToDisplay[0]] ?.availableAtMonthStart || 0; let totalUsed = 0; monthsToDisplay.forEach( (ym) => (totalUsed += this.monthlyLeaveBalances[person.id]?.[type]?.[ym] ?.usedInMonth || 0), ); const endBal = Math.max(0, startBal - totalUsed); const fmt = (n) => n > 0 ? (Number.isInteger(n) ? n : n.toFixed(1)) : "0"; let alertHtml = ""; const cMonth = parseInt(monthsToDisplay[0].split("-")[1]); if (endBal > 0) { if (type === "CP" && cMonth >= 6 && cMonth <= 7) alertHtml = `
🔥
`; else if ( type === "JRA" && (cMonth === 1 || cMonth === 2) && endBal > 2 ) alertHtml = `
🔥
`; } cards += `
${type}${fmt(endBal)}${totalUsed > 0 ? `-${fmt(totalUsed)}` : ""}${alertHtml}
`; }); return cards + `
`; }) .join(""); } syncSummaryWithMonth() { const typeSelect = document.getElementById("summType"), valueSelect = document.getElementById("summValue"); if (typeSelect && valueSelect) { if (typeSelect.value !== "month") { typeSelect.value = "month"; typeSelect.dispatchEvent(new Event("change")); } valueSelect.value = `${this.currentMonth.getFullYear()}-${String(this.currentMonth.getMonth() + 1).padStart(2, "0")}`; this.updateGlobalSummary(); } } generateMonthHTML(year, month) { let html = ``; ["L", "M", "M", "J", "V"].forEach((d) => (html += ``)); html += ``; const daysInMonth = new Date(year, month + 1, 0).getDate(); let currentRenderedCols = 0, startDay = (new Date(year, month, 1).getDay() + 6) % 7; if (startDay < 5) { for (let i = 0; i < startDay; i++) { html += ``; currentRenderedCols++; } } for (let d = 1; d <= daysInMonth; d++) { const dateObj = new Date(year, month, d), dayOfWeek = dateObj.getDay(); if (dayOfWeek === 0 || dayOfWeek === 6) continue; if (currentRenderedCols === 5) { html += ``; currentRenderedCols = 0; } const iso = this.getLocalIsoDate(dateObj), todayIso = this.getLocalIsoDate(new Date()); let cls = "fc-month-day" + (iso === todayIso ? " fc-day--today" : ""); const dayEvts = this.events.filter((e) => e.date === iso); if (dayEvts.some((e) => e.type === "VACANCES_SCOLAIRES")) cls += " fc-day--school-holiday"; if (dayEvts.some((e) => e.type === "PUBLIC_HOLIDAY")) cls += " fc-day--public-holiday"; if (dayEvts.some((e) => e.type === "OFF_CAROLE")) cls += " fc-day--off-carole"; if (dayEvts.some((e) => e.type === "EXTRA_OFF_CAROLE")) cls += " fc-day--extra-off-carole"; if (dayEvts.some((e) => ["CENTRE", "AVIS"].includes(e.type))) cls += " fc-day--has-guard"; let content = `
${d}`; if (dayEvts.some((e) => e.type === "PEP_SICK")) content += `🤒`; const dayLeaves = this.leaves.filter((l) => l.leave_date === iso); if (dayLeaves.length) { content += `
`; parents.forEach((parent, index) => { if ( dayLeaves.some( (l) => parseInt(l.person_id) === parseInt(parent.id), ) ) { content += `${parent.name.charAt(0).toUpperCase()} `; } }); content += `
`; } html += `
`; currentRenderedCols++; } while (currentRenderedCols < 5 && currentRenderedCols > 0) { html += ``; currentRenderedCols++; } return html + `
${d}
${content}
`; } initSummaryControls() { const typeSelect = document.getElementById("summType"), valueSelect = document.getElementById("summValue"); if (!typeSelect || !valueSelect) return; const years = new Set(), months = new Set(); this.weeks.forEach((w) => Object.values(w.dayDates).forEach((d) => { years.add(d.getFullYear()); months.add( `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`, ); }), ); const populateValues = () => { const currentType = typeSelect.value, prevValue = valueSelect.value; valueSelect.innerHTML = ""; if (currentType === "year") { Array.from(years) .sort() .forEach((y) => valueSelect.add(new Option(y, y))); valueSelect.value = years.has(parseInt(prevValue)) ? prevValue : new Date().getFullYear(); } else { Array.from(months) .sort() .forEach((m) => { const [y, mo] = m.split("-"); valueSelect.add( new Option( new Intl.DateTimeFormat("fr-FR", { month: "long", year: "numeric", }).format(new Date(y, mo - 1, 1)), m, ), ); }); const nowIso = `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, "0")}`; if (months.has(nowIso)) valueSelect.value = nowIso; } this.updateGlobalSummary(); }; if (!this.summaryListenersAttached) { typeSelect.addEventListener("change", populateValues); valueSelect.addEventListener("change", () => this.updateGlobalSummary(), ); this.summaryListenersAttached = true; } populateValues(); } updateGlobalSummary() { const div = document.getElementById("globalSummary"), typeSelect = document.getElementById("summType"), valueSelect = document.getElementById("summValue"); if (!div || !valueSelect?.value) return; const filterType = typeSelect.value, filterValue = valueSelect.value; const stats = { off: 0, extra: 0, sick: 0, pep: 0 }; this.weeks.forEach((w) => { Object.values(w.dayDates).forEach((dateObj) => { if ( (filterType === "year" && dateObj.getFullYear().toString() === filterValue) || (filterType === "month" && this.getLocalIsoDate(dateObj).slice(0, 7) === filterValue) ) { const dayEvents = this.events.filter( (e) => e.date === this.getLocalIsoDate(dateObj), ); dayEvents.forEach((e) => { const dur = parseFloat(e.duration) || 1; if (e.type === "OFF_CAROLE") stats.off += dur; if (e.type === "EXTRA_OFF_CAROLE") stats.extra += dur; if (e.type === "PEP_SICK") stats.sick += dur; }); if (!this.publicHolidayDates.has(this.getLocalIsoDate(dateObj))) { let dayAbsence = 0; dayEvents.forEach((e) => { if ( ["OFF_CAROLE", "EXTRA_OFF_CAROLE", "PEP_SICK"].includes( e.type, ) ) dayAbsence += parseFloat(e.duration) || 1; }); stats.pep += Math.max(0, 1 - dayAbsence); } } }); }); const tr = window.I18N || {}; div.innerHTML = `
${tr.leg_off_carole || "Off"}${parseFloat(stats.off.toFixed(1))} j
${tr.leg_extra_off || "Extra"}${parseFloat(stats.extra.toFixed(1))} j
${tr.leg_pep_sick || "Maladie"}${parseFloat(stats.sick.toFixed(1))} j
${tr.leg_presence || "Présence"}${parseFloat(stats.pep.toFixed(1))} j
`; } updateSchoolYearLabel() { const lbl = document.getElementById("fc-current-school-year-label"); if (lbl) lbl.textContent = `${this.currentSchoolYearStart} – ${this.currentSchoolYearStart + 1}`; } processWeeks(rawWeeks) { return rawWeeks.map((w) => ({ id: `${w.week_iso_year}-W${w.week_iso_number}`, monthKey: `${w.year}-${String(w.month).padStart(2, "0")}`, monthName: w.month_name, weekLabel: w.week_label, dayDates: { mon: new Date(w.mon_date + "T00:00:00"), tue: new Date(w.tue_date + "T00:00:00"), wed: new Date(w.wed_date + "T00:00:00"), thu: new Date(w.thu_date + "T00:00:00"), fri: new Date(w.fri_date + "T00:00:00"), }, dayFlags: { mon: { events: [] }, tue: { events: [] }, wed: { events: [] }, thu: { events: [] }, fri: { events: [] }, }, totals: { offCarole: 0, extraOffCarole: 0, centre: 0, avis: 0, pepSick: 0, presencePep: 0, alexCP: 0, alexJRA: 0, alexJA: 0, laiaCP: 0, laiaJRA: 0, laiaJA: 0, }, })); } async fetchApi(url) { return pachaFetch(url); } async postApi(url, data) { return pachaFetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); } async changeSchoolYear(delta) { this.currentSchoolYearStart += delta; this.updateSchoolYearLabel(); await this.refreshAllData(); } setupEventListeners() { // -- BRANCHEMENT MODALE DE CONFIGURATION (NOUVEAU) -- const btnSettings = document.getElementById("btnOpenCalendarSettings"); if (btnSettings) btnSettings.addEventListener("click", openCalendarSettings); if (this.planningBody) { this.planningBody.addEventListener("mousedown", (e) => this.handleMouseDown(e), ); document.addEventListener("mousemove", (e) => this.handleMouseMove(e)); document.addEventListener("mouseup", (e) => this.handleMouseUp(e)); this.planningBody.addEventListener( "touchstart", (e) => this.handleTouchStart(e), { passive: false }, ); document.addEventListener("touchmove", (e) => this.handleTouchMove(e), { passive: false, }); document.addEventListener("touchend", (e) => this.handleTouchEnd(e)); } const scrollWrapper = document.getElementById("planningTable-wrapper"); if (scrollWrapper) { scrollWrapper.addEventListener("scroll", async () => { if (this._isAutoLoading) return; if ( scrollWrapper.scrollTop + scrollWrapper.clientHeight >= scrollWrapper.scrollHeight - 5 ) { this._isAutoLoading = true; await this.changeSchoolYear(1); scrollWrapper.scrollTop = 5; setTimeout(() => (this._isAutoLoading = false), 500); } else if (scrollWrapper.scrollTop === 0) { this._isAutoLoading = true; await this.changeSchoolYear(-1); scrollWrapper.scrollTop = scrollWrapper.scrollHeight - scrollWrapper.clientHeight - 5; setTimeout(() => (this._isAutoLoading = false), 500); } }); } const btnOpen = document.getElementById("btnOpenHolidays"), btnClose = document.getElementById("btnCloseHolidays"), modal = document.getElementById("modalHolidays"); if (btnOpen && modal) btnOpen.addEventListener("click", () => { modal.classList.add("open"); document.body.classList.add("no-scroll"); this.renderModalHolidays(); }); if (btnClose && modal) btnClose.addEventListener("click", () => { modal.classList.remove("open"); document.body.classList.remove("no-scroll"); }); if (modal) modal.addEventListener("click", (e) => { if (e.target === modal) { modal.classList.remove("open"); document.body.classList.remove("no-scroll"); } }); const btnSnap = document.getElementById("btnOpenSnapshotModal"), modalSnap = document.getElementById("modalSnapshot"), btnCloseSnap = document.getElementById("btnCloseSnapshot"), formSnap = document.getElementById("formSnapshot"); if (btnSnap && modalSnap) btnSnap.addEventListener("click", () => { modalSnap.classList.add("open"); document.body.classList.add("no-scroll"); const snapDateInput = document.getElementById("snapDate"); if (snapDateInput) snapDateInput.value = `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, "0")}-01`; }); if (btnCloseSnap && modalSnap) btnCloseSnap.addEventListener("click", () => { modalSnap.classList.remove("open"); document.body.classList.remove("no-scroll"); }); if (modalSnap) modalSnap.addEventListener("click", (e) => { if (e.target === modalSnap) { modalSnap.classList.remove("open"); document.body.classList.remove("no-scroll"); } }); if (formSnap) formSnap.addEventListener("submit", async (e) => { e.preventDefault(); try { await this.postApi( "/modules/family-calendar/includes/api/save-leave-snapshot.php", { person_id: document.getElementById("snapPerson").value, leave_type: document.getElementById("snapType").value, snapshot_date: document.getElementById("snapDate").value, remaining_balance: document.getElementById("snapBalance").value, }, ); modalSnap.classList.remove("open"); document.body.classList.remove("no-scroll"); formSnap.reset(); await this.refreshAllData(); } catch (error) { alert("Erreur lors de la sauvegarde."); } }); if (this.monthCalendar) this.monthCalendar.addEventListener("click", (e) => { const td = e.target.closest("td[data-date]"); if (td && td.dataset.date) { this.monthSelectedCells = [td]; this.showMenu(e.pageX, e.pageY, [td.dataset.date], true); } }); document.addEventListener("click", (e) => this.closeMenusIfOutside(e)); const handleMenu = (e) => { const btn = e.target.closest("button"); if (btn && btn.dataset.action) this.handleMenuAction(btn.dataset); }; if (this.selectionMenu) this.selectionMenu.addEventListener("click", handleMenu); if (this.monthSelectionMenu) this.monthSelectionMenu.addEventListener("click", handleMenu); document .getElementById("fc-prev-month") ?.addEventListener("click", () => { this.currentMonth.setMonth(this.currentMonth.getMonth() - 1); this.renderMonthCalendar(); }); document .getElementById("fc-next-month") ?.addEventListener("click", () => { this.currentMonth.setMonth(this.currentMonth.getMonth() + 1); this.renderMonthCalendar(); }); document.getElementById("fc-today-btn")?.addEventListener("click", () => { this.currentMonth = new Date(); this.currentMonth.setDate(1); this.renderMonthCalendar(); }); if (this.monthCalendar) { let startX = 0, startY = 0; this.monthCalendar.addEventListener( "touchstart", (e) => { startX = e.changedTouches[0].screenX; startY = e.changedTouches[0].screenY; }, { passive: true }, ); this.monthCalendar.addEventListener( "touchend", (e) => { const dX = e.changedTouches[0].screenX - startX, dY = e.changedTouches[0].screenY - startY; if (Math.abs(dX) > Math.abs(dY) && Math.abs(dX) > 50) { const num = this.viewMode === "2months" ? 2 : this.viewMode === "3months" ? 3 : 1; this.currentMonth.setMonth( this.currentMonth.getMonth() + (dX < 0 ? num : -num), ); this.renderMonthCalendar(); } }, { passive: true }, ); } document .getElementById("fc-prev-school-year") ?.addEventListener("click", () => this.changeSchoolYear(-1)); document .getElementById("fc-next-school-year") ?.addEventListener("click", () => this.changeSchoolYear(1)); document.querySelectorAll(".fc-view-button").forEach((btn) => btn.addEventListener("click", (e) => { document .querySelectorAll(".fc-view-button") .forEach((b) => b.classList.remove("fc-view-button--active")); e.target.classList.add("fc-view-button--active"); this.viewMode = e.target.dataset.view; this.renderMonthCalendar(); }), ); } handleMouseDown(e) { const td = e.target.closest("#planningTable td[data-date]"); if (!td) return; e.preventDefault(); this.clearSelection(); this.isSelecting = true; this.selectCell(td); } handleMouseMove(e) { if (!this.isSelecting) return; const td = e.target.closest("#planningTable td[data-date]"); if (td && !this.selectedCells.includes(td)) this.selectCell(td); } handleMouseUp(e) { if (!this.isSelecting) return; const td = e.target.closest("#planningTable td[data-date]"); if (td && !this.selectedCells.includes(td)) this.selectCell(td); this.isSelecting = false; if (this.selectedCells.length) this.showMenu( e.pageX, e.pageY, this.selectedCells.map((c) => c.dataset.date), false, ); } handleTouchStart(e) { const td = e.target.closest("#planningTable td[data-date]"); if (!td) return; e.preventDefault(); this.clearSelection(); this.isSelecting = true; this.selectCell(td); } handleTouchMove(e) { if (!this.isSelecting) return; e.preventDefault(); const touch = e.touches[0], target = document.elementFromPoint(touch.clientX, touch.clientY); if (!target) return; const td = target.closest("#planningTable td[data-date]"); if (td && !this.selectedCells.includes(td)) this.selectCell(td); } handleTouchEnd(e) { if (!this.isSelecting) return; this.isSelecting = false; if (this.selectedCells.length) this.showMenu( 0, 0, this.selectedCells.map((c) => c.dataset.date), false, ); } selectCell(cell) { cell.classList.add("fc-day--selected"); this.selectedCells.push(cell); } clearSelection() { this.selectedCells.forEach((c) => c.classList.remove("fc-day--selected")); this.selectedCells = []; if (this.selectionMenu) this.selectionMenu.style.display = "none"; if (this.monthSelectionMenu) this.monthSelectionMenu.style.display = "none"; } closeMenusIfOutside(e) { if ( !this.selectionMenu?.contains(e.target) && !this.monthSelectionMenu?.contains(e.target) && !this.menuJustOpened ) this.clearSelection(); } showMenu(x, y, dates, isMonthView) { const menu = isMonthView ? this.monthSelectionMenu : this.selectionMenu; if (!menu) return; this._currentBulkInfo = { dates }; const activeEvents = new Set(); this.events.forEach((e) => { if (dates.includes(e.date)) activeEvents.add(e.type); }); const activeLeaves = {}; parents.forEach((p) => (activeLeaves[p.id] = new Set())); this.leaves.forEach((l) => { if (dates.includes(l.leave_date) && activeLeaves[l.person_id]) activeLeaves[l.person_id].add(l.leave_type); }); const getBtnClass = (type, pid = null) => (pid ? activeLeaves[pid]?.has(type) : activeEvents.has(type)) ? "fc-menu-btn--active" : ""; const getBtnIcon = (type, pid = null) => (pid ? activeLeaves[pid]?.has(type) : activeEvents.has(type)) ? "✓ " : ""; const tr = window.I18N || {}; const dateLabel = dates.length > 1 ? `${dates.length} Jours` : new Date(dates[0]).toLocaleDateString(window.I18N_LANG || "fr-FR"); const trashSvg = ``; const buildHeader = (title, action, cat) => `
${title}${action ? `` : ""}
`; let html = `
${dateLabel}
`; html += `
${buildHeader(tr.fc_menu_carole || "Carole", "clear-type", "CONGE")}
`; html += `
${buildHeader(tr.leg_centre || "Centre", "clear-type", "GARDE")}
`; html += `
${buildHeader("Pep", "clear-type", "PEP")}
`; html += `
${buildHeader(tr.fc_menu_kids_leaves || "Congés Parents", null, null)}
`; parents.forEach( (p) => (html += ``), ); html += ``; ["CP", "JRA", "JA"].forEach((t) => { html += ``; parents.forEach( (p) => (html += ``), ); html += ``; }); html += `
${p.name}
`; menu.innerHTML = html; let left = x + 10; if (left + 240 > window.innerWidth) left = x - 250; menu.style.left = `${left}px`; menu.style.top = `${y + 10}px`; menu.style.display = "block"; this.menuJustOpened = true; setTimeout(() => (this.menuJustOpened = false), 100); } async handleMenuAction(dataset) { if (this.selectionMenu) this.selectionMenu.style.display = "none"; if (this.monthSelectionMenu) this.monthSelectionMenu.style.display = "none"; const { action, type, pid, cat } = dataset, dates = this._currentBulkInfo.dates; try { if (action === "add") { let typesToClear = []; if (["OFF_CAROLE", "EXTRA_OFF_CAROLE"].includes(type)) typesToClear = CONGE_TYPES; if (["CENTRE", "AVIS"].includes(type)) typesToClear = GUARDE_TYPES; if (type === "PEP_SICK") typesToClear = PEP_TYPES; if (typesToClear.length) await this.postApi( "/modules/family-calendar/includes/api/manage-event.php", { action: "bulk_delete_day_types", dates, types: typesToClear }, ); await this.postApi( "/modules/family-calendar/includes/api/save-events.php", dates.map((d) => ({ date: d, type: type, duration: 1, person: "Carole", })), ); } else if (action === "clear-type") { let typesToClear = cat === "CONGE" ? CONGE_TYPES : cat === "GARDE" ? GUARDE_TYPES : cat === "PEP" ? PEP_TYPES : []; await this.postApi( "/modules/family-calendar/includes/api/manage-event.php", { action: "bulk_delete_day_types", dates, types: typesToClear }, ); } else if (action === "add-leave") { await this.postApi( "/modules/family-calendar/includes/api/manage-leaf.php", { action: "bulk_delete_day_person", dates, person_id: parseInt(pid), }, ); await this.postApi( "/modules/family-calendar/includes/api/save-leaves.php", dates.map((d) => ({ date: d, person_id: parseInt(pid), leave_type: type, duration: 1, })), ); } else if (action === "clear-leaves-person") { await this.postApi( "/modules/family-calendar/includes/api/manage-leaf.php", { action: "bulk_delete_day_person", dates, person_id: parseInt(pid), }, ); } await this.refreshAllData(); } catch (e) { alert("Erreur: " + e.message); } } } new FamilyCalendar(); });