opti vue mensuelle

This commit is contained in:
2026-05-06 20:49:52 +02:00
parent 2b2150870c
commit 8a6abe5f8e
6 changed files with 183 additions and 22 deletions
+2 -1
View File
@@ -121,9 +121,10 @@ require __DIR__ . '/header.php';
</div> </div>
<div class="fc-view-controls"> <div class="fc-view-controls">
<button id="fc-today-btn" class="fc-today-button" title="<?= tr('fc_today_title') ?>"><?= tr('fc_today_short') ?></button> <div class="fc-view-divider"></div>
<button class="fc-view-button fc-view-button--active" data-view="1month"><?= tr('fc_view_1m') ?></button> <button class="fc-view-button fc-view-button--active" data-view="1month"><?= tr('fc_view_1m') ?></button>
<button class="fc-view-button" data-view="2months"><?= tr('fc_view_2m') ?></button> <button class="fc-view-button" data-view="2months"><?= tr('fc_view_2m') ?></button>
<button class="fc-view-button" data-view="3months">3 Mois</button> <button class="fc-view-button" data-view="3months"><?= tr('fc_view_3m') ?></button>
</div> </div>
</div> </div>
+3 -1
View File
@@ -134,8 +134,10 @@ return [
'fc_err_fetch_gov' => 'Error en la connexió amb lAPI governamental.', 'fc_err_fetch_gov' => 'Error en la connexió amb lAPI governamental.',
'fc_err_no_data_gov' => 'No shan trobat dades a lAPI del govern per a aquest any.', 'fc_err_no_data_gov' => 'No shan trobat dades a lAPI del govern per a aquest any.',
'fc_err_action' => 'Error en lacció: ', 'fc_err_action' => 'Error en lacció: ',
'leg_presence' => 'Presència Pep', 'fc_today_short' => 'Avui',
'fc_today_title' => 'Anar a avui',
'leg_presence' => 'Presència Pep',
'leg_school_holidays' => 'Vacances Escolars', 'leg_school_holidays' => 'Vacances Escolars',
'leg_public_holiday' => 'Festiu', 'leg_public_holiday' => 'Festiu',
'leg_off_carole' => 'Off Carole', 'leg_off_carole' => 'Off Carole',
+3 -1
View File
@@ -137,8 +137,10 @@ return [
'fc_err_fetch_gov' => 'Erreur lors de la connexion à lAPI gouvernementale.', 'fc_err_fetch_gov' => 'Erreur lors de la connexion à lAPI gouvernementale.',
'fc_err_no_data_gov' => 'Aucune donnée trouvée sur lAPI du gouvernement pour cette année.', 'fc_err_no_data_gov' => 'Aucune donnée trouvée sur lAPI du gouvernement pour cette année.',
'fc_err_action' => 'Erreur lors de laction : ', 'fc_err_action' => 'Erreur lors de laction : ',
'leg_presence' => 'Présence Pep', 'fc_today_short' => 'Auj.',
'fc_today_title' => "Aujourd'hui",
'leg_presence' => 'Présence Pep',
'leg_school_holidays' => 'Vacances Scolaires', 'leg_school_holidays' => 'Vacances Scolaires',
'leg_public_holiday' => 'Férié', 'leg_public_holiday' => 'Férié',
'leg_off_carole' => 'Off Carole', 'leg_off_carole' => 'Off Carole',
@@ -740,6 +740,15 @@ td.col-laia-sub {
gap: 4px; gap: 4px;
} }
/* --- BOUTON DE MENU ACTIF (Élément déjà enregistré) --- */
.fc-menu-btn--active {
background: #eff6ff;
border-color: var(--pf-primary);
color: var(--pf-primary);
/* Un léger inset shadow pour rendre la bordure un peu plus épaisse sans casser la taille */
box-shadow: inset 0 0 0 1px var(--pf-primary);
}
/* Table specifique modale vacances */ /* Table specifique modale vacances */
.fc-holidays-table { .fc-holidays-table {
width: 100%; width: 100%;
@@ -827,6 +836,55 @@ td.col-laia-sub {
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15); box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
} }
/* --- BOUTON AUJOURD'HUI & CONTROLES --- */
.fc-view-controls {
align-items: center; /* Pour bien centrer le nouveau bouton */
}
.fc-today-button {
background: white;
border: 1px solid var(--pf-border);
border-radius: 8px;
padding: 6px 12px;
font-size: 0.85rem;
font-weight: 700;
color: var(--pf-primary);
cursor: pointer;
transition: all 0.2s;
box-shadow: var(--pf-shadow-sm);
}
.fc-today-button:hover {
background: #eff6ff;
border-color: var(--pf-primary);
}
.fc-view-divider {
width: 1px;
height: 20px;
background: #cbd5e1;
margin: 0 8px;
}
/* --- MISE EN ÉVIDENCE DU JOUR EN COURS --- */
.fc-day--today {
background: #eff6ff !important;
box-shadow: inset 0 0 0 1px #bfdbfe !important;
}
.fc-day--today .fc-day-number {
display: inline;
background: none;
width: auto;
height: auto;
box-shadow: none;
border-radius: 0;
margin: 0;
color: var(--pf-primary);
font-weight: 900;
font-size: 1.1em;
}
@media (max-width: 768px) { @media (max-width: 768px) {
.fc-smart-select { .fc-smart-select {
font-size: 1rem; font-size: 1rem;
@@ -890,4 +948,10 @@ td.col-laia-sub {
border-radius: 10px; border-radius: 10px;
margin: 0 auto 15px auto; margin: 0 auto 15px auto;
} }
.fc-two-months-container,
.fc-three-months-container {
display: flex !important;
flex-direction: column !important;
gap: 20px !important;
}
} }
+106 -19
View File
@@ -940,7 +940,10 @@ document.addEventListener("DOMContentLoaded", () => {
} }
const iso = this.getLocalIsoDate(dateObj); const iso = this.getLocalIsoDate(dateObj);
const todayIso = this.getLocalIsoDate(new Date()); // Date du jour
let cls = "fc-month-day"; let cls = "fc-month-day";
if (iso === todayIso) cls += " fc-day--today"; // On applique la classe !
const dayEvts = this.events.filter((e) => e.date === iso); const dayEvts = this.events.filter((e) => e.date === iso);
if (dayEvts.some((e) => e.type === "VACANCES_SCOLAIRES")) if (dayEvts.some((e) => e.type === "VACANCES_SCOLAIRES"))
@@ -958,7 +961,7 @@ document.addEventListener("DOMContentLoaded", () => {
if (dayEvts.some((e) => e.type === "AVIS")) cls += " fc-day--avis"; if (dayEvts.some((e) => e.type === "AVIS")) cls += " fc-day--avis";
} }
let content = `<div style="position:relative; height:100%;">${dayCounter}`; let content = `<div style="position:relative; height:100%;"><span class="fc-day-number">${dayCounter}</span>`;
if (dayEvts.some((e) => e.type === "PEP_SICK")) if (dayEvts.some((e) => e.type === "PEP_SICK"))
content += `<span style="position:absolute; bottom:2px; right:2px;">🤒</span>`; content += `<span style="position:absolute; bottom:2px; right:2px;">🤒</span>`;
const dayLeaves = this.leaves.filter((l) => l.leave_date === iso); const dayLeaves = this.leaves.filter((l) => l.leave_date === iso);
@@ -1357,6 +1360,66 @@ document.addEventListener("DOMContentLoaded", () => {
this.currentMonth.setMonth(this.currentMonth.getMonth() + 1); this.currentMonth.setMonth(this.currentMonth.getMonth() + 1);
this.renderMonthCalendar(); this.renderMonthCalendar();
}); });
// --- BOUTON AUJOURD'HUI ---
document.getElementById("fc-today-btn")?.addEventListener("click", () => {
this.currentMonth = new Date();
this.currentMonth.setDate(1); // Force au 1er du mois pour éviter les bugs de fins de mois
this.renderMonthCalendar();
});
// --- GESTION DU SWIPE MENSUEL (MOBILE) ---
if (this.monthCalendar) {
let touchStartX = 0;
let touchStartY = 0;
// On écoute le début du toucher
this.monthCalendar.addEventListener(
"touchstart",
(e) => {
touchStartX = e.changedTouches[0].screenX;
touchStartY = e.changedTouches[0].screenY;
},
{ passive: true },
);
// On écoute la fin du toucher
this.monthCalendar.addEventListener(
"touchend",
(e) => {
const touchEndX = e.changedTouches[0].screenX;
const touchEndY = e.changedTouches[0].screenY;
const deltaX = touchEndX - touchStartX;
const deltaY = touchEndY - touchStartY;
// Détection d'un swipe horizontal (on exclut les swipes verticaux de défilement)
// Seuil de 50px pour éviter les déclenchements par erreur
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 50) {
const numMonths =
this.viewMode === "2months"
? 2
: this.viewMode === "3months"
? 3
: 1;
if (deltaX < 0) {
// Swipe vers la GAUCHE = Aller dans le FUTUR (Mois suivants)
this.currentMonth.setMonth(
this.currentMonth.getMonth() + numMonths,
);
} else {
// Swipe vers la DROITE = Revenir dans le PASSÉ (Mois précédents)
this.currentMonth.setMonth(
this.currentMonth.getMonth() - numMonths,
);
}
this.renderMonthCalendar(); // Rafraîchit l'UI
}
},
{ passive: true },
);
}
document document
.getElementById("fc-prev-school-year") .getElementById("fc-prev-school-year")
?.addEventListener("click", () => this.changeSchoolYear(-1)); ?.addEventListener("click", () => this.changeSchoolYear(-1));
@@ -1466,16 +1529,43 @@ document.addEventListener("DOMContentLoaded", () => {
this._currentBulkInfo = { dates }; this._currentBulkInfo = { dates };
// --- 🔍 DÉTECTION DES ÉVÉNEMENTS & CONGÉS EXISTANTS ---
const activeEvents = new Set();
this.events.forEach((e) => {
if (dates.includes(e.date)) activeEvents.add(e.type);
});
const activeLeaves = { 2: new Set(), 3: new Set() };
this.leaves.forEach((l) => {
if (dates.includes(l.leave_date)) {
if (activeLeaves[l.person_id])
activeLeaves[l.person_id].add(l.leave_type);
}
});
// Petites fonctions utilitaires pour gérer l'UI du bouton
const getBtnClass = (type, personId = null) => {
let isActive = personId
? activeLeaves[personId] && activeLeaves[personId].has(type)
: activeEvents.has(type);
return isActive ? "fc-menu-btn--active" : "";
};
const getBtnIcon = (type, personId = null) => {
let isActive = personId
? activeLeaves[personId] && activeLeaves[personId].has(type)
: activeEvents.has(type);
return isActive ? "✓ " : "";
};
// Gestion de la date affichée (Singulier/Pluriel) // Gestion de la date affichée (Singulier/Pluriel)
const dateLabel = const dateLabel =
dates.length > 1 dates.length > 1
? `${dates.length} ${tr("fc_unit_days")}` ? `${dates.length} ${tr("fc_unit_days")}`
: new Date(dates[0]).toLocaleDateString(window.I18N_LANG || "fr-FR"); : new Date(dates[0]).toLocaleDateString(window.I18N_LANG || "fr-FR");
// Icône SVG Poubelle (Uniformisée) // Icône SVG Poubelle
const trashSvg = `<svg viewBox="0 0 24 24" 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 trashSvg = `<svg viewBox="0 0 24 24" 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>`;
// Fonction utilitaire interne pour les en-têtes de section avec suppression
const buildHeader = (title, action, cat) => ` const buildHeader = (title, action, cat) => `
<div class="fc-menu-header"> <div class="fc-menu-header">
<strong>${title}</strong> <strong>${title}</strong>
@@ -1483,36 +1573,35 @@ document.addEventListener("DOMContentLoaded", () => {
</div> </div>
`; `;
// 1. En-tête du menu (Date ou nombre de jours)
let html = `<div class="fc-menu-section" style="border-bottom: none; padding-bottom: 0;"> let html = `<div class="fc-menu-section" style="border-bottom: none; padding-bottom: 0;">
<strong style="font-size:0.85rem; color:var(--text-main); margin-bottom: 4px;">${dateLabel}</strong> <strong style="font-size:0.85rem; color:var(--text-main); margin-bottom: 4px;">${dateLabel}</strong>
</div>`; </div>`;
// 2. Section Carole (Congés) // 2. Section Carole
html += `<div class="fc-menu-section"> html += `<div class="fc-menu-section">
${buildHeader(tr("fc_menu_carole"), "clear-type", "CONGE")} ${buildHeader(tr("fc_menu_carole"), "clear-type", "CONGE")}
<div class="fc-menu-grid"> <div class="fc-menu-grid">
<button class="fc-menu-btn" data-action="add" data-type="OFF_CAROLE" data-person="Carole">${tr("btn_off")}</button> <button class="fc-menu-btn ${getBtnClass("OFF_CAROLE")}" data-action="add" data-type="OFF_CAROLE" data-person="Carole">${getBtnIcon("OFF_CAROLE")}${tr("btn_off")}</button>
<button class="fc-menu-btn" data-action="add" data-type="EXTRA_OFF_CAROLE" data-person="Carole">${tr("btn_extra")}</button> <button class="fc-menu-btn ${getBtnClass("EXTRA_OFF_CAROLE")}" data-action="add" data-type="EXTRA_OFF_CAROLE" data-person="Carole">${getBtnIcon("EXTRA_OFF_CAROLE")}${tr("btn_extra")}</button>
</div> </div>
</div>`; </div>`;
// 3. Section Garde (Centre / Avis) // 3. Section Garde
html += `<div class="fc-menu-section"> html += `<div class="fc-menu-section">
${buildHeader(tr("leg_centre"), "clear-type", "GARDE")} ${buildHeader(tr("leg_centre"), "clear-type", "GARDE")}
<div class="fc-menu-grid"> <div class="fc-menu-grid">
<button class="fc-menu-btn" data-action="add" data-type="CENTRE">${tr("leg_centre")}</button> <button class="fc-menu-btn ${getBtnClass("CENTRE")}" data-action="add" data-type="CENTRE">${getBtnIcon("CENTRE")}${tr("leg_centre")}</button>
<button class="fc-menu-btn" data-action="add" data-type="AVIS">${tr("leg_avis")}</button> <button class="fc-menu-btn ${getBtnClass("AVIS")}" data-action="add" data-type="AVIS">${getBtnIcon("AVIS")}${tr("leg_avis")}</button>
</div> </div>
</div>`; </div>`;
// 4. Section Pep (Maladie) // 4. Section Pep
html += `<div class="fc-menu-section"> html += `<div class="fc-menu-section">
${buildHeader("Pep", "clear-type", "PEP")} ${buildHeader("Pep", "clear-type", "PEP")}
<button class="fc-menu-btn" data-action="add" data-type="PEP_SICK" style="width:100%">${tr("leg_pep_sick")} 🤒</button> <button class="fc-menu-btn ${getBtnClass("PEP_SICK")}" data-action="add" data-type="PEP_SICK" style="width:100%">${getBtnIcon("PEP_SICK")}${tr("leg_pep_sick")} 🤒</button>
</div>`; </div>`;
// 5. Section Enfants (Alex & Laia) // 5. Section Enfants
html += `<div class="fc-menu-section"> html += `<div class="fc-menu-section">
${buildHeader(tr("fc_menu_kids_leaves"), null, null)} ${buildHeader(tr("fc_menu_kids_leaves"), null, null)}
<div class="fc-menu-leaves-table"> <div class="fc-menu-leaves-table">
@@ -1521,14 +1610,12 @@ document.addEventListener("DOMContentLoaded", () => {
<tr> <tr>
<th> <th>
<div class="fc-th-inline"> <div class="fc-th-inline">
Alex Alex <button class="fc-menu-clear-icon fc-menu-btn-th" data-action="clear-leaves-person" data-pid="2" title="${tr("fc_clear")} Alex">${trashSvg}</button>
<button class="fc-menu-clear-icon fc-menu-btn-th" data-action="clear-leaves-person" data-pid="2" title="${tr("fc_clear")} Alex">${trashSvg}</button>
</div> </div>
</th> </th>
<th> <th>
<div class="fc-th-inline"> <div class="fc-th-inline">
Laia Laia <button class="fc-menu-clear-icon fc-menu-btn-th" data-action="clear-leaves-person" data-pid="3" title="${tr("fc_clear")} Laia">${trashSvg}</button>
<button class="fc-menu-clear-icon fc-menu-btn-th" data-action="clear-leaves-person" data-pid="3" title="${tr("fc_clear")} Laia">${trashSvg}</button>
</div> </div>
</th> </th>
</tr> </tr>
@@ -1537,8 +1624,8 @@ document.addEventListener("DOMContentLoaded", () => {
["CP", "JRA", "JA"].forEach((t) => { ["CP", "JRA", "JA"].forEach((t) => {
html += `<tr> 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 ${getBtnClass(t, 2)}" data-action="add-leave" data-pid="2" data-type="${t}">${getBtnIcon(t, 2)}${t}</button></td>
<td><button class="fc-menu-btn" data-action="add-leave" data-pid="3" data-type="${t}">${t}</button></td> <td><button class="fc-menu-btn ${getBtnClass(t, 3)}" data-action="add-leave" data-pid="3" data-type="${t}">${getBtnIcon(t, 3)}${t}</button></td>
</tr>`; </tr>`;
}); });
@@ -46,3 +46,8 @@
[2026-05-06T10:14:34+02:00] RAW INPUT: [{"date":"2026-02-23","type":"CENTRE","duration":1,"person":"Carole"}] [2026-05-06T10:14:34+02:00] RAW INPUT: [{"date":"2026-02-23","type":"CENTRE","duration":1,"person":"Carole"}]
[2026-05-06T10:16:01+02:00] RAW INPUT: [{"date":"2026-01-19","type":"PEP_SICK","duration":1,"person":"Carole"}] [2026-05-06T10:16:01+02:00] RAW INPUT: [{"date":"2026-01-19","type":"PEP_SICK","duration":1,"person":"Carole"}]
[2026-05-06T10:16:57+02:00] RAW INPUT: [{"date":"2026-03-19","type":"PEP_SICK","duration":1,"person":"Carole"}] [2026-05-06T10:16:57+02:00] RAW INPUT: [{"date":"2026-03-19","type":"PEP_SICK","duration":1,"person":"Carole"}]
[2026-05-06T20:38:19+02:00] RAW INPUT: [{"date":"2026-05-08","type":"AVIS","duration":1,"person":"Carole"}]
[2026-05-06T20:38:22+02:00] RAW INPUT: [{"date":"2026-05-08","type":"CENTRE","duration":1,"person":"Carole"}]
[2026-05-06T20:38:24+02:00] RAW INPUT: [{"date":"2026-05-08","type":"AVIS","duration":1,"person":"Carole"}]
[2026-05-06T20:43:43+02:00] RAW INPUT: [{"date":"2026-05-06","type":"AVIS","duration":1,"person":"Carole"}]
[2026-05-06T20:44:36+02:00] RAW INPUT: [{"date":"2026-05-06","type":"PEP_SICK","duration":1,"person":"Carole"}]