diff --git a/css/style.css b/css/style.css index 0f4f378..1d9b906 100644 --- a/css/style.css +++ b/css/style.css @@ -3532,6 +3532,93 @@ a.ms1-trad-link.btn-aide-trad{ margin:8px 0 0; } +/* MSIN-4401 — Multi-sélection statut de course (tactile / mobile). */ +.inscr-gestion-statut-multi{ + position:relative; + margin-top:0; +} + +.inscr-gestion-statut-multi__summary{ + list-style:none; + display:flex; + align-items:center; + justify-content:space-between; + gap:8px; + min-height:38px; + padding:6px 10px; + border:1px solid #ced4da; + background:#fff; + font-size:14px; + color:#212529; + cursor:pointer; + -webkit-user-select:none; + user-select:none; +} + +.inscr-gestion-statut-multi__summary::-webkit-details-marker{ + display:none; +} + +.inscr-gestion-statut-multi__summary::after{ + content:'\f078'; + font-family:FontAwesome; + font-size:12px; + color:#6c757d; + flex-shrink:0; +} + +.inscr-gestion-statut-multi[open] > .inscr-gestion-statut-multi__summary::after{ + content:'\f077'; +} + +.inscr-gestion-statut-multi__summary-text{ + flex:1; + min-width:0; + overflow:hidden; + text-overflow:ellipsis; + white-space:nowrap; +} + +.inscr-gestion-statut-multi__panel{ + display:flex; + flex-direction:column; + gap:2px; + margin-top:4px; + padding:8px; + border:1px solid #ced4da; + background:#fff; + max-height:min(280px, 50vh); + overflow-y:auto; + -webkit-overflow-scrolling:touch; +} + +.inscr-gestion-statut-multi__opt{ + display:flex; + align-items:center; + gap:10px; + min-height:40px; + padding:6px 8px; + margin:0; + font-size:14px; + font-weight:400; + color:#212529; + cursor:pointer; + border-radius:2px; + -webkit-tap-highlight-color:transparent; +} + +.inscr-gestion-statut-multi__opt:active, +.inscr-gestion-statut-multi__opt:hover{ + background:#f1f3f5; +} + +.inscr-gestion-statut-multi__opt input{ + width:18px; + height:18px; + flex-shrink:0; + margin:0; +} + .inscr-gestion-form-actions{ display:flex; flex-wrap:wrap; diff --git a/js/v2/inscr-gestion.js b/js/v2/inscr-gestion.js index fdf1642..e960fb1 100644 --- a/js/v2/inscr-gestion.js +++ b/js/v2/inscr-gestion.js @@ -890,6 +890,32 @@ Ms1Loader.show(inscrLoaderMsg); } }); + + // MSIN-4401 — Libellé du multi-select statut (Tous / N sélectionnés). + (function () { + function updateStatutMultiSummary(details) { + if (!details) { + return; + } + var textEl = details.querySelector('.inscr-gestion-statut-multi__summary-text'); + if (!textEl) { + return; + } + var checked = details.querySelectorAll('input[name="sel_rech_statut[]"]:checked'); + var n = checked.length; + var labelAll = details.getAttribute('data-label-all') || ''; + var labelN = details.getAttribute('data-label-n') || '%d'; + textEl.textContent = (n === 0) ? labelAll : labelN.replace('%d', String(n)); + } + + moduleInscr.addEventListener('change', function (e) { + var input = e.target; + if (!input || input.name !== 'sel_rech_statut[]') { + return; + } + updateStatutMultiSummary(input.closest('.inscr-gestion-statut-multi')); + }); + })(); })(); })(); diff --git a/php/inc_fx_inscriptions_gestion.php b/php/inc_fx_inscriptions_gestion.php index 8cb0972..162b081 100644 --- a/php/inc_fx_inscriptions_gestion.php +++ b/php/inc_fx_inscriptions_gestion.php @@ -370,6 +370,10 @@ function fxInscrGestionFallbackTexte($strClef, $strLangue) { 'inscr_gestion_chk_cancelled' => $blnFr ? 'Montrer les inscriptions annulées ou transférées' : 'Show cancelled or transferred registrations', + // MSIN-4401 — Filtre recherche par statut de course (multi-sélection). + 'inscr_gestion_rech_statut' => $blnFr ? 'Statut de course' : 'Race status', + 'inscr_gestion_rech_statut_all' => $blnFr ? 'Tous les statuts' : 'All statuses', + 'inscr_gestion_rech_statut_n' => $blnFr ? '%d sélectionnés' : '%d selected', ); } @@ -546,6 +550,27 @@ function fxInscrGestionParseEveId($strRaw) { return (int)$strDecoded; } +/** + * MSIN-4401 — Codes statut de course soumis (GET sel_rech_statut[]), whitelistes. + * Tableau vide = aucun filtre (tous les statuts). + */ +function fxInscrGestionParseStatutFilter($mixRaw) { + if (!is_array($mixRaw)) { + return array(); + } + + $tabOut = array(); + foreach ($mixRaw as $mixCode) { + $strCode = strtoupper(trim((string)$mixCode)); + $strCode = preg_replace('/[^A-Z0-9_]/', '', $strCode); + if ($strCode !== '') { + $tabOut[$strCode] = $strCode; + } + } + + return array_values($tabOut); +} + function fxInscrGestionParseRequest() { $arr = array( 'rech_prenom' => isset($_GET['rech_prenom']) ? trim($_GET['rech_prenom']) : '', @@ -555,6 +580,10 @@ function fxInscrGestionParseRequest() { 'chk_bib' => !empty($_GET['chk_bib']) ? 1 : 0, 'chk_cancelled' => !empty($_GET['chk_cancelled']) ? 1 : 0, 'sel_rech_epreuve' => 0, + // MSIN-4401 — Filtre multi statut de course (vide = tous). + 'sel_rech_statut' => fxInscrGestionParseStatutFilter( + isset($_GET['sel_rech_statut']) ? $_GET['sel_rech_statut'] : array() + ), '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. @@ -576,7 +605,8 @@ function fxInscrGestionParseRequest() { // ou au moins un critere present (utile pour le retour depuis une fiche / la pagination). $blnSubmit = !empty($_GET['btn_recherche']) || !empty($_GET['apply_filters']) || !empty($_GET['q']); $blnCriteria = ($arr['rech_prenom'] !== '' || $arr['rech_nom'] !== '' || $arr['rech_no_commande'] !== '' - || $arr['rech_nom_equipe'] !== '' || $arr['sel_rech_epreuve'] > 0 || $arr['chk_bib'] || $arr['chk_cancelled']); + || $arr['rech_nom_equipe'] !== '' || $arr['sel_rech_epreuve'] > 0 || $arr['chk_bib'] || $arr['chk_cancelled'] + || count($arr['sel_rech_statut']) > 0); $arr['q'] = ($blnSubmit || $blnCriteria) ? 1 : 0; return $arr; @@ -593,6 +623,9 @@ function fxInscrGestionFilterActiveCount($arrReq) { if ($arrReq['chk_cancelled']) { $int++; } + if (!empty($arrReq['sel_rech_statut']) && is_array($arrReq['sel_rech_statut']) && count($arrReq['sel_rech_statut']) > 0) { + $int++; + } return $int; } @@ -605,6 +638,7 @@ function fxInscrGestionEmptyListRequest() { 'chk_bib' => 0, 'chk_cancelled' => 0, 'sel_rech_epreuve' => 0, + 'sel_rech_statut' => array(), 'pg' => 1, 'pec_id' => 0, 'inscr_return' => '', @@ -652,6 +686,27 @@ function fxInscrGestionBuildWhere($intEveId, $arrReq) { $strWhere .= ' AND e.is_cancelled = 0 AND e.pec_actif = 1'; } + // MSIN-4401 — Filtre statut de course (vide = tous). CONF inclut aussi les valeurs vides (défaut). + if (!empty($arrReq['sel_rech_statut']) && is_array($arrReq['sel_rech_statut'])) { + $tabCodes = array(); + foreach ($arrReq['sel_rech_statut'] as $strCode) { + $strCode = strtoupper(trim((string)$strCode)); + $strCode = preg_replace('/[^A-Z0-9_]/', '', $strCode); + if ($strCode !== '') { + $tabCodes[$strCode] = "'" . $objDatabase->fxEscape($strCode) . "'"; + } + } + if (count($tabCodes) > 0) { + $strIn = implode(',', $tabCodes); + if (isset($tabCodes['CONF'])) { + $strWhere .= ' AND (p.par_statut_course IN (' . $strIn . ')' + . " OR TRIM(IFNULL(p.par_statut_course, '')) = '')"; + } else { + $strWhere .= ' AND p.par_statut_course IN (' . $strIn . ')'; + } + } + } + return $strWhere; } @@ -1042,6 +1097,58 @@ function fxInscrGestionRenderStatutField($intParId, $strCurrent, $intEveId, $str echo ''; } +/** + * MSIN-4401 — Multi-sélection statut de course (mobile-friendly : panneau + cases à cocher). + * Aucune case cochée = tous les statuts. + */ +function fxInscrGestionRenderStatutFilter($arrReq, $strLangue) { + $tabOptions = fxInscrGestionGetStatutCourseOptions($strLangue); + if (count($tabOptions) === 0) { + return; + } + + $tabSelected = (!empty($arrReq['sel_rech_statut']) && is_array($arrReq['sel_rech_statut'])) + ? $arrReq['sel_rech_statut'] + : array(); + $tabSelectedMap = array(); + foreach ($tabSelected as $strCode) { + $tabSelectedMap[$strCode] = true; + } + $intNb = count($tabSelected); + $strSummary = ($intNb === 0) + ? fxInscrGestionT('inscr_gestion_rech_statut_all') + : sprintf(fxInscrGestionT('inscr_gestion_rech_statut_n'), $intNb); + + echo ''; + echo '
'; + echo '' + . '' . fxInscrGestionEsc($strSummary) . '' + . ''; + echo '
'; + + foreach ($tabOptions as $arrOpt) { + $strCode = trim((string)$arrOpt['info_option2']); + $strLabel = trim((string)$arrOpt['info_texte']); + if ($strCode === '') { + continue; + } + $strId = 'sel_rech_statut_' . preg_replace('/[^A-Za-z0-9_]/', '_', $strCode); + $blnChecked = isset($tabSelectedMap[$strCode]); + echo ''; + } + + echo '
'; +} + /** Paramètres GET pour liens (sans double-encodage). */ function fxInscrGestionQueryParams($intEveId, $arrReq, $arrExtra = array()) { $tab = array( @@ -1069,6 +1176,10 @@ function fxInscrGestionQueryParams($intEveId, $arrReq, $arrExtra = array()) { if ($arrReq['sel_rech_epreuve'] > 0) { $tab['sel_rech_epreuve'] = base64_encode((string)$arrReq['sel_rech_epreuve']); } + // MSIN-4401 — Conserver le filtre multi-statut dans pagination / fiche. + if (!empty($arrReq['sel_rech_statut']) && is_array($arrReq['sel_rech_statut'])) { + $tab['sel_rech_statut'] = array_values($arrReq['sel_rech_statut']); + } if (!empty($_GET['lng'])) { $tab['lng'] = $_GET['lng']; } @@ -1391,6 +1502,9 @@ function fxInscrGestionRenderList($intEveId, $strLangue, $strBaseUrl, $arrReq) { } echo ''; + // MSIN-4401 — Filtre multi statut de course (tous si aucune sélection). + fxInscrGestionRenderStatutFilter($arrReq, $strLangue); + echo ''; echo ''; diff --git a/php/inc_settings.php b/php/inc_settings.php index 895cf57..d75a735 100644 --- a/php/inc_settings.php +++ b/php/inc_settings.php @@ -7,8 +7,8 @@ * Constantes * * **************/ -define('_VERSION_CODE', '4.72.784'); -define('_DATE_CODE', '2026-07-09'); +define('_VERSION_CODE', '4.72.785'); +define('_DATE_CODE', '2026-07-13'); //MSIN-4290 define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe'); // Outil temporaire ms1_kc_set.php — modifier key_chain_http (lien /kc/{pk}/)