/**
* 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) =>
`${p.name} (${p.role === "parent" ? tr("fc_role_adult") : tr("fc_role_child")}) `,
)
.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) => `
${m}
`,
)
.join("") || `${tr("fc_no_care_mode")} `
}
${tr("btn_save_rights")}
`;
} 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) => `
Fixe
Cumul
🗑️
`,
)
.join("");
zone.innerHTML = `
🗓️ Matrice des Congés
Définissez les quotas et méthodes d'acquisition.
➕ Ajouter
Code
Méthode
Quota
Renouv. / Fin
${tableRows || `Aucun compteur défini. `}
Enregistrer
`;
}
}
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 = `
Fixe
Cumul
🗑️
`;
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 += `${y} - ${y + 1} `;
}
// 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}
${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,
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"}
Importer
`;
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 += `${d} `));
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 += `
${content} `;
currentRenderedCols++;
}
while (currentRenderedCols < 5 && currentRenderedCols > 0) {
html += `
`;
currentRenderedCols++;
}
return html + `
`;
}
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) =>
``;
let html = ``;
html += ``;
html += ``;
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, 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();
});