/** * family-calendar.js (FIXED: Selection, View Buttons, Colors) */ document.addEventListener("DOMContentLoaded", () => { const CONGE_TYPES = ["OFF_CAROLE", "EXTRA_OFF_CAROLE"]; const GUARDE_TYPES = ["CENTRE", "AVIS"]; const PEP_TYPES = ["PEP_SICK"]; const MODIFIABLE_TYPES = [...CONGE_TYPES, ...GUARDE_TYPES, ...PEP_TYPES]; class FamilyCalendar { constructor() { this.planningBody = document.getElementById("planningBody"); this.selectionMenu = document.getElementById("selectionMenu"); // Menu dans le body pour position absolue correcte if ( this.selectionMenu && this.selectionMenu.parentElement !== document.body ) { document.body.appendChild(this.selectionMenu); } this.schoolHolidaysTableBody = document.querySelector( "#schoolHolidaysTable tbody", ); this.monthCalendar = document.getElementById("fc-month-calendar"); this.monthSelectionMenu = document.getElementById( "fc-month-selectionMenu", ); if ( this.monthSelectionMenu && this.monthSelectionMenu.parentElement !== document.body ) { document.body.appendChild(this.monthSelectionMenu); } // Etat this.currentMonth = new Date(); this.currentMonth.setDate(1); this.viewMode = "1month"; this.currentSchoolYearStart = null; this.isSelecting = false; this.selectedCells = []; this.monthSelectedCells = []; this.isMonthSelecting = false; this._currentBulkInfo = null; // Données 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(); } async init() { this.setupEventListeners(); const now = new Date(); this.currentSchoolYearStart = now.getMonth() >= 8 ? now.getFullYear() : now.getFullYear() - 1; await this.refreshAllData(); this.updateSchoolYearLabel(); } async refreshAllData() { try { const weeksData = await this.fetchApi( `/modules/family-calendar/includes/api/get-calendar-weeks-scolaire.php?school_year_start=${this.currentSchoolYearStart}`, ); this.weeks = this.processWeeks(weeksData.weeks || []); const eventsData = await this.fetchApi( "/modules/family-calendar/includes/api/get-events.php", ); this.dbEvents = (eventsData.events || []).map((e) => ({ ...e, duration: parseFloat(e.duration), })); this.fixedEvents = await this.fetchPublicAndSchoolHolidays(); const leavesData = await this.fetchApi( "/modules/family-calendar/includes/api/get-leaves.php", ); this.leaves = leavesData.leaves || []; const balancesData = await this.fetchApi( "/modules/family-calendar/includes/api/get-leave-balances.php", ); this.leaveBalances = balancesData.balances || []; this.events = [...this.dbEvents, ...this.fixedEvents]; this.publicHolidayDates = new Set( this.fixedEvents .filter((e) => e.type === "PUBLIC_HOLIDAY") .map((e) => e.date), ); this.reprocessAndRender(); } catch (e) { console.error("Erreur chargement", e); } } // Appel API pour les vacances scolaires (Zone C) avec dédoublonnage async fetchPublicAndSchoolHolidays() { // 1. Jours fériés 2025-2026 (Statique) const publicHolidays = [ "2025-11-01", "2025-11-11", "2025-12-25", "2026-01-01", "2026-04-06", "2026-05-01", "2026-05-08", "2026-05-14", "2026-05-25", "2026-07-14", "2026-08-15", ].map((date, idx) => ({ id: `ph-${idx}`, date, type: "PUBLIC_HOLIDAY", duration: 1, })); // 2. Vacances Scolaires (API Gouv) const schoolHolidayEvents = []; try { const url = "https://data.education.gouv.fr/api/explore/v2.1/catalog/datasets/fr-en-calendrier-scolaire/records?where=annee_scolaire='2025-2026' AND zones LIKE '%Zone C%'&limit=100"; const res = await fetch(url); const data = await res.json(); const rawRecords = data.results || []; // --- DÉDOUBLONNAGE --- // On utilise une Map pour ne garder qu'une entrée unique par (Description + Date Début) const uniqueHolidaysMap = new Map(); rawRecords.forEach((r) => { // Clé unique : "Vacances de Noël|2025-12-20" const key = `${r.description}|${r.start_date}`; if (!uniqueHolidaysMap.has(key)) { uniqueHolidaysMap.set(key, r); } }); // Convertir la Map en tableau const uniqueRecords = Array.from(uniqueHolidaysMap.values()); // Remplir le tableau HTML du haut (avec la liste propre) this.renderSchoolHolidaysTable(uniqueRecords); // Générer les événements jour par jour (uniquement sur la liste propre) uniqueRecords.forEach((r, idx) => { let curr = new Date(r.start_date); const end = new Date(r.end_date); // Boucle jour par jour while (curr < end) { const iso = curr.toISOString().split("T")[0]; schoolHolidayEvents.push({ id: `sh-${iso}-${idx}`, date: iso, type: "VACANCES_SCOLAIRES", duration: 1, }); curr.setDate(curr.getDate() + 1); } }); } catch (e) { console.warn( "Impossible de charger les vacances scolaires depuis l'API Gouv.", e, ); } return [...publicHolidays, ...schoolHolidayEvents]; } 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, }, })); } reprocessAndRender() { this.reprocessEvents(); this.calculateMonthlyBalances(); this.updateGlobalSummary(); 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; const prefix = l.person_id === 2 ? "alex" : l.person_id === 3 ? "laia" : null; if (prefix) w.totals[`${prefix}${l.leave_type}`] += dur; } }); let workingDays = 0; Object.values(w.dayDates).forEach((d) => { if (!this.publicHolidayDates.has(d.toISOString().split("T")[0])) workingDays++; }); w.totals.presencePep = Math.max( 0, workingDays - (w.totals.offCarole + w.totals.extraOffCarole + w.totals.pepSick), ); }); } calculateMonthlyBalances() { const balances = { 2: { CP: {}, JRA: {}, JA: {} }, 3: { 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; const type = l.leave_type; const 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); }); [2, 3].forEach((pid) => { ["CP", "JRA", "JA"].forEach((type) => { ymList.forEach((ym) => { const [currYear, currMonth] = ym.split("-").map(Number); let refYear = currYear; if (type === "CP") refYear = currMonth >= 6 ? currYear : currYear - 1; const initialObj = this.leaveBalances.find( (b) => b.person_id == pid && b.leave_type == type && b.balance_year == refYear, ); const initial = initialObj ? parseFloat(initialObj.initial_balance) : type === "CP" ? 25 : type === "JRA" ? 10 : 0; let usedBefore = 0; let periodStartMonth = type === "CP" ? "06" : "01"; const periodStart = `${refYear}-${periodStartMonth}`; Object.keys(usageByMonth[pid]?.[type] || {}).forEach((usedYm) => { if (usedYm >= periodStart && usedYm < ym) { usedBefore += usageByMonth[pid][type][usedYm]; } }); const available = Math.max(0, initial - usedBefore); const usedInMonth = usageByMonth[pid]?.[type]?.[ym] || 0; balances[pid][type][ym] = { availableAtMonthStart: available, usedInMonth: usedInMonth, }; }); }); }); this.monthlyLeaveBalances = balances; } renderTable() { 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"); 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"; td.textContent = w.monthName; td.rowSpan = monthSpans[w.monthKey]; tr.appendChild(td); } const tdW = document.createElement("td"); tdW.className = "col-month"; 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 = dateObj.toISOString().split("T")[0]; td.dataset.date = iso; td.textContent = String(dateObj.getDate()).padStart(2, "0"); td.className = "col-day"; const dayEvents = w.dayFlags[d].events; dayEvents.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 = `
`; // --- CORRECTION COULEURS --- if (dayLeaves.some((l) => l.person_id === 2)) html += `A`; // Teal if (dayLeaves.some((l) => l.person_id === 3)) html += `L`; // Amber html += `
`; td.innerHTML += html; } tr.appendChild(td); }); [ "offCarole", "extraOffCarole", "centre", "avis", "pepSick", "presencePep", ].forEach((k) => { const td = document.createElement("td"); td.className = "col-total"; td.textContent = fmt(w.totals[k]); tr.appendChild(td); }); if (!processedLeavesCols[w.monthKey]) { processedLeavesCols[w.monthKey] = true; const span = monthSpans[w.monthKey]; const ym = w.monthKey; const renderPersonCols = (pid, prefix) => { ["CP", "JRA", "JA"].forEach((type) => { const info = this.monthlyLeaveBalances[pid][type][ym]; const tdAv = document.createElement("td"); tdAv.className = `${prefix}-sub ${prefix}-av`; tdAv.rowSpan = span; tdAv.textContent = info ? fmt(info.availableAtMonthStart) : "-"; tr.appendChild(tdAv); const tdUse = document.createElement("td"); tdUse.className = `${prefix}-sub ${prefix}-use`; tdUse.rowSpan = span; tdUse.textContent = info ? fmt(info.usedInMonth) : ""; tr.appendChild(tdUse); }); }; renderPersonCols(2, "col-alex"); renderPersonCols(3, "col-laia"); } this.planningBody.appendChild(tr); }); } renderMonthCalendar() { if (!this.monthCalendar) return; this.monthCalendar.innerHTML = ""; // CLEAN UP const y = this.currentMonth.getFullYear(); const m = this.currentMonth.getMonth(); const titleEl = document.querySelector("#fc-current-month-year"); if (titleEl) { if (this.viewMode === "year") { titleEl.textContent = `Année scolaire ${this.currentSchoolYearStart} – ${this.currentSchoolYearStart + 1}`; } else if (this.viewMode === "2months") { const nextM = new Date(y, m + 1, 1); const m1 = new Intl.DateTimeFormat("fr-FR", { month: "long" }).format( this.currentMonth, ); const m2 = new Intl.DateTimeFormat("fr-FR", { month: "long", year: "numeric", }).format(nextM); titleEl.textContent = `${m1} - ${m2}`; } else { titleEl.textContent = new Intl.DateTimeFormat("fr-FR", { month: "long", year: "numeric", }).format(this.currentMonth); } } if (this.viewMode === "year") { this.renderYearView(); return; } if (this.viewMode === "2months") { this.renderTwoMonthsView(); return; } this.monthCalendar.innerHTML = this.generateMonthHTML(y, m); } renderTwoMonthsView() { const y = this.currentMonth.getFullYear(); const m = this.currentMonth.getMonth(); const nextDate = new Date(y, m + 1, 1); let html = `
`; html += `
${new Intl.DateTimeFormat("fr-FR", { month: "long" }).format(this.currentMonth)}
${this.generateMonthHTML(y, m)}
`; html += `
${new Intl.DateTimeFormat("fr-FR", { month: "long" }).format(nextDate)}
${this.generateMonthHTML(nextDate.getFullYear(), nextDate.getMonth())}
`; html += `
`; this.monthCalendar.innerHTML = html; } renderYearView() { const startYear = this.currentSchoolYearStart; let html = '
'; // Sept-Dec for (let i = 8; i < 12; i++) html += `
${new Intl.DateTimeFormat("fr-FR", { month: "long" }).format(new Date(startYear, i))}
${this.generateMonthHTML(startYear, i)}
`; // Jan-Aug for (let i = 0; i < 8; i++) html += `
${new Intl.DateTimeFormat("fr-FR", { month: "long" }).format(new Date(startYear + 1, i))}
${this.generateMonthHTML(startYear + 1, i)}
`; html += "
"; this.monthCalendar.innerHTML = html; } generateMonthHTML(year, month) { let html = ``; ["L", "M", "M", "J", "V"].forEach((d) => (html += ``)); html += ``; const firstDay = new Date(year, month, 1); const startDay = (firstDay.getDay() + 6) % 7; const daysInMonth = new Date(year, month + 1, 0).getDate(); let dayCounter = 1; for (let row = 0; row < 6; row++) { html += ``; for (let col = 0; col < 5; col++) { if ( (row === 0 && col < startDay && startDay < 5) || dayCounter > daysInMonth ) { html += ``; } else if (row === 0 && startDay >= 5) { html += ``; } else { const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(dayCounter).padStart(2, "0")}`; let cls = "fc-month-day"; 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"; if (dayEvts.some((e) => e.type === "CENTRE")) cls += " fc-day--centre"; if (dayEvts.some((e) => e.type === "AVIS")) cls += " fc-day--avis"; } let content = `
${dayCounter}`; if (dayEvts.some((e) => e.type === "PEP_SICK")) content += `🤒`; const dayLeaves = this.leaves.filter((l) => l.leave_date === iso); if (dayLeaves.length) { content += `
`; if (dayLeaves.some((l) => l.person_id === 2)) content += `A `; if (dayLeaves.some((l) => l.person_id === 3)) content += `L`; content += `
`; } content += `
`; html += ``; dayCounter++; } } html += ``; if (dayCounter > daysInMonth) break; } html += `
${d}
${content}
`; return html; } renderSchoolHolidaysTable(records) { if (!this.schoolHolidaysTableBody) return; records.sort((a, b) => new Date(a.start_date) - new Date(b.start_date)); this.schoolHolidaysTableBody.innerHTML = records .map( (r) => ` ${r.description} ${new Date(r.start_date).toLocaleDateString("fr-FR")} ${new Date(r.end_date).toLocaleDateString("fr-FR")} ${r.zones} `, ) .join(""); } updateGlobalSummary() { const div = document.getElementById("globalSummary"); if (!div) return; const stats = { off: 0, extra: 0, sick: 0, pep: 0, totalWorking: 0 }; this.weeks.forEach((w) => { stats.off += w.totals.offCarole; stats.extra += w.totals.extraOffCarole; stats.sick += w.totals.pepSick; stats.pep += w.totals.presencePep; Object.values(w.dayDates).forEach((d) => { if (!this.publicHolidayDates.has(d.toISOString().split("T")[0])) stats.totalWorking++; }); }); div.innerHTML = `

Off Carole : ${stats.off} jours

Extra Off Carole : ${stats.extra} jours

Pep malade : ${stats.sick} jours

Présence Pep : ${stats.pep} jours

(Sur jours ouvrés totaux : ${stats.totalWorking})

`; } updateSchoolYearLabel() { const lbl = document.getElementById("fc-current-school-year-label"); if (lbl) lbl.textContent = `${this.currentSchoolYearStart} – ${this.currentSchoolYearStart + 1}`; } // ================== INTERACTIONS ================== setupEventListeners() { // Planning Hebdo this.planningBody.addEventListener("mousedown", (e) => this.handleMouseDown(e), ); document.addEventListener("mousemove", (e) => this.handleMouseMove(e)); document.addEventListener("mouseup", (e) => this.handleMouseUp(e)); // Calendrier Mensuel (Clic simple pour ouvrir le menu) if (this.monthCalendar) { this.monthCalendar.addEventListener("click", (e) => { const td = e.target.closest("td[data-date]"); if (td) { this.monthSelectedCells = [td]; // Simuler sélection unique this.showMenu(e.pageX, e.pageY, [td.dataset.date], true); } }); } // Menus Contextuels document.addEventListener("click", (e) => this.closeMenusIfOutside(e)); const handleMenu = (e) => { const btn = e.target.closest("button"); if (btn) this.handleMenuAction(btn.dataset); }; if (this.selectionMenu) this.selectionMenu.addEventListener("click", handleMenu); if (this.monthSelectionMenu) this.monthSelectionMenu.addEventListener("click", handleMenu); // Boutons Navigation Mensuelle 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(); }); // Boutons Navigation Année Scolaire document .getElementById("fc-prev-school-year") ?.addEventListener("click", () => this.changeSchoolYear(-1)); document .getElementById("fc-next-school-year") ?.addEventListener("click", () => this.changeSchoolYear(1)); // Boutons de Vue (1 mois / 2 mois / Année) 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; // FIX CRITIQUE : Si on relâche la souris sur une case qu'on n'a pas eu le temps de "survoler" (mouvement rapide) // on l'ajoute maintenant pour être sûr qu'elle est dans la sélection. 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) return; const dates = this.selectedCells.map((c) => c.dataset.date); this.showMenu(e.pageX, e.pageY, dates, 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 = []; 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 dateLabel = dates.length > 1 ? `${dates.length} jours` : new Date(dates[0]).toLocaleDateString("fr-FR"); let html = `
${dateLabel}
`; // Carole html += `
Congés Carole
`; html += ``; html += ``; html += `
`; // Garde html += `
Garde
`; html += ``; html += ``; html += `
`; // Pep html += `
Pep`; html += ``; html += `
`; // Alex/Laia html += `
Congés Alex / Laia
`; ["CP", "JRA", "JA"].forEach((t) => { html += ``; html += ``; }); html += `
AlexLaia
`; html += `
`; menu.innerHTML = html; const menuWidth = 240; let left = x + 10; if (left + menuWidth > window.innerWidth) left = x - menuWidth - 10; 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) { this.selectionMenu.style.display = "none"; if (this.monthSelectionMenu) this.monthSelectionMenu.style.display = "none"; const { action, type, pid, cat } = dataset; const 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, }, ); } const payload = dates.map((d) => ({ date: d, type: type, duration: 1, person: "Carole", })); await this.postApi( "/modules/family-calendar/includes/api/save-events.php", payload, ); } else if (action === "clear-type") { let typesToClear = []; if (cat === "CONGE") typesToClear = CONGE_TYPES; if (cat === "GARDE") typesToClear = GUARDE_TYPES; if (cat === "PEP") typesToClear = 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: pid, }, ); const payload = dates.map((d) => ({ date: d, person_id: pid, leave_type: type, duration: 1, })); await this.postApi( "/modules/family-calendar/includes/api/save-leaves.php", payload, ); } else if (action === "clear-leaves") { await this.postApi( "/modules/family-calendar/includes/api/manage-leaf.php", { action: "bulk_delete_day_person", dates, person_id: 2 }, ); await this.postApi( "/modules/family-calendar/includes/api/manage-leaf.php", { action: "bulk_delete_day_person", dates, person_id: 3 }, ); } await this.refreshAllData(); } catch (e) { alert("Erreur action: " + e.message); } } async fetchApi(url) { return fetch(url).then((r) => r.json()); } async postApi(url, data) { const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); if (!res.ok) throw new Error(await res.text()); return res.json(); } async changeSchoolYear(delta) { this.currentSchoolYearStart += delta; this.updateSchoolYearLabel(); await this.refreshAllData(); } } new FamilyCalendar(); });