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.
This commit is contained in:
2026-07-07 15:01:55 -04:00
parent 666c4ad180
commit 20c1f898a1
5 changed files with 169 additions and 8 deletions

View File

@ -2139,6 +2139,17 @@ a.ms1-trad-link.btn-aide-trad{
cursor:text; 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--pick:focus,
.bib-field--calc:focus{ .bib-field--calc:focus{
outline:none; outline:none;

View File

@ -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) { function bibRangeUpdatePreview(row) {
var fields = bibRangeGetFields(row); var fields = bibRangeGetFields(row);
if (!fields || !fields.preview) return; if (!fields || !fields.preview) return;
if (fields.preview.classList.contains('bib-range-preview--error')) {
return;
}
var start = bibRangeParseInt(fields.start && fields.start.value); var start = bibRangeParseInt(fields.start && fields.start.value);
var end = bibRangeParseInt(fields.end && fields.end.value); var end = bibRangeParseInt(fields.end && fields.end.value);
var qty = bibRangeParseInt(fields.qty && fields.qty.value); var qty = bibRangeParseInt(fields.qty && fields.qty.value);
fields.preview.classList.remove('bib-range-preview--error');
if (start > 0 && end >= start && qty > 0) { if (start > 0 && end >= start && qty > 0) {
fields.preview.textContent = bibJsFmt('jsRangePreview', start, end, qty); fields.preview.textContent = bibJsFmt('jsRangePreview', start, end, qty);
fields.preview.classList.remove('bib-ui-hidden'); fields.preview.classList.remove('bib-ui-hidden');
@ -2787,19 +2896,34 @@ if ($strLangue == 'fr') {
row.dataset.rangeLastStart = String(bibRangeParseInt(fields.start.value)); row.dataset.rangeLastStart = String(bibRangeParseInt(fields.start.value));
fields.start.addEventListener('input', function () { fields.start.addEventListener('input', function () {
bibRangeClearValidationUi(row);
onBibRangeStartChange(row); onBibRangeStartChange(row);
}); });
fields.qty.addEventListener('input', function () { fields.qty.addEventListener('input', function () {
bibRangeClearValidationUi(row);
row.dataset.rangePilot = 'qty'; row.dataset.rangePilot = 'qty';
applyBibRangeFieldState(row); applyBibRangeFieldState(row);
}); });
fields.end.addEventListener('input', function () { fields.end.addEventListener('input', function () {
bibRangeClearValidationUi(row);
row.dataset.rangePilot = 'end'; row.dataset.rangePilot = 'end';
applyBibRangeFieldState(row); 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 () { fields.qty.addEventListener('click', function () {
onBibRangeFieldPick(row, 'qty'); onBibRangeFieldPick(row, 'qty');
}); });
@ -3402,6 +3526,7 @@ if ($strLangue == 'fr') {
let start = bibRangeParseInt(row.querySelector('.bib-start').value); let start = bibRangeParseInt(row.querySelector('.bib-start').value);
let end = bibRangeParseInt(row.querySelector('.bib-end').value); let end = bibRangeParseInt(row.querySelector('.bib-end').value);
let id = btnSave.dataset.id; let id = btnSave.dataset.id;
let validation = bibRangeValidate(row);
// ligne temporaire vide = on la retire // ligne temporaire vide = on la retire
if (id == 0 && start <= 0) { if (id == 0 && start <= 0) {
@ -3413,18 +3538,25 @@ if ($strLangue == 'fr') {
} }
if (start <= 0 || end <= 0) { if (start <= 0 || end <= 0) {
if (errorDiv) { validation = validation || { key: 'incomplete' };
errorDiv.innerText = bibJs('jsRangeIncomplete') || bibJs('jsFieldsRequired');
errorDiv.style.display = 'block';
}
return;
} }
if (end < start) { if (!validation && end < start) {
validation = { key: 'invalidEnd', field: 'end', start: start };
}
if (validation) {
bibRangeShowValidation(row, validation);
if (errorDiv) { if (errorDiv) {
errorDiv.innerText = bibJs('jsRangeInvalid') || bibJs('jsFieldsRequired'); errorDiv.innerText = bibRangeValidationMessage(validation);
errorDiv.style.display = 'block'; errorDiv.style.display = 'block';
} }
var invalidField = validation.field === 'qty'
? row.querySelector('.bib-qty')
: row.querySelector('.bib-end');
if (invalidField) {
invalidField.focus();
}
return; return;
} }

View File

@ -3106,6 +3106,8 @@ function fxBibModuleJsDataAttrs() {
'range-invalid' => 'bib_v4_js_range_invalid', 'range-invalid' => 'bib_v4_js_range_invalid',
'range-preview' => 'bib_v4_js_range_preview', 'range-preview' => 'bib_v4_js_range_preview',
'range-pick-hint' => 'bib_v4_js_range_pick_hint', '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 = ''; $html = '';
foreach ($map as $attr => $clef) { foreach ($map as $attr => $clef) {

View File

@ -7,7 +7,7 @@
* Constantes * * Constantes *
* *
**************/ **************/
define('_VERSION_CODE', '4.72.730'); define('_VERSION_CODE', '4.72.731');
define('_DATE_CODE', '2026-07-07'); define('_DATE_CODE', '2026-07-07');
//MSIN-4290 //MSIN-4290
define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe'); define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe');

View File

@ -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) 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() 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'); 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');