filtre multiple

This commit is contained in:
2026-05-05 20:50:22 +02:00
parent 54edb5ed58
commit be2edcce82
2 changed files with 432 additions and 282 deletions
+290 -235
View File
@@ -8,15 +8,15 @@ require_once __DIR__ . '/includes/i18n.php';
if (session_status() === PHP_SESSION_NONE) { session_start(); }
// --- 1. CONFIGURATION & DONNÉES ---
// --- 1. CONFIGURATION ---
$year = (int)date('Y');
$pageTitle = tr('gift_page_title');
$activePage = "gift-list";
$bodyClass = "pf-gift-list";
$pageCss = "/modules/gift-list/gift-list.css";
$baseAdults = ['Laia', 'Laura', 'Avi Iaia'];
$children = ['Pol', 'Pep', 'Elna', 'Bru', 'Guim'];
$baseAdults = ['Laia', 'Laura', 'Avi Iaia'];
$extraAdults = ['Pauline', 'Papy JC', 'Mamy Caro'];
$adultsByChildForAnniv = [
@@ -35,71 +35,46 @@ $VIEWS = [
$currentView = strtolower($_GET['view'] ?? ($_SESSION['gift_view'] ?? 'nadal'));
if (!isset($VIEWS[$currentView])) $currentView = 'nadal';
$_SESSION['gift_view'] = $currentView;
$allowedOccasions = $VIEWS[$currentView];
$allOccasionLabels = [
'TIO' => tr('gift_occ_tio'),
'NOEL' => tr('gift_occ_noel'),
'ROIS' => tr('gift_occ_rois'),
'ANNIV' => tr('gift_occ_anniv'),
'SANT' => tr('gift_occ_sant'),
'TIO' => tr('gift_occ_tio'), 'NOEL' => tr('gift_occ_noel'), 'ROIS' => tr('gift_occ_rois'),
'ANNIV' => tr('gift_occ_anniv'), 'SANT' => tr('gift_occ_sant')
];
$occasionIcons = [
'TIO' => '/modules/gift-list/assets/img/tio.png',
'NOEL' => '/modules/gift-list/assets/img/santa.png',
'ROIS' => '/modules/gift-list/assets/img/reis.png',
'ANNIV' => '/modules/gift-list/assets/img/corona.png',
'SANT' => '/modules/gift-list/assets/img/sant.png',
'TIO' => '/modules/gift-list/assets/img/tio.png', 'NOEL' => '/modules/gift-list/assets/img/santa.png',
'ROIS' => '/modules/gift-list/assets/img/reis.png', 'ANNIV' => '/modules/gift-list/assets/img/corona.png',
'SANT' => '/modules/gift-list/assets/img/sant.png'
];
// --- 2. RÉCUPÉRATION DES DONNÉES ---
// --- 2. DONNÉES ---
$inMarks = implode(',', array_fill(0, count($allowedOccasions), '?'));
// Le tri SQL regroupe par Occasion, puis par Enfant, puis par Adulte
$sql = "SELECT * FROM pf_gifts WHERE year = ? AND occasion IN ($inMarks) ORDER BY occasion ASC, child_name ASC, adult_name ASC, created_at DESC";
// Tri: Par Fête, puis Enfant, puis Adulte
$sql = "SELECT * FROM pf_gifts WHERE year = ? AND occasion IN ($inMarks) ORDER BY occasion ASC, child_name ASC, adult_name ASC";
$stmt = $pdo->prepare($sql);
$stmt->execute(array_merge([$year], $allowedOccasions));
$gifts = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
$gifts = $stmt->fetchAll(PDO::FETCH_ASSOC);
$dataByOccasion = [];
$data = [];
$adultsInView = [];
foreach ($gifts as $g) {
$occ = $g['occasion'];
$child = $g['child_name'];
$adult = $g['adult_name'];
// Groupement structuré : Occasion -> Enfant -> Cadeaux
$dataByOccasion[$occ][$child]['gifts'][] = $g;
// Totaux pour les petites pills sous le nom de l'enfant
if (!isset($dataByOccasion[$occ][$child]['totals'][$adult])) {
$dataByOccasion[$occ][$child]['totals'][$adult] = 0;
}
$dataByOccasion[$occ][$child]['totals'][$adult] += (float)$g['amount'];
$adultsInView[$adult] = true;
$data[$g['occasion']][$g['child_name']]['gifts'][] = $g;
$data[$g['occasion']][$g['child_name']]['totals'][$g['adult_name']] = ($data[$g['occasion']][$g['child_name']]['totals'][$g['adult_name']] ?? 0) + $g['amount'];
$adultsInView[$g['adult_name']] = true;
}
$allAdultsList = array_keys($adultsInView); sort($allAdultsList);
$allAdultsList = array_keys($adultsInView);
sort($allAdultsList);
$occasionsToShow = array_values(array_intersect(array_keys($allOccasionLabels), $allowedOccasions));
// --- 3. CALCUL TRICOUNT (Bilan financier global de la vue) ---
// --- 3. TRICOUNT ---
$people = array_values(array_unique(array_merge($baseAdults, array_column($gifts, 'adult_name'), array_column($gifts, 'payer_name'))));
$people = array_filter($people);
$matrix = [];
foreach ($people as $p1) {
foreach ($people as $p2) $matrix[$p1][$p2] = 0.0;
}
foreach ($people as $p1) { foreach ($people as $p2) $matrix[$p1][$p2] = 0.0; }
foreach ($gifts as $g) {
$adult = $g['adult_name'];
$payer = $g['payer_name'] ?? $g['adult_name'];
$amt = (float)$g['amount'];
if ($amt > 0 && $adult && $payer && $adult !== $payer) {
$matrix[$adult][$payer] += $amt;
}
@@ -109,8 +84,7 @@ $settlements = [];
$countPeople = count($people);
for ($i = 0; $i < $countPeople; $i++) {
for ($j = $i + 1; $j < $countPeople; $j++) {
$a = $people[$i];
$b = $people[$j];
$a = $people[$i]; $b = $people[$j];
$net = $matrix[$a][$b] - $matrix[$b][$a];
if ($net > 0.01) { $settlements[] = ['from' => $a, 'to' => $b, 'amount' => $net]; }
elseif ($net < -0.01) { $settlements[] = ['from' => $b, 'to' => $a, 'amount' => -$net]; }
@@ -123,109 +97,98 @@ require __DIR__ . '/header.php';
<div class="pf-container cl-view-<?= htmlspecialchars($currentView) ?>">
<div class="cl-titlebar">
<h1><?= sprintf(tr('gift_main_title'), $year) ?></h1>
<div class="cl-view-switch" aria-label="<?= tr('gift_aria_change_view') ?>">
<h1>🎁 <?= sprintf(tr('gift_main_title'), $year) ?></h1>
<div class="cl-view-switch">
<a href="?view=nadal" class="cl-view-btn <?= $currentView === 'nadal' ? 'is-active' : '' ?>"><?= tr('gift_view_nadal') ?></a>
<a href="?view=anniversary" class="cl-view-btn <?= $currentView === 'anniversary' ? 'is-active' : '' ?>"><?= tr('gift_view_anniv') ?></a>
</div>
</div>
<div class="pf-filter-bar">
<div class="pf-filter-label">🔍</div>
<select id="filterChild" class="pf-filter-select" onchange="applyGiftFilters()">
<option value="all">👦 <?= tr('gift_filter_all_children') ?></option>
<?php foreach ($children as $c): ?>
<option value="<?= htmlspecialchars($c) ?>"><?= htmlspecialchars($c) ?></option>
<?php endforeach; ?>
</select>
<span style="font-size:1.2rem;">🔍</span>
<select id="filterAdult" class="pf-filter-select" onchange="applyGiftFilters()">
<option value="all">👤 <?= tr('gift_filter_all_adults') ?></option>
<?php foreach ($allAdultsList as $a): ?>
<option value="<?= htmlspecialchars($a) ?>"><?= htmlspecialchars($a) ?></option>
<div class="pf-multi-select" id="ms-child">
<div class="pf-ms-trigger" onclick="toggleMS('ms-child-list', this)">
👦 <span id="ms-child-label"><?= tr('gift_filter_all_children') ?></span>
</div>
<div class="pf-ms-dropdown" id="ms-child-list">
<label class="pf-ms-option is-all">
<input type="checkbox" value="all" checked onchange="handleMSChange(this, 'child')">
<?= tr('gift_filter_all_children') ?>
</label>
<?php foreach($children as $c): ?>
<label class="pf-ms-option"><input type="checkbox" value="<?= htmlspecialchars($c) ?>" onchange="handleMSChange(this, 'child')"> <?= htmlspecialchars($c) ?></label>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="pf-multi-select" id="ms-adult">
<div class="pf-ms-trigger" onclick="toggleMS('ms-adult-list', this)">
👤 <span id="ms-adult-label"><?= tr('gift_filter_all_adults') ?></span>
</div>
<div class="pf-ms-dropdown" id="ms-adult-list">
<label class="pf-ms-option is-all">
<input type="checkbox" value="all" checked onchange="handleMSChange(this, 'adult')">
<?= tr('gift_filter_all_adults') ?>
</label>
<?php foreach($allAdultsList as $a): ?>
<label class="pf-ms-option"><input type="checkbox" value="<?= htmlspecialchars($a) ?>" onchange="handleMSChange(this, 'adult')"> <?= htmlspecialchars($a) ?></label>
<?php endforeach; ?>
</div>
</div>
</div>
<section class="pf-section">
<?php if (empty($gifts)): ?>
<div style="text-align:center; padding:40px; color:var(--text-muted); font-style:italic; background:white; border-radius:12px; border:1px solid var(--border-light);">
<?= sprintf(tr('gift_no_gifts'), $year) ?>
</div>
<?php endif; ?>
<?php foreach ($occasionsToShow as $occCode): ?>
<div class="pf-occasion-wrapper js-occ-wrapper">
<?php foreach ($allowedOccasions as $occCode): ?>
<div class="js-occ-section">
<h2 class="cl-occasion-title">
<?php if (!empty($occasionIcons[$occCode])): ?>
<img class="cl-occasion-icon" src="<?= htmlspecialchars($occasionIcons[$occCode]) ?>" alt="" aria-hidden="true">
<?php endif; ?>
<?= htmlspecialchars($allOccasionLabels[$occCode] ?? $occCode) ?>
<?php if(!empty($occasionIcons[$occCode])): ?><img src="<?= $occasionIcons[$occCode] ?>" class="cl-occasion-icon"><?php endif; ?>
<?= $allOccasionLabels[$occCode] ?>
</h2>
<?php foreach ($children as $childName):
$childData = $dataByOccasion[$occCode][$childName] ?? ['gifts' => [], 'totals' => []];
$childGifts = $childData['gifts'] ?? [];
$childTotals = $childData['totals'] ?? [];
$adultsForChild = ($currentView === 'anniversary') ? ($adultsByChildForAnniv[$childName] ?? $baseAdults) : $baseAdults;
// Sécurisation du JSON pour éviter l'erreur "openGiftModal is not defined"
$addBtnData = json_encode([
'child' => $childName,
'occ' => $occCode,
'adults' => array_values($adultsForChild)
], JSON_HEX_APOS | JSON_HEX_QUOT);
<?php foreach ($children as $child):
$childData = $data[$occCode][$child] ?? ['gifts' => [], 'totals' => []];
$adultsForThisChild = ($currentView === 'anniversary' && in_array($child, ['Pol', 'Pep'])) ? array_merge($baseAdults, $extraAdults) : $baseAdults;
?>
<div class="pf-child-section js-child-section" data-child="<?= htmlspecialchars($childName) ?>">
<div class="pf-child-section js-child" data-name="<?= htmlspecialchars($child) ?>">
<div class="pf-child-header">
<h3>👦 <?= htmlspecialchars($childName) ?></h3>
<button type="button" class="pf-btn pf-btn-small" onclick='openGiftModal("add", <?= $addBtnData ?>)'>
<h3>👦 <?= htmlspecialchars($child) ?></h3>
<button class="pf-btn pf-btn-small btn-add-gift"
data-child="<?= htmlspecialchars($child) ?>"
data-occ="<?= htmlspecialchars($occCode) ?>"
data-adults="<?= htmlspecialchars(json_encode(array_values($adultsForThisChild))) ?>">
<?= tr('gift_add_gift') ?>
</button>
</div>
<?php if (!empty($childTotals)): ?>
<div class="pf-child-totals-bar">
<?php foreach ($childTotals as $adult => $tot): ?>
<span class="pf-summary-pill js-pill-adult" data-adult="<?= htmlspecialchars($adult) ?>">
👤 <?= htmlspecialchars($adult) ?> : <strong><?= number_format($tot, 2, ',', '') ?> €</strong>
</span>
<?php foreach ($childData['totals'] as $adult => $tot): ?>
<span class="pf-summary-pill js-pill-adult" data-adult="<?= htmlspecialchars($adult) ?>">👤 <?= htmlspecialchars($adult) ?> : <strong><?= number_format($tot, 2, ',', '') ?> €</strong></span>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php if (empty($childGifts)): ?>
<?php if (empty($childData['gifts'])): ?>
<p class="js-empty-state" style="color:var(--text-muted); font-size:0.9rem; font-style:italic; margin:0;"><?= tr('gift_empty_state_no_gifts') ?></p>
<?php else: ?>
<p class="js-empty-state" style="color:var(--text-muted); font-size:0.9rem; font-style:italic; display:none; margin:0;"><?= tr('gift_empty_state_no_filter') ?></p>
<div class="pf-gift-feed">
<?php foreach ($childGifts as $gift):
$editBtnData = json_encode($gift, JSON_HEX_APOS | JSON_HEX_QUOT);
?>
<div class="pf-gift-card-compact js-gift-card" data-adult="<?= htmlspecialchars($gift['adult_name']) ?>">
<?php foreach ($childData['gifts'] as $g): ?>
<div class="pf-gift-card-compact js-gift-card" data-adult="<?= htmlspecialchars($g['adult_name']) ?>">
<div>
<h4 class="pf-gift-title">
<?= htmlspecialchars($gift['gift_description']) ?>
<?php if(!empty($gift['product_link'])): ?>
<a href="<?= htmlspecialchars($gift['product_link']) ?>" target="_blank" class="pf-gift-link" title="Voir l'article">🔗</a>
<?php endif; ?>
<?= htmlspecialchars($g['gift_description']) ?>
<?php if($g['product_link']): ?><a href="<?= htmlspecialchars($g['product_link']) ?>" target="_blank" class="pf-gift-link">🔗</a><?php endif; ?>
</h4>
<div class="pf-gift-badges-col">
<span class="pf-pill-adult">👤 <?= htmlspecialchars($gift['adult_name']) ?></span>
</div>
</div>
<?php if(!empty($gift['payer_name']) && $gift['payer_name'] !== $gift['adult_name']): ?>
<div class="pf-gift-payer"><?= sprintf(tr('gift_paid_by'), htmlspecialchars($gift['payer_name'])) ?></div>
<span class="pf-pill-adult">👤 <?= htmlspecialchars($g['adult_name']) ?></span>
<?php if($g['payer_name'] && $g['payer_name'] !== $g['adult_name']): ?>
<div class="pf-gift-payer"><?= sprintf(tr('gift_paid_by'), htmlspecialchars($g['payer_name'])) ?></div>
<?php endif; ?>
</div>
<div class="pf-gift-footer">
<span class="pf-gift-price"><?= number_format($gift['amount'], 2, ',', ' ') ?> €</span>
<span class="pf-gift-price"><?= number_format($g['amount'], 2, ',', ' ') ?> €</span>
<div class="pf-gift-actions">
<button type="button" class="btn-icon-action edit" aria-label="<?= tr('edit') ?>" onclick='openGiftModal("edit", <?= $editBtnData ?>)'>✏️</button>
<button type="button" class="btn-icon-action delete" aria-label="<?= tr('delete') ?>" onclick="deleteGift(<?= $gift['id'] ?>)">🗑️</button>
<button class="btn-icon-action edit btn-edit-gift" data-gift="<?= htmlspecialchars(json_encode($g)) ?>">✏️</button>
<button class="btn-icon-action delete btn-delete-gift" data-id="<?= $g['id'] ?>">🗑️</button>
</div>
</div>
</div>
@@ -270,8 +233,7 @@ require __DIR__ . '/header.php';
<th style="position:sticky; left:0; background:white; z-index:2; border-right:2px solid #e2e8f0;"><?= htmlspecialchars($debtor) ?></th>
<?php foreach ($people as $creditor):
$val = $matrix[$debtor][$creditor] ?? 0;
$isDiag = ($debtor === $creditor);
$display = $isDiag || $val == 0 ? '—' : number_format($val, 2, ',', ' ') . ' €';
$display = ($debtor === $creditor) || $val == 0 ? '—' : number_format($val, 2, ',', ' ') . ' €';
?>
<td style="<?= $val > 0 ? 'color:var(--danger); font-weight:700; background:#fef2f2;' : 'color:var(--text-muted);' ?>"><?= $display ?></td>
<?php endforeach; ?>
@@ -287,44 +249,25 @@ require __DIR__ . '/header.php';
<div id="pf-gift-modal" class="pf-modal">
<div class="pf-modal-content" style="max-width: 500px; width: 95%;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
<h3 id="pf-modal-title" class="pf-modal-title" style="margin:0; border:none; padding:0;"><?= tr('gift_col_gift') ?></h3>
<button type="button" onclick="closeGiftModal()" style="background:none; border:none; font-size:1.8rem; cursor:pointer; color:var(--text-muted); line-height:1;">&times;</button>
<h3 id="modalTitle" class="pf-modal-title" style="margin:0; border:none; padding:0;">Cadeau</h3>
<button type="button" class="btn-modal-close" style="background:none; border:none; font-size:1.8rem; cursor:pointer; color:var(--text-muted); line-height:1;">&times;</button>
</div>
<form method="post" action="/modules/gift-list/save-gift.php" id="pf-gift-form">
<input type="hidden" name="year" id="m_year" value="<?= $year ?>">
<input type="hidden" name="child_name" id="m_child">
<input type="hidden" name="occasion" id="m_occ">
<input type="hidden" name="action" id="m_action">
<input type="hidden" name="gift_id" id="m_id">
<form method="post" action="/modules/gift-list/save-gift.php" id="giftForm">
<input type="hidden" name="year" value="<?= $year ?>">
<input type="hidden" name="child_name" id="modalChild">
<input type="hidden" name="occasion" id="modalOccasion">
<input type="hidden" name="action" id="modalAction">
<input type="hidden" name="gift_id" id="modalGiftId">
<div class="pf-form-group">
<label class="pf-label"><?= tr('gift_col_adult') ?></label>
<select name="adult_name" id="m_adult" class="pf-input" required></select>
</div>
<div class="pf-form-group">
<label class="pf-label"><?= tr('gift_modal_payer') ?></label>
<select name="payer_name" id="m_payer" class="pf-input" required></select>
</div>
<div class="pf-form-group">
<label class="pf-label"><?= tr('gift_modal_gift_name') ?></label>
<input type="text" name="gift_description" id="m_gift" class="pf-input" placeholder="<?= tr('gift_modal_ph_name') ?>" required>
</div>
<div class="pf-form-group">
<label class="pf-label"><?= tr('gift_modal_price') ?></label>
<input type="number" name="amount" id="m_amount" class="pf-input" placeholder="0.00" step="0.01" min="0">
</div>
<div class="pf-form-group" style="margin-bottom:25px;">
<label class="pf-label"><?= tr('gift_modal_link') ?></label>
<input type="url" name="product_link" id="m_link" class="pf-input" placeholder="https://...">
</div>
<div class="pf-form-group"><label class="pf-label"><?= tr('gift_col_adult') ?></label><select name="adult_name" id="modalAdult" class="pf-input" required></select></div>
<div class="pf-form-group"><label class="pf-label"><?= tr('gift_modal_payer') ?></label><select name="payer_name" id="modalPayer" class="pf-input" required></select></div>
<div class="pf-form-group"><label class="pf-label"><?= tr('gift_modal_gift_name') ?></label><input type="text" name="gift_description" id="modalDesc" class="pf-input" required></div>
<div class="pf-form-group"><label class="pf-label"><?= tr('gift_modal_price') ?></label><input type="number" step="0.01" name="amount" id="modalAmount" class="pf-input"></div>
<div class="pf-form-group" style="margin-bottom:25px;"><label class="pf-label"><?= tr('gift_modal_link') ?></label><input type="url" name="product_link" id="modalLink" class="pf-input"></div>
<div class="modal-footer" style="padding-top:15px; border-top:1px solid #e2e8f0; display:flex; justify-content:flex-end; gap:10px;">
<button type="button" onclick="closeGiftModal()" class="pf-btn btn-secondary"><?= tr('btn_cancel') ?></button>
<button type="button" class="pf-btn btn-secondary btn-modal-close"><?= tr('btn_cancel') ?></button>
<button type="submit" class="pf-btn"><?= tr('btn_save') ?></button>
</div>
</form>
@@ -332,63 +275,132 @@ require __DIR__ . '/header.php';
</div>
<script>
// --- SYNCHRONISATION AUTO : ADULTE -> PAYEUR ---
document.getElementById('m_adult').addEventListener('change', function() {
const payerSelect = document.getElementById('m_payer');
// Si la valeur choisie pour l'adulte existe dans la liste des payeurs, on l'applique
if (Array.from(payerSelect.options).some(o => o.value === this.value)) {
payerSelect.value = this.value;
// ==========================================
// 1. GESTION DU COMPOSANT MULTI-SELECT VANILLA
// ==========================================
function toggleMS(listId, triggerEl) {
document.querySelectorAll('.pf-ms-dropdown').forEach(el => { if (el.id !== listId) el.classList.remove('open'); });
document.querySelectorAll('.pf-ms-trigger').forEach(el => { if (el !== triggerEl) el.classList.remove('active'); });
const list = document.getElementById(listId);
if (list) list.classList.toggle('open');
if (triggerEl) triggerEl.classList.toggle('active');
}
document.addEventListener('click', e => {
if (!e.target.closest('.pf-multi-select')) {
document.querySelectorAll('.pf-ms-dropdown').forEach(el => el.classList.remove('open'));
document.querySelectorAll('.pf-ms-trigger').forEach(el => el.classList.remove('active'));
}
});
// --- LOGIQUE DES FILTRES ---
function applyGiftFilters() {
const fChild = document.getElementById('filterChild').value;
const fAdult = document.getElementById('filterAdult').value;
function handleMSChange(cb, type) {
const container = document.getElementById('ms-' + type + '-list');
if (!container) return;
document.querySelectorAll('.js-occ-wrapper').forEach(occWrapper => {
let hasVisibleChildrenInOccasion = false;
occWrapper.querySelectorAll('.js-child-section').forEach(childSec => {
const cName = childSec.dataset.child;
const matchChild = (fChild === 'all' || cName === fChild);
let visibleCount = 0;
childSec.querySelectorAll('.js-gift-card').forEach(card => {
const matchAdult = (fAdult === 'all' || card.dataset.adult === fAdult);
if (matchChild && matchAdult) {
card.style.display = 'flex';
visibleCount++;
if (cb.value === 'all' && cb.checked) {
container.querySelectorAll('input[type="checkbox"]:not([value="all"])').forEach(i => i.checked = false);
} else if (cb.checked) {
const allCb = container.querySelector('input[value="all"]');
if (allCb) allCb.checked = false;
} else {
card.style.display = 'none';
const checkedSpecifics = container.querySelectorAll('input[type="checkbox"]:checked:not([value="all"])');
if (checkedSpecifics.length === 0) {
const allCb = container.querySelector('input[value="all"]');
if (allCb) allCb.checked = true;
}
}
const checkedSpecifics = container.querySelectorAll('input[type="checkbox"]:checked:not([value="all"])');
const labelEl = document.getElementById('ms-' + type + '-label');
if (labelEl) {
if (checkedSpecifics.length === 0) {
let defaultText = 'Tous';
if (window.I18N) {
if (type === 'child' && window.I18N['gift_filter_all_children']) defaultText = window.I18N['gift_filter_all_children'];
if (type === 'adult' && window.I18N['gift_filter_all_adults']) defaultText = window.I18N['gift_filter_all_adults'];
}
labelEl.innerText = defaultText;
} else if (checkedSpecifics.length === 1) {
labelEl.innerText = checkedSpecifics[0].value;
} else {
labelEl.innerText = checkedSpecifics.length + ' sélections';
}
}
applyGiftFilters();
}
function applyGiftFilters() {
const cList = document.getElementById('ms-child-list');
const aList = document.getElementById('ms-adult-list');
if (!cList || !aList) return;
const allChildCb = cList.querySelector('input[value="all"]');
const cAll = allChildCb ? allChildCb.checked : true;
const cVals = Array.from(cList.querySelectorAll('input[type="checkbox"]:checked:not([value="all"])')).map(i => i.value);
const allAdultCb = aList.querySelector('input[value="all"]');
const aAll = allAdultCb ? allAdultCb.checked : true;
const aVals = Array.from(aList.querySelectorAll('input[type="checkbox"]:checked:not([value="all"])')).map(i => i.value);
document.querySelectorAll('.js-occ-section').forEach(occSec => {
let occHasVisible = false;
occSec.querySelectorAll('.js-child').forEach(childSec => {
const cName = childSec.getAttribute('data-name');
const matchChild = cAll || (cVals.indexOf(cName) !== -1);
let childHasVisibleCard = false;
if (matchChild) {
childSec.querySelectorAll('.js-gift-card').forEach(card => {
const aName = card.getAttribute('data-adult');
const matchAdult = aAll || (aVals.indexOf(aName) !== -1);
card.style.display = matchAdult ? 'flex' : 'none';
if (matchAdult) childHasVisibleCard = true;
});
childSec.querySelectorAll('.js-pill-adult').forEach(pill => {
pill.style.opacity = (fAdult === 'all' || pill.dataset.adult === fAdult) ? '1' : '0.3';
const aName = pill.getAttribute('data-adult');
const matchAdult = aAll || (aVals.indexOf(aName) !== -1);
pill.style.opacity = matchAdult ? '1' : '0.2';
});
const emptyState = childSec.querySelector('.js-empty-state');
if (matchChild) {
const empty = childSec.querySelector('.js-empty-state');
if (empty) empty.style.display = childHasVisibleCard ? 'none' : 'block';
childSec.style.display = 'block';
hasVisibleChildrenInOccasion = true;
if (emptyState) emptyState.style.display = (visibleCount === 0) ? 'block' : 'none';
occHasVisible = true;
} else {
childSec.style.display = 'none';
}
});
// Masquer complètement la fête (ex: Tió) s'il n'y a plus aucun enfant visible dedans
occWrapper.style.display = hasVisibleChildrenInOccasion ? 'block' : 'none';
occSec.style.display = occHasVisible ? 'block' : 'none';
});
}
// --- LOGIQUE DE LA MODALE ---
// ==========================================
// 3. LOGIQUE DE LA MODALE & DELEGATION JS
// ==========================================
const modal = document.getElementById('pf-gift-modal');
const adultSelect = document.getElementById('m_adult');
const payerSelect = document.getElementById('m_payer');
const adultSelect = document.getElementById('modalAdult');
const payerSelect = document.getElementById('modalPayer');
// Synchronisation automatique : Adulte -> Payeur
if (adultSelect && payerSelect) {
adultSelect.addEventListener('change', function() {
let exists = false;
for(let i = 0; i < payerSelect.options.length; i++) {
if(payerSelect.options[i].value === this.value) exists = true;
}
if (exists) payerSelect.value = this.value;
});
}
function populateSelects(adults) {
if (!adultSelect || !payerSelect) return;
adultSelect.innerHTML = '';
payerSelect.innerHTML = '';
adults.forEach(name => {
@@ -397,75 +409,92 @@ function populateSelects(adults) {
});
}
function openGiftModal(mode, data) {
if (mode === 'add') {
document.getElementById('m_action').value = 'create';
document.getElementById('m_id').value = '';
document.getElementById('m_child').value = data.child;
document.getElementById('m_occ').value = data.occ; // L'occasion est passée par le bouton !
document.getElementById('m_gift').value = '';
document.getElementById('m_amount').value = '';
document.getElementById('m_link').value = '';
// L'écouteur d'événements global pour tous les boutons !
document.body.addEventListener('click', async function(e) {
populateSelects(data.adults);
document.getElementById('pf-modal-title').textContent = (window.I18N && window.I18N['gift_modal_title_add'] ? window.I18N['gift_modal_title_add'] : 'Ajouter pour %s').replace('%s', data.child);
// --- 1. BOUTON FERMER MODALE ---
if (e.target.closest('.btn-modal-close')) {
if (modal) {
modal.classList.remove('open');
document.body.classList.remove('no-scroll');
}
else if (mode === 'edit') {
document.getElementById('m_action').value = 'update';
document.getElementById('m_id').value = data.id;
document.getElementById('m_child').value = data.child_name;
document.getElementById('m_occ').value = data.occasion;
document.getElementById('m_gift').value = data.gift_description;
document.getElementById('m_amount').value = data.amount;
document.getElementById('m_link').value = data.product_link;
return;
}
// --- 2. BOUTON AJOUTER (+) ---
const btnAdd = e.target.closest('.btn-add-gift');
if (btnAdd) {
const childName = btnAdd.dataset.child;
const occCode = btnAdd.dataset.occ;
const adultsList = JSON.parse(btnAdd.dataset.adults || '[]');
document.getElementById('modalAction').value = 'create';
document.getElementById('modalGiftId').value = '';
document.getElementById('modalChild').value = childName;
document.getElementById('modalOccasion').value = occCode;
document.getElementById('modalDesc').value = '';
document.getElementById('modalAmount').value = '';
document.getElementById('modalLink').value = '';
populateSelects(adultsList);
if (adultSelect && payerSelect) {
if (adultsList.indexOf('Laia') !== -1) {
adultSelect.value = 'Laia'; payerSelect.value = 'Laia';
} else if (adultsList.length > 0) {
adultSelect.value = adultsList[0]; payerSelect.value = adultsList[0];
}
}
const titleStr = window.I18N && window.I18N['gift_modal_title_add'] ? window.I18N['gift_modal_title_add'] : 'Ajouter pour %s';
document.getElementById('modalTitle').textContent = titleStr.replace('%s', childName);
if (modal) { modal.classList.add('open'); document.body.classList.add('no-scroll'); }
return;
}
// --- 3. BOUTON MODIFIER (CRAYON) ---
const btnEdit = e.target.closest('.btn-edit-gift');
if (btnEdit) {
const data = JSON.parse(btnEdit.dataset.gift || '{}');
document.getElementById('modalAction').value = 'update';
document.getElementById('modalGiftId').value = data.id;
document.getElementById('modalChild').value = data.child_name;
document.getElementById('modalOccasion').value = data.occasion;
document.getElementById('modalDesc').value = data.gift_description;
document.getElementById('modalAmount').value = data.amount;
document.getElementById('modalLink').value = data.product_link;
const payerVal = data.payer_name || data.adult_name;
if (!Array.from(adultSelect.options).some(o => o.value === data.adult_name)) {
adultSelect.appendChild(new Option(data.adult_name, data.adult_name));
}
if (!Array.from(payerSelect.options).some(o => o.value === payerVal)) {
payerSelect.appendChild(new Option(payerVal, payerVal));
}
if (adultSelect && payerSelect) {
let adultExists = false, payerExists = false;
for(let i=0; i<adultSelect.options.length; i++) { if(adultSelect.options[i].value === data.adult_name) adultExists = true; }
for(let i=0; i<payerSelect.options.length; i++) { if(payerSelect.options[i].value === payerVal) payerExists = true; }
if (!adultExists) adultSelect.appendChild(new Option(data.adult_name, data.adult_name));
if (!payerExists) payerSelect.appendChild(new Option(payerVal, payerVal));
adultSelect.value = data.adult_name;
payerSelect.value = payerVal;
document.getElementById('pf-modal-title').textContent = window.I18N && window.I18N['gift_modal_title_edit'] ? window.I18N['gift_modal_title_edit'] : 'Modifier le cadeau';
}
modal.classList.add('open');
document.body.classList.add('no-scroll');
}
document.getElementById('modalTitle').textContent = window.I18N && window.I18N['gift_modal_title_edit'] ? window.I18N['gift_modal_title_edit'] : 'Modifier le cadeau';
function closeGiftModal() {
modal.classList.remove('open');
document.body.classList.remove('no-scroll');
}
// --- SOUMISSION AJAX ---
document.getElementById('pf-gift-form').addEventListener('submit', async (e) => {
e.preventDefault();
const btn = e.target.querySelector('button[type="submit"]');
const oldText = btn.innerText;
btn.innerText = '...'; btn.disabled = true;
try {
await fetch(e.target.action, { method: 'POST', body: new FormData(e.target) });
window.location.reload();
} catch(err) {
console.error(err);
btn.innerText = oldText; btn.disabled = false;
if (modal) { modal.classList.add('open'); document.body.classList.add('no-scroll'); }
return;
}
});
// --- SUPPRESSION AJAX ---
async function deleteGift(id) {
// --- 4. BOUTON SUPPRIMER (POUBELLE) ---
const btnDel = e.target.closest('.btn-delete-gift');
if (btnDel) {
const giftId = btnDel.dataset.id;
const msg = window.I18N && window.I18N['gift_confirm_delete'] ? window.I18N['gift_confirm_delete'] : 'Supprimer ce cadeau ?';
if (!confirm(msg)) return;
const fd = new FormData();
fd.append('action', 'delete');
fd.append('gift_id', id);
fd.append('gift_id', giftId);
try {
await fetch('/modules/gift-list/save-gift.php', { method: 'POST', body: fd });
@@ -473,6 +502,32 @@ async function deleteGift(id) {
} catch(err) {
console.error(err);
}
return;
}
});
// ==========================================
// 4. SOUMISSION AJAX DU FORMULAIRE
// ==========================================
const giftForm = document.getElementById('giftForm');
if (giftForm) {
giftForm.addEventListener('submit', async (e) => {
e.preventDefault();
const btn = e.target.querySelector('button[type="submit"]');
const oldText = btn.innerText;
btn.innerText = '...';
btn.disabled = true;
try {
await fetch(e.target.getAttribute('action'), { method: 'POST', body: new FormData(e.target) });
window.location.reload();
} catch(err) {
console.error(err);
btn.innerText = oldText;
btn.disabled = false;
}
});
}
</script>
+95
View File
@@ -282,3 +282,98 @@
grid-template-columns: 1fr;
}
}
/* === COMPOSANT MULTI-SELECT VANILLA === */
.pf-multi-select {
position: relative;
display: inline-block;
}
/* Le bouton déclencheur (réutilise le style des filtres) */
.pf-ms-trigger {
padding: 6px 16px;
border-radius: 20px;
border: 1px solid var(--border-light);
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(8px);
font-size: 0.85rem;
font-weight: 600;
color: var(--text-main);
cursor: pointer;
box-shadow: var(--shadow-sm);
display: flex;
align-items: center;
gap: 6px;
user-select: none;
}
.pf-ms-trigger:hover {
border-color: #cbd5e1;
}
.pf-ms-trigger.active {
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.1);
}
/* Le menu déroulant */
.pf-ms-dropdown {
display: none;
position: absolute;
top: calc(100% + 8px);
left: 0;
background: white;
border: 1px solid var(--border-light);
border-radius: 12px;
padding: 8px;
box-shadow: var(--shadow-lg);
z-index: 100;
min-width: 200px;
flex-direction: column;
gap: 4px;
max-height: 60vh;
overflow-y: auto;
}
.pf-ms-dropdown.open {
display: flex;
animation: popDown 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
/* Les options (checkboxes) */
.pf-ms-option {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.85rem;
font-weight: 500;
cursor: pointer;
padding: 8px 10px;
border-radius: 6px;
color: var(--text-main);
transition: 0.2s;
}
.pf-ms-option:hover {
background: #f8fafc;
}
.pf-ms-option input[type="checkbox"] {
margin: 0;
cursor: pointer;
accent-color: var(--primary);
width: 16px;
height: 16px;
}
.pf-ms-option.is-all {
font-weight: 700;
border-bottom: 1px solid #e2e8f0;
padding-bottom: 10px;
margin-bottom: 4px;
border-radius: 6px 6px 0 0;
}
@keyframes popDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}