diff --git a/js/v2/bib-assignments.js b/js/v2/bib-assignments.js index f39b979..fe3e17d 100644 --- a/js/v2/bib-assignments.js +++ b/js/v2/bib-assignments.js @@ -1072,19 +1072,84 @@ return row.querySelectorAll('.batch-select-range:checked').length; } + /** MSIN-4436 — Développe un bloc repliable #module-bib. */ + function bibExpandBlock(block) { + if (!block || !block.classList.contains('is-collapsed')) { + return; + } + + block.classList.remove('is-collapsed'); + let btn = block.querySelector('.epr-block-toggle'); + if (btn) { + btn.setAttribute('aria-expanded', 'true'); + let label = btn.getAttribute('data-label-collapse') || ''; + if (label) { + btn.setAttribute('aria-label', label); + btn.setAttribute('title', label); + } + let icon = btn.querySelector('.fa'); + if (icon) { + icon.classList.add('fa-chevron-up'); + icon.classList.remove('fa-chevron-down'); + } + } + } + + let bibFocusApplied = false; + + /** MSIN-4436 — Retour depuis fiche inscription : rouvrir le panneau ciblé. */ + function bibApplyFocusFromUrlOnce() { + if (bibFocusApplied) { + return; + } + + let focus = new URLSearchParams(window.location.search).get('bib_focus'); + if (!focus) { + return; + } + + bibFocusApplied = true; + + let panelId = null; + if (focus === 'anomalies') { + panelId = 'bib-anomalies-panel'; + } else if (focus === 'qty_summary') { + panelId = 'bib-qty-summary-panel'; + } else if (focus === 'event_config') { + panelId = 'bib-event-config-panel'; + } + + if (!panelId) { + return; + } + + let panel = document.getElementById(panelId); + if (!panel) { + return; + } + + bibExpandBlock(panel); + + if (focus === 'qty_summary' && panel.getAttribute('data-bib-qty-summary-deferred') === '1') { + refreshBibQtySummaryPanel(); + } + + panel.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + /** MSIN-4379 — Rafraîchit le dashboard anomalies (chien de garde). */ function refreshBibAnomaliesPanel() { let mount = document.getElementById('bib-anomalies-mount'); if (!mount || !moduleBib) { - return; + return Promise.resolve(); } let eveId = moduleBib.dataset.eveId || ''; if (!eveId) { - return; + return Promise.resolve(); } - bibFetch('/ajax_bib_range.php', { + return bibFetch('/ajax_bib_range.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' @@ -2868,7 +2933,11 @@ // MSIN-4436 — Anomalies : coquille légère au chargement, détail en AJAX. if (document.querySelector('#bib-anomalies-panel[data-bib-anomalies-deferred="1"]')) { - refreshBibAnomaliesPanel(); + refreshBibAnomaliesPanel().then(function () { + bibApplyFocusFromUrlOnce(); + }); + } else { + bibApplyFocusFromUrlOnce(); } } })(); diff --git a/php/inc_fx_inscriptions_gestion.php b/php/inc_fx_inscriptions_gestion.php index 8dd6a97..7369e9e 100644 --- a/php/inc_fx_inscriptions_gestion.php +++ b/php/inc_fx_inscriptions_gestion.php @@ -7,6 +7,9 @@ * - Promoteur : /compte/inc_tableau_inscriptions_gestion → retour inc_tableau_promoteur * - Acces v2 seul : /mobile → retour /mobile ou Mon compte * + * MSIN-4436 — Parametre inscr_return (URL interne encodee) : retour contextuel depuis la fiche + * (ex. panneau anomalies dossards) au lieu de la liste des inscriptions. + * * Droits : v2 uniquement (fxInscrGestionCanAccess). i18n : toujours info_prg compte.php. */ @@ -148,6 +151,85 @@ function fxInscrGestionBaseUrl($strLangue, $blnMobileEntry = null) { return $vDomaine . ($strLangue === 'fr' ? '/compte/inc_tableau_inscriptions_gestion' : '/account/inc_tableau_inscriptions_gestion'); } +/** + * MSIN-4436 — Encode une URL interne relative (/compte/... ou /account/...) pour inscr_return. + */ +function fxInscrGestionEncodeReturnUrl($strRelativePath) { + $strRelativePath = trim((string)$strRelativePath); + if ($strRelativePath === '' || $strRelativePath[0] !== '/') { + return ''; + } + + return rtrim(strtr(base64_encode($strRelativePath), '+/', '-_'), '='); +} + +/** + * MSIN-4436 — Décode inscr_return ; retourne le chemin relatif validé ou ''. + */ +function fxInscrGestionDecodeReturnPath($strToken) { + $strToken = trim((string)$strToken); + if ($strToken === '') { + return ''; + } + + $strB64 = strtr($strToken, '-_', '+/'); + $intPad = strlen($strB64) % 4; + if ($intPad > 0) { + $strB64 .= str_repeat('=', 4 - $intPad); + } + + $strPath = base64_decode($strB64, true); + if ($strPath === false || $strPath === '' || $strPath[0] !== '/') { + return ''; + } + if (preg_match('#^https?://#i', $strPath)) { + return ''; + } + if (!preg_match('#^/(compte|account)/#', $strPath)) { + return ''; + } + + return $strPath; +} + +/** MSIN-4436 — URL absolue de retour depuis inscr_return, ou '' si invalide. */ +function fxInscrGestionDecodeReturnUrl($strToken) { + global $vDomaine; + + $strPath = fxInscrGestionDecodeReturnPath($strToken); + if ($strPath === '') { + return ''; + } + + return $vDomaine . $strPath; +} + +/** MSIN-4436 — Bouton retour fiche : URL externe (inscr_return) ou liste filtrée. */ +function fxInscrGestionResolveBackUrl($strBaseUrl, $intEveId, $arrReq) { + if (!empty($arrReq['inscr_return'])) { + $strReturn = fxInscrGestionDecodeReturnUrl($arrReq['inscr_return']); + if ($strReturn !== '') { + return $strReturn; + } + } + + return fxInscrGestionBuildUrl($strBaseUrl, $intEveId, $arrReq, array('pec_id' => null, 'pg' => $arrReq['pg'])); +} + +/** MSIN-4436 — Libellé du bouton retour selon le contexte d'origine. */ +function fxInscrGestionResolveBackLabel($arrReq, $strLangue = 'fr') { + if (!empty($arrReq['inscr_return'])) { + $strPath = fxInscrGestionDecodeReturnPath($arrReq['inscr_return']); + if ($strPath !== '' && strpos($strPath, 'inc_tableau_gestion_epreuves') !== false) { + return fxInscrGestionT('inscr_gestion_back_bib'); + } + + return fxInscrGestionT('inscr_gestion_back_prev'); + } + + return fxInscrGestionT('inscr_gestion_back_list'); +} + function fxInscrGestionEsc($str) { return htmlspecialchars((string)$str, ENT_QUOTES, 'UTF-8'); } @@ -284,6 +366,8 @@ function fxInscrGestionFallbackTexte($strClef, $strLangue) { 'inscr_gestion_statut_annule' => $blnFr ? 'Annulée' : 'Cancelled', 'inscr_gestion_statut_transfert' => $blnFr ? 'Transférée' : 'Transferred', 'inscr_gestion_statut_modif' => $blnFr ? 'Modifiée (changement d\'épreuve)' : 'Modified (race change)', + 'inscr_gestion_back_bib' => $blnFr ? 'Retour aux dossards' : 'Back to bibs', + 'inscr_gestion_back_prev' => $blnFr ? 'Retour' : 'Back', ); } @@ -471,6 +555,8 @@ function fxInscrGestionParseRequest() { 'sel_rech_epreuve' => 0, 'pg' => isset($_GET['pg']) ? max(1, intval($_GET['pg'])) : 1, 'pec_id' => isset($_GET['pec_id']) ? intval($_GET['pec_id']) : 0, + // MSIN-4436 — Page d'origine (ex. gestion dossards) pour le bouton retour de la fiche. + 'inscr_return' => isset($_GET['inscr_return']) ? trim((string)$_GET['inscr_return']) : '', // q = 1 lorsque l'utilisateur a lance une recherche/filtre. La liste reste vide tant que q = 0. 'q' => 0, ); @@ -519,6 +605,7 @@ function fxInscrGestionEmptyListRequest() { 'sel_rech_epreuve' => 0, 'pg' => 1, 'pec_id' => 0, + 'inscr_return' => '', 'q' => 0, ); } @@ -990,6 +1077,9 @@ function fxInscrGestionQueryParams($intEveId, $arrReq, $arrExtra = array()) { if (!empty($arrReq['pg']) && (int)$arrReq['pg'] > 1) { $tab['pg'] = (int)$arrReq['pg']; } + if (!empty($arrReq['inscr_return'])) { + $tab['inscr_return'] = $arrReq['inscr_return']; + } foreach ($arrExtra as $strKey => $mixVal) { if ($mixVal === null || $mixVal === '' || ($strKey === 'pec_id' && (int)$mixVal === 0)) { @@ -1473,7 +1563,7 @@ function fxInscrGestionRenderFiche($intEveId, $strLangue, $strBaseUrl, $arrReq, $blnHasGestion = ($blnCanEdit || $blnCanCancel || $blnCanRestore || $blnCanRefund); $blnInspect = function_exists('fxAdminPermInspectModeActive') && fxAdminPermInspectModeActive(); - $strListUrl = fxInscrGestionBuildUrl($strBaseUrl, $intEveId, $arrReq, array('pec_id' => null, 'pg' => $arrReq['pg'])); + $strBackUrl = fxInscrGestionResolveBackUrl($strBaseUrl, $intEveId, $arrReq); $strEveCode = fxGetEvenementsUrl($arrRow['eve_id']); $strBib = fxInscrGestionBibDisplay($arrRow); $intParId = $arrRow['par_id']; @@ -1499,7 +1589,7 @@ function fxInscrGestionRenderFiche($intEveId, $strLangue, $strBaseUrl, $arrReq, } echo '>'; - fxInscrGestionRenderNavBack($strListUrl, fxInscrGestionT('inscr_gestion_back_list')); + fxInscrGestionRenderNavBack($strBackUrl, fxInscrGestionResolveBackLabel($arrReq, $strLangue)); echo '