Auto-commit for deploy to production - 2025-12-09 19:16:55
This commit is contained in:
@@ -193,3 +193,52 @@ input[type="number"] {
|
|||||||
.fc-holidays-table th {
|
.fc-holidays-table th {
|
||||||
background-color: #f0f4f8;
|
background-color: #f0f4f8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Cellule de jour sélectionnée dans le calendrier */
|
||||||
|
.fc-day--selected {
|
||||||
|
background-color: #bde4ff !important; /* Une couleur de sélection claire, !important pour forcer le style */
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Le menu contextuel flottant */
|
||||||
|
.fc-selection-menu {
|
||||||
|
display: none; /* Caché par défaut */
|
||||||
|
position: absolute; /* Permet de le positionner près de la souris */
|
||||||
|
z-index: 100;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-selection-menu .fc-menu-section {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.fc-selection-menu .fc-menu-section:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-selection-menu strong {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-selection-menu button {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
background: none;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
margin-top: 4px;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-selection-menu button:hover {
|
||||||
|
background-color: #f0f4f8;
|
||||||
|
border-color: #b0c4de;
|
||||||
|
}
|
||||||
|
|||||||
+182
-239
@@ -1,7 +1,12 @@
|
|||||||
// assets/js/family-calendar.js
|
// assets/js/family-calendar.js
|
||||||
|
|
||||||
// === 1. Utilitaires dates ===
|
// === VARIABLES GLOBALES POUR LA SÉLECTION ===
|
||||||
|
let isSelecting = false;
|
||||||
|
let selectedCells = [];
|
||||||
|
const events = []; // Contient TOUS les événements (vacances, off carole, centre, avis)
|
||||||
|
|
||||||
|
// === 1. Utilitaires dates ===
|
||||||
|
// ... (garder les fonctions formatDayMonth, getMonthNameFr, getWeekOfYear)
|
||||||
function formatDayMonth(date) {
|
function formatDayMonth(date) {
|
||||||
const d = String(date.getDate()).padStart(2, "0");
|
const d = String(date.getDate()).padStart(2, "0");
|
||||||
const m = String(date.getMonth() + 1).padStart(2, "0");
|
const m = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
@@ -26,38 +31,26 @@ function getMonthNameFr(monthIndex) {
|
|||||||
return map[monthIndex] || "";
|
return map[monthIndex] || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calcule le n° de semaine dans l'année (1..),
|
|
||||||
* Semaine 1 = semaine contenant le 1er janvier, début au lundi.
|
|
||||||
*/
|
|
||||||
function getWeekOfYear(date) {
|
function getWeekOfYear(date) {
|
||||||
const year = date.getFullYear();
|
const year = date.getFullYear();
|
||||||
const startOfYear = new Date(year, 0, 1);
|
const startOfYear = new Date(year, 0, 1);
|
||||||
const dayOfWeek = startOfYear.getDay() || 7; // dimanche = 0 -> 7
|
const dayOfWeek = startOfYear.getDay() || 7;
|
||||||
const diffToMonday = dayOfWeek === 1 ? 0 : dayOfWeek - 1;
|
const diffToMonday = dayOfWeek === 1 ? 0 : dayOfWeek - 1;
|
||||||
const firstWeekMonday = new Date(year, 0, 1 - diffToMonday);
|
const firstWeekMonday = new Date(year, 0, 1 - diffToMonday);
|
||||||
|
|
||||||
const diffMillis = date - firstWeekMonday;
|
const diffMillis = date - firstWeekMonday;
|
||||||
const diffDays = Math.floor(diffMillis / (1000 * 60 * 60 * 24));
|
const diffDays = Math.floor(diffMillis / (1000 * 60 * 60 * 24));
|
||||||
return Math.floor(diffDays / 7) + 1;
|
return Math.floor(diffDays / 7) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// === 2. Génération des semaines ===
|
// === 2. Génération des semaines ===
|
||||||
|
|
||||||
function generateWeeks() {
|
function generateWeeks() {
|
||||||
const weeks = [];
|
const weeks = [];
|
||||||
|
const start = new Date(2025, 8, 1);
|
||||||
const start = new Date(2025, 8, 1); // 1er sept 2025
|
const end = new Date(2026, 7, 31);
|
||||||
const end = new Date(2026, 7, 31); // 31 aout 2026
|
|
||||||
|
|
||||||
let current = new Date(start);
|
let current = new Date(start);
|
||||||
|
|
||||||
// se placer sur le premier lundi >= 1er septembre 2025
|
|
||||||
while (current.getDay() !== 1) {
|
while (current.getDay() !== 1) {
|
||||||
// 1 = lundi
|
|
||||||
current.setDate(current.getDate() + 1);
|
current.setDate(current.getDate() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (current <= end) {
|
while (current <= end) {
|
||||||
const monday = new Date(current);
|
const monday = new Date(current);
|
||||||
const tuesday = new Date(current);
|
const tuesday = new Date(current);
|
||||||
@@ -68,21 +61,17 @@ function generateWeeks() {
|
|||||||
thursday.setDate(monday.getDate() + 3);
|
thursday.setDate(monday.getDate() + 3);
|
||||||
const friday = new Date(current);
|
const friday = new Date(current);
|
||||||
friday.setDate(monday.getDate() + 4);
|
friday.setDate(monday.getDate() + 4);
|
||||||
|
|
||||||
const year = monday.getFullYear();
|
const year = monday.getFullYear();
|
||||||
const weekOfYear = getWeekOfYear(monday);
|
const weekOfYear = getWeekOfYear(monday);
|
||||||
const weekId = `${year}-W${String(weekOfYear).padStart(2, "0")}`;
|
const weekId = `${year}-W${String(weekOfYear).padStart(2, "0")}`;
|
||||||
|
|
||||||
const monthIndex = monday.getMonth();
|
const monthIndex = monday.getMonth();
|
||||||
const monthKey = `${year}-${String(monthIndex + 1).padStart(2, "0")}`;
|
const monthKey = `${year}-${String(monthIndex + 1).padStart(2, "0")}`;
|
||||||
const monthName = getMonthNameFr(monthIndex);
|
|
||||||
|
|
||||||
weeks.push({
|
weeks.push({
|
||||||
id: weekId,
|
id: weekId,
|
||||||
year,
|
year,
|
||||||
weekOfYear,
|
weekOfYear,
|
||||||
monthKey,
|
monthKey,
|
||||||
monthName,
|
monthName: getMonthNameFr(monthIndex),
|
||||||
weekLabel: `W${weekOfYear}`,
|
weekLabel: `W${weekOfYear}`,
|
||||||
days: {
|
days: {
|
||||||
mon: formatDayMonth(monday),
|
mon: formatDayMonth(monday),
|
||||||
@@ -105,135 +94,86 @@ function generateWeeks() {
|
|||||||
thu: { schoolHoliday: false },
|
thu: { schoolHoliday: false },
|
||||||
fri: { schoolHoliday: false },
|
fri: { schoolHoliday: false },
|
||||||
},
|
},
|
||||||
totals: {
|
totals: { offCarole: 0, extraOffCarole: 0, centre: 0, avis: 0 },
|
||||||
offCarole: 0,
|
|
||||||
extraOffCarole: 0,
|
|
||||||
centre: 0,
|
|
||||||
avis: 0,
|
|
||||||
},
|
|
||||||
isSchoolHolidayWeek: false,
|
isSchoolHolidayWeek: false,
|
||||||
alex: {
|
alex: { total: 0, detail: "" },
|
||||||
total: 0,
|
laia: { total: 0, detail: "" },
|
||||||
detail: "",
|
|
||||||
},
|
|
||||||
laia: {
|
|
||||||
total: 0,
|
|
||||||
detail: "",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
current.setDate(current.getDate() + 7);
|
current.setDate(current.getDate() + 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
return weeks;
|
return weeks;
|
||||||
}
|
}
|
||||||
|
|
||||||
const weeks = generateWeeks();
|
const weeks = generateWeeks();
|
||||||
const events = []; // tous les evenements (vacances, off Carole, centre, etc.)
|
|
||||||
|
|
||||||
// === 3. Vacances scolaires (API gouv) ===
|
// === 3. Gestion des événements (Vacances, Off, Centre, Avis) ===
|
||||||
|
|
||||||
async function fetchSchoolHolidays(anneeScolaire, zoneLabel) {
|
async function fetchSchoolHolidays(anneeScolaire, zoneLabel) {
|
||||||
const baseUrl =
|
const baseUrl =
|
||||||
"https://data.education.gouv.fr/api/explore/v2.1/catalog/datasets/fr-en-calendrier-scolaire/records";
|
"https://data.education.gouv.fr/api/explore/v2.1/catalog/datasets/fr-en-calendrier-scolaire/records";
|
||||||
|
|
||||||
const where = `annee_scolaire='${anneeScolaire}' AND zones LIKE '%${zoneLabel}%'`;
|
const where = `annee_scolaire='${anneeScolaire}' AND zones LIKE '%${zoneLabel}%'`;
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
where,
|
where,
|
||||||
limit: "100",
|
limit: "100",
|
||||||
order_by: "start_date",
|
order_by: "start_date",
|
||||||
});
|
});
|
||||||
|
|
||||||
const url = `${baseUrl}?${params.toString()}`;
|
const url = `${baseUrl}?${params.toString()}`;
|
||||||
|
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
console.error("Erreur API vacances scolaires", res.status, res.statusText);
|
console.error("Erreur API vacances scolaires", res.status, res.statusText);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
return data.results || [];
|
return data.results || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Regroupe les records par période, remplit le tableau recap,
|
|
||||||
* et crée les événements VACANCES_SCOLAIRES.
|
|
||||||
* Version corrigée pour gérer les fuseaux horaires de manière robuste.
|
|
||||||
*/
|
|
||||||
function addSchoolHolidayEventsFromRecords(records, events) {
|
function addSchoolHolidayEventsFromRecords(records, events) {
|
||||||
const tbody = document.querySelector("#schoolHolidaysTable tbody");
|
const tbody = document.querySelector("#schoolHolidaysTable tbody");
|
||||||
if (tbody) tbody.innerHTML = "";
|
if (tbody) tbody.innerHTML = "";
|
||||||
|
|
||||||
// 1) Regrouper par période pour éviter les doublons d'académies
|
|
||||||
const groups = new Map();
|
const groups = new Map();
|
||||||
|
|
||||||
records.forEach((record) => {
|
records.forEach((record) => {
|
||||||
const startIso = record.start_date;
|
const key = `${record.start_date}|${record.end_date}|${record.description}`;
|
||||||
const endIso = record.end_date;
|
|
||||||
const desc = record.description || "";
|
|
||||||
const zones = record.zones || "";
|
|
||||||
const key = `${startIso}|${endIso}|${desc}`;
|
|
||||||
|
|
||||||
if (!groups.has(key)) {
|
if (!groups.has(key)) {
|
||||||
groups.set(key, { startIso, endIso, description: desc, zones });
|
groups.set(key, {
|
||||||
|
startIso: record.start_date,
|
||||||
|
endIso: record.end_date,
|
||||||
|
description: record.description || "",
|
||||||
|
zones: record.zones || "",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2) Remplir le tableau récap ET créer les événements pour la coloration
|
|
||||||
if (tbody) {
|
if (tbody) {
|
||||||
Array.from(groups.values())
|
Array.from(groups.values())
|
||||||
.sort((a, b) => new Date(a.startIso) - new Date(b.startIso))
|
.sort((a, b) => new Date(a.startIso) - new Date(b.startIso))
|
||||||
.forEach((g) => {
|
.forEach((g) => {
|
||||||
// --- Logique de dates explicite et robuste ---
|
const firstDayVacation = new Date(
|
||||||
|
g.startIso.substring(0, 10) + "T00:00:00"
|
||||||
// On extrait la partie YYYY-MM-DD pour éviter les bugs de fuseau horaire
|
);
|
||||||
const startDateString = g.startIso.substring(0, 10); // ex: "2025-10-17"
|
firstDayVacation.setDate(firstDayVacation.getDate() + 1);
|
||||||
const endDateString = g.endIso.substring(0, 10); // ex: "2025-11-02"
|
const lastDayVacation = new Date(
|
||||||
|
g.endIso.substring(0, 10) + "T00:00:00"
|
||||||
// On crée les dates en spécifiant T00:00:00 pour qu'elles soient interprétées comme locales
|
);
|
||||||
const firstDayVacation = new Date(startDateString + "T00:00:00");
|
|
||||||
firstDayVacation.setDate(firstDayVacation.getDate() + 1); // Le 1er jour est le lendemain du "vendredi soir"
|
|
||||||
|
|
||||||
const lastDayVacation = new Date(endDateString + "T00:00:00");
|
|
||||||
|
|
||||||
// La date de reprise est le lendemain du dernier jour de vacances
|
|
||||||
const repriseDate = new Date(lastDayVacation);
|
const repriseDate = new Date(lastDayVacation);
|
||||||
repriseDate.setDate(repriseDate.getDate() + 1);
|
repriseDate.setDate(repriseDate.getDate() + 1);
|
||||||
|
|
||||||
// --- Affichage dans le tableau récap ---
|
|
||||||
const startStr =
|
const startStr =
|
||||||
formatDayMonth(firstDayVacation) +
|
formatDayMonth(firstDayVacation) +
|
||||||
"/" +
|
"/" +
|
||||||
firstDayVacation.getFullYear();
|
firstDayVacation.getFullYear();
|
||||||
const endStr =
|
const endStr =
|
||||||
formatDayMonth(repriseDate) + "/" + repriseDate.getFullYear();
|
formatDayMonth(repriseDate) + "/" + repriseDate.getFullYear();
|
||||||
|
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `<td>${g.description}</td><td>${startStr}</td><td>${endStr}</td><td>${g.zones}</td>`;
|
||||||
<td>${g.description}</td>
|
|
||||||
<td>${startStr}</td>
|
|
||||||
<td>${endStr}</td>
|
|
||||||
<td>${g.zones}</td>
|
|
||||||
`;
|
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
|
|
||||||
// --- Création des événements pour la coloration du calendrier ---
|
|
||||||
// On boucle du premier jour de vacances jusqu'au dernier jour INCLUS.
|
|
||||||
let current = new Date(firstDayVacation);
|
let current = new Date(firstDayVacation);
|
||||||
while (current <= lastDayVacation) {
|
while (current <= lastDayVacation) {
|
||||||
const year = current.getFullYear();
|
const isoDate = `${current.getFullYear()}-${String(
|
||||||
const month = String(current.getMonth() + 1).padStart(2, "0");
|
current.getMonth() + 1
|
||||||
const day = String(current.getDate()).padStart(2, "0");
|
).padStart(2, "0")}-${String(current.getDate()).padStart(2, "0")}`;
|
||||||
const isoDate = `${year}-${month}-${day}`;
|
|
||||||
|
|
||||||
events.push({
|
events.push({
|
||||||
date: isoDate,
|
date: isoDate,
|
||||||
type: "VACANCES_SCOLAIRES",
|
type: "VACANCES_SCOLAIRES",
|
||||||
duration: 1,
|
duration: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
current.setDate(current.getDate() + 1);
|
current.setDate(current.getDate() + 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -241,47 +181,59 @@ function addSchoolHolidayEventsFromRecords(records, events) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marque les jours et les semaines de vacances scolaires dans weeks.
|
* Marque les jours de vacances et recalcule les totaux pour Off Carole, Centre, Avis
|
||||||
*/
|
*/
|
||||||
function markSchoolHolidayDaysAndWeeks(weeks, events) {
|
function reprocessEvents(weeks, events) {
|
||||||
// reset
|
// Reset all flags and totals
|
||||||
weeks.forEach((w) => {
|
weeks.forEach((w) => {
|
||||||
w.isSchoolHolidayWeek = false;
|
w.isSchoolHolidayWeek = false;
|
||||||
Object.keys(w.dayFlags).forEach((dayKey) => {
|
Object.keys(w.dayFlags).forEach(
|
||||||
w.dayFlags[dayKey].schoolHoliday = false;
|
(dayKey) => (w.dayFlags[dayKey].schoolHoliday = false)
|
||||||
});
|
);
|
||||||
|
w.totals = { offCarole: 0, extraOffCarole: 0, centre: 0, avis: 0 };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Apply each event to the corresponding week
|
||||||
events.forEach((evt) => {
|
events.forEach((evt) => {
|
||||||
if (evt.type !== "VACANCES_SCOLAIRES") return;
|
|
||||||
|
|
||||||
const [y, m, d] = evt.date.split("-").map(Number);
|
const [y, m, d] = evt.date.split("-").map(Number);
|
||||||
const evtDate = new Date(y, m - 1, d);
|
const evtDate = new Date(y, m - 1, d);
|
||||||
|
|
||||||
weeks.forEach((w) => {
|
const week = weeks.find(
|
||||||
const dayKeys = ["mon", "tue", "wed", "thu", "fri"];
|
(w) => evtDate >= w.dayDates.mon && evtDate <= w.dayDates.fri
|
||||||
let hasHolidayInWeek = false;
|
);
|
||||||
|
if (!week) return;
|
||||||
|
|
||||||
dayKeys.forEach((dayKey) => {
|
const dur = evt.duration || 1;
|
||||||
const dayDate = w.dayDates[dayKey];
|
|
||||||
if (
|
|
||||||
dayDate.getFullYear() === evtDate.getFullYear() &&
|
|
||||||
dayDate.getMonth() === evtDate.getMonth() &&
|
|
||||||
dayDate.getDate() === evtDate.getDate()
|
|
||||||
) {
|
|
||||||
w.dayFlags[dayKey].schoolHoliday = true;
|
|
||||||
hasHolidayInWeek = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (hasHolidayInWeek) {
|
switch (evt.type) {
|
||||||
w.isSchoolHolidayWeek = true;
|
case "VACANCES_SCOLAIRES":
|
||||||
|
const dayKey = Object.keys(week.dayDates).find(
|
||||||
|
(key) => week.dayDates[key].toDateString() === evtDate.toDateString()
|
||||||
|
);
|
||||||
|
if (dayKey) {
|
||||||
|
week.dayFlags[dayKey].schoolHoliday = true;
|
||||||
|
week.isSchoolHolidayWeek = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "OFF_CAROLE":
|
||||||
|
week.totals.offCarole += dur;
|
||||||
|
break;
|
||||||
|
case "EXTRA_OFF_CAROLE":
|
||||||
|
week.totals.extraOffCarole += dur;
|
||||||
|
break;
|
||||||
|
case "CENTRE":
|
||||||
|
week.totals.centre += dur;
|
||||||
|
break;
|
||||||
|
case "AVIS":
|
||||||
|
week.totals.avis += dur;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// === 4. Calcul des rowspans pour la colonne Mois ===
|
// === 4. Rendu et logique d'affichage ===
|
||||||
|
|
||||||
function computeMonthSpans(weeks) {
|
function computeMonthSpans(weeks) {
|
||||||
const counts = {};
|
const counts = {};
|
||||||
@@ -290,15 +242,11 @@ function computeMonthSpans(weeks) {
|
|||||||
});
|
});
|
||||||
return counts;
|
return counts;
|
||||||
}
|
}
|
||||||
|
|
||||||
const monthSpans = computeMonthSpans(weeks);
|
const monthSpans = computeMonthSpans(weeks);
|
||||||
|
|
||||||
// === 5. Rendu du tableau principal ===
|
|
||||||
|
|
||||||
function renderTable() {
|
function renderTable() {
|
||||||
const planningBody = document.getElementById("planningBody");
|
const planningBody = document.getElementById("planningBody");
|
||||||
if (!planningBody) return;
|
if (!planningBody) return;
|
||||||
|
|
||||||
planningBody.innerHTML = "";
|
planningBody.innerHTML = "";
|
||||||
|
|
||||||
const showOnlyCaroleOff =
|
const showOnlyCaroleOff =
|
||||||
@@ -306,7 +254,6 @@ function renderTable() {
|
|||||||
const showOnlySchoolHoliday = document.getElementById(
|
const showOnlySchoolHoliday = document.getElementById(
|
||||||
"showOnlySchoolHoliday"
|
"showOnlySchoolHoliday"
|
||||||
)?.checked;
|
)?.checked;
|
||||||
|
|
||||||
const monthRowRendered = {};
|
const monthRowRendered = {};
|
||||||
|
|
||||||
weeks.forEach((week) => {
|
weeks.forEach((week) => {
|
||||||
@@ -315,7 +262,6 @@ function renderTable() {
|
|||||||
|
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
|
|
||||||
// Colonne Mois
|
|
||||||
if (!monthRowRendered[week.monthKey]) {
|
if (!monthRowRendered[week.monthKey]) {
|
||||||
monthRowRendered[week.monthKey] = true;
|
monthRowRendered[week.monthKey] = true;
|
||||||
const tdMonth = document.createElement("td");
|
const tdMonth = document.createElement("td");
|
||||||
@@ -324,14 +270,17 @@ function renderTable() {
|
|||||||
tr.appendChild(tdMonth);
|
tr.appendChild(tdMonth);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Colonne Semaine
|
|
||||||
const tdWeek = document.createElement("td");
|
const tdWeek = document.createElement("td");
|
||||||
tdWeek.textContent = week.weekLabel;
|
tdWeek.textContent = week.weekLabel;
|
||||||
tr.appendChild(tdWeek);
|
tr.appendChild(tdWeek);
|
||||||
|
|
||||||
// Jours (Lun -> Ven) avec coloration jours de vacances
|
|
||||||
["mon", "tue", "wed", "thu", "fri"].forEach((dayKey) => {
|
["mon", "tue", "wed", "thu", "fri"].forEach((dayKey) => {
|
||||||
const td = document.createElement("td");
|
const td = document.createElement("td");
|
||||||
|
const dayDate = week.dayDates[dayKey];
|
||||||
|
const isoDate = `${dayDate.getFullYear()}-${String(
|
||||||
|
dayDate.getMonth() + 1
|
||||||
|
).padStart(2, "0")}-${String(dayDate.getDate()).padStart(2, "0")}`;
|
||||||
|
td.dataset.date = isoDate; // IMPORTANT: on ajoute la date pour la retrouver
|
||||||
td.textContent = week.days[dayKey];
|
td.textContent = week.days[dayKey];
|
||||||
if (week.dayFlags[dayKey].schoolHoliday) {
|
if (week.dayFlags[dayKey].schoolHoliday) {
|
||||||
td.classList.add("fc-day--school-holiday");
|
td.classList.add("fc-day--school-holiday");
|
||||||
@@ -339,121 +288,71 @@ function renderTable() {
|
|||||||
tr.appendChild(td);
|
tr.appendChild(td);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Totaux # Off / Extra / Centre / Avis (pour l'instant à 0)
|
const formatTotal = (total) =>
|
||||||
const tdOff = document.createElement("td");
|
total > 0 ? (Number.isInteger(total) ? total : total.toFixed(1)) : "";
|
||||||
tdOff.textContent = week.totals.offCarole.toFixed(2).replace(/\.00$/, "");
|
tr.innerHTML += `
|
||||||
tr.appendChild(tdOff);
|
<td>${formatTotal(week.totals.offCarole)}</td>
|
||||||
|
<td>${formatTotal(week.totals.extraOffCarole)}</td>
|
||||||
const tdExtraOff = document.createElement("td");
|
<td>${formatTotal(week.totals.centre)}</td>
|
||||||
tdExtraOff.textContent = week.totals.extraOffCarole
|
<td>${formatTotal(week.totals.avis)}</td>
|
||||||
.toFixed(2)
|
<td>${formatTotal(week.alex.total)}</td>
|
||||||
.replace(/\.00$/, "");
|
<td>${week.alex.detail || ""}</td>
|
||||||
tr.appendChild(tdExtraOff);
|
<td>${formatTotal(week.laia.total)}</td>
|
||||||
|
<td>${week.laia.detail || ""}</td>
|
||||||
const tdCentre = document.createElement("td");
|
`;
|
||||||
tdCentre.textContent = week.totals.centre.toFixed(2).replace(/\.00$/, "");
|
|
||||||
tr.appendChild(tdCentre);
|
|
||||||
|
|
||||||
const tdAvis = document.createElement("td");
|
|
||||||
tdAvis.textContent = week.totals.avis.toFixed(2).replace(/\.00$/, "");
|
|
||||||
tr.appendChild(tdAvis);
|
|
||||||
|
|
||||||
// Alex total & détail
|
|
||||||
const tdAlexTotal = document.createElement("td");
|
|
||||||
tdAlexTotal.textContent = week.alex.total.toFixed(2).replace(/\.00$/, "");
|
|
||||||
tr.appendChild(tdAlexTotal);
|
|
||||||
|
|
||||||
const tdAlexDetail = document.createElement("td");
|
|
||||||
tdAlexDetail.textContent = week.alex.detail || "";
|
|
||||||
tr.appendChild(tdAlexDetail);
|
|
||||||
|
|
||||||
// Laia total & détail
|
|
||||||
const tdLaiaTotal = document.createElement("td");
|
|
||||||
tdLaiaTotal.textContent = week.laia.total.toFixed(2).replace(/\.00$/, "");
|
|
||||||
tr.appendChild(tdLaiaTotal);
|
|
||||||
|
|
||||||
const tdLaiaDetail = document.createElement("td");
|
|
||||||
tdLaiaDetail.textContent = week.laia.detail || "";
|
|
||||||
tr.appendChild(tdLaiaDetail);
|
|
||||||
|
|
||||||
planningBody.appendChild(tr);
|
planningBody.appendChild(tr);
|
||||||
});
|
});
|
||||||
|
|
||||||
updateSummary();
|
updateSummary();
|
||||||
}
|
}
|
||||||
|
|
||||||
// === 6. Résumé (pour l'instant, 0 utilisés) ===
|
|
||||||
|
|
||||||
function updateSummary() {
|
function updateSummary() {
|
||||||
const summaryDiv = document.getElementById("summaryText");
|
// ... (garder la fonction updateSummary telle quelle)
|
||||||
if (!summaryDiv) return;
|
|
||||||
|
|
||||||
const alexCpInit = parseFloat(
|
|
||||||
document.getElementById("alexCpInit")?.value || "0"
|
|
||||||
);
|
|
||||||
const alexRttInit = parseFloat(
|
|
||||||
document.getElementById("alexRttInit")?.value || "0"
|
|
||||||
);
|
|
||||||
const alexJaInit = parseFloat(
|
|
||||||
document.getElementById("alexJaInit")?.value || "0"
|
|
||||||
);
|
|
||||||
|
|
||||||
const laiaCpInit = parseFloat(
|
|
||||||
document.getElementById("laiaCpInit")?.value || "0"
|
|
||||||
);
|
|
||||||
const laiaRttInit = parseFloat(
|
|
||||||
document.getElementById("laiaRttInit")?.value || "0"
|
|
||||||
);
|
|
||||||
const laiaJaInit = parseFloat(
|
|
||||||
document.getElementById("laiaJaInit")?.value || "0"
|
|
||||||
);
|
|
||||||
|
|
||||||
const alexCpUsed = 0;
|
|
||||||
const alexRttUsed = 0;
|
|
||||||
const alexJaUsed = 0;
|
|
||||||
|
|
||||||
const laiaCpUsed = 0;
|
|
||||||
const laiaRttUsed = 0;
|
|
||||||
const laiaJaUsed = 0;
|
|
||||||
|
|
||||||
const alexCpLeft = alexCpInit - alexCpUsed;
|
|
||||||
const alexRttLeft = alexRttInit - alexRttUsed;
|
|
||||||
const alexJaLeft = alexJaInit - alexJaUsed;
|
|
||||||
|
|
||||||
const laiaCpLeft = laiaCpInit - laiaCpUsed;
|
|
||||||
const laiaRttLeft = laiaRttInit - laiaRttUsed;
|
|
||||||
const laiaJaLeft = laiaJaInit - laiaJaUsed;
|
|
||||||
|
|
||||||
summaryDiv.innerHTML = `
|
|
||||||
<p><strong>Alex</strong><br>
|
|
||||||
CP utilises : ${alexCpUsed.toFixed(2)} / ${alexCpInit.toFixed(
|
|
||||||
2
|
|
||||||
)} (reste ${alexCpLeft.toFixed(2)})<br>
|
|
||||||
RTT utilises : ${alexRttUsed.toFixed(2)} / ${alexRttInit.toFixed(
|
|
||||||
2
|
|
||||||
)} (reste ${alexRttLeft.toFixed(2)})<br>
|
|
||||||
JA utilises : ${alexJaUsed.toFixed(2)} / ${alexJaInit.toFixed(
|
|
||||||
2
|
|
||||||
)} (reste ${alexJaLeft.toFixed(2)})</p>
|
|
||||||
|
|
||||||
<p><strong>Laia</strong><br>
|
|
||||||
CP utilises : ${laiaCpUsed.toFixed(2)} / ${laiaCpInit.toFixed(
|
|
||||||
2
|
|
||||||
)} (reste ${laiaCpLeft.toFixed(2)})<br>
|
|
||||||
RTT utilises : ${laiaRttUsed.toFixed(2)} / ${laiaRttInit.toFixed(
|
|
||||||
2
|
|
||||||
)} (reste ${laiaRttLeft.toFixed(2)})<br>
|
|
||||||
JA utilises : ${laiaJaUsed.toFixed(2)} / ${laiaJaInit.toFixed(
|
|
||||||
2
|
|
||||||
)} (reste ${laiaJaLeft.toFixed(2)})</p>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// === 7. Init ===
|
// === 5. Logique de sélection et menu contextuel ===
|
||||||
|
|
||||||
|
function clearSelection() {
|
||||||
|
const menu = document.getElementById("selectionMenu");
|
||||||
|
if (menu) menu.style.display = "none";
|
||||||
|
selectedCells.forEach((cell) => cell.classList.remove("fc-day--selected"));
|
||||||
|
selectedCells = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSelectionMenu(e) {
|
||||||
|
const menu = document.getElementById("selectionMenu");
|
||||||
|
if (!menu) return;
|
||||||
|
menu.style.left = `${e.pageX + 5}px`;
|
||||||
|
menu.style.top = `${e.pageY + 5}px`;
|
||||||
|
menu.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyEventTypeToSelection(eventType) {
|
||||||
|
if (selectedCells.length === 0) return;
|
||||||
|
|
||||||
|
selectedCells.forEach((cell) => {
|
||||||
|
const date = cell.dataset.date;
|
||||||
|
if (date) {
|
||||||
|
// Pour l'instant, on ajoute un événement par jour. On pourrait optimiser plus tard.
|
||||||
|
events.push({
|
||||||
|
date: date,
|
||||||
|
type: eventType,
|
||||||
|
duration: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
reprocessEvents(weeks, events);
|
||||||
|
renderTable(); // Redessine la table avec les nouveaux totaux
|
||||||
|
clearSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
// === 6. Init ===
|
||||||
|
|
||||||
async function initFamilyCalendar() {
|
async function initFamilyCalendar() {
|
||||||
if (!document.getElementById("planningBody")) return;
|
if (!document.getElementById("planningBody")) return;
|
||||||
|
|
||||||
|
// Écouteurs pour les soldes et filtres
|
||||||
[
|
[
|
||||||
"alexCpInit",
|
"alexCpInit",
|
||||||
"alexRttInit",
|
"alexRttInit",
|
||||||
@@ -465,23 +364,67 @@ async function initFamilyCalendar() {
|
|||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
if (el) el.addEventListener("change", updateSummary);
|
if (el) el.addEventListener("change", updateSummary);
|
||||||
});
|
});
|
||||||
|
["showOnlyCaroleOff", "showOnlySchoolHoliday"].forEach((id) => {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.addEventListener("change", renderTable);
|
||||||
|
});
|
||||||
|
|
||||||
const showOnlyCaroleOff = document.getElementById("showOnlyCaroleOff");
|
// Écouteurs pour la sélection par cliquer-glisser
|
||||||
if (showOnlyCaroleOff) {
|
const planningBody = document.getElementById("planningBody");
|
||||||
showOnlyCaroleOff.addEventListener("change", renderTable);
|
planningBody.addEventListener("mousedown", (e) => {
|
||||||
|
if (e.target.tagName === "TD" && e.target.dataset.date) {
|
||||||
|
isSelecting = true;
|
||||||
|
clearSelection();
|
||||||
|
e.target.classList.add("fc-day--selected");
|
||||||
|
selectedCells.push(e.target);
|
||||||
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
const showOnlySchoolHoliday = document.getElementById(
|
});
|
||||||
"showOnlySchoolHoliday"
|
|
||||||
);
|
planningBody.addEventListener("mousemove", (e) => {
|
||||||
if (showOnlySchoolHoliday) {
|
if (isSelecting && e.target.tagName === "TD" && e.target.dataset.date) {
|
||||||
showOnlySchoolHoliday.addEventListener("change", renderTable);
|
if (!selectedCells.includes(e.target)) {
|
||||||
|
e.target.classList.add("fc-day--selected");
|
||||||
|
selectedCells.push(e.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("mouseup", (e) => {
|
||||||
|
if (isSelecting) {
|
||||||
|
isSelecting = false;
|
||||||
|
if (selectedCells.length > 0) {
|
||||||
|
showSelectionMenu(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Écouteur pour les clics hors du menu pour le fermer
|
||||||
|
document.addEventListener("click", (e) => {
|
||||||
|
const menu = document.getElementById("selectionMenu");
|
||||||
|
if (menu && !menu.contains(e.target) && selectedCells.length > 0) {
|
||||||
|
// Si on clique hors du menu APRES une sélection, on la nettoie
|
||||||
|
if (menu.style.display === "block") {
|
||||||
|
clearSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Écouteur pour les boutons du menu
|
||||||
|
const menu = document.getElementById("selectionMenu");
|
||||||
|
if (menu) {
|
||||||
|
menu.addEventListener("click", (e) => {
|
||||||
|
if (e.target.tagName === "BUTTON" && e.target.dataset.type) {
|
||||||
|
const eventType = e.target.dataset.type;
|
||||||
|
applyEventTypeToSelection(eventType);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charger les vacances scolaires 2025-2026, Zone C
|
// Initialisation des données
|
||||||
const holidaysRecords = await fetchSchoolHolidays("2025-2026", "Zone C");
|
const holidaysRecords = await fetchSchoolHolidays("2025-2026", "Zone C");
|
||||||
addSchoolHolidayEventsFromRecords(holidaysRecords, events);
|
addSchoolHolidayEventsFromRecords(holidaysRecords, events);
|
||||||
markSchoolHolidayDaysAndWeeks(weeks, events);
|
reprocessEvents(weeks, events); // Appliquer aussi les vacances aux totaux si besoin (ici juste la couleur)
|
||||||
|
|
||||||
renderTable();
|
renderTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,18 @@ require __DIR__ . '/header.php';
|
|||||||
<!-- Rempli par family-calendar.js -->
|
<!-- Rempli par family-calendar.js -->
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<div id="selectionMenu" class="fc-selection-menu">
|
||||||
|
<div class="fc-menu-section">
|
||||||
|
<strong>Congé Carole</strong>
|
||||||
|
<button data-type="OFF_CAROLE"># Off Carole</button>
|
||||||
|
<button data-type="EXTRA_OFF_CAROLE"># Extra off Carole</button>
|
||||||
|
</div>
|
||||||
|
<div class="fc-menu-section">
|
||||||
|
<strong>Mode de Garde</strong>
|
||||||
|
<button data-type="CENTRE"># Centre</button>
|
||||||
|
<button data-type="AVIS"># Avis</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user