1 parent
cfe8962bd4
commit
a2e0904132
7 files changed
+536
-442
No files matched your search
@@ -481,7 +481,7 @@ td.col-laia-sub {
|
||||
width: 40px;
|
||||
min-width: 40px;
|
||||
text-align: center;
|
||||
padding: 4px; /* Réduit les marges internes */
|
||||
padding-bottom: 10px; /* Réduit les marges internes */
|
||||
}
|
||||
|
||||
/* 3. On s'assure que le tableau utilise tout l'espace disponible intelligemment */
|
||||
@@ -536,7 +536,7 @@ td.col-laia-sub {
|
||||
#planningTable tbody td.col-sticky-sem {
|
||||
writing-mode: horizontal-tb;
|
||||
transform: none;
|
||||
padding: 6px !important;
|
||||
padding-bottom: 10px !important;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -660,27 +660,32 @@ td.col-laia-sub {
|
||||
#selectionMenu,
|
||||
#fc-month-selectionMenu,
|
||||
.fc-selection-menu {
|
||||
position: absolute;
|
||||
z-index: 9000;
|
||||
position: fixed !important;
|
||||
top: 50% !important;
|
||||
left: 50% !important;
|
||||
transform: translate(-50%, -50%) !important;
|
||||
z-index: 9999;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(8px);
|
||||
border: 1px solid rgba(226, 232, 240, 0.8);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2) !important;
|
||||
border-radius: 12px;
|
||||
padding: 8px 12px;
|
||||
min-width: 210px;
|
||||
padding: 16px;
|
||||
width: 90%;
|
||||
max-width: 320px;
|
||||
max-height: 85vh;
|
||||
overflow-y: auto;
|
||||
display: none;
|
||||
animation: menuPopIn 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
transform-origin: top center;
|
||||
animation: menuPopCenter 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards !important;
|
||||
}
|
||||
@keyframes menuPopIn {
|
||||
@keyframes menuPopCenter {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95) translateY(10px);
|
||||
transform: translate(-50%, -45%) scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -917,30 +922,6 @@ td.col-laia-sub {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#selectionMenu,
|
||||
#fc-month-selectionMenu,
|
||||
.fc-selection-menu {
|
||||
position: fixed !important;
|
||||
top: auto !important;
|
||||
left: 0 !important;
|
||||
bottom: 0 !important;
|
||||
width: 100% !important;
|
||||
border-radius: 24px 24px 0 0;
|
||||
padding: 24px 20px;
|
||||
transform: translateY(100%);
|
||||
animation: slideUpSheet 0.3s forwards;
|
||||
z-index: 10000 !important;
|
||||
}
|
||||
#selectionMenu::before,
|
||||
#fc-month-selectionMenu::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 5px;
|
||||
background: #cbd5e1;
|
||||
border-radius: 10px;
|
||||
margin: 0 auto 15px auto;
|
||||
}
|
||||
.fc-two-months-container,
|
||||
.fc-three-months-container {
|
||||
display: flex !important;
|
||||
@@ -977,7 +958,6 @@ td.col-laia-sub {
|
||||
font-size: 0.85rem !important;
|
||||
}
|
||||
|
||||
/* 3. CORRECTION CRITIQUE : Le décalage "Sticky" de la colonne Semaine */
|
||||
/* Puisque la colonne Mois fait 35px, la colonne Semaine doit démarrer à 35px (et non plus 54px) */
|
||||
#planningTable tbody td.col-sticky-sem,
|
||||
#planningTable thead tr th.col-sticky-sem {
|
||||
@@ -1037,3 +1017,112 @@ td.col-laia-sub {
|
||||
#planningTable thead tr:nth-child(3) th {
|
||||
top: 64px !important;
|
||||
}
|
||||
|
||||
#planningTable th.rotated-text {
|
||||
height: 120px; /* Augmente la hauteur pour laisser respirer le texte */
|
||||
vertical-align: bottom;
|
||||
padding-bottom: 10px !important;
|
||||
}
|
||||
.rotated-text span {
|
||||
white-space: nowrap; /* Force le texte sur une seule ligne */
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* --- RÉCAPITULATIF INTERVENANT (DASHBOARD) --- */
|
||||
.fc-recap-controls {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.fc-recap-custom-dates {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
background: var(--pf-bg-lighter);
|
||||
padding: 4px 8px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--pf-border);
|
||||
}
|
||||
.fc-recap-kpi-container {
|
||||
background: var(--pf-bg-lighter);
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--pf-border);
|
||||
padding: 20px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.fc-recap-kpi-main {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--pf-border);
|
||||
}
|
||||
.fc-recap-kpi-main-label {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.fc-recap-kpi-main-val {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 800;
|
||||
color: var(--pf-primary);
|
||||
line-height: 1;
|
||||
}
|
||||
.fc-recap-kpi-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
.fc-recap-kpi-card {
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--pf-border);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
box-shadow: var(--pf-shadow-sm);
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
.fc-recap-kpi-card:hover {
|
||||
border-color: var(--warning);
|
||||
}
|
||||
.fc-recap-kpi-label {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.fc-recap-kpi-val {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 800;
|
||||
color: var(--warning);
|
||||
line-height: 1;
|
||||
}
|
||||
.fc-recap-empty {
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
padding: 20px;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.fc-recap-error {
|
||||
text-align: center;
|
||||
color: var(--danger);
|
||||
font-weight: bold;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .fc-recap-custom-dates,
|
||||
[data-theme="dark"] .fc-recap-kpi-container {
|
||||
background: var(--bg-panel);
|
||||
border-color: var(--border-light);
|
||||
}
|
||||
[data-theme="dark"] .fc-recap-kpi-card {
|
||||
background: var(--bg-page);
|
||||
}
|
||||
@@ -782,6 +782,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
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(
|
||||
@@ -793,9 +794,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const currentY = new Date().getFullYear();
|
||||
for (let y = currentY - 2; y <= currentY + 5; y++)
|
||||
for (let y = currentY - 2; y <= currentY + 5; y++) {
|
||||
selectYear.add(new Option(y, y));
|
||||
}
|
||||
|
||||
const handleChange = () => {
|
||||
this.currentMonth = new Date(
|
||||
@@ -805,11 +808,248 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
);
|
||||
this.renderMonthCalendar();
|
||||
};
|
||||
|
||||
selectMonth.addEventListener("change", handleChange);
|
||||
selectYear.addEventListener("change", handleChange);
|
||||
|
||||
// --- Selecteurs Recap Intervenant (Logique masquée, pilotée par le nouveau DOM généré) ---
|
||||
const recapMonth = document.getElementById("recapHelperMonth");
|
||||
const recapYear = document.getElementById("recapHelperYear");
|
||||
|
||||
if (recapMonth && recapYear) {
|
||||
recapMonth.add(new Option("Année complète", "all"));
|
||||
recapMonth.add(new Option("Période Spécifique...", "custom"));
|
||||
for (let i = 0; i < 12; i++) {
|
||||
recapMonth.add(
|
||||
new Option(
|
||||
new Intl.DateTimeFormat(lang, { month: "long" }).format(
|
||||
new Date(2000, i, 1),
|
||||
),
|
||||
i + 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
for (let y = currentY - 2; y <= currentY + 5; y++) {
|
||||
recapYear.add(new Option(y, y));
|
||||
}
|
||||
recapMonth.value = new Date().getMonth() + 1;
|
||||
recapYear.value = currentY;
|
||||
|
||||
// L'écouteur est conservé si appelé manuellement, mais l'interaction se fera via les selecteurs injectés
|
||||
}
|
||||
}
|
||||
|
||||
renderHelperRecap() {
|
||||
const content = document.getElementById("recapHelperContent");
|
||||
const hiddenMonthSel = document.getElementById("recapHelperMonth");
|
||||
const hiddenYearSel = document.getElementById("recapHelperYear");
|
||||
const hiddenStartInp = document.getElementById("recapHelperStart");
|
||||
const hiddenEndInp = document.getElementById("recapHelperEnd");
|
||||
|
||||
if (
|
||||
!content ||
|
||||
!hiddenMonthSel ||
|
||||
!hiddenYearSel ||
|
||||
this.helpers.length === 0
|
||||
)
|
||||
return;
|
||||
|
||||
const mStr = hiddenMonthSel.value;
|
||||
const y = parseInt(hiddenYearSel.value);
|
||||
let startDateStr, endDateStr;
|
||||
let isCustom = mStr === "custom";
|
||||
|
||||
if (isCustom) {
|
||||
startDateStr = hiddenStartInp.value;
|
||||
endDateStr = hiddenEndInp.value;
|
||||
} else if (mStr === "all") {
|
||||
startDateStr = `${y}-01-01`;
|
||||
endDateStr = `${y}-12-31`;
|
||||
} else {
|
||||
const m = parseInt(mStr);
|
||||
startDateStr = `${y}-${String(m).padStart(2, "0")}-01`;
|
||||
const lastDay = new Date(y, m, 0).getDate();
|
||||
endDateStr = `${y}-${String(m).padStart(2, "0")}-${String(lastDay).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
let theoreticalWorkingDays = 0;
|
||||
let hasValidDates = true;
|
||||
|
||||
if (
|
||||
isCustom &&
|
||||
(!startDateStr || !endDateStr || startDateStr > endDateStr)
|
||||
) {
|
||||
hasValidDates = false;
|
||||
} else {
|
||||
let dObj = new Date(startDateStr + "T00:00:00");
|
||||
const endObj = new Date(endDateStr + "T00:00:00");
|
||||
|
||||
while (dObj <= endObj) {
|
||||
const dayOfWeek = dObj.getDay();
|
||||
if (dayOfWeek !== 0 && dayOfWeek !== 6) {
|
||||
const iso = this.getLocalIsoDate(dObj);
|
||||
if (!this.publicHolidayDates.has(iso)) theoreticalWorkingDays++;
|
||||
}
|
||||
dObj.setDate(dObj.getDate() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
let html = "";
|
||||
this.helpers.forEach((h) => {
|
||||
const hLeaves =
|
||||
this.leaveMatrix[h.id] || this.leaveMatrix[Number(h.id)] || [];
|
||||
|
||||
const helperEvents = [...this.leaves, ...this.events]
|
||||
.filter((e) => {
|
||||
const eDate = e.leave_date || e.date;
|
||||
const eType = e.leave_type || e.type;
|
||||
const isOldOff =
|
||||
eType === "HELPER_OFF" ||
|
||||
eType === "HELPER_EXTRA" ||
|
||||
eType.includes("OFF");
|
||||
|
||||
if (e.person_id != h.id) return false;
|
||||
if (!e.leave_date && !isOldOff) return false;
|
||||
|
||||
if (isCustom && !hasValidDates) return false;
|
||||
return eDate >= startDateStr && eDate <= endDateStr;
|
||||
})
|
||||
.map((e) => ({
|
||||
type: e.leave_type || e.type,
|
||||
duration: parseFloat(e.duration) || 1,
|
||||
}));
|
||||
|
||||
const totals = {};
|
||||
hLeaves.forEach(
|
||||
(lt) => (totals[typeof lt === "object" ? lt.type : lt] = 0),
|
||||
);
|
||||
totals["OFF"] = 0;
|
||||
totals["EXTRA"] = 0;
|
||||
|
||||
let totalLeavesTaken = 0;
|
||||
|
||||
helperEvents.forEach((e) => {
|
||||
let mappedType =
|
||||
e.type === "HELPER_OFF"
|
||||
? "OFF"
|
||||
: e.type === "HELPER_EXTRA"
|
||||
? "EXTRA"
|
||||
: e.type;
|
||||
if (totals[mappedType] === undefined) totals[mappedType] = 0;
|
||||
totals[mappedType] += e.duration;
|
||||
totalLeavesTaken += e.duration;
|
||||
});
|
||||
|
||||
const workedDays = hasValidDates
|
||||
? Math.max(0, theoreticalWorkingDays - totalLeavesTaken)
|
||||
: 0;
|
||||
|
||||
// Construction des options du sélecteur
|
||||
const lang = window.appLang || "fr-FR";
|
||||
const trLang = window.I18N || {};
|
||||
let optionsHtml = `<option value="all" ${mStr === "all" ? "selected" : ""}>${trLang.fc_full_year || "Année complète"}</option>`;
|
||||
optionsHtml += `<option value="custom" ${mStr === "custom" ? "selected" : ""}>${trLang.fc_custom_period || "Période Spécifique..."}</option>`;
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const monthName = new Intl.DateTimeFormat(lang, {
|
||||
month: "long",
|
||||
}).format(new Date(2000, i, 1));
|
||||
const capitalizedMonth =
|
||||
monthName.charAt(0).toUpperCase() + monthName.slice(1);
|
||||
optionsHtml += `<option value="${i + 1}" ${mStr == i + 1 ? "selected" : ""}>${capitalizedMonth}</option>`;
|
||||
}
|
||||
|
||||
let yearOptionsHtml = "";
|
||||
const currentY = new Date().getFullYear();
|
||||
for (let yr = currentY - 2; yr <= currentY + 5; yr++) {
|
||||
yearOptionsHtml += `<option value="${yr}" ${y == yr ? "selected" : ""}>${yr}</option>`;
|
||||
}
|
||||
|
||||
const isCustomDisplay = isCustom ? "flex" : "none";
|
||||
const isYearDisplay = isCustom ? "none" : "block";
|
||||
|
||||
// --- GÉNÉRATION DU HTML (Design Ultra-Compact + Micro Pilules) ---
|
||||
let cardHtml = `
|
||||
<div style="background: var(--bg-panel); border: 1px solid var(--border-light); border-radius: 12px; padding: 12px 20px; margin-bottom: 20px; box-shadow: var(--pf-shadow-sm); display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 15px; width: 100%;">
|
||||
|
||||
<!-- Zone Gauche : Identité + KPI + Pilules -->
|
||||
<div style="display: flex; align-items: center; gap: 20px; flex-wrap: wrap;">
|
||||
|
||||
<!-- Identité -->
|
||||
<div style="display: flex; flex-direction: column; gap: 2px;">
|
||||
<span style="font-size: 0.95rem; font-weight: 700; color: var(--text-main); display: flex; align-items: center; gap: 6px;">📅 ${h.name}</span>
|
||||
<span style="font-size: 0.65rem; color: var(--text-muted); text-transform: uppercase; font-weight: 700; letter-spacing: 0.05em;">Récapitulatif</span>
|
||||
</div>
|
||||
|
||||
<!-- Jours Travaillés -->
|
||||
<div style="display: flex; align-items: baseline; gap: 6px; padding-right: 20px; border-right: 1px solid var(--border-light);">
|
||||
<span style="font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; font-weight: 700;">Travaillés (Est.)</span>
|
||||
<span style="font-size: 1.4rem; font-weight: 800; color: var(--primary); line-height: 1;">${hasValidDates ? workedDays : "-"} <span style="font-size: 0.85rem;">j</span></span>
|
||||
</div>
|
||||
|
||||
<!-- Les Micro-Pilules de Congés -->
|
||||
<div style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">`;
|
||||
|
||||
if (!hasValidDates && isCustom) {
|
||||
cardHtml += `<div style="font-size: 0.85rem; color: var(--danger); font-weight: bold;">Sélectionnez des dates valides.</div>`;
|
||||
} else {
|
||||
let hasLeaves = false;
|
||||
for (const [t, val] of Object.entries(totals)) {
|
||||
if (
|
||||
val > 0 ||
|
||||
hLeaves.some(
|
||||
(lt) => (typeof lt === "object" ? lt.type : lt) === t,
|
||||
)
|
||||
) {
|
||||
hasLeaves = true;
|
||||
let label =
|
||||
t === "HELPER_OFF" ? "Off" : t === "HELPER_EXTRA" ? "Extra" : t;
|
||||
cardHtml += `
|
||||
<div style="display: flex; align-items: center; gap: 8px; background: var(--bg-page); border: 1px solid var(--border-light); border-left: 3px solid var(--warning); border-radius: 6px; padding: 4px 10px;">
|
||||
<span style="font-size: 0.75rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase;">${label}</span>
|
||||
<span style="font-size: 1rem; font-weight: 800; color: var(--text-main); line-height: 1;">${val}<span style="font-size: 0.8rem; margin-left: 2px;">j</span></span>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
if (!hasLeaves)
|
||||
cardHtml += `<div style="font-size: 0.85rem; color: var(--text-muted); font-style: italic;">Aucun congé posé.</div>`;
|
||||
}
|
||||
|
||||
cardHtml += `
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Zone Droite : Sélecteurs de Période Dynamiques -->
|
||||
<div style="display: flex; gap: 8px; align-items: center; flex-wrap: wrap;">
|
||||
<select class="pf-input" style="padding: 4px 8px; width: auto; height: 32px; font-size: 0.85rem; border-color: var(--border-strong); background: var(--bg-page);" onchange="updateHiddenRecap(this, 'month')">
|
||||
${optionsHtml}
|
||||
</select>
|
||||
<select class="pf-input" style="padding: 4px 8px; width: auto; height: 32px; font-size: 0.85rem; border-color: var(--border-strong); background: var(--bg-page); display: ${isYearDisplay};" onchange="updateHiddenRecap(this, 'year')">
|
||||
${yearOptionsHtml}
|
||||
</select>
|
||||
|
||||
<div style="display: ${isCustomDisplay}; align-items: center; gap: 6px; background: var(--bg-page); border: 1px solid var(--border-strong); border-radius: 6px; padding: 2px 6px;">
|
||||
<input type="date" style="border: none; background: transparent; padding: 2px; height: 26px; font-size: 0.8rem; width: 115px; color: var(--text-main); font-family: inherit;" value="${hiddenStartInp.value}" onchange="updateHiddenRecap(this, 'start')">
|
||||
<span style="color: var(--text-muted); font-size: 0.8rem;">➔</span>
|
||||
<input type="date" style="border: none; background: transparent; padding: 2px; height: 26px; font-size: 0.8rem; width: 115px; color: var(--text-main); font-family: inherit;" value="${hiddenEndInp.value}" onchange="updateHiddenRecap(this, 'end')">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>`;
|
||||
html += cardHtml;
|
||||
});
|
||||
|
||||
content.innerHTML = html;
|
||||
|
||||
// Attachement de la fonction globale pour les selecteurs injectés
|
||||
window.updateHiddenRecap = (el, type) => {
|
||||
if (type === "month") hiddenMonthSel.value = el.value;
|
||||
if (type === "year") hiddenYearSel.value = el.value;
|
||||
if (type === "start") hiddenStartInp.value = el.value;
|
||||
if (type === "end") hiddenEndInp.value = el.value;
|
||||
this.renderHelperRecap();
|
||||
};
|
||||
}
|
||||
|
||||
// 🔥 LE FIX (refreshAllData avec schoolHols injecté et fusionné)
|
||||
async refreshAllData() {
|
||||
try {
|
||||
const zone = window.calGlobalData?.foyer?.zone_scolaire || "C";
|
||||
@@ -984,6 +1224,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
this.calculateMonthlyBalances();
|
||||
this.renderTable();
|
||||
this.renderMonthCalendar();
|
||||
if (typeof this.renderHelperRecap === "function")
|
||||
this.renderHelperRecap();
|
||||
}
|
||||
|
||||
reprocessEvents() {
|
||||
@@ -994,8 +1236,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
this.careModes.forEach((m) => (w.totals["mode_" + m] = 0));
|
||||
this.kids.forEach((k) => (w.totals["sick_" + k.id] = 0));
|
||||
this.helpers.forEach((h) => {
|
||||
w.totals["off_" + h.id] = 0;
|
||||
w.totals["extra_" + h.id] = 0;
|
||||
const hLeaves =
|
||||
this.leaveMatrix[h.id] || this.leaveMatrix[Number(h.id)] || [];
|
||||
hLeaves.forEach((lt) => {
|
||||
const typeName = typeof lt === "object" ? lt.type : lt;
|
||||
w.totals[`leave_${h.id}_${typeName}`] = 0;
|
||||
});
|
||||
});
|
||||
this.parents.forEach((p) => {
|
||||
const types = this.leaveMatrix[p.id] || [];
|
||||
@@ -1051,12 +1297,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
|
||||
let helperAbsences = 0;
|
||||
this.helpers.forEach(
|
||||
(h) =>
|
||||
(helperAbsences +=
|
||||
(w.totals["off_" + h.id] || 0) +
|
||||
(w.totals["extra_" + h.id] || 0)),
|
||||
);
|
||||
this.helpers.forEach((h) => {
|
||||
const hLeaves =
|
||||
this.leaveMatrix[h.id] || this.leaveMatrix[Number(h.id)] || [];
|
||||
hLeaves.forEach((lt) => {
|
||||
const typeName = typeof lt === "object" ? lt.type : lt;
|
||||
helperAbsences += w.totals[`leave_${h.id}_${typeName}`] || 0;
|
||||
});
|
||||
});
|
||||
|
||||
if (this.kids.length > 0) {
|
||||
w.totals.presenceKid = Math.max(
|
||||
@@ -1261,10 +1509,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
td.classList.add("fc-day--public-holiday");
|
||||
if (evt.type === "VACANCES_SCOLAIRES")
|
||||
td.classList.add("fc-day--school-holiday");
|
||||
if (evt.type === "HELPER_OFF")
|
||||
td.classList.add("fc-day--off-carole");
|
||||
if (evt.type === "HELPER_EXTRA")
|
||||
td.classList.add("fc-day--extra-off-carole");
|
||||
if (upperCareModes.includes(evt.type))
|
||||
td.classList.add("fc-day--has-guard");
|
||||
});
|
||||
@@ -1320,13 +1564,30 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
tr.appendChild(td);
|
||||
});
|
||||
|
||||
// 1. Congés intervenant (Dynamiques)
|
||||
(this.helpers || []).forEach((h) => {
|
||||
const hLeaves =
|
||||
this.leaveMatrix[h.id] || this.leaveMatrix[Number(h.id)] || [];
|
||||
hLeaves.forEach((lt) => {
|
||||
const typeName = typeof lt === "object" ? lt.type : lt;
|
||||
const td = document.createElement("td");
|
||||
td.className = "col-total";
|
||||
td.textContent = fmt(w.totals[`leave_${h.id}_${typeName}`] || 0);
|
||||
tr.appendChild(td);
|
||||
});
|
||||
});
|
||||
|
||||
// 2. Modes de garde (On ignore Nounou si Helper présent)
|
||||
(this.careModes || []).forEach((mode) => {
|
||||
if (mode.toLowerCase() === "nounou" && this.helpers.length > 0)
|
||||
return;
|
||||
const td = document.createElement("td");
|
||||
td.className = "col-total";
|
||||
td.textContent = fmt(w.totals["mode_" + mode] || 0);
|
||||
tr.appendChild(td);
|
||||
});
|
||||
|
||||
// 3. Maladie enfants
|
||||
(this.kids || []).forEach((kid) => {
|
||||
const td = document.createElement("td");
|
||||
td.className = "col-total";
|
||||
@@ -1371,7 +1632,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("🔥 Erreur fatale dans renderTable :", e);
|
||||
this.planningBody.innerHTML = `<tr><td colspan="15" style="color:red; font-weight:bold; padding:20px; text-align:center;">Erreur d'affichage : ${e.message} <br> <small>Regarde la console pour plus de détails.</small></td></tr>`;
|
||||
this.planningBody.innerHTML = `<tr><td colspan="15" style="color:red; font-weight:bold; padding:20px; text-align:center;">Erreur d'affichage : ${e.message}</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2053,7 +2314,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
|
||||
const activeLeaves = {};
|
||||
this.parents.forEach((p) => (activeLeaves[p.id] = new Set()));
|
||||
[...this.parents, ...this.helpers].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);
|
||||
@@ -2079,40 +2342,54 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
|
||||
const trashSvg = `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"></path><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path></svg>`;
|
||||
const buildHeader = (title, action, cat) => `
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; font-size:0.65rem; font-weight:700; color:var(--text-muted); text-transform:uppercase; margin: 8px 0 4px 0;">
|
||||
<span>${title}</span>
|
||||
${action ? `<button style="background:none; border:none; cursor:pointer; color:var(--danger); padding:0; display:flex; opacity:0.6;" data-action="${action}" data-cat="${cat}">${trashSvg}</button>` : ""}
|
||||
</div>`;
|
||||
|
||||
// Ajout de la classe "btn-icon-action delete" pour activer le hover natif (rouge clair)
|
||||
const buildHeader = (title, action, cat, pid) =>
|
||||
`<div style="display:flex; justify-content:space-between; align-items:center; font-size:0.65rem; font-weight:700; color:var(--text-muted); text-transform:uppercase; margin: 8px 0 4px 0;"><span>${title}</span>${action ? `<button type="button" class="btn-icon-action delete" style="width:24px; height:24px; padding:4px;" data-action="${action}" data-cat="${cat}" ${pid ? `data-pid="${pid}"` : ""}>${trashSvg}</button>` : ""}</div>`;
|
||||
|
||||
let html = `<div class="fc-menu-section" style="padding-bottom: 4px;"><strong style="font-size:0.85rem; color:var(--text-main); text-transform:capitalize;">${dateLabel}</strong></div>`;
|
||||
|
||||
// 1. Congés Intervenants
|
||||
if (this.helpers.length > 0) {
|
||||
this.helpers.forEach((h) => {
|
||||
html += `<div class="fc-menu-section">${buildHeader(h.name, "clear-type", "HELPER_" + h.id)}<div class="fc-menu-grid">
|
||||
<button class="fc-menu-btn" style="${getActiveStyleE("HELPER_OFF", h.id, "var(--warning)")}" data-action="add" data-type="HELPER_OFF" data-person="${h.id}">Off</button>
|
||||
<button class="fc-menu-btn" style="${getActiveStyleE("HELPER_EXTRA", h.id, "var(--danger)")}" data-action="add" data-type="HELPER_EXTRA" data-person="${h.id}">Extra</button>
|
||||
</div></div>`;
|
||||
const hLeaves =
|
||||
this.leaveMatrix[h.id] || this.leaveMatrix[Number(h.id)] || [];
|
||||
if (hLeaves.length > 0) {
|
||||
const hColor = h.color || "var(--warning)";
|
||||
html += `<div class="fc-menu-section">${buildHeader(h.name, "clear-leaves-person", null, h.id)}<div class="fc-menu-grid">`;
|
||||
hLeaves.forEach((lt) => {
|
||||
const lType = typeof lt === "object" ? lt.type : lt;
|
||||
html += `<button class="fc-menu-btn" style="${getActiveStyleL(lType, h.id, hColor)}" data-action="add-leave" data-pid="${h.id}" data-type="${lType}">${lType}</button>`;
|
||||
});
|
||||
html += `</div></div>`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Modes de Garde
|
||||
if (this.careModes.length > 0) {
|
||||
html += `<div class="fc-menu-section">${buildHeader(trLang.fc_care_modes_title || "Modes de garde", "clear-type", "CARE_MODE")}<div class="fc-menu-grid">`;
|
||||
this.careModes.forEach((m) => {
|
||||
let iconHtml =
|
||||
m.toLowerCase() === "avis"
|
||||
? `<img src="/modules/family-calendar/assets/img/avis.svg" class="fc-icon-avis" title="Avis" style="width:14px; height:14px; object-fit:contain; margin-right:4px;"> `
|
||||
: m.toLowerCase() === "centre"
|
||||
? `<span class="fc-icon-centre" style="font-size:1.1rem; line-height:1; margin-right:4px;">🏫</span> `
|
||||
: "";
|
||||
const modeType = m.toUpperCase();
|
||||
html += `<button class="fc-menu-btn" style="display:flex; align-items:center; ${getActiveStyleE(modeType, "0", "var(--primary)")}" data-action="add" data-type="${modeType}" data-person="0">${iconHtml}${m}</button>`;
|
||||
});
|
||||
html += `</div></div>`;
|
||||
const displayModes = this.careModes.filter(
|
||||
(m) => !(m.toLowerCase() === "nounou" && this.helpers.length > 0),
|
||||
);
|
||||
if (displayModes.length > 0) {
|
||||
html += `<div class="fc-menu-section">${buildHeader(trLang.fc_care_modes_title || "Modes de garde", "clear-type", "CARE_MODE", null)}<div class="fc-menu-grid">`;
|
||||
displayModes.forEach((m) => {
|
||||
let iconHtml =
|
||||
m.toLowerCase() === "avis"
|
||||
? `<img src="/modules/family-calendar/assets/img/avis.svg" class="fc-icon-avis" title="Avis" style="width:14px; height:14px; object-fit:contain; margin-right:4px;"> `
|
||||
: m.toLowerCase() === "centre"
|
||||
? `<span class="fc-icon-centre" style="font-size:1.1rem; line-height:1; margin-right:4px;">🏫</span> `
|
||||
: "";
|
||||
const modeType = m.toUpperCase();
|
||||
html += `<button class="fc-menu-btn" style="display:flex; align-items:center; ${getActiveStyleE(modeType, "0", "var(--primary)")}" data-action="add" data-type="${modeType}" data-person="0">${iconHtml}${m}</button>`;
|
||||
});
|
||||
html += `</div></div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Maladie Enfants
|
||||
if (this.kids.length > 0) {
|
||||
html += `<div class="fc-menu-section">${buildHeader(trLang.leg_pep_sick || "Maladie", "clear-type", "CHILD_SICK")}<div class="fc-menu-grid" style="grid-template-columns: 1fr;">`;
|
||||
html += `<div class="fc-menu-section">${buildHeader(trLang.fc_menu_sick || "Maladie", "clear-type", "CHILD_SICK", null)}<div class="fc-menu-grid" style="grid-template-columns: 1fr;">`;
|
||||
this.kids.forEach((k) => {
|
||||
const color = k.color || "var(--danger)";
|
||||
html += `<button class="fc-menu-btn" style="${getActiveStyleE("CHILD_SICK", k.id, color)}" data-action="add" data-type="CHILD_SICK" data-person="${k.id}">${k.name} 🤒</button>`;
|
||||
@@ -2120,39 +2397,40 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
html += `</div></div>`;
|
||||
}
|
||||
|
||||
html += `<div class="fc-menu-section" style="border-bottom:none;">${buildHeader(trLang.fc_menu_kids_leaves || "Congés Adultes", null, null)}<div style="display:grid; grid-template-columns: auto 1fr; gap:6px; align-items:center; font-size:0.8rem;">`;
|
||||
// 4. Congés Parents (Répartition en 2 colonnes !)
|
||||
html += `<div class="fc-menu-section" style="border-bottom:none;">${buildHeader(trLang.fc_menu_adult_leaves || "Congés Parents", null, null, null)}`;
|
||||
html += `<div style="display:grid; grid-template-columns: 1fr 1fr; gap:12px; align-items:start; font-size:0.8rem;">`;
|
||||
|
||||
const allParentLeaveTypes = new Set();
|
||||
this.parents.forEach((p) => {
|
||||
(this.leaveMatrix[p.id] || []).forEach((l) =>
|
||||
allParentLeaveTypes.add(l.type),
|
||||
allParentLeaveTypes.add(typeof l === "object" ? l.type : l),
|
||||
);
|
||||
});
|
||||
|
||||
this.parents.forEach((p) => {
|
||||
const pColor = p.color || "var(--primary)";
|
||||
html += `<div style="font-weight:600; color:${pColor}; display:flex; align-items:center; justify-content:space-between; padding-right:8px; border-right:1px solid var(--border-light);">
|
||||
${p.name} <button style="background:none; border:none; cursor:pointer; color:var(--danger); padding:0; display:flex; opacity:0.4; margin-left:6px;" data-action="clear-leaves-person" data-pid="${p.id}">${trashSvg}</button>
|
||||
html += `<div style="display:flex; flex-direction:column; gap:6px; background:var(--bg-subtle); padding:6px; border-radius:8px; border:1px solid var(--border-light);">`;
|
||||
html += `<div style="font-weight:600; color:${pColor}; display:flex; align-items:center; justify-content:space-between;">
|
||||
${p.name} <button type="button" class="btn-icon-action delete" style="width:24px; height:24px; padding:4px;" data-action="clear-leaves-person" data-pid="${p.id}">${trashSvg}</button>
|
||||
</div>`;
|
||||
html += `<div style="display:flex; gap:4px; flex-wrap:wrap;">`;
|
||||
Array.from(allParentLeaveTypes).forEach((t) => {
|
||||
const hasThisLeave = (this.leaveMatrix[p.id] || []).some(
|
||||
(l) => l.type === t,
|
||||
);
|
||||
if (hasThisLeave) {
|
||||
html += `<button class="fc-menu-btn" style="padding: 2px 6px; font-size:0.75rem; ${getActiveStyleL(t, p.id, pColor)}" data-action="add-leave" data-pid="${p.id}" data-type="${t}">${t}</button>`;
|
||||
if (
|
||||
(this.leaveMatrix[p.id] || []).some(
|
||||
(l) => (typeof l === "object" ? l.type : l) === t,
|
||||
)
|
||||
) {
|
||||
html += `<button class="fc-menu-btn" style="padding: 4px 6px; font-size:0.75rem; width:auto; flex-grow:1; text-align:center; ${getActiveStyleL(t, p.id, pColor)}" data-action="add-leave" data-pid="${p.id}" data-type="${t}">${t}</button>`;
|
||||
}
|
||||
});
|
||||
html += `</div>`;
|
||||
html += `</div></div>`;
|
||||
});
|
||||
html += `</div></div>`;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user