opti family calendar

This commit is contained in:
2026-02-20 16:33:54 +01:00
parent 80d39ab8b9
commit e22a7e183c
5 changed files with 439 additions and 241 deletions
+3 -3
View File
@@ -98,8 +98,8 @@ require __DIR__ . '/header.php';
</colgroup> </colgroup>
<thead> <thead>
<tr> <tr>
<th rowspan="3" class="col-month">Mois</th> <th rowspan="3" class="col-month col-sticky-mois">Mois</th>
<th rowspan="3" class="col-month">Sem.</th> <th rowspan="3" class="col-month col-sticky-mois">Sem.</th>
<th rowspan="3" class="col-day">Lun</th> <th rowspan="3" class="col-day">Lun</th>
<th rowspan="3" class="col-day">Mar</th> <th rowspan="3" class="col-day">Mar</th>
<th rowspan="3" class="col-day">Mer</th> <th rowspan="3" class="col-day">Mer</th>
@@ -140,7 +140,7 @@ require __DIR__ . '/header.php';
<h2 class="pf-card-title">Légende</h2> <h2 class="pf-card-title">Légende</h2>
<div class="pf-card-body"> <div class="pf-card-body">
<div class="pf-legend-grid"> <div class="pf-legend-grid">
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-school-holiday"></div><span>Vacances</span></div> <div class="pf-legend-item"><div class="pf-legend-color fc-legend-school-holiday"></div><span>Vacances Scolaires</span></div>
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-public-holiday"></div><span>Férié</span></div> <div class="pf-legend-item"><div class="pf-legend-color fc-legend-public-holiday"></div><span>Férié</span></div>
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-off-carole"></div><span>Off Carole</span></div> <div class="pf-legend-item"><div class="pf-legend-color fc-legend-off-carole"></div><span>Off Carole</span></div>
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-extra-off-carole"></div><span>Extra Off</span></div> <div class="pf-legend-item"><div class="pf-legend-color fc-legend-extra-off-carole"></div><span>Extra Off</span></div>
+1 -1
View File
@@ -140,7 +140,7 @@ body {
.pf-main { .pf-main {
flex: 1; flex: 1;
width: 100%; width: 100%;
max-width: 1200px; max-width: 1400px;
margin: 0 auto; margin: 0 auto;
padding: 32px 20px; padding: 32px 20px;
} }
@@ -796,3 +796,88 @@
justify-content: center; justify-content: center;
} }
} }
/* --- SUPERPOSITION : GARDE/CONGÉS + VACANCES SCOLAIRES --- */
/* 1. OFF CAROLE + VACANCES
On force le fond orange/pêche, et on met la barre violette en bas */
.fc-day--off-carole.fc-day--school-holiday {
background: var(--c-off-carole) !important;
box-shadow: inset 0 -8px 0 0 var(--c-school-holiday) !important;
}
/* 2. EXTRA OFF CAROLE + VACANCES
On force le fond rouge clair, et on met la barre violette en bas */
.fc-day--extra-off-carole.fc-day--school-holiday {
background: var(--c-extra-off) !important;
box-shadow: inset 0 -8px 0 0 var(--c-school-holiday) !important;
}
/* 3. CENTRE / AVIS + VACANCES
Comme ce sont juste des icônes sur fond normal, on se contente d'ajouter la barre violette */
.fc-day--centre.fc-day--school-holiday,
.fc-day--avis.fc-day--school-holiday {
box-shadow: inset 0 -8px 0 0 var(--c-school-holiday) !important;
}
/* =========================================================
8. OPTIMISATIONS MOBILES ET ERGONOMIE (AJOUTS)
========================================================= */
/* --- A. COLONNES MOIS ET SEMAINE FIGÉES (Sticky Left) --- */
.col-sticky-mois {
position: sticky !important;
left: 0;
z-index: 15;
background: white;
box-shadow: 1px 0 2px rgba(0, 0, 0, 0.05); /* Petite ombre pour séparer */
}
.col-sticky-sem {
position: sticky !important;
left: 50px; /* On décale de la largeur de la colonne 'Mois' (50px) */
z-index: 15;
background: white;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.05);
}
/* On s'assure que l'en-tête reste bien au-dessus du reste */
#planningTable thead .col-sticky-mois,
#planningTable thead .col-sticky-sem {
z-index: 25;
background: var(--bg-header);
}
/* --- B. MENU BOTTOM SHEET (Mobile Uniquement) --- */
@media (max-width: 768px) {
.fc-selection-menu {
position: fixed !important; /* Fixé à l'écran, pas au tableau */
top: auto !important;
left: 0 !important;
bottom: 0 !important;
width: 100% !important;
border-radius: 24px 24px 0 0; /* Bords arrondis en haut */
box-shadow: 0 -10px 40px rgba(0, 0, 0, 0.2);
padding: 24px 20px;
/* Padding dynamique pour les iPhone (encoche du bas) */
padding-bottom: env(safe-area-inset-bottom, 24px);
transform: translateY(100%); /* Caché en bas par défaut */
animation: slideUpSheet 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
max-height: 85vh;
overflow-y: auto;
}
@keyframes slideUpSheet {
to {
transform: translateY(0);
}
}
/* Optionnel : On grossit un peu les boutons pour les gros doigts */
.fc-menu-btn,
.fc-menu-danger {
padding: 10px;
font-size: 0.95rem;
font-weight: 600;
}
}
+348 -237
View File
@@ -1,5 +1,5 @@
/** /**
* family-calendar.js (Version Sécurisée) * family-calendar.js (Version Optimisée - API BDD & Décalage Vendredi)
*/ */
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
const CONGE_TYPES = ["OFF_CAROLE", "EXTRA_OFF_CAROLE"]; const CONGE_TYPES = ["OFF_CAROLE", "EXTRA_OFF_CAROLE"];
@@ -8,7 +8,6 @@ document.addEventListener("DOMContentLoaded", () => {
class FamilyCalendar { class FamilyCalendar {
constructor() { constructor() {
// Éléments DOM (avec sécurité null)
this.planningBody = document.getElementById("planningBody"); this.planningBody = document.getElementById("planningBody");
this.selectionMenu = document.getElementById("selectionMenu"); this.selectionMenu = document.getElementById("selectionMenu");
this.schoolHolidaysTableBody = document.querySelector( this.schoolHolidaysTableBody = document.querySelector(
@@ -19,7 +18,6 @@ document.addEventListener("DOMContentLoaded", () => {
"fc-month-selectionMenu", "fc-month-selectionMenu",
); );
// Déplacement des menus dans le body pour affichage correct
if ( if (
this.selectionMenu && this.selectionMenu &&
this.selectionMenu.parentElement !== document.body this.selectionMenu.parentElement !== document.body
@@ -37,15 +35,15 @@ document.addEventListener("DOMContentLoaded", () => {
this.currentMonth.setDate(1); this.currentMonth.setDate(1);
this.viewMode = "1month"; this.viewMode = "1month";
this.currentSchoolYearStart = null; this.currentSchoolYearStart = null;
this.modalSelectedYear = null; // Pour la modale des vacances
this.isSelecting = false; this.isSelecting = false;
this.selectedCells = []; this.selectedCells = [];
this.monthSelectedCells = []; this.monthSelectedCells = [];
this._currentBulkInfo = null; this._currentBulkInfo = null;
// Données
this.dbEvents = []; this.dbEvents = [];
this.fixedEvents = []; this.fixedEvents = []; // Fériés statiques
this.events = []; this.events = [];
this.leaves = []; this.leaves = [];
this.weeks = []; this.weeks = [];
@@ -54,25 +52,51 @@ document.addEventListener("DOMContentLoaded", () => {
3: { CP: {}, JRA: {}, JA: {} }, 3: { CP: {}, JRA: {}, JA: {} },
}; };
// Si le tableau principal n'est pas là, on arrête tout pour ne pas crasher if (!this.planningBody) return;
if (!this.planningBody) {
console.warn("Element #planningBody introuvable. Le JS s'arrête.");
return;
}
this.init(); 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() { async init() {
this.setupEventListeners(); this.setupEventListeners();
const now = new Date(); const now = new Date();
this.currentSchoolYearStart = this.currentSchoolYearStart =
now.getMonth() >= 8 ? now.getFullYear() : now.getFullYear() - 1; now.getMonth() >= 8 ? now.getFullYear() : now.getFullYear() - 1;
this.modalSelectedYear = this.currentSchoolYearStart;
this.setupModalUI(); // Prépare le selecteur d'année dans la modale
await this.refreshAllData(); await this.refreshAllData();
this.updateSchoolYearLabel(); this.updateSchoolYearLabel();
} }
// Modifie dynamiquement le titre de la modale pour y insérer le selecteur d'année
setupModalUI() {
const headerH2 = document.querySelector(".fc-modal-header h2");
if (headerH2 && !document.getElementById("holidayYearSelect")) {
headerH2.innerHTML = `Vacances Scolaires (Zone C)
<select id="holidayYearSelect" style="margin-left:15px; font-size:1rem; padding:4px; border-radius:4px; border:1px solid #cbd5e1;">
<option value="${this.currentSchoolYearStart - 1}">${this.currentSchoolYearStart - 1} - ${this.currentSchoolYearStart}</option>
<option value="${this.currentSchoolYearStart}" selected>${this.currentSchoolYearStart} - ${this.currentSchoolYearStart + 1}</option>
<option value="${this.currentSchoolYearStart + 1}">${this.currentSchoolYearStart + 1} - ${this.currentSchoolYearStart + 2}</option>
<option value="${this.currentSchoolYearStart + 2}">${this.currentSchoolYearStart + 2} - ${this.currentSchoolYearStart + 3}</option>
</select>`;
document
.getElementById("holidayYearSelect")
.addEventListener("change", (e) => {
this.modalSelectedYear = parseInt(e.target.value);
this.renderModalHolidays();
});
}
}
async refreshAllData() { async refreshAllData() {
try { try {
const weeksData = await this.fetchApi( const weeksData = await this.fetchApi(
@@ -88,7 +112,7 @@ document.addEventListener("DOMContentLoaded", () => {
duration: parseFloat(e.duration), duration: parseFloat(e.duration),
})); }));
this.fixedEvents = await this.fetchPublicAndSchoolHolidays(); this.fixedEvents = this.getPublicHolidays(); // Uniquement les jours fériés maintenant
const leavesData = await this.fetchApi( const leavesData = await this.fetchApi(
"/modules/family-calendar/includes/api/get-leaves.php", "/modules/family-calendar/includes/api/get-leaves.php",
@@ -100,6 +124,7 @@ document.addEventListener("DOMContentLoaded", () => {
); );
this.leaveBalances = balancesData.balances || []; this.leaveBalances = balancesData.balances || [];
// Les vacances scolaires sont maintenant dans dbEvents !
this.events = [...this.dbEvents, ...this.fixedEvents]; this.events = [...this.dbEvents, ...this.fixedEvents];
this.publicHolidayDates = new Set( this.publicHolidayDates = new Set(
this.fixedEvents this.fixedEvents
@@ -108,14 +133,23 @@ document.addEventListener("DOMContentLoaded", () => {
); );
this.reprocessAndRender(); this.reprocessAndRender();
this.renderModalHolidays(); // Met à jour la modale
} catch (e) { } catch (e) {
console.error("Erreur chargement données:", e); console.error("Erreur chargement données:", e);
} }
} }
async fetchPublicAndSchoolHolidays() { // Les jours fériés restent hardcodés car ils sont fixes.
// 1. Jours fériés (Statique) getPublicHolidays() {
const publicHolidays = [ return [
"2025-01-01",
"2025-04-21",
"2025-05-01",
"2025-05-08",
"2025-05-29",
"2025-06-09",
"2025-07-14",
"2025-08-15",
"2025-11-01", "2025-11-01",
"2025-11-11", "2025-11-11",
"2025-12-25", "2025-12-25",
@@ -127,79 +161,189 @@ document.addEventListener("DOMContentLoaded", () => {
"2026-05-25", "2026-05-25",
"2026-07-14", "2026-07-14",
"2026-08-15", "2026-08-15",
"2026-11-01",
"2026-11-11",
"2026-12-25",
].map((date, idx) => ({ ].map((date, idx) => ({
id: `ph-${idx}`, id: `ph-${idx}`,
date, date,
type: "PUBLIC_HOLIDAY", type: "PUBLIC_HOLIDAY",
duration: 1, duration: 1,
})); }));
}
const schoolHolidayEvents = []; // --- RECONSTRUCTION DE LA MODALE VIA LA BDD (Par blocs consécutifs) ---
renderModalHolidays() {
if (!this.schoolHolidaysTableBody) return;
// Filtrer les événements de type VACANCES_SCOLAIRES pour l'année scolaire sélectionnée
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,
);
// S'il n'y a pas de vacances en base pour cette année, on affiche le bouton "Générer"
if (yearHolidays.length === 0) {
this.schoolHolidaysTableBody.innerHTML = `
<tr>
<td colspan="3" style="text-align:center; padding: 30px;">
<p style="color:#64748b; margin-bottom:15px;">Les vacances de cette année ne sont pas encore enregistrées.</p>
<button id="btnFetchGovHolidays" class="pf-btn">Importer depuis l'API Gouvernement</button>
</td>
</tr>
`;
document
.getElementById("btnFetchGovHolidays")
.addEventListener("click", (e) => {
e.target.innerText = "Téléchargement en cours...";
e.target.disabled = true;
this.fetchAndSaveGovHolidays(this.modalSelectedYear);
});
return;
}
// 1. Tri chronologique strict des jours
yearHolidays.sort((a, b) => new Date(a.date) - new Date(b.date));
// 2. Regroupement par blocs de jours consécutifs
const blocks = [];
let currentBlock = null;
yearHolidays.forEach((e) => {
const d = new Date(e.date + "T00:00:00"); // Force locale
if (!currentBlock) {
currentBlock = { start: d, end: d };
blocks.push(currentBlock);
} else {
// Calcul de l'écart en jours entre la date actuelle et la fin du bloc en cours
const diffTime = d.getTime() - currentBlock.end.getTime();
const diffDays = Math.round(diffTime / (1000 * 60 * 60 * 24));
// Si l'écart est minime (<= 4 jours, pour absorber un éventuel week-end non stocké)
// on considère qu'on est toujours dans la même période de vacances.
if (diffDays <= 4) {
currentBlock.end = d;
} else {
// Sinon, l'écart est grand : c'est une NOUVELLE période de vacances
currentBlock = { start: d, end: d };
blocks.push(currentBlock);
}
}
});
// 3. Rendu HTML et déduction des noms
let html = "";
blocks.forEach((block) => {
// Déduction intelligente du nom selon le mois de départ ET la durée
const m = block.start.getMonth() + 1;
const durationDays =
Math.round(
(block.end.getTime() - block.start.getTime()) /
(1000 * 60 * 60 * 24),
) + 1;
let name = "Vacances scolaires";
if (m === 10 || m === 11) {
name = "Vacances de la Toussaint";
} else if (m === 12 || m === 1) {
name = "Vacances de Noël";
} else if (m === 2 || m === 3) {
name = "Vacances d'Hiver";
} else if (m === 4 || (m === 5 && durationDays > 6)) {
name = "Vacances de Printemps";
} else if (m === 5 && durationDays <= 6) {
name = "Pont de Mai (Ascension)";
} else if (m === 7 || m === 8) {
name = "Vacances d'Été";
}
html += `
<tr>
<td><strong>${name}</strong></td>
<td>${block.start.toLocaleDateString("fr-FR")}</td>
<td>${block.end.toLocaleDateString("fr-FR")}</td>
</tr>
`;
});
this.schoolHolidaysTableBody.innerHTML = html;
}
// --- IMPORTATION DEPUIS L'API & SAUVEGARDE EN BDD ---
async fetchAndSaveGovHolidays(yearStart) {
try { try {
// API Gouv const yearStr = `${yearStart}-${yearStart + 1}`;
const url = 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 C%'&limit=100`;
"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 res = await fetch(url);
const data = await res.json(); const data = await res.json();
const rawRecords = data.results || []; const rawRecords = data.results || [];
// Dédoublonnage // Dédoublonnage
const uniqueHolidaysMap = new Map(); const uniqueMap = new Map();
rawRecords.forEach((r) => { rawRecords.forEach((r) => {
const key = `${r.description}|${r.start_date}`; const key = `${r.description}|${r.start_date}`;
if (!uniqueHolidaysMap.has(key)) uniqueHolidaysMap.set(key, r); if (!uniqueMap.has(key)) uniqueMap.set(key, r);
}); });
const uniqueRecords = Array.from(uniqueHolidaysMap.values());
// APPEL DE LA MÉTHODE D'AFFICHAGE (séparée pour la propreté) const payload = [];
this.renderSchoolHolidaysTable(uniqueRecords);
Array.from(uniqueMap.values()).forEach((r) => {
const startDateStr = r.start_date.split("T")[0];
const endDateStr = r.end_date.split("T")[0];
let curr = new Date(startDateStr + "T00:00:00");
const end = new Date(endDateStr + "T00:00:00");
// REGLE METIER : Si le 1er jour est un VENDREDI (jour=5), on décale le début au SAMEDI.
if (curr.getDay() === 5) {
curr.setDate(curr.getDate() + 1);
}
// Génération des events pour le calendrier (cases colorées)
uniqueRecords.forEach((r, idx) => {
let curr = new Date(r.start_date);
const end = new Date(r.end_date);
while (curr < end) { while (curr < end) {
const iso = curr.toISOString().split("T")[0]; const iso = this.getLocalIsoDate(curr);
schoolHolidayEvents.push({ payload.push({
id: `sh-${iso}-${idx}`,
date: iso, date: iso,
type: "VACANCES_SCOLAIRES", type: "VACANCES_SCOLAIRES",
duration: 1, duration: 1,
person: r.description, // ASTUCE: On stocke le nom de la vacance ici !
}); });
curr.setDate(curr.getDate() + 1); curr.setDate(curr.getDate() + 1);
} }
}); });
if (payload.length > 0) {
// On envoie le gros lot à la base de données via l'API existante
await this.postApi(
"/modules/family-calendar/includes/api/save-events.php",
payload,
);
await this.refreshAllData(); // Recharge tout, ce qui mettra à jour la modale
} else {
alert(
"Aucune donnée trouvée sur l'API du gouvernement pour cette année.",
);
document.getElementById("btnFetchGovHolidays").innerText =
"Réessayer";
document.getElementById("btnFetchGovHolidays").disabled = false;
}
} catch (e) { } catch (e) {
console.warn("Erreur API Vacances (pas grave, on continue)", e); console.error("Erreur API", e);
alert("Erreur lors de la connexion à l'API gouvernementale.");
} }
// On retourne TOUJOURS les événements, même si l'affichage du tableau échoue
return [...publicHolidays, ...schoolHolidayEvents];
} }
renderSchoolHolidaysTable(records) { // ================================================================
// Si le tableau HTML n'existe pas (ex: on n'est pas sur la bonne page), on ne fait rien // LE RESTE DU CODE (AFFICHAGE) RESTE INCHANGÉ MAIS OPTIMISÉ
if (!this.schoolHolidaysTableBody) return; // ================================================================
// 1. Tri chronologique strict (du plus ancien au plus récent)
records.sort((a, b) => new Date(a.start_date) - new Date(b.start_date));
// 2. Génération du HTML
this.schoolHolidaysTableBody.innerHTML = records
.map(
(r) => `
<tr>
<td>${r.description}</td>
<td>${new Date(r.start_date).toLocaleDateString("fr-FR")}</td>
<td>${new Date(r.end_date).toLocaleDateString("fr-FR")}</td>
</tr>
`,
)
.join("");
}
// ================== LOGIQUE MÉTIER ==================
reprocessAndRender() { reprocessAndRender() {
this.reprocessEvents(); this.reprocessEvents();
@@ -246,7 +390,7 @@ document.addEventListener("DOMContentLoaded", () => {
let workingDays = 0; let workingDays = 0;
Object.values(w.dayDates).forEach((d) => { Object.values(w.dayDates).forEach((d) => {
if (!this.publicHolidayDates.has(d.toISOString().split("T")[0])) if (!this.publicHolidayDates.has(this.getLocalIsoDate(d)))
workingDays++; workingDays++;
}); });
w.totals.presencePep = Math.max( w.totals.presencePep = Math.max(
@@ -304,9 +448,8 @@ document.addEventListener("DOMContentLoaded", () => {
const periodStart = `${refYear}-${periodStartMonth}`; const periodStart = `${refYear}-${periodStartMonth}`;
Object.keys(usageByMonth[pid]?.[type] || {}).forEach((usedYm) => { Object.keys(usageByMonth[pid]?.[type] || {}).forEach((usedYm) => {
if (usedYm >= periodStart && usedYm < ym) { if (usedYm >= periodStart && usedYm < ym)
usedBefore += usageByMonth[pid][type][usedYm]; usedBefore += usageByMonth[pid][type][usedYm];
}
}); });
const available = Math.max(0, initial - usedBefore); const available = Math.max(0, initial - usedBefore);
@@ -346,21 +489,21 @@ document.addEventListener("DOMContentLoaded", () => {
if (!processedMonths[w.monthKey]) { if (!processedMonths[w.monthKey]) {
processedMonths[w.monthKey] = true; processedMonths[w.monthKey] = true;
const td = document.createElement("td"); const td = document.createElement("td");
td.className = "col-month"; td.className = "col-month col-sticky-mois";
td.textContent = w.monthName; td.textContent = w.monthName;
td.rowSpan = monthSpans[w.monthKey]; td.rowSpan = monthSpans[w.monthKey];
tr.appendChild(td); tr.appendChild(td);
} }
const tdW = document.createElement("td"); const tdW = document.createElement("td");
tdW.className = "col-month"; tdW.className = "col-month col-sticky-sem";
tdW.textContent = w.weekLabel; tdW.textContent = w.weekLabel;
tr.appendChild(tdW); tr.appendChild(tdW);
["mon", "tue", "wed", "thu", "fri"].forEach((d) => { ["mon", "tue", "wed", "thu", "fri"].forEach((d) => {
const td = document.createElement("td"); const td = document.createElement("td");
const dateObj = w.dayDates[d]; const dateObj = w.dayDates[d];
const iso = dateObj.toISOString().split("T")[0]; const iso = this.getLocalIsoDate(dateObj);
td.dataset.date = iso; td.dataset.date = iso;
td.textContent = String(dateObj.getDate()).padStart(2, "0"); td.textContent = String(dateObj.getDate()).padStart(2, "0");
td.className = "col-day"; td.className = "col-day";
@@ -470,7 +613,6 @@ document.addEventListener("DOMContentLoaded", () => {
this.renderTwoMonthsView(); this.renderTwoMonthsView();
return; return;
} }
this.monthCalendar.innerHTML = this.generateMonthHTML(y, m); this.monthCalendar.innerHTML = this.generateMonthHTML(y, m);
} }
@@ -499,79 +641,82 @@ document.addEventListener("DOMContentLoaded", () => {
generateMonthHTML(year, month) { generateMonthHTML(year, month) {
let html = `<table class="fc-month-table"><thead><tr>`; let html = `<table class="fc-month-table"><thead><tr>`;
["L", "M", "M", "J", "V"].forEach((d) => (html += `<th>${d}</th>`)); ["L", "M", "M", "J", "V"].forEach((d) => (html += `<th>${d}</th>`));
html += `</tr></thead><tbody>`; html += `</tr></thead><tbody><tr>`;
const daysInMonth = new Date(year, month + 1, 0).getDate();
let currentRenderedCols = 0;
const firstDay = new Date(year, month, 1); const firstDay = new Date(year, month, 1);
const startDay = (firstDay.getDay() + 6) % 7; let startDay = (firstDay.getDay() + 6) % 7;
const daysInMonth = new Date(year, month + 1, 0).getDate();
let dayCounter = 1;
for (let row = 0; row < 6; row++) { if (startDay < 5) {
html += `<tr>`; for (let i = 0; i < startDay; i++) {
for (let col = 0; col < 5; col++) { html += `<td class="fc-day--other-month"></td>`;
if ( currentRenderedCols++;
(row === 0 && col < startDay && startDay < 5) ||
dayCounter > daysInMonth
) {
html += `<td class="fc-day--other-month"></td>`;
} else if (row === 0 && startDay >= 5) {
html += `<td class="fc-day--other-month"></td>`;
} 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 = `<div style="position:relative; height:100%;">${dayCounter}`;
if (dayEvts.some((e) => e.type === "PEP_SICK"))
content += `<span style="position:absolute; bottom:2px; right:2px;">🤒</span>`;
const dayLeaves = this.leaves.filter((l) => l.leave_date === iso);
if (dayLeaves.length) {
content += `<div style="position:absolute; bottom:2px; left:2px; font-size:10px; font-weight:bold;">`;
if (dayLeaves.some((l) => l.person_id === 2))
content += `<span style="color:#0f766e">A</span> `;
if (dayLeaves.some((l) => l.person_id === 3))
content += `<span style="color:#b45309">L</span>`;
content += `</div>`;
}
content += `</div>`;
html += `<td class="${cls}" data-date="${iso}">${content}</td>`;
dayCounter++;
}
} }
html += `</tr>`;
if (dayCounter > daysInMonth) break;
} }
html += `</tbody></table>`;
for (let dayCounter = 1; dayCounter <= daysInMonth; dayCounter++) {
const dateObj = new Date(year, month, dayCounter);
const dayOfWeek = dateObj.getDay();
if (dayOfWeek === 0 || dayOfWeek === 6) continue;
if (currentRenderedCols === 5) {
html += `</tr><tr>`;
currentRenderedCols = 0;
}
const iso = this.getLocalIsoDate(dateObj);
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 = `<div style="position:relative; height:100%;">${dayCounter}`;
if (dayEvts.some((e) => e.type === "PEP_SICK"))
content += `<span style="position:absolute; bottom:2px; right:2px;">🤒</span>`;
const dayLeaves = this.leaves.filter((l) => l.leave_date === iso);
if (dayLeaves.length) {
content += `<div style="position:absolute; bottom:2px; left:2px; font-size:10px; font-weight:bold;">`;
if (dayLeaves.some((l) => l.person_id === 2))
content += `<span style="color:#0f766e">A</span> `;
if (dayLeaves.some((l) => l.person_id === 3))
content += `<span style="color:#b45309">L</span>`;
content += `</div>`;
}
content += `</div>`;
html += `<td class="${cls}" data-date="${iso}">${content}</td>`;
currentRenderedCols++;
}
while (currentRenderedCols < 5 && currentRenderedCols > 0) {
html += `<td class="fc-day--other-month"></td>`;
currentRenderedCols++;
}
html += `</tr></tbody></table>`;
return html; return html;
} }
// --- NOUVELLES MÉTHODES POUR LE RÉCAPITULATIF DYNAMIQUE ---
/**
* Initialise les menus déroulants (Année / Mois)
*/
initSummaryControls() { initSummaryControls() {
const typeSelect = document.getElementById("summType"); const typeSelect = document.getElementById("summType");
const valueSelect = document.getElementById("summValue"); const valueSelect = document.getElementById("summValue");
if (!typeSelect || !valueSelect) return; if (!typeSelect || !valueSelect) return;
// 1. Scanner les données pour trouver les années et mois disponibles
const years = new Set(); const years = new Set();
const months = new Set(); const months = new Set();
@@ -586,12 +731,10 @@ document.addEventListener("DOMContentLoaded", () => {
const sortedYears = Array.from(years).sort(); const sortedYears = Array.from(years).sort();
const sortedMonths = Array.from(months).sort(); const sortedMonths = Array.from(months).sort();
// 2. Fonction interne pour remplir le 2ème menu (Valeurs)
const populateValues = () => { const populateValues = () => {
const currentType = typeSelect.value; const currentType = typeSelect.value;
const previousValue = valueSelect.value; // Pour essayer de garder la sélection si possible const previousValue = valueSelect.value;
valueSelect.innerHTML = "";
valueSelect.innerHTML = ""; // On vide
if (currentType === "year") { if (currentType === "year") {
sortedYears.forEach((y) => { sortedYears.forEach((y) => {
@@ -600,8 +743,6 @@ document.addEventListener("DOMContentLoaded", () => {
opt.textContent = y; opt.textContent = y;
valueSelect.appendChild(opt); valueSelect.appendChild(opt);
}); });
// Sélection intelligente : garder l'ancienne ou prendre l'année en cours
if (sortedYears.includes(parseInt(previousValue))) { if (sortedYears.includes(parseInt(previousValue))) {
valueSelect.value = previousValue; valueSelect.value = previousValue;
} else { } else {
@@ -610,34 +751,28 @@ document.addEventListener("DOMContentLoaded", () => {
valueSelect.value = currentYear; valueSelect.value = currentYear;
} }
} else { } else {
// Mode Mois
sortedMonths.forEach((m) => { sortedMonths.forEach((m) => {
const [y, mo] = m.split("-"); const [y, mo] = m.split("-");
const dateObj = new Date(y, mo - 1, 1); const dateObj = new Date(y, mo - 1, 1);
// Format : "octobre 2025"
const label = new Intl.DateTimeFormat("fr-FR", { const label = new Intl.DateTimeFormat("fr-FR", {
month: "long", month: "long",
year: "numeric", year: "numeric",
}).format(dateObj); }).format(dateObj);
const opt = document.createElement("option"); const opt = document.createElement("option");
opt.value = m; opt.value = m;
opt.textContent = label; opt.textContent = label;
valueSelect.appendChild(opt); valueSelect.appendChild(opt);
}); });
// Sélection intelligente : mois courant const now = new Date();
const nowIso = new Date().toISOString().slice(0, 7); // "2025-01" const nowIso = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
if (sortedMonths.includes(nowIso)) { if (sortedMonths.includes(nowIso)) {
valueSelect.value = nowIso; valueSelect.value = nowIso;
} }
} }
// Une fois rempli, on met à jour les chiffres
this.updateGlobalSummary(); this.updateGlobalSummary();
}; };
// 3. Attacher les écouteurs (UNE SEULE FOIS)
if (!this.summaryListenersAttached) { if (!this.summaryListenersAttached) {
typeSelect.addEventListener("change", populateValues); typeSelect.addEventListener("change", populateValues);
valueSelect.addEventListener("change", () => valueSelect.addEventListener("change", () =>
@@ -645,49 +780,35 @@ document.addEventListener("DOMContentLoaded", () => {
); );
this.summaryListenersAttached = true; this.summaryListenersAttached = true;
} }
// 4. Premier appel pour remplir dès le chargement
populateValues(); populateValues();
} }
/**
* Calcule et affiche les KPIs en fonction des filtres
*/
updateGlobalSummary() { updateGlobalSummary() {
const div = document.getElementById("globalSummary"); const div = document.getElementById("globalSummary");
const typeSelect = document.getElementById("summType"); const typeSelect = document.getElementById("summType");
const valueSelect = document.getElementById("summValue"); const valueSelect = document.getElementById("summValue");
if (!div) return; if (!div || (typeSelect && valueSelect && !valueSelect.value)) return;
// Si les contrôles n'ont pas encore de valeur (chargement initial), on attend
if (typeSelect && valueSelect && !valueSelect.value) return;
const filterType = typeSelect ? typeSelect.value : "year"; const filterType = typeSelect ? typeSelect.value : "year";
const filterValue = valueSelect const filterValue = valueSelect
? valueSelect.value ? valueSelect.value
: new Date().getFullYear().toString(); // Fallback : new Date().getFullYear().toString();
const stats = { off: 0, extra: 0, sick: 0, pep: 0, totalWorking: 0 }; const stats = { off: 0, extra: 0, sick: 0, pep: 0, totalWorking: 0 };
// Parcours précis jour par jour
this.weeks.forEach((w) => { this.weeks.forEach((w) => {
Object.entries(w.dayDates).forEach(([dayName, dateObj]) => { Object.entries(w.dayDates).forEach(([dayName, dateObj]) => {
// Vérification du filtre
let match = false; let match = false;
if (filterType === "year") { if (filterType === "year") {
if (dateObj.getFullYear().toString() === filterValue) match = true; if (dateObj.getFullYear().toString() === filterValue) match = true;
} else { } else {
// Month (YYYY-MM) const isoMonth = this.getLocalIsoDate(dateObj).slice(0, 7);
const isoMonth = dateObj.toISOString().slice(0, 7);
if (isoMonth === filterValue) match = true; if (isoMonth === filterValue) match = true;
} }
if (match) { if (match) {
// Récupérer les events de CE jour spécifique
const dayEvents = w.dayFlags[dayName].events; const dayEvents = w.dayFlags[dayName].events;
// Additions
dayEvents.forEach((e) => { dayEvents.forEach((e) => {
const dur = parseFloat(e.duration) || 1; const dur = parseFloat(e.duration) || 1;
if (e.type === "OFF_CAROLE") stats.off += dur; if (e.type === "OFF_CAROLE") stats.off += dur;
@@ -695,15 +816,9 @@ document.addEventListener("DOMContentLoaded", () => {
if (e.type === "PEP_SICK") stats.sick += dur; if (e.type === "PEP_SICK") stats.sick += dur;
}); });
// Calcul présence (Jour ouvré - Absences) const dateIso = this.getLocalIsoDate(dateObj);
// Est-ce un jour ouvré ? (Pas férié, pas we - WE déjà exclu par structure semainier lun-ven)
// NOTE : Ma structure `weeks` ne contient que Lun-Ven par défaut.
const dateIso = dateObj.toISOString().split("T")[0];
if (!this.publicHolidayDates.has(dateIso)) { if (!this.publicHolidayDates.has(dateIso)) {
// C'est un jour potentiellement ouvré
stats.totalWorking++; stats.totalWorking++;
// Calcul des absences sur ce jour
let dayAbsence = 0; let dayAbsence = 0;
dayEvents.forEach((e) => { dayEvents.forEach((e) => {
if ( if (
@@ -714,31 +829,17 @@ document.addEventListener("DOMContentLoaded", () => {
dayAbsence += parseFloat(e.duration) || 1; dayAbsence += parseFloat(e.duration) || 1;
} }
}); });
stats.pep += Math.max(0, 1 - dayAbsence); stats.pep += Math.max(0, 1 - dayAbsence);
} }
} }
}); });
}); });
// Affichage HTML Vertical
div.innerHTML = ` div.innerHTML = `
<div class="fc-summary-item"> <div class="fc-summary-item"><span class="fc-summary-label">Off Carole</span><span class="fc-summary-value">${parseFloat(stats.off.toFixed(1))} jours</span></div>
<span class="fc-summary-label">Off Carole</span> <div class="fc-summary-item"><span class="fc-summary-label">Extra Off Carole</span><span class="fc-summary-value">${parseFloat(stats.extra.toFixed(1))} jours</span></div>
<span class="fc-summary-value">${parseFloat(stats.off.toFixed(1))} jours</span> <div class="fc-summary-item"><span class="fc-summary-label">Pep Malade</span><span class="fc-summary-value">${parseFloat(stats.sick.toFixed(1))} jours</span></div>
</div> <div class="fc-summary-item"><span class="fc-summary-label">Présence Pep</span><span class="fc-summary-value">${parseFloat(stats.pep.toFixed(1))} jours</span></div>
<div class="fc-summary-item">
<span class="fc-summary-label">Extra Off Carole</span>
<span class="fc-summary-value">${parseFloat(stats.extra.toFixed(1))} jours</span>
</div>
<div class="fc-summary-item">
<span class="fc-summary-label">Pep Malade</span>
<span class="fc-summary-value">${parseFloat(stats.sick.toFixed(1))} jours</span>
</div>
<div class="fc-summary-item">
<span class="fc-summary-label">Présence Pep</span>
<span class="fc-summary-value">${parseFloat(stats.pep.toFixed(1))} jours</span>
</div>
`; `;
} }
@@ -805,24 +906,41 @@ document.addEventListener("DOMContentLoaded", () => {
await this.refreshAllData(); await this.refreshAllData();
} }
// ================== INTERACTIONS ==================
setupEventListeners() { setupEventListeners() {
// Planning Hebdo
if (this.planningBody) { if (this.planningBody) {
// Souris (PC)
this.planningBody.addEventListener("mousedown", (e) => this.planningBody.addEventListener("mousedown", (e) =>
this.handleMouseDown(e), this.handleMouseDown(e),
); );
document.addEventListener("mousemove", (e) => this.handleMouseMove(e)); document.addEventListener("mousemove", (e) => this.handleMouseMove(e));
document.addEventListener("mouseup", (e) => this.handleMouseUp(e)); document.addEventListener("mouseup", (e) => this.handleMouseUp(e));
// Tactile (Mobile/Tablette) - NOUVEAU
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));
} }
// Modale
const btnOpen = document.getElementById("btnOpenHolidays"); const btnOpen = document.getElementById("btnOpenHolidays");
const btnClose = document.getElementById("btnCloseHolidays"); const btnClose = document.getElementById("btnCloseHolidays");
const modal = document.getElementById("modalHolidays"); const modal = document.getElementById("modalHolidays");
if (btnOpen && modal)
btnOpen.addEventListener("click", () => (modal.style.display = "flex")); if (btnOpen && modal) {
btnOpen.addEventListener("click", () => {
modal.style.display = "flex";
// Remet l'année par défaut sur l'année actuellement affichée dans le calendrier
const yearSelect = document.getElementById("holidayYearSelect");
if (yearSelect) yearSelect.value = this.currentSchoolYearStart;
this.modalSelectedYear = this.currentSchoolYearStart;
this.renderModalHolidays();
});
}
if (btnClose && modal) if (btnClose && modal)
btnClose.addEventListener( btnClose.addEventListener(
"click", "click",
@@ -833,29 +951,26 @@ document.addEventListener("DOMContentLoaded", () => {
if (e.target === modal) modal.style.display = "none"; if (e.target === modal) modal.style.display = "none";
}); });
// Calendrier Mensuel
if (this.monthCalendar) { if (this.monthCalendar) {
this.monthCalendar.addEventListener("click", (e) => { this.monthCalendar.addEventListener("click", (e) => {
const td = e.target.closest("td[data-date]"); const td = e.target.closest("td[data-date]");
if (td) { if (td && td.dataset.date) {
this.monthSelectedCells = [td]; this.monthSelectedCells = [td];
this.showMenu(e.pageX, e.pageY, [td.dataset.date], true); this.showMenu(e.pageX, e.pageY, [td.dataset.date], true);
} }
}); });
} }
// Menus
document.addEventListener("click", (e) => this.closeMenusIfOutside(e)); document.addEventListener("click", (e) => this.closeMenusIfOutside(e));
const handleMenu = (e) => { const handleMenu = (e) => {
const btn = e.target.closest("button"); const btn = e.target.closest("button");
if (btn) this.handleMenuAction(btn.dataset); if (btn && btn.dataset.action) this.handleMenuAction(btn.dataset);
}; };
if (this.selectionMenu) if (this.selectionMenu)
this.selectionMenu.addEventListener("click", handleMenu); this.selectionMenu.addEventListener("click", handleMenu);
if (this.monthSelectionMenu) if (this.monthSelectionMenu)
this.monthSelectionMenu.addEventListener("click", handleMenu); this.monthSelectionMenu.addEventListener("click", handleMenu);
// Nav Mensuelle
document document
.getElementById("fc-prev-month") .getElementById("fc-prev-month")
?.addEventListener("click", () => { ?.addEventListener("click", () => {
@@ -868,8 +983,6 @@ document.addEventListener("DOMContentLoaded", () => {
this.currentMonth.setMonth(this.currentMonth.getMonth() + 1); this.currentMonth.setMonth(this.currentMonth.getMonth() + 1);
this.renderMonthCalendar(); this.renderMonthCalendar();
}); });
// Nav Année
document document
.getElementById("fc-prev-school-year") .getElementById("fc-prev-school-year")
?.addEventListener("click", () => this.changeSchoolYear(-1)); ?.addEventListener("click", () => this.changeSchoolYear(-1));
@@ -877,7 +990,6 @@ document.addEventListener("DOMContentLoaded", () => {
.getElementById("fc-next-school-year") .getElementById("fc-next-school-year")
?.addEventListener("click", () => this.changeSchoolYear(1)); ?.addEventListener("click", () => this.changeSchoolYear(1));
// Boutons Vue
document.querySelectorAll(".fc-view-button").forEach((btn) => { document.querySelectorAll(".fc-view-button").forEach((btn) => {
btn.addEventListener("click", (e) => { btn.addEventListener("click", (e) => {
document document
@@ -907,15 +1019,48 @@ document.addEventListener("DOMContentLoaded", () => {
handleMouseUp(e) { handleMouseUp(e) {
if (!this.isSelecting) return; if (!this.isSelecting) return;
// Fix : inclure la dernière cellule
const td = e.target.closest("#planningTable td[data-date]"); const td = e.target.closest("#planningTable td[data-date]");
if (td && !this.selectedCells.includes(td)) this.selectCell(td); 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);
}
// --- GESTION DU TACTILE (Swipe pour sélectionner) ---
handleTouchStart(e) {
const td = e.target.closest("#planningTable td[data-date]");
if (!td) return;
// Si l'utilisateur touche une case, on bloque le scroll de la page pour le laisser sélectionner
e.preventDefault();
this.clearSelection();
this.isSelecting = true;
this.selectCell(td);
}
handleTouchMove(e) {
if (!this.isSelecting) return;
e.preventDefault(); // Bloque le défilement de l'écran pendant qu'on glisse le doigt
const touch = e.touches[0];
// elementFromPoint permet de savoir sur quelle case se trouve le doigt actuellement
const 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; this.isSelecting = false;
if (!this.selectedCells.length) return; if (!this.selectedCells.length) return;
const dates = this.selectedCells.map((c) => c.dataset.date); const dates = this.selectedCells.map((c) => c.dataset.date);
this.showMenu(e.pageX, e.pageY, dates, false); // Sur mobile, on s'en fiche de X et Y car le menu s'affiche en bas de l'écran
this.showMenu(0, 0, dates, false);
} }
selectCell(cell) { selectCell(cell) {
@@ -950,45 +1095,23 @@ document.addEventListener("DOMContentLoaded", () => {
dates.length > 1 dates.length > 1
? `${dates.length} jours` ? `${dates.length} jours`
: new Date(dates[0]).toLocaleDateString("fr-FR"); : new Date(dates[0]).toLocaleDateString("fr-FR");
let html = `<div class="fc-menu-section"><strong>${dateLabel}</strong></div>`; let html = `<div class="fc-menu-section"><strong>${dateLabel}</strong></div>`;
html += `<div class="fc-menu-section"><strong>Congés Carole</strong><div class="fc-menu-grid"><button class="fc-menu-btn" data-action="add" data-type="OFF_CAROLE" data-person="Carole">Off Carole</button><button class="fc-menu-btn" data-action="add" data-type="EXTRA_OFF_CAROLE" data-person="Carole">Extra Off</button></div><button class="fc-menu-danger" data-action="clear-type" data-cat="CONGE">Effacer congés Carole</button></div>`;
// Carole html += `<div class="fc-menu-section"><strong>Garde</strong><div class="fc-menu-grid"><button class="fc-menu-btn" data-action="add" data-type="CENTRE">Centre</button><button class="fc-menu-btn" data-action="add" data-type="AVIS">Avis</button></div><button class="fc-menu-danger" data-action="clear-type" data-cat="GARDE">Effacer Garde</button></div>`;
html += `<div class="fc-menu-section"><strong>Congés Carole</strong><div class="fc-menu-grid">`; html += `<div class="fc-menu-section"><strong>Pep</strong><button class="fc-menu-btn" data-action="add" data-type="PEP_SICK" style="width:100%">Pep Malade</button><button class="fc-menu-danger" data-action="clear-type" data-cat="PEP">Effacer Pep</button></div>`;
html += `<button class="fc-menu-btn" data-action="add" data-type="OFF_CAROLE" data-person="Carole">Off Carole</button>`;
html += `<button class="fc-menu-btn" data-action="add" data-type="EXTRA_OFF_CAROLE" data-person="Carole">Extra Off</button>`;
html += `</div><button class="fc-menu-danger" data-action="clear-type" data-cat="CONGE">Effacer congés Carole</button></div>`;
// Garde
html += `<div class="fc-menu-section"><strong>Garde</strong><div class="fc-menu-grid">`;
html += `<button class="fc-menu-btn" data-action="add" data-type="CENTRE">Centre</button>`;
html += `<button class="fc-menu-btn" data-action="add" data-type="AVIS">Avis</button>`;
html += `</div><button class="fc-menu-danger" data-action="clear-type" data-cat="GARDE">Effacer Garde</button></div>`;
// Pep
html += `<div class="fc-menu-section"><strong>Pep</strong>`;
html += `<button class="fc-menu-btn" data-action="add" data-type="PEP_SICK" style="width:100%">Pep Malade</button>`;
html += `<button class="fc-menu-danger" data-action="clear-type" data-cat="PEP">Effacer Pep</button></div>`;
// Alex/Laia
html += `<div class="fc-menu-section"><strong>Congés Alex / Laia</strong><div class="fc-menu-leaves-table"><table><thead><tr><th>Alex</th><th>Laia</th></tr></thead><tbody>`; html += `<div class="fc-menu-section"><strong>Congés Alex / Laia</strong><div class="fc-menu-leaves-table"><table><thead><tr><th>Alex</th><th>Laia</th></tr></thead><tbody>`;
["CP", "JRA", "JA"].forEach((t) => { ["CP", "JRA", "JA"].forEach((t) => {
html += `<tr><td><button class="fc-menu-btn" data-action="add-leave" data-pid="2" data-type="${t}">${t}</button></td>`; html += `<tr><td><button class="fc-menu-btn" data-action="add-leave" data-pid="2" data-type="${t}">${t}</button></td><td><button class="fc-menu-btn" data-action="add-leave" data-pid="3" data-type="${t}">${t}</button></td></tr>`;
html += `<td><button class="fc-menu-btn" data-action="add-leave" data-pid="3" data-type="${t}">${t}</button></td></tr>`;
}); });
html += `</tbody></table></div>`; html += `</tbody></table></div><button class="fc-menu-danger" data-action="clear-leaves">Effacer Alex/Laia</button></div>`;
html += `<button class="fc-menu-danger" data-action="clear-leaves">Effacer Alex/Laia</button></div>`;
menu.innerHTML = html; menu.innerHTML = html;
const menuWidth = 240; const menuWidth = 240;
let left = x + 10; let left = x + 10;
if (left + menuWidth > window.innerWidth) left = x - menuWidth - 10; if (left + menuWidth > window.innerWidth) left = x - menuWidth - 10;
menu.style.left = `${left}px`; menu.style.left = `${left}px`;
menu.style.top = `${y + 10}px`; menu.style.top = `${y + 10}px`;
menu.style.display = "block"; menu.style.display = "block";
this.menuJustOpened = true; this.menuJustOpened = true;
setTimeout(() => (this.menuJustOpened = false), 100); setTimeout(() => (this.menuJustOpened = false), 100);
} }
@@ -1012,11 +1135,7 @@ document.addEventListener("DOMContentLoaded", () => {
if (typesToClear.length) { if (typesToClear.length) {
await this.postApi( await this.postApi(
"/modules/family-calendar/includes/api/manage-event.php", "/modules/family-calendar/includes/api/manage-event.php",
{ { action: "bulk_delete_day_types", dates, types: typesToClear },
action: "bulk_delete_day_types",
dates,
types: typesToClear,
},
); );
} }
const payload = dates.map((d) => ({ const payload = dates.map((d) => ({
@@ -1037,20 +1156,12 @@ document.addEventListener("DOMContentLoaded", () => {
await this.postApi( await this.postApi(
"/modules/family-calendar/includes/api/manage-event.php", "/modules/family-calendar/includes/api/manage-event.php",
{ { action: "bulk_delete_day_types", dates, types: typesToClear },
action: "bulk_delete_day_types",
dates,
types: typesToClear,
},
); );
} else if (action === "add-leave") { } else if (action === "add-leave") {
await this.postApi( await this.postApi(
"/modules/family-calendar/includes/api/manage-leaf.php", "/modules/family-calendar/includes/api/manage-leaf.php",
{ { action: "bulk_delete_day_person", dates, person_id: pid },
action: "bulk_delete_day_person",
dates,
person_id: pid,
},
); );
const payload = dates.map((d) => ({ const payload = dates.map((d) => ({
date: d, date: d,
File diff suppressed because one or more lines are too long