From 20c1f898a1539eb9c90b3e41139e4972da6c1a4d Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 7 Jul 2026 15:01:55 -0400 Subject: [PATCH] Enhance bib range validation and user feedback in management system This commit introduces new JavaScript functions for validating range inputs for quantity and end values in the bib management system. It adds visual feedback for invalid fields and updates the preview display based on user input. CSS styles are also added to highlight invalid fields and error messages. Additionally, new SQL entries are created for localized validation messages, improving user guidance. The version code is incremented to reflect these enhancements, aiming to improve user experience and data integrity in the bib input process. --- css/style.css | 11 +++ inc_footer_scripts.php | 146 ++++++++++++++++++++++++++++++-- php/inc_fx_promoteur.php | 2 + php/inc_settings.php | 2 +- sql/MSIN-4429-bib-range-qty.sql | 16 ++++ 5 files changed, 169 insertions(+), 8 deletions(-) diff --git a/css/style.css b/css/style.css index f71d830..bce08bd 100644 --- a/css/style.css +++ b/css/style.css @@ -2139,6 +2139,17 @@ a.ms1-trad-link.btn-aide-trad{ cursor:text; } +.bib-field--invalid{ + border-color:#dc3545 !important; + background-color:#fff5f5 !important; + color:inherit; +} + +.bib-range-preview--error{ + color:#dc3545; + font-weight:600; +} + .bib-field--pick:focus, .bib-field--calc:focus{ outline:none; diff --git a/inc_footer_scripts.php b/inc_footer_scripts.php index f9aef3e..cfa53de 100644 --- a/inc_footer_scripts.php +++ b/inc_footer_scripts.php @@ -2645,14 +2645,123 @@ if ($strLangue == 'fr') { }); } + function bibRangeClearValidationUi(row) { + var fields = bibRangeGetFields(row); + if (!fields) return; + + [fields.start, fields.qty, fields.end].forEach(function (el) { + if (el) { + el.classList.remove('bib-field--invalid'); + } + }); + + if (fields.preview) { + fields.preview.classList.remove('bib-range-preview--error'); + } + } + + /** MSIN-4429 — Validation différée (blur / OK), pas pendant la frappe. */ + function bibRangeValidate(row) { + var fields = bibRangeGetFields(row); + if (!fields) return null; + + var start = bibRangeParseInt(fields.start && fields.start.value); + if (start <= 0) { + return null; + } + + var pilot = row.dataset.rangePilot || ''; + if (!pilot) { + return null; + } + + if (pilot === 'end') { + var strEnd = String(fields.end && fields.end.value || '').trim(); + if (strEnd === '') { + return null; + } + var end = bibRangeParseInt(strEnd); + if (end <= 0) { + return { key: 'incomplete', field: 'end' }; + } + if (end < start) { + return { key: 'invalidEnd', field: 'end', start: start }; + } + } + + if (pilot === 'qty') { + var strQty = String(fields.qty && fields.qty.value || '').trim(); + if (strQty === '') { + return null; + } + var qty = bibRangeParseInt(strQty); + if (qty <= 0) { + return { key: 'invalidQty', field: 'qty' }; + } + } + + return null; + } + + function bibRangeValidationMessage(validation) { + if (!validation) { + return ''; + } + if (validation.key === 'invalidEnd') { + return bibJsFmt('jsRangeInvalidEnd', validation.start); + } + if (validation.key === 'invalidQty') { + return bibJs('jsRangeInvalidQty'); + } + if (validation.key === 'incomplete') { + return bibJs('jsRangeIncomplete') || bibJs('jsFieldsRequired'); + } + return bibJs('jsRangeInvalid') || bibJs('jsFieldsRequired'); + } + + function bibRangeShowValidation(row, validation) { + var fields = bibRangeGetFields(row); + if (!fields || !fields.preview) { + return; + } + + bibRangeClearValidationUi(row); + + if (!validation) { + bibRangeUpdatePreview(row); + return; + } + + var msg = bibRangeValidationMessage(validation); + var target = validation.field === 'qty' ? fields.qty : fields.end; + if (target) { + target.classList.add('bib-field--invalid'); + } + + fields.preview.textContent = msg; + fields.preview.classList.remove('bib-ui-hidden'); + fields.preview.classList.add('bib-range-preview--error'); + } + + function bibRangeValidateOnBlur(row) { + applyBibRangeFieldState(row); + bibRangeShowValidation(row, bibRangeValidate(row)); + } + function bibRangeUpdatePreview(row) { var fields = bibRangeGetFields(row); if (!fields || !fields.preview) return; + if (fields.preview.classList.contains('bib-range-preview--error')) { + return; + } + var start = bibRangeParseInt(fields.start && fields.start.value); var end = bibRangeParseInt(fields.end && fields.end.value); var qty = bibRangeParseInt(fields.qty && fields.qty.value); + fields.preview.classList.remove('bib-range-preview--error'); + if (start > 0 && end >= start && qty > 0) { fields.preview.textContent = bibJsFmt('jsRangePreview', start, end, qty); fields.preview.classList.remove('bib-ui-hidden'); @@ -2787,19 +2896,34 @@ if ($strLangue == 'fr') { row.dataset.rangeLastStart = String(bibRangeParseInt(fields.start.value)); fields.start.addEventListener('input', function () { + bibRangeClearValidationUi(row); onBibRangeStartChange(row); }); fields.qty.addEventListener('input', function () { + bibRangeClearValidationUi(row); row.dataset.rangePilot = 'qty'; applyBibRangeFieldState(row); }); fields.end.addEventListener('input', function () { + bibRangeClearValidationUi(row); row.dataset.rangePilot = 'end'; applyBibRangeFieldState(row); }); + fields.start.addEventListener('blur', function () { + bibRangeValidateOnBlur(row); + }); + + fields.qty.addEventListener('blur', function () { + bibRangeValidateOnBlur(row); + }); + + fields.end.addEventListener('blur', function () { + bibRangeValidateOnBlur(row); + }); + fields.qty.addEventListener('click', function () { onBibRangeFieldPick(row, 'qty'); }); @@ -3402,6 +3526,7 @@ if ($strLangue == 'fr') { let start = bibRangeParseInt(row.querySelector('.bib-start').value); let end = bibRangeParseInt(row.querySelector('.bib-end').value); let id = btnSave.dataset.id; + let validation = bibRangeValidate(row); // ligne temporaire vide = on la retire if (id == 0 && start <= 0) { @@ -3413,18 +3538,25 @@ if ($strLangue == 'fr') { } if (start <= 0 || end <= 0) { - if (errorDiv) { - errorDiv.innerText = bibJs('jsRangeIncomplete') || bibJs('jsFieldsRequired'); - errorDiv.style.display = 'block'; - } - return; + validation = validation || { key: 'incomplete' }; } - if (end < start) { + if (!validation && end < start) { + validation = { key: 'invalidEnd', field: 'end', start: start }; + } + + if (validation) { + bibRangeShowValidation(row, validation); if (errorDiv) { - errorDiv.innerText = bibJs('jsRangeInvalid') || bibJs('jsFieldsRequired'); + errorDiv.innerText = bibRangeValidationMessage(validation); errorDiv.style.display = 'block'; } + var invalidField = validation.field === 'qty' + ? row.querySelector('.bib-qty') + : row.querySelector('.bib-end'); + if (invalidField) { + invalidField.focus(); + } return; } diff --git a/php/inc_fx_promoteur.php b/php/inc_fx_promoteur.php index b4f5a22..6cc4a90 100644 --- a/php/inc_fx_promoteur.php +++ b/php/inc_fx_promoteur.php @@ -3106,6 +3106,8 @@ function fxBibModuleJsDataAttrs() { 'range-invalid' => 'bib_v4_js_range_invalid', 'range-preview' => 'bib_v4_js_range_preview', 'range-pick-hint' => 'bib_v4_js_range_pick_hint', + 'range-invalid-end' => 'bib_v4_js_range_invalid_end', + 'range-invalid-qty' => 'bib_v4_js_range_invalid_qty', ]; $html = ''; foreach ($map as $attr => $clef) { diff --git a/php/inc_settings.php b/php/inc_settings.php index a0348bc..257aaa3 100644 --- a/php/inc_settings.php +++ b/php/inc_settings.php @@ -7,7 +7,7 @@ * Constantes * * **************/ -define('_VERSION_CODE', '4.72.730'); +define('_VERSION_CODE', '4.72.731'); define('_DATE_CODE', '2026-07-07'); //MSIN-4290 define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe'); diff --git a/sql/MSIN-4429-bib-range-qty.sql b/sql/MSIN-4429-bib-range-qty.sql index 144a6ae..e249fb9 100644 --- a/sql/MSIN-4429-bib-range-qty.sql +++ b/sql/MSIN-4429-bib-range-qty.sql @@ -72,3 +72,19 @@ FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_js_rang INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) SELECT 'bib_v4_js_range_pick_hint', 'en', 'Click Qty or End to choose which value to enter.', '', 'compte.php', '', 0, 1, '', '', '', NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_js_range_pick_hint' AND info_langue = 'en' AND info_prg = 'compte.php'); + +INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) +SELECT 'bib_v4_js_range_invalid_end', 'fr', 'La fin doit être supérieure ou égale au début (%d).', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_js_range_invalid_end' AND info_langue = 'fr' AND info_prg = 'compte.php'); + +INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) +SELECT 'bib_v4_js_range_invalid_end', 'en', 'End must be greater than or equal to start (%d).', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_js_range_invalid_end' AND info_langue = 'en' AND info_prg = 'compte.php'); + +INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) +SELECT 'bib_v4_js_range_invalid_qty', 'fr', 'La quantité doit être d''au moins 1.', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_js_range_invalid_qty' AND info_langue = 'fr' AND info_prg = 'compte.php'); + +INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) +SELECT 'bib_v4_js_range_invalid_qty', 'en', 'Quantity must be at least 1.', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_js_range_invalid_qty' AND info_langue = 'en' AND info_prg = 'compte.php');