/**
* family-calendar.js (Version Multi-tenant 100% Purifiée)
* 100% Dynamique - Zero nom hardcodé.
*/
// ============================================================================
// 1. VARIABLES GLOBALES & LOGIQUE DE CONFIGURATION (SETTINGS)
// ============================================================================
let calGlobalData = null;
let currentSelectedMemberId = null;
let localCareModes = [];
async function openCalendarSettings() {
try {
const res = await pachaFetch(
"/modules/family-calendar/includes/api/calendar-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) => {
let roleDisplay = tr("fc_role_unknown") || "Inconnu";
if (p.role === "parent")
roleDisplay = "👨👩👦 " + (tr("fc_role_adult") || "Adulte");
else if (p.role === "child" || p.role === "enfant")
roleDisplay = "👶 " + (tr("fc_role_child") || "Enfant");
else if (p.role === "helper")
roleDisplay = "💼 " + (tr("fc_role_helper") || "Intervenant");
return `${p.name} (${roleDisplay}) `;
})
.join("");
}
if (typeof populateCalendarSettings === "function") {
populateCalendarSettings(calGlobalData.calendar_settings);
}
switchCalendarTab("foyer");
const modalSettings = document.getElementById("modalCalendarSettings");
if (modalSettings) {
modalSettings.classList.add("open");
document.body.classList.add("no-scroll");
}
} catch (err) {
alert((tr("fc_error") || "Erreur : ") + err.message);
}
}
function closeCalendarSettings() {
document.getElementById("modalCalendarSettings").classList.remove("open");
document.body.classList.remove("no-scroll");
}
function switchCalendarTab(tabId) {
// 1. On réinitialise l'affichage
document
.querySelectorAll(".bs-tab-btn")
.forEach((btn) => btn.classList.remove("active"));
document
.querySelectorAll(".cal-settings-pane")
.forEach((pane) => (pane.style.display = "none"));
// 2. On active l'onglet cliqué
document.getElementById(`tab-btn-${tabId}`).classList.add("active");
document.getElementById(`cal-pane-${tabId}`).style.display = "block";
// 3. Actions spécifiques
if (tabId === "foyer") {
loadLeaveCatalog();
} else if (tabId === "membres") {
// On charge la vue du membre sélectionné par défaut dans la liste
if (document.getElementById("selectCalMember").value) {
loadMemberConfigView();
}
}
}
function renderCareModeTags() {
const container = document.getElementById("careModesContainer");
if (!container) return; // 🛑 LA BARRIÈRE DE SÉCURITÉ EST ICI
container.innerHTML = localCareModes
.map(
(mode, i) => `
${mode} ×
`,
)
.join("");
}
// Ajoutons la même sécurité pour l'ajout au cas où :
function addCareModeTag() {
const input = document.getElementById("inputNewCareMode");
if (!input) return; // 🛑 SÉCURITÉ
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/calendar-settings.php",
{ method: "POST", body: formData },
);
if (!res.success) throw new Error(res.error);
if (window.showToast)
showToast(
tr("fc_settings_updated") || "Configuration enregistrée !",
"success",
);
else alert(tr("fc_settings_updated") || "Configuration enregistrée !");
setTimeout(() => window.location.reload(), 800);
} catch (err) {
alert("Erreur : " + err.message);
}
}
window.submitChildCareModes = async function () {
if (!window.currentSelectedMemberId) return;
const checkboxes = document.querySelectorAll(".js-care-mode-cb:checked");
const selectedModes = Array.from(checkboxes).map((cb) => cb.value);
try {
const formData = new FormData();
formData.append("action", "save_child_care_modes");
formData.append("person_id", window.currentSelectedMemberId);
formData.append("care_modes", JSON.stringify(selectedModes));
const res = await pachaFetch(
"/modules/family-calendar/includes/api/calendar-settings.php",
{ method: "POST", body: formData },
);
if (!res.success) throw new Error(res.error);
if (window.showToast)
showToast(tr("fc_child_saved") || "Sauvegardé !", "success");
else alert(tr("fc_child_saved") || "Sauvegardé !");
const currentPerson = calGlobalData.people.find(
(k) => parseInt(k.id) === window.currentSelectedMemberId,
);
if (currentPerson) {
currentPerson.care_modes = JSON.stringify(selectedModes);
}
} catch (err) {
alert("Erreur: " + err.message);
}
};
// ==========================================
// NOUVEAU CATALOGUE GLOBAL (FOYER)
// ==========================================
let globalLeaveCatalog = [];
async function loadLeaveCatalog() {
try {
const res = await pachaFetch(
"/modules/family-calendar/includes/api/calendar-settings.php?action=get_leave_types",
);
globalLeaveCatalog = res.data || [];
renderLeaveCatalog(globalLeaveCatalog);
} catch (err) {
console.error("Erreur chargement catalogue", err);
}
}
function renderLeaveCatalog(types) {
const container = document.getElementById("leaveTypesContainer");
if (!container) return;
container.innerHTML = "";
if (types.length === 0) {
container.innerHTML = `
Aucun congé configuré.
`;
return;
}
types.forEach((lt) => {
container.innerHTML += `
${lt.label}
${lt.code}
✏️
🗑️
`;
});
}
function editLeaveType(code, label) {
document.getElementById("leaveTypeFormTitle").innerText =
"✏️ Modifier : " + label;
document.getElementById("lt-mode").value = "edit";
const codeInput = document.getElementById("lt-code");
codeInput.value = code;
codeInput.readOnly = true;
codeInput.style.backgroundColor = "var(--bg-subtle)";
codeInput.style.opacity = "0.6";
codeInput.style.cursor = "not-allowed";
document.getElementById("lt-code-note").innerText = "Non modifiable";
document.getElementById("lt-label").value = label;
}
function resetLeaveTypeForm() {
document.getElementById("leaveTypeFormTitle").innerText = "+ Ajouter";
document.getElementById("lt-mode").value = "add";
const codeInput = document.getElementById("lt-code");
codeInput.value = "";
codeInput.readOnly = false;
codeInput.style.backgroundColor = "";
codeInput.style.opacity = "1";
codeInput.style.cursor = "text";
document.getElementById("lt-code-note").innerText = "Irréversible";
document.getElementById("lt-label").value = "";
}
// NOUVELLE FONCTION DE SUPPRESSION
async function deleteLeaveType(code, label) {
if (
!confirm(
`Supprimer définitivement le congé "${label}" du catalogue ? Cela le retirera également des membres qui l'utilisent.`,
)
)
return;
const fd = new FormData();
fd.append("action", "delete_leave_type");
fd.append("code", code);
try {
const res = await pachaFetch(
"/modules/family-calendar/includes/api/calendar-settings.php",
{ method: "POST", body: fd },
);
if (!res.success) throw new Error(res.error);
if (window.showToast) showToast("Congé supprimé avec succès !", "success");
resetLeaveTypeForm();
loadLeaveCatalog();
// On recharge l'onglet membre au cas où le membre affiché utilisait ce congé
if (document.getElementById("selectCalMember").value) {
loadMemberConfigView();
}
} catch (err) {
alert("Erreur de suppression : " + err.message);
}
}
async function saveLeaveType() {
const code = document.getElementById("lt-code").value.toUpperCase();
const label = document.getElementById("lt-label").value;
if (!code || !label)
return window.showToast
? showToast("Code et Label requis", "error")
: alert("Code et Label requis");
const fd = new FormData();
fd.append("action", "save_leave_type");
fd.append("mode", document.getElementById("lt-mode").value);
fd.append("code", code);
fd.append("label", label);
try {
const res = await pachaFetch(
"/modules/family-calendar/includes/api/calendar-settings.php",
{ method: "POST", body: fd },
);
if (!res.success) throw new Error(res.error);
if (window.showToast) showToast("Catalogue mis à jour !", "success");
resetLeaveTypeForm();
loadLeaveCatalog();
} catch (err) {
alert("Erreur d'enregistrement : " + err.message);
}
}
// ==========================================
// MODALE SETTINGS : AFFECTATION (MEMBRES)
// ==========================================
// Quand on choisit un membre dans la liste déroulante
async function loadMemberConfigView() {
const select = document.getElementById("selectCalMember");
const zone = document.getElementById("memberConfigZone");
if (!select || !select.value || !zone) return;
const personId = parseInt(select.value);
window.currentSelectedMemberId = personId;
const role = (
select.options[select.selectedIndex].dataset.role || ""
).toLowerCase();
zone.innerHTML = 'Chargement...
';
// === CAS 1 : C'EST UN ENFANT (On gère les modes de garde) ===
if (role === "child" || role === "enfant") {
const currentPerson = calGlobalData.people.find(
(k) => parseInt(k.id) === personId,
);
let savedModes = [];
try {
if (currentPerson.modes && Array.isArray(currentPerson.modes))
savedModes = currentPerson.modes;
else if (typeof currentPerson.care_modes === "string")
savedModes = JSON.parse(currentPerson.care_modes);
} catch (e) {}
const activeModesInFoyer = calGlobalData.foyer.care_modes || [];
const modesHtml = activeModesInFoyer
.map(
(m) => `
${m}
`,
)
.join("");
zone.innerHTML = `
${tr("fc_care_modes_title") || "Modes de Garde"}
Quels modes de garde s'appliquent à cet enfant ?
${modesHtml || `Aucun mode de garde configuré dans le foyer. `}
${tr("btn_save_rights") || "Enregistrer"}
`;
}
// === CAS 2 : C'EST UN ADULTE (On gère les congés) ===
else {
try {
const res = await pachaFetch(
`/modules/family-calendar/includes/api/calendar-settings.php?action=get_person_leaves&person_id=${personId}`,
);
const memberLeaves = res.data || [];
renderMemberLeavesView(personId, memberLeaves);
} catch (err) {
zone.innerHTML =
'Erreur de chargement.
';
}
}
}
// Affichage des congés du membre et du formulaire d'ajout
function renderMemberLeavesView(personId, memberLeaves) {
const zone = document.getElementById("memberConfigZone");
let html = `Congés attribués `;
if (memberLeaves.length === 0) {
html += `Aucun congé attribué à ce membre.
`;
} else {
html += ``;
memberLeaves.forEach((ml) => {
// On extrait le mois de la date anniversaire (ex: "2000-06-01" -> 6)
const moisRenouv = ml.anniversary_date
? parseInt(ml.anniversary_date.split("-")[1])
: 1;
html += `
${ml.leave_type}
(Quota: ${ml.allowance}j - Renouv: Mois ${moisRenouv} )
🗑️
`;
});
html += `
`;
}
// Ajout du selecteur de mois dans l'attribution
html += `
+ Attribuer un congé
Type de congé
-- Sélectionner --
${globalLeaveCatalog.map((lt) => `${lt.label} `).join("")}
Quota
Renouvellement
${tr("month_jan") || "Janvier"}
${tr("month_feb") || "Février"}
${tr("month_mar") || "Mars"}
${tr("month_apr") || "Avril"}
${tr("month_may") || "Mai"}
${tr("month_jun") || "Juin"}
${tr("month_jul") || "Juillet"}
${tr("month_aug") || "Août"}
${tr("month_sep") || "Septembre"}
${tr("month_oct") || "Octobre"}
${tr("month_nov") || "Novembre"}
${tr("month_dec") || "Décembre"}
Acquisition
Fixe
Graduel
Ajouter
`;
zone.innerHTML = html;
}
async function addMemberLeave(personId) {
const leaveCode = document.getElementById("new-member-leave-type").value;
const allowance = document.getElementById("new-member-leave-allowance").value;
const resetMonth = document.getElementById("new-member-leave-reset").value;
const method = document.getElementById("new-member-leave-method").value; // <-- Ajout
fd.append("method", method);
if (!leaveCode) return showToast("Veuillez sélectionner un type", "error");
const fd = new FormData();
fd.append("action", "add_person_leave");
fd.append("person_id", personId);
fd.append("leave_type", leaveCode);
fd.append("allowance", allowance);
fd.append("reset_month", resetMonth);
try {
const res = await pachaFetch(
"/modules/family-calendar/includes/api/calendar-settings.php",
{ method: "POST", body: fd },
);
if (!res.success) throw new Error(res.error);
if (window.showToast) showToast("Congé attribué avec succès !", "success");
loadMemberConfigView();
} catch (err) {
alert("Erreur: " + err.message);
}
}
// Supprimer un congé d'un membre
async function deleteMemberLeave(leaveId, personId) {
if (!confirm(tr("confirm_delete") || "Retirer ce congé pour ce membre ?"))
return;
const fd = new FormData();
fd.append("action", "delete_person_leave");
fd.append("id", leaveId);
try {
const res = await pachaFetch(
"/modules/family-calendar/includes/api/calendar-settings.php",
{ method: "POST", body: fd },
);
if (!res.success) throw new Error(res.error);
if (window.showToast) showToast("Congé retiré !", "success");
loadMemberConfigView();
} catch (err) {
alert("Erreur: " + err.message);
}
}
// ============================================================================
// 2. MOTEUR PRINCIPAL DU CALENDRIER (DOM CONTENT LOADED)
// ============================================================================
document.addEventListener("DOMContentLoaded", () => {
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.parents = [];
this.kids = [];
this.helpers = [];
this.careModes = [];
this.leaveMatrix = {};
this.monthlyLeaveBalances = {};
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();
try {
const res = await pachaFetch(
"/modules/family-calendar/includes/api/calendar-settings.php?action=get_all",
);
if (res && res.success && res.data) {
window.calGlobalData = res.data;
this.parents = (res.data.people || []).filter(
(p) => p.role === "parent",
);
this.kids = (res.data.people || []).filter((p) =>
["child", "enfant"].includes(p.role),
);
this.helpers = (res.data.people || []).filter(
(p) => p.role === "helper",
);
this.careModes = res.data.foyer.care_modes || [];
this.leaveMatrix = res.data.leaves || {};
const settings = res.data.calendar_settings || {};
if (settings.calendar_default_view) {
this.viewMode = settings.calendar_default_view;
document
.querySelectorAll(".fc-view-button")
.forEach((b) => b.classList.remove("fc-view-button--active"));
const activeBtn = document.querySelector(
`.fc-view-button[data-view="${this.viewMode}"]`,
);
if (activeBtn) activeBtn.classList.add("fc-view-button--active");
}
}
} catch (e) {
console.warn("Erreur chargement de l'annuaire familial:", e);
}
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 += `${y} - ${y + 1} `;
}
const zoneText =
window.calGlobalData?.foyer?.zone_scolaire &&
window.calGlobalData.foyer.zone_scolaire !== "Autre"
? `(Zone ${window.calGlobalData.foyer.zone_scolaire})`
: "";
headerTitle.innerHTML = `🏖️ ${tr("fc_modal_holidays_title") || "Vacances"} ${zoneText}
${options}
`;
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,
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-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.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() {}
async fetchAndSaveGovHolidays(yearStart) {}
reprocessAndRender() {
this.reprocessEvents();
this.calculateMonthlyBalances();
this.renderTable();
this.renderMonthCalendar();
}
reprocessEvents() {
const upperCareModes = this.careModes.map((m) => m.toUpperCase());
this.weeks.forEach((w) => {
w.totals = {};
this.careModes.forEach((m) => (w.totals["mode_" + m] = 0));
this.kids.forEach((k) => (w.totals["sick_" + k.id] = 0));
this.helpers.forEach((h) => {
w.totals["off_" + h.id] = 0;
w.totals["extra_" + h.id] = 0;
});
this.parents.forEach((p) => {
const types = this.leaveMatrix[p.id] || [];
types.forEach((t) => (w.totals[`leave_${p.id}_${t.type}`] = 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;
if (e.type === "CHILD_SICK")
w.totals["sick_" + e.person_id] =
(w.totals["sick_" + e.person_id] || 0) + dur;
if (e.type === "HELPER_OFF")
w.totals["off_" + e.person_id] =
(w.totals["off_" + e.person_id] || 0) + dur;
if (e.type === "HELPER_EXTRA")
w.totals["extra_" + e.person_id] =
(w.totals["extra_" + e.person_id] || 0) + dur;
// Modes de garde dynamiques
if (upperCareModes.includes(e.type)) {
const originalMode =
this.careModes.find((m) => m.toUpperCase() === e.type) ||
e.type;
w.totals["mode_" + originalMode] =
(w.totals["mode_" + originalMode] || 0) + 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;
w.totals[`leave_${l.person_id}_${l.leave_type}`] =
(w.totals[`leave_${l.person_id}_${l.leave_type}`] || 0) + dur;
}
});
let workingDays = 0;
Object.values(w.dayDates).forEach((d) => {
if (!this.publicHolidayDates.has(this.getLocalIsoDate(d)))
workingDays++;
});
let helperAbsences = 0;
this.helpers.forEach(
(h) =>
(helperAbsences +=
(w.totals["off_" + h.id] || 0) +
(w.totals["extra_" + h.id] || 0)),
);
if (this.kids.length > 0) {
w.totals.presenceKid = Math.max(
0,
workingDays -
(helperAbsences + (w.totals["sick_" + this.kids[0].id] || 0)),
);
}
});
}
calculateMonthlyBalances() {
const balances = {};
this.parents.forEach((p) => (balances[p.id] = {}));
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);
});
this.parents.forEach((parent) => {
const pid = parent.id;
const matrix = this.leaveMatrix[pid] || [];
matrix.forEach((conf) => {
const type = conf.type;
balances[pid][type] = {};
ymList.forEach((ym) => {
const [currYear, currMonth] = ym.split("-").map(Number);
let cycleStartStr = "",
initialBalance = parseFloat(conf.allowance || 0);
if (conf.date) {
const parts = conf.date.split("-");
if (parts.length >= 2) {
const monthRenouvellement = parseInt(parts[1]);
const dayRenouvellement =
parts.length === 3 ? parseInt(parts[2]) : 1;
const isPastAnniversary =
currMonth > monthRenouvellement ||
(currMonth === monthRenouvellement && 1 >= dayRenouvellement);
const refYear = isPastAnniversary ? currYear : currYear - 1;
cycleStartStr = `${refYear}-${String(monthRenouvellement).padStart(2, "0")}`;
}
} else {
cycleStartStr = `${currYear}-01`;
}
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)) : "";
const upperCareModes = this.careModes.map((m) => m.toUpperCase());
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.className = "col-day";
w.dayFlags[d].events.forEach((evt) => {
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 === "HELPER_OFF")
td.classList.add("fc-day--off-carole");
if (evt.type === "HELPER_EXTRA")
td.classList.add("fc-day--extra-off-carole");
if (upperCareModes.includes(evt.type))
td.classList.add("fc-day--has-guard");
});
let content = `
${String(dateObj.getDate()).padStart(2, "0")} `;
let iconsHtml = `
`;
w.dayFlags[d].events.forEach((evt) => {
if (upperCareModes.includes(evt.type)) {
const modeName = evt.type.toLowerCase();
if (modeName === "avis")
iconsHtml += `
`;
else if (modeName === "centre")
iconsHtml += `
🏫 `;
else
iconsHtml += `
${modeName.substring(0, 3)} `;
}
});
content += iconsHtml + `
`;
let sickHtml = `
`;
w.dayFlags[d].events.forEach((evt) => {
if (evt.type === "CHILD_SICK") {
const k = this.kids.find(
(x) => parseInt(x.id) === parseInt(evt.person_id),
);
if (k) {
sickHtml += `${k.name}🤒 `;
}
}
});
content += sickHtml + `
`;
const dayLeaves = this.leaves.filter((l) => l.leave_date === iso);
if (dayLeaves.length) {
let html = `
`;
this.parents.forEach((person) => {
if (
dayLeaves.some(
(l) => parseInt(l.person_id) === parseInt(person.id),
)
) {
html += `${person.name.charAt(0).toUpperCase()} `;
}
});
content += html + `
`;
}
td.innerHTML = content + `
`;
tr.appendChild(td);
});
this.careModes.forEach((mode) => {
const td = document.createElement("td");
td.className = "col-total";
td.textContent = fmt(w.totals["mode_" + mode] || 0);
tr.appendChild(td);
});
this.kids.forEach((kid) => {
const td = document.createElement("td");
td.className = "col-total";
td.textContent = fmt(w.totals["sick_" + kid.id] || 0);
tr.appendChild(td);
});
if (!processedLeavesCols[w.monthKey]) {
processedLeavesCols[w.monthKey] = true;
this.parents.forEach((parent, index) => {
const cssPrefix = index % 2 === 0 ? "col-alex" : "col-laia";
(this.leaveMatrix[parent.id] || []).forEach((conf) => {
const info =
this.monthlyLeaveBalances[parent.id]?.[conf.type]?.[w.monthKey];
tr.innerHTML += `${info ? fmt(info.availableAtMonthStart) : "-"} `;
tr.innerHTML += `${info ? fmt(info.usedInMonth) : ""} `;
});
});
}
this.planningBody.appendChild(tr);
});
}
generateMonthHTML(year, month) {
let html = ``;
["L", "M", "M", "J", "V"].forEach((d) => (html += `${d} `));
html += ` `;
const daysInMonth = new Date(year, month + 1, 0).getDate();
let currentRenderedCols = 0,
startDay = (new Date(year, month, 1).getDay() + 6) % 7;
const upperCareModes = this.careModes.map((m) => m.toUpperCase());
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 === "HELPER_OFF"))
cls += " fc-day--off-carole";
if (dayEvts.some((e) => e.type === "HELPER_EXTRA"))
cls += " fc-day--extra-off-carole";
if (dayEvts.some((e) => upperCareModes.includes(e.type)))
cls += " fc-day--has-guard";
let content = `${d} `;
let iconsHtml = `
`;
dayEvts.forEach((evt) => {
if (upperCareModes.includes(evt.type)) {
const modeName = evt.type.toLowerCase();
if (modeName === "avis")
iconsHtml += `
`;
else if (modeName === "centre")
iconsHtml += `
🏫 `;
else
iconsHtml += `
${modeName.substring(0, 3)} `;
}
});
content += iconsHtml + `
`;
let sickHtml = `
`;
dayEvts.forEach((evt) => {
if (evt.type === "CHILD_SICK") {
const k = this.kids.find(
(x) => parseInt(x.id) === parseInt(evt.person_id),
);
if (k) {
sickHtml += `${k.name} 🤒 `;
}
}
});
content += sickHtml + `
`;
const dayLeaves = this.leaves.filter((l) => l.leave_date === iso);
if (dayLeaves.length) {
content += `
`;
this.parents.forEach((parent) => {
if (
dayLeaves.some(
(l) => parseInt(l.person_id) === parseInt(parent.id),
)
) {
content += `${parent.name.charAt(0).toUpperCase()} `;
}
});
content += `
`;
}
html += `
${content} `;
currentRenderedCols++;
}
while (currentRenderedCols < 5 && currentRenderedCols > 0) {
html += `
`;
currentRenderedCols++;
}
return html + `
`;
}
generateMonthSummaryHTML(year, month) {
const stats = { off: 0, extra: 0, sick: 0, presence: 0 };
const daysInMonth = new Date(year, month + 1, 0).getDate();
for (let d = 1; d <= daysInMonth; d++) {
const dateObj = new Date(year, month, d);
const dayOfWeek = dateObj.getDay();
if (dayOfWeek === 0 || dayOfWeek === 6) continue;
const iso = this.getLocalIsoDate(dateObj);
const dayEvents = this.events.filter((e) => e.date === iso);
dayEvents.forEach((e) => {
const dur = parseFloat(e.duration) || 1;
if (e.type === "HELPER_OFF") stats.off += dur;
if (e.type === "HELPER_EXTRA") stats.extra += dur;
if (e.type === "CHILD_SICK") stats.sick += dur;
});
if (
!this.publicHolidayDates.has(iso) &&
!dayEvents.some((e) => e.type === "VACANCES_SCOLAIRES")
) {
let dayAbsence = 0;
dayEvents.forEach((e) => {
if (["HELPER_OFF", "HELPER_EXTRA", "CHILD_SICK"].includes(e.type)) {
dayAbsence += parseFloat(e.duration) || 1;
}
});
stats.presence += Math.max(0, 1 - dayAbsence);
}
}
return `
Off ${parseFloat(stats.off.toFixed(1))} j
Extra ${parseFloat(stats.extra.toFixed(1))} j
Maladie ${parseFloat(stats.sick.toFixed(1))} j
Présence ${parseFloat(stats.presence.toFixed(1))} j
`;
}
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.generateMonthSummaryHTML(y, m)}
`;
}
this.renderMonthBalances();
}
renderTwoMonthsView() {
const y = this.currentMonth.getFullYear(),
m = this.currentMonth.getMonth();
const nextDate = new Date(y, m + 1, 1);
const lang = window.appLang || "fr-FR";
this.monthCalendar.innerHTML = `
${new Intl.DateTimeFormat(lang, { month: "long" }).format(this.currentMonth)}
${this.generateMonthHTML(y, m)}${this.generateMonthSummaryHTML(y, m)}
${new Intl.DateTimeFormat(lang, { month: "long" }).format(nextDate)}
${this.generateMonthHTML(nextDate.getFullYear(), nextDate.getMonth())}${this.generateMonthSummaryHTML(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 = ``;
const lang = window.appLang || "fr-FR";
[d1, d2, d3].forEach(
(d) =>
(html += `
${new Intl.DateTimeFormat(lang, { month: "long" }).format(d)}
${this.generateMonthHTML(d.getFullYear(), d.getMonth())}${this.generateMonthSummaryHTML(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 = this.parents
.map((person) => {
let cards = `${person.name.toUpperCase()} `;
const types = this.leaveMatrix[person.id] || [];
types.forEach((conf) => {
const type = conf.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]);
// On récupère le mois de renouvellement depuis la date (ex: "2000-06-01" -> 6)
if (endBal > 0 && conf.date) {
const resetMonth = parseInt(conf.date.split("-")[1]);
// On alerte le mois même, ou le mois juste avant !
const alertMonth = resetMonth - 1 === 0 ? 12 : resetMonth - 1;
if (cMonth === alertMonth || cMonth === resetMonth) {
alertHtml = `
🔥
`;
}
}
cards += `
${type} ${fmt(endBal)} ${totalUsed > 0 ? `-${fmt(totalUsed)} ` : ""}${alertHtml}
`;
});
return cards + `
`;
})
.join("");
}
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: {},
}));
}
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() {
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);
}
});
}
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();
});
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 activeEventMap = new Set();
this.events.forEach((e) => {
if (dates.includes(e.date)) {
const personKey =
e.person_id !== null && e.person_id !== undefined
? e.person_id.toString()
: "0";
activeEventMap.add(`${e.type}_${personKey}`);
}
});
const activeLeaves = {};
this.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 getActiveStyleE = (type, personStr, color) =>
activeEventMap.has(`${type}_${personStr}`)
? `border: 1px solid ${color} !important; background: var(--bg-soft) !important; color: ${color} !important; font-weight: 700;`
: "";
const getActiveStyleL = (type, pid, color) =>
activeLeaves[pid]?.has(type)
? `border: 1px solid ${color} !important; background: var(--bg-soft) !important; color: ${color} !important; font-weight: 700;`
: "";
const trLang = window.I18N || {};
const dateLabel =
dates.length > 1
? `${dates.length} Jours sélectionnés`
: new Date(dates[0]).toLocaleDateString(window.appLang || "fr-FR", {
weekday: "long",
day: "numeric",
month: "long",
});
const trashSvg = ` `;
const buildHeader = (title, action, cat) => `
${title}
${action ? `${trashSvg} ` : ""}
`;
let html = ``;
if (this.helpers.length > 0) {
this.helpers.forEach((h) => {
html += ``;
});
}
if (this.careModes.length > 0) {
html += ``;
}
if (this.kids.length > 0) {
html += ``;
}
html += ``;
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, person, pid, cat } = dataset;
const dates = this._currentBulkInfo.dates;
const upperCareModes = this.careModes.map((m) => m.toUpperCase());
try {
if (action === "add") {
let typesToClear = [];
if (["HELPER_OFF", "HELPER_EXTRA"].includes(type))
typesToClear = ["HELPER_OFF", "HELPER_EXTRA"];
if (upperCareModes.includes(type)) typesToClear = [...upperCareModes];
if (type === "CHILD_SICK") typesToClear = ["CHILD_SICK"];
if (typesToClear.length) {
await this.postApi(
"/modules/family-calendar/includes/api/manage-event.php",
{
action: "bulk_delete_day_types_person",
dates,
types: typesToClear,
person_id: parseInt(person) || 0,
},
);
}
await this.postApi(
"/modules/family-calendar/includes/api/save-events.php",
dates.map((d) => ({
date: d,
type: type,
duration: 1,
person_id: parseInt(person) || 0,
})),
);
} else if (action === "clear-type") {
let typesToClear = [];
let personToClear = null;
if (cat.startsWith("HELPER_")) {
typesToClear = ["HELPER_OFF", "HELPER_EXTRA"];
personToClear = parseInt(cat.split("_")[1]) || 0;
} else if (cat === "CARE_MODE") {
typesToClear = [...upperCareModes];
personToClear = 0;
} else if (cat === "CHILD_SICK") {
typesToClear = ["CHILD_SICK"];
}
await this.postApi(
"/modules/family-calendar/includes/api/manage-event.php",
{
action: "bulk_delete_day_types_person",
dates,
types: typesToClear,
person_id: personToClear,
},
);
} 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();
});