recap carole
Deploy HouseHub / deploy (push) Successful in 1s

This commit is contained in:
FeFeClochette committed 2026-09-03 13:40:50 +02:00
1 parent cfe8962bd4
commit a2e0904132
7 files changed
+536 -442

No files matched your search

+44 -13
View File
@@ -207,7 +207,7 @@ require __DIR__ . '/header.php';
<!-- FOOTER INTÉGRÉ DANS LE FORMULAIRE POUR ÉVITER LE BUG DE CIBLAGE -->
<div class="pf-modal-footer" style="margin-top: 20px; padding-top: 15px; border-top: 1px solid var(--pf-border); display: flex; justify-content: flex-end; gap: 10px;">
<button type="button" class="pf-btn pf-btn-secondary" onclick="document.getElementById('modalSnapshot').classList.remove('open'); document.body.classList.remove('no-scroll');"><?= tr('btn_cancel') ?></button>
<button type="button" class="pf-btn pf-btn-secondary" onclick="document.getElementById('modalSnapshot').style.display = 'none'; document.body.classList.remove('no-scroll');"><?= tr('btn_cancel') ?></button>
<button type="submit" id="btnSubmitSnapshot" class="pf-btn pf-btn-primary"><?= tr('fc_btn_save_snap') ?></button>
</div>
</form>
@@ -393,20 +393,32 @@ require __DIR__ . '/header.php';
<table id="planningTable">
<thead>
<tr>
<th rowspan="3" class="col-month col-sticky-mois"><?= tr('fc_col_month') ?></th>
<th rowspan="3" class="col-month col-sticky-mois"><?= tr('fc_col_week_short') ?></th>
<th rowspan="3" class="col-month col-sticky-mois rotated-text"><span><?= tr('fc_col_month') ?></span></th>
<th rowspan="3" class="col-month col-sticky-sem rotated-text"><span><?= tr('fc_col_week') ?></span></th>
<th rowspan="3" class="col-day"><?= tr('day_mon') ?></th>
<th rowspan="3" class="col-day"><?= tr('day_tue') ?></th>
<th rowspan="3" class="col-day"><?= tr('day_wed') ?></th>
<th rowspan="3" class="col-day"><?= tr('day_thu') ?></th>
<th rowspan="3" class="col-day"><?= tr('day_fri') ?></th>
<?php foreach ($activeCareModes as $mode): ?>
<!-- 1. Congés de l'intervenant -->
<?php foreach ($helpers as $helper):
$hLeaves = $leaveMatrix[$helper['id']] ?? [];
foreach ($hLeaves as $type):
?>
<th rowspan="3" class="col-total rotated-text"><span><?= htmlspecialchars($type) ?> <?= htmlspecialchars($helper['name']) ?></span></th>
<?php endforeach; endforeach; ?>
<!-- 2. Méthodes de garde (sans Nounou si l'intervenant est configuré) -->
<?php foreach ($activeCareModes as $mode):
if (strtolower($mode) === 'nounou' && !empty($helpers)) continue;
?>
<th rowspan="3" class="col-total rotated-text"><span><?= htmlspecialchars($mode) ?></span></th>
<?php endforeach; ?>
<!-- 3. Maladie par enfant -->
<?php foreach ($kids as $kid): ?>
<th rowspan="3" class="col-total rotated-text"><span>Maladie <?= htmlspecialchars($kid['name']) ?></span></th>
<th rowspan="3" class="col-total rotated-text"><span><?= tr('fc_menu_sick') ?> <?= htmlspecialchars($kid['name']) ?></span></th>
<?php endforeach; ?>
<?php foreach ($parents as $index => $parent):
@@ -447,6 +459,19 @@ require __DIR__ . '/header.php';
</div>
</section>
<?php if (!empty($helpers)): ?>
<section class="pf-section pf-mt-15">
<div id="recapHelperContent"></div>
<div style="display: none;">
<!-- Champs masqués pour maintenir la compatibilité avec la logique JS actuelle -->
<select id="recapHelperMonth" class="pf-input"></select>
<select id="recapHelperYear" class="pf-input"></select>
<input type="date" id="recapHelperStart" class="pf-input">
<input type="date" id="recapHelperEnd" class="pf-input">
</div>
</section>
<?php endif; ?>
<section class="pf-section pf-section--bottom-panels" style="margin-top: 1.5rem;">
<div class="pf-card">
<h2 class="pf-card-title"><?= tr('fc_legend_title') ?></h2>
@@ -456,15 +481,22 @@ require __DIR__ . '/header.php';
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-school-holiday"></div><span><?= tr('leg_school_holidays') ?></span></div>
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-public-holiday"></div><span><?= tr('leg_public_holiday') ?></span></div>
<!-- Intervenants (Dynamique) -->
<?php foreach ($helpers as $helper): ?>
<div class="pf-legend-item"><div class="pf-legend-color" style="background: var(--warning);"></div><span>Off <?= htmlspecialchars($helper['name']) ?></span></div>
<div class="pf-legend-item"><div class="pf-legend-color" style="background: var(--danger);"></div><span>Extra <?= htmlspecialchars($helper['name']) ?></span></div>
<?php endforeach; ?>
<!-- Intervenants (Dynamique selon les congés paramétrés) -->
<?php foreach ($helpers as $helper):
$hLeaves = $leaveMatrix[$helper['id']] ?? [];
$colors = ['var(--warning)', 'var(--danger)', 'var(--primary)'];
foreach ($hLeaves as $index => $type):
?>
<div class="pf-legend-item">
<div class="pf-legend-color" style="background: <?= $colors[$index % count($colors)] ?>;"></div>
<span><?= htmlspecialchars($type) ?> <?= htmlspecialchars($helper['name']) ?></span>
</div>
<?php endforeach; endforeach; ?>
<!-- Modes de garde (Dynamique avec icônes) -->
<!-- Modes de garde (On masque "Nounou" si l'intervenant gère lui-même ses présences) -->
<?php foreach ($activeCareModes as $index => $mode):
$modeLower = strtolower($mode);
if ($modeLower === 'nounou' && !empty($helpers)) continue;
if ($modeLower === 'avis'): ?>
<div class="pf-legend-item">
<img src="/modules/family-calendar/assets/img/avis.svg" style="width:16px;height:16px;object-fit:contain;margin-right:6px;" alt="Avis">
@@ -483,9 +515,8 @@ require __DIR__ . '/header.php';
<?php endif;
endforeach; ?>
<!-- Enfants Malades (Dynamique avec couleur BDD) -->
<!-- Enfants Malades (Une puce par enfant) -->
<?php foreach ($kids as $kid):
// Fallback aligné exactement sur celui du JS (#e11d48)
$color = !empty($kid['color']) ? $kid['color'] : '#e11d48';
?>
<div class="pf-legend-item">
+12 -2
View File
@@ -300,6 +300,7 @@ return [
'fc_weekly_planning' => 'Planificació setmanal',
'fc_col_month' => 'Mes',
'fc_col_week_short' => 'Set.',
'fc_col_week' => 'Setmana',
'fc_col_av' => 'Av.',
'fc_col_use' => 'Use',
'fc_legend_title' => 'Llegenda',
@@ -310,7 +311,7 @@ return [
'fc_unit_days' => 'dies',
'fc_menu_carole' => 'Permisos Carole',
'fc_clear' => 'Esborrar',
'fc_menu_kids_leaves' => 'Permisos nens',
'fc_menu_kids_leaves' => 'Permisos pares',
'fc_err_save_snap' => 'Error en desar el correctiu.',
'fc_err_fetch_gov' => 'Error en la connexió amb l\'API governamental.',
'fc_err_no_data_gov' => 'No s\'han trobat dades a l\'API del govern per a aquest any.',
@@ -342,6 +343,15 @@ return [
'fc_add_mode_placeholder' => 'Ex: Mainadera, Casal, Avis...',
'fc_settings_updated' => 'Configuració actualitzada correctament!',
'fc_first_day' => 'Primer dia de la setmana',
'fc_helper_recap' => 'Resum Interventor',
'fc_no_helper_leaves' => 'Cap permís sol·licitat aquest mes.',
'fc_col_date' => 'Data',
'fc_col_type' => 'Tipus',
'fc_col_duration' => 'Durada',
'fc_full_year' => 'Any sencer',
'fc_custom_period' => 'Període específic...',
'fc_worked_days' => 'Treballats (Est.)',
// --- CALENDARI : PARÀMETRES (MEMBRES I VISUALITZACIÓ) ---
'fc_tab_display' => '🖥️ Visualització',
'fc_display_title' => 'Ajustos de visualització',
@@ -456,7 +466,7 @@ return [
'leg_extra_off' => 'Extra Off',
'leg_centre' => 'Casal',
'leg_avis' => 'Avís',
'leg_pep_sick' => 'Pep Malalt',
'fc_menu_sick' => 'Malalt',
'care_nounou' => 'Kangur',
'care_centre' => 'Casal',
'care_avis' => 'Avis',
+12 -2
View File
@@ -301,6 +301,7 @@ return [
'fc_weekly_planning' => 'Weekly planning',
'fc_col_month' => 'Month',
'fc_col_week_short' => 'Wk.',
'fc_col_week' => 'Week',
'fc_col_av' => 'Av.',
'fc_col_use' => 'Use',
'fc_legend_title' => 'Legend',
@@ -311,7 +312,7 @@ return [
'fc_unit_days' => 'days',
'fc_menu_carole' => 'Carole\'s Leave',
'fc_clear' => 'Clear',
'fc_menu_kids_leaves' => 'Children\'s Leave',
'fc_menu_kids_leaves' => 'Parent\'s Leave',
'fc_err_save_snap' => 'Error saving adjustment.',
'fc_err_fetch_gov' => 'Error connecting to the government API.',
'fc_err_no_data_gov' => 'No data found on the government API for this year.',
@@ -343,6 +344,15 @@ return [
'fc_add_mode_placeholder' => 'E.g., Nanny, Center, Grandparents...',
'fc_settings_updated' => 'Settings updated successfully!',
'fc_first_day' => 'First day of the week',
'fc_helper_recap' => 'Helper Summary',
'fc_no_helper_leaves' => 'No leaves taken this month.',
'fc_col_date' => 'Date',
'fc_col_type' => 'Type',
'fc_col_duration' => 'Duration',
'fc_full_year' => 'Full year',
'fc_custom_period' => 'Custom period...',
'fc_worked_days' => 'Worked (Est.)',
// --- CALENDAR: SETTINGS (MEMBERS & DISPLAY) ---
'fc_tab_display' => '🖥️ Display',
'fc_display_title' => 'Display Settings',
@@ -457,7 +467,7 @@ return [
'leg_extra_off' => 'Extra off',
'leg_centre' => 'Centre',
'leg_avis' => 'Notice',
'leg_pep_sick' => 'Pep sick',
'fc_menu_sick' => 'Sick',
'care_nounou' => 'Nanny',
'care_centre' => 'Daycare',
'care_avis' => 'Grandparents',
+12 -2
View File
@@ -302,6 +302,7 @@ return [
'fc_weekly_planning' => 'Planning hebdomadaire',
'fc_col_month' => 'Mois',
'fc_col_week_short' => 'Sem.',
'fc_col_week' => 'Semaine',
'fc_col_av' => 'Av.',
'fc_col_use' => 'Use',
'fc_legend_title' => 'Légende',
@@ -312,7 +313,7 @@ return [
'fc_unit_days' => 'jours',
'fc_menu_carole' => 'Congés Carole',
'fc_clear' => 'Effacer',
'fc_menu_kids_leaves' => 'Congés Enfants',
'fc_menu_kids_leaves' => 'Congés Parents',
'fc_err_save_snap' => 'Erreur lors de la sauvegarde du correctif.',
'fc_err_fetch_gov' => 'Erreur lors de la connexion à l\'API gouvernementale.',
'fc_err_no_data_gov' => 'Aucune donnée trouvée sur l\'API du gouvernement pour cette année.',
@@ -347,6 +348,15 @@ return [
'fc_add_mode_placeholder' => 'Ex: Nounou, Casal, Grands-parents...',
'fc_settings_updated' => 'Paramètres mis à jour avec succès !',
'fc_first_day' => 'Premier jour de la semaine',
'fc_helper_recap' => 'Récapitulatif Intervenant',
'fc_no_helper_leaves' => 'Aucun congé posé ce mois-ci.',
'fc_col_date' => 'Date',
'fc_col_type' => 'Type',
'fc_col_duration' => 'Durée',
'fc_full_year' => 'Année complète',
'fc_custom_period' => 'Période spécifique...',
'fc_worked_days' => 'Travaillés (Est.)',
// --- CALENDRIER : PARAMÈTRES (MEMBRES & AFFICHAGE) ---
'fc_tab_display' => '🖥️ Affichage',
'fc_display_title' => 'Paramètres d\'affichage',
@@ -462,7 +472,7 @@ return [
'leg_extra_off' => 'Extra Off',
'leg_centre' => 'Centre',
'leg_avis' => 'Avis',
'leg_pep_sick' => 'Pep Malade',
'fc_menu_sick' => 'Malade',
'vac_toussaint' => 'Vacances de la Toussaint',
'vac_noel' => 'Vacances de Noël',
-334
View File
@@ -1,334 +0,0 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mockup Épargne HouseHub</title>
<style>
:root {
--primary: #3b82f6;
--bg-page: #f8fafc;
--bg-panel: #ffffff;
--text-main: #0f172a;
--text-muted: #64748b;
--border-light: #e2e8f0;
--radius: 12px;
--shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--focus-ring: rgba(37, 99, 235, 0.1);
}
body {
font-family: system-ui, sans-serif;
background: var(--bg-page);
color: var(--text-main);
padding: 40px;
}
.controls {
margin-bottom: 20px;
display: flex;
gap: 10px;
}
button {
padding: 8px 16px;
cursor: pointer;
border-radius: 8px;
border: 1px solid var(--primary);
background: white;
color: var(--primary);
font-weight: bold;
}
button.active {
background: var(--primary);
color: white;
}
/* BASE GRID (Ta structure actuelle simplifiée) */
.epargne-grid-table {
display: flex;
flex-direction: column;
background: var(--bg-panel);
border-radius: var(--radius);
box-shadow: var(--shadow);
overflow: hidden;
border: 1px solid var(--border-light);
width: 100%;
max-width: 900px;
}
.eg-row,
.eg-header-row {
display: grid;
grid-template-columns: 180px repeat(3, minmax(115px, 1fr));
transition: 0.2s;
}
.eg-cell {
display: flex;
align-items: center;
justify-content: center;
font-size: 0.9rem;
}
.eg-sticky {
position: sticky;
left: 0;
justify-content: flex-start;
z-index: 10;
font-weight: 600;
}
.eg-input {
width: 70px;
text-align: center;
border-radius: 6px;
border: 1px solid transparent;
background: transparent;
font-family: inherit;
font-size: 0.9rem;
padding: 4px;
transition: 0.2s;
}
.eg-input:focus {
background: var(--bg-panel);
border-color: var(--primary);
outline: none;
box-shadow: 0 0 0 3px var(--focus-ring);
}
/* ========================================= */
/* STYLE A : COMPACT & AÉRIEN */
/* ========================================= */
.style-a .eg-row,
.style-a .eg-header-row {
border-bottom: 1px solid var(--border-light);
}
.style-a .eg-cell {
padding: 6px 8px;
}
.style-a .eg-header-row {
background: #f8fafc;
font-size: 0.75rem;
text-transform: uppercase;
color: var(--text-muted);
}
.style-a .eg-header-row .eg-sticky {
background: #f8fafc;
}
.style-a .eg-row .eg-sticky {
background: var(--bg-panel);
border-right: 1px solid var(--border-light);
}
.style-a .eg-row:hover {
background: #f1f5f9;
}
.style-a .eg-row:hover .eg-sticky {
background: #f1f5f9;
}
.style-a .eg-input:hover {
background: #e2e8f0;
}
/* ========================================= */
/* STYLE B : ZÉBRÉ DOUX */
/* ========================================= */
.style-b .eg-cell {
padding: 8px 10px;
border-right: 1px dashed #e2e8f0;
}
.style-b .eg-header-row {
background: #1e293b;
color: white;
font-size: 0.8rem;
text-transform: uppercase;
}
.style-b .eg-header-row .eg-sticky {
background: #1e293b;
border-right: 2px solid #334155;
}
.style-b .eg-row:nth-child(even) {
background: #f8fafc;
}
.style-b .eg-row:nth-child(even) .eg-sticky {
background: #f8fafc;
}
.style-b .eg-row .eg-sticky {
background: var(--bg-panel);
border-right: 2px solid var(--border-light);
}
.style-b .eg-input {
background: #f1f5f9;
border: 1px solid #e2e8f0;
}
.style-b .eg-input:hover {
border-color: #cbd5e1;
}
/* ========================================= */
/* STYLE C : SAAS DASHBOARD */
/* ========================================= */
.style-c .eg-row,
.style-c .eg-header-row {
border-bottom: 1px solid #f1f5f9;
}
.style-c .eg-cell {
padding: 10px 12px;
}
.style-c .eg-header-row {
background: #ffffff;
color: var(--text-muted);
font-size: 0.8rem;
border-bottom: 2px solid var(--border-light);
}
.style-c .eg-header-row .eg-sticky {
background: #ffffff;
}
.style-c .eg-row .eg-sticky {
background: var(--bg-panel);
position: relative;
}
/* Barre de surbrillance au hover */
.style-c .eg-row:hover {
background: #eff6ff;
}
.style-c .eg-row:hover .eg-sticky {
background: #eff6ff;
}
.style-c .eg-row:hover .eg-sticky::before {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
background: var(--primary);
border-radius: 0 4px 4px 0;
}
.style-c .eg-input {
background: transparent;
border-bottom: 1px dashed #cbd5e1;
border-radius: 0;
padding: 2px 4px;
}
.style-c .eg-input:focus {
border-bottom: 1px solid var(--primary);
border-radius: 4px;
}
</style>
</head>
<body>
<div class="controls">
<button onclick="setStyle('style-a')" id="btn-a" class="active">
Style A : Compact
</button>
<button onclick="setStyle('style-b')" id="btn-b">Style B : Zébré</button>
<button onclick="setStyle('style-c')" id="btn-c">Style C : SaaS</button>
</div>
<!-- Grille Mockup -->
<div class="epargne-grid-table style-a" id="grid-container">
<div class="eg-header-row">
<div class="eg-cell eg-sticky">Poste / Mois</div>
<div class="eg-cell">Août 2026</div>
<div class="eg-cell">Juillet 2026</div>
<div class="eg-cell">Juin 2026</div>
</div>
<div class="eg-row" style="font-weight: bold; background: #f0f9ff">
<div
class="eg-cell eg-sticky"
style="background: #f0f9ff; color: #0369a1"
>
TOTAL BANQUE
</div>
<div class="eg-cell">
<input
type="number"
class="eg-input"
style="color: #0369a1; font-weight: bold"
value="7947"
/>
€
</div>
<div class="eg-cell">
<input
type="number"
class="eg-input"
style="color: #0369a1; font-weight: bold"
value="6807"
/>
€
</div>
<div class="eg-cell">
<input
type="number"
class="eg-input"
style="color: #0369a1; font-weight: bold"
value="7846"
/>
€
</div>
</div>
<div class="eg-row">
<div class="eg-cell eg-sticky">Ecole P&P</div>
<div class="eg-cell">
<input type="number" class="eg-input" value="480" />
</div>
<div class="eg-cell">
<input type="number" class="eg-input" value="480" />
</div>
<div class="eg-cell">
<input type="number" class="eg-input" value="480" />
</div>
</div>
<div class="eg-row">
<div class="eg-cell eg-sticky">Extra casa</div>
<div class="eg-cell">
<input type="number" class="eg-input" value="635" />
</div>
<div class="eg-cell">
<input type="number" class="eg-input" value="635" />
</div>
<div class="eg-cell">
<input type="number" class="eg-input" value="635" />
</div>
</div>
<div class="eg-row">
<div class="eg-cell eg-sticky">Vacances</div>
<div class="eg-cell">
<input type="number" class="eg-input" value="1002" />
</div>
<div class="eg-cell">
<input type="number" class="eg-input" value="1002" />
</div>
<div class="eg-cell">
<input type="number" class="eg-input" value="2002" />
</div>
</div>
<div class="eg-row" style="background: #fffbeb">
<div
class="eg-cell eg-sticky"
style="background: #fffbeb; color: #b45309"
>
NON ALLOUÉ
</div>
<div class="eg-cell" style="color: #10b981; font-weight: bold">
1230 €
</div>
<div class="eg-cell" style="color: #10b981; font-weight: bold">
660 €
</div>
<div class="eg-cell" style="color: #10b981; font-weight: bold">
699 €
</div>
</div>
</div>
<script>
function setStyle(styleClass) {
const grid = document.getElementById("grid-container");
grid.className = "epargne-grid-table " + styleClass;
document
.querySelectorAll(".controls button")
.forEach((btn) => btn.classList.remove("active"));
document
.getElementById("btn-" + styleClass.split("-")[1])
.classList.add("active");
}
</script>
</body>
</html>
+126 -37
View File
@@ -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);
}
+330 -52
View File
@@ -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);
}