From 5184544d6fcfcfcb0b1539590740d995750e7c7d Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 8 Jul 2026 10:26:52 -0400 Subject: [PATCH] Implement quantity summary panel and global configuration settings for bib assignments (MSIN-4424) This commit introduces a new AJAX action for retrieving a summary of quantities related to bib assignments, enhancing event management capabilities. A collapsible configuration panel is added for global settings, allowing users to manage bib assignment options more effectively. The CSS is updated to style the new elements, and relevant database entries are created to support the new features. The version code is incremented to 4.72.751 to reflect these changes. --- ajax_bib_range.php | 23 ++ css/style.css | 136 +++++++++--- js/v2/bib-assignments.js | 71 ++++++ php/inc_fx_promoteur.php | 303 +++++++++++++++++++++++++- php/inc_settings.php | 2 +- sql/MSIN-4424-bib-allow-inter-epr.sql | 97 ++++++++- 6 files changed, 590 insertions(+), 42 deletions(-) diff --git a/ajax_bib_range.php b/ajax_bib_range.php index 5997e3f..97f1408 100644 --- a/ajax_bib_range.php +++ b/ajax_bib_range.php @@ -1132,6 +1132,29 @@ if ($action == 'anomalies') { exit; } +// MSIN-4424 — Sommaire des quantités (toutes épreuves) +if ($action == 'qty_summary') { + + $int_eve_id = (int)($_POST['eve_id'] ?? $_GET['eve_id'] ?? 0); + + if ($int_eve_id <= 0) { + echo json_encode([ + 'success' => false, + 'message' => fxBibMsg('bib_v4_ajax_epr_invalid') + ]); + exit; + } + + $tabSummary = fxBibCollectEventQtySummary($int_eve_id, $strLangue); + + echo json_encode([ + 'success' => true, + 'totals' => $tabSummary['totals'] ?? [], + 'html' => renderBibQtySummaryPanel($int_eve_id, $strLangue, $tabSummary), + ]); + exit; +} + // ===================== // ACTION INVALIDE // ===================== diff --git a/css/style.css b/css/style.css index 3046aad..2601850 100644 --- a/css/style.css +++ b/css/style.css @@ -1064,34 +1064,8 @@ ul.ms1-menu-compte li a:hover, ul.ms1-menu-compte li a:active { line-height:1.45; } -/* MSIN-4424 — Bandeau réglages dossards (haut du module). */ -.bib-event-settings{ - border:1px solid #c8d6e5; - border-radius:6px; - background:#f8fafc; - padding:10px 14px; - margin-bottom:4px; -} -.bib-event-settings__label{ - display:flex; - align-items:flex-start; - gap:8px; - margin:0; - font-weight:600; - font-size:14px; - line-height:1.4; - cursor:pointer; -} - -.bib-event-settings__label input[type="checkbox"]{ - margin-top:3px; - flex-shrink:0; -} - -.bib-event-settings__text{ - flex:1 1 auto; -} +/* MSIN-4424 — ancien bandeau remplacé par panneau repliable .bib-event-config-panel */ .bib-anomaly-block--inter-epr-dupes .bib-anomaly-block-title{ color:#8a3b12; @@ -2537,6 +2511,114 @@ a.ms1-trad-link.btn-aide-trad{ margin:0; } +/* MSIN-4424 — Panneau configuration globale (contenu dans .epr-content). */ +.bib-event-config-content{ + background:#fff; +} + +.bib-event-config-content .bib-event-settings__label{ + display:flex; + align-items:flex-start; + gap:8px; + margin:0; + font-weight:600; + font-size:14px; + line-height:1.4; + cursor:pointer; +} + +.bib-event-config-content .bib-event-settings__label input[type="checkbox"]{ + margin-top:3px; + flex-shrink:0; +} + +.bib-event-config-content .bib-event-settings__text{ + flex:1 1 auto; +} + +/* MSIN-4424 — Sommaire des quantités (même gabarit que anomalies). */ +.bib-qty-summary-panel--alert{ + border-color:#f0ad4e; +} + +.bib-qty-summary-badges{ + display:inline-flex; + flex-wrap:wrap; + align-items:center; + gap:6px; +} + +.bib-qty-summary-badges--pending{ + font-size:12px; + opacity:.85; +} + +.bib-qty-summary-badge{ + font-size:12px; + font-weight:600; +} + +.bib-qty-summary-badge--warn{ + background:rgba(220,53,69,.9); + border-color:rgba(220,53,69,.9); +} + +.bib-qty-summary-content{ + background:#fff; +} + +.bib-qty-summary-section-title{ + margin:0 0 10px; + font-size:14px; + font-weight:700; + color:#212529; +} + +.bib-qty-summary-totals{ + margin-bottom:16px; +} + +.bib-qty-summary-dl{ + display:grid; + grid-template-columns:repeat(auto-fill, minmax(200px, 1fr)); + gap:8px 16px; + margin:0; +} + +.bib-qty-summary-dl__row{ + display:flex; + justify-content:space-between; + gap:10px; + padding:8px 10px; + background:#f8f9fa; + border:1px solid #e9ecef; + border-radius:4px; + font-size:13px; +} + +.bib-qty-summary-dl__row dt{ + margin:0; + font-weight:600; + color:#495057; +} + +.bib-qty-summary-dl__row dd{ + margin:0; + font-weight:700; + color:#212529; +} + +.bib-qty-summary-table th, +.bib-qty-summary-table td{ + font-size:13px; + vertical-align:middle; +} + +.bib-qty-summary-by-epr{ + border-top:1px solid #e9ecef; + padding-top:14px; +} + .bib-anomalies-panel{ margin-bottom:14px; border:1px solid #ced4da; diff --git a/js/v2/bib-assignments.js b/js/v2/bib-assignments.js index 9b2912f..1411f6e 100644 --- a/js/v2/bib-assignments.js +++ b/js/v2/bib-assignments.js @@ -920,6 +920,12 @@ icon.classList.toggle('fa-chevron-up', !collapsed); icon.classList.toggle('fa-chevron-down', collapsed); } + + // MSIN-4424 — Sommaire quantités : chargement à la première ouverture. + if (!collapsed && block.id === 'bib-qty-summary-panel' + && block.getAttribute('data-bib-qty-summary-deferred') === '1') { + refreshBibQtySummaryPanel(); + } }); // ============================================================ @@ -1065,6 +1071,64 @@ }); } + /** MSIN-4424 — Rafraîchit le sommaire des quantités (si déjà chargé ou ouverture). */ + function refreshBibQtySummaryPanel() { + let mount = document.getElementById('bib-qty-summary-mount'); + if (!mount || !moduleBib) { + return; + } + + let panel = document.getElementById('bib-qty-summary-panel'); + let blnDeferred = panel && panel.getAttribute('data-bib-qty-summary-deferred') === '1'; + let blnLoaded = panel && panel.getAttribute('data-bib-qty-summary-loaded') === '1'; + if (!blnDeferred && !blnLoaded) { + return; + } + + let eveId = moduleBib.dataset.eveId || ''; + if (!eveId) { + return; + } + + let blnWasExpanded = panel && !panel.classList.contains('is-collapsed'); + + bibFetch('/ajax_bib_range.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: 'action=qty_summary&eve_id=' + encodeURIComponent(eveId) + bibAjaxLangSuffix() + }, { loader: false }) + .then(function (res) { return res.json(); }) + .then(function (data) { + if (!data.success || !data.html) { + return; + } + mount.innerHTML = data.html; + initModuleBibTippy(mount); + if (blnWasExpanded) { + let newPanel = document.getElementById('bib-qty-summary-panel'); + if (newPanel) { + newPanel.classList.remove('is-collapsed'); + let btn = newPanel.querySelector('.epr-block-toggle'); + if (btn) { + btn.setAttribute('aria-expanded', 'true'); + let icon = btn.querySelector('.fa'); + if (icon) { + icon.classList.remove('fa-chevron-down'); + icon.classList.add('fa-chevron-up'); + } + } + } + } + }); + } + + function bibQtySummaryShouldRefresh() { + let panel = document.getElementById('bib-qty-summary-panel'); + return panel && panel.getAttribute('data-bib-qty-summary-loaded') === '1'; + } + function setBibContainerHtml(row, html, opts) { opts = opts || {}; let container = getBibContainer(row); @@ -1084,6 +1148,9 @@ if (!opts.skipAnomalies) { refreshBibAnomaliesPanel(); } + if (!opts.skipQtySummary && bibQtySummaryShouldRefresh()) { + refreshBibQtySummaryPanel(); + } return container; } @@ -2746,6 +2813,10 @@ mount.innerHTML = data.anomalies_html; initModuleBibTippy(mount); } + + if (bibQtySummaryShouldRefresh()) { + refreshBibQtySummaryPanel(); + } }) .catch(function () { chkAllowInterEpr.checked = !chkAllowInterEpr.checked; diff --git a/php/inc_fx_promoteur.php b/php/inc_fx_promoteur.php index 730b808..b67199a 100644 --- a/php/inc_fx_promoteur.php +++ b/php/inc_fx_promoteur.php @@ -2997,6 +2997,18 @@ function fxBibStaticFallback($clef) { 'bib_v4_js_global_no_seq' => ['fr' => 'Cochez au moins une séquence pour les épreuves sélectionnées.', 'en' => 'Check at least one sequence for the selected races.'], 'bib_v4_global_batch_plan_invalid' => ['fr' => 'Sélection invalide : choisissez au moins une épreuve solo et des séquences.', 'en' => 'Invalid selection: pick at least one solo race and sequences.'], 'bib_v4_global_batch_need_two' => ['fr' => "L'assignation globale nécessite au moins deux épreuves solo avec des séquences disponibles.", 'en' => 'Global assignment requires at least two solo races with available sequences.'], + 'bib_v4_event_config_title' => ['fr' => "Configuration globale pour l'assignation de dossard", 'en' => 'Global bib assignment settings'], + 'bib_v4_qty_summary_title' => ['fr' => 'Sommaire des quantités', 'en' => 'Quantity summary'], + 'bib_v4_qty_summary_a_assigner' => ['fr' => 'À assigner', 'en' => 'To assign'], + 'bib_v4_qty_summary_bib_restant' => ['fr' => 'Dossards restants', 'en' => 'Bibs remaining'], + 'bib_v4_qty_summary_orphelins' => ['fr' => 'Personnes orphelines', 'en' => 'Orphan participants'], + 'bib_v4_qty_summary_total' => ['fr' => 'Inscrits', 'en' => 'Registered'], + 'bib_v4_qty_summary_avec_bib' => ['fr' => 'Avec dossard', 'en' => 'With bib'], + 'bib_v4_qty_summary_sans_bib' => ['fr' => 'Sans dossard', 'en' => 'Without bib'], + 'bib_v4_qty_summary_places_dispo' => ['fr' => 'Places disponibles', 'en' => 'Places available'], + 'bib_v4_qty_summary_global' => ['fr' => 'Total événement', 'en' => 'Event total'], + 'bib_v4_qty_summary_by_epr' => ['fr' => 'Détail par épreuve', 'en' => 'Breakdown by race'], + 'bib_v4_qty_summary_col_epr' => ['fr' => 'Épreuve', 'en' => 'Race'], 'bib_v4_ajax_global_sim_summary' => ['fr' => '%d épreuve(s) · %d dispo / %d à assigner → %d assignables (%d manque)', 'en' => '%d race(s) · %d avail. / %d to assign → %d assignable (%d short)'], ]; @@ -4226,7 +4238,11 @@ function fxShowBibTool4($str_code, $int_eve_id, $strLangue){
> - + + +
+ +
@@ -5112,22 +5128,285 @@ function fxBibAnomalyTotalCount(array $tabAnomalies) { return $intTotal; } -/** MSIN-4424 — Bandeau réglages dossards (haut du module, au-dessus des épreuves). */ -function renderBibEventSettingsBanner($int_eve_id, $strLangue = 'fr') { +/** MSIN-4424 — Corps du panneau configuration globale (réglages événement). */ +function renderBibEventConfigPanelContent($int_eve_id) { $int_eve_id = (int)$int_eve_id; $blnAllow = fxBibEventAllowsInterEprDuplicates($int_eve_id); ob_start(); ?> -
- + + + + , epreuves: array>} + */ +function fxBibCollectEventQtySummary($int_eve_id, $strLangue = 'fr') { + global $objDatabase; + + $int_eve_id = (int)$int_eve_id; + $tabTotals = [ + 'a_assigner' => 0, + 'bib_restant' => 0, + 'orphelins' => 0, + 'total' => 0, + 'avec_bib' => 0, + 'sans_bib' => 0, + 'places_dispo' => 0, + ]; + $tabEpreuvesRows = []; + + if ($int_eve_id <= 0) { + return ['totals' => $tabTotals, 'epreuves' => $tabEpreuvesRows]; + } + + $sqlEpreuves = " + SELECT * + FROM inscriptions_epreuves + WHERE eve_id = $int_eve_id + AND epr_actif = 1 + ORDER BY epr_id + "; + $tabEpreuves = $objDatabase->fxGetResults($sqlEpreuves); + if (!is_array($tabEpreuves)) { + $tabEpreuves = []; + } + + $tabOrphans = fxBibAnomalyOrphanBibs($int_eve_id, $strLangue); + $tabOrphansByEpr = []; + foreach ($tabOrphans as $item) { + $intEprId = (int)($item['epr_id'] ?? 0); + if ($intEprId > 0) { + $tabOrphansByEpr[$intEprId] = ($tabOrphansByEpr[$intEprId] ?? 0) + 1; + } + } + + foreach ($tabEpreuves as $epr) { + $epr_id = (int)$epr['epr_id']; + $infoBib = fxInfosBibEpreuve($epr_id); + $arrQte = fxGetQuantites($epr_id); + + $intBibRestant = 0; + $tabRanges = fxInfosBibRange($epr_id, 0, false); + if (is_array($tabRanges)) { + foreach ($tabRanges as $range) { + if (is_array($range)) { + $intBibRestant += fxBibRangeDispoFromRow($range); + } + } + } + + $tabRow = [ + 'epr_id' => $epr_id, + 'epr_label' => fxBibAnomalyEprLabel($epr, $strLangue), + 'total' => (int)($infoBib['total'] ?? 0), + 'avec_bib' => (int)($infoBib['avec_bib'] ?? 0), + 'sans_bib' => (int)($infoBib['sans_bib'] ?? 0), + 'a_assigner' => (int)($infoBib['a_assigner'] ?? 0), + 'bib_restant' => $intBibRestant, + 'orphelins' => (int)($tabOrphansByEpr[$epr_id] ?? 0), + 'places_dispo' => (int)($arrQte['qte_restante'] ?? 0), + ]; + $tabEpreuvesRows[] = $tabRow; + + $tabTotals['a_assigner'] += $tabRow['a_assigner']; + $tabTotals['bib_restant'] += $tabRow['bib_restant']; + $tabTotals['total'] += $tabRow['total']; + $tabTotals['avec_bib'] += $tabRow['avec_bib']; + $tabTotals['sans_bib'] += $tabRow['sans_bib']; + $tabTotals['places_dispo'] += $tabRow['places_dispo']; + } + + $tabTotals['orphelins'] = count($tabOrphans); + + return ['totals' => $tabTotals, 'epreuves' => $tabEpreuvesRows]; +} + +/** MSIN-4424 — Coquille sommaire quantités (détail chargé à la première ouverture). */ +function renderBibQtySummaryPanelShell($int_eve_id, $strLangue = 'fr') { + global $vDomaine; + + $int_eve_id = (int)$int_eve_id; + $strWait = fxBibTexte('bib_v4_js_loader_wait', 0); + if ($strWait === '' || $strWait === 'bib_v4_js_loader_wait') { + $strWait = ($strLangue === 'en') ? 'Loading...' : 'Chargement...'; + } + $strRunnerSrc = $vDomaine . '/images/ms1-runner-loader.gif?v=' . _VERSION_CODE; + + ob_start(); + ?> + + 0; + $blnHasOrphans = (int)($tabTotals['orphelins'] ?? 0) > 0; + + ob_start(); + ?> +