From de820e8d1b0805877b03fadebd6a6a92184a7cd7 Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 3 Aug 2026 13:29:39 -0400 Subject: [PATCH] =?UTF-8?q?MSIN-4515=20=E2=80=94=20Refine=20UI=20kit=20for?= =?UTF-8?q?=20production=20fields=20by=20enhancing=20side=20option=20selec?= =?UTF-8?q?tion=20and=20updating=20related=20styles=20and=20scripts.=20Int?= =?UTF-8?q?roduce=20new=20side=20pick=20functionality=20and=20improve=20in?= =?UTF-8?q?teraction=20handling.=20Adjust=20version=20code=20to=204.72.945?= =?UTF-8?q?=20to=20reflect=20these=20enhancements.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- css/style.css | 64 ++++++++++++++--- js/v2/bib-assignments.js | 120 ++++++++++++++++++++----------- js/v2/ms1-ui-chooser.js | 151 ++++++++++++++++++++++++++++++++------- php/inc_fx_ms1_ui.php | 23 ++++++ php/inc_fx_promoteur.php | 1 + php/inc_settings.php | 2 +- 6 files changed, 279 insertions(+), 82 deletions(-) diff --git a/css/style.css b/css/style.css index e02fb78..a45ed23 100644 --- a/css/style.css +++ b/css/style.css @@ -4357,52 +4357,94 @@ a.ms1-trad-link.btn-aide-trad{ color:#b91c1c; } -/* MSIN-4515 — meta Face/Endos/Info sur chaque ligne sélectionnée (reuse kit). */ +/* MSIN-4515 — meta Face/Endos/Info : même visuel que le choix validé (toujours colorées). */ .ms1-ui-chooser-item-sides{ display:inline-flex; flex:0 0 auto; flex-wrap:wrap; - gap:4px; + gap:6px; align-items:center; } +.ms1-ui-chooser-side-pick{ + margin-top:10px; + padding:12px; + background:#f5f9ff; + border:1px solid #c5d9f5; + border-radius:6px; +} +.ms1-ui-chooser-side-pick-title{ + margin:0 0 8px; + font-size:13px; + font-weight:600; + color:#1a3a6b; +} +.ms1-ui-chooser-side-pick-btns{ + display:flex; + flex-wrap:wrap; + gap:8px; +} +.ms1-ui-chooser-side-pick .ms1-ui-chooser-item-side{ + opacity:1; + outline:none; + min-height:32px; + padding:6px 14px; + font-size:12px; +} +.ms1-ui-chooser-chip.is-pending{ + outline:2px solid var(--ms1-ui-accent, #2f5fd0); + outline-offset:1px; +} .ms1-ui-chooser-item-side{ display:inline-flex; align-items:center; + justify-content:center; margin:0; - padding:4px 10px; + min-height:28px; + padding:6px 12px; border:1px solid #ced4da; border-radius:4px; - font-size:11px; + font-size:12px; font-weight:600; line-height:1.2; cursor:pointer; - background:#f8f9fa; - color:#495057; user-select:none; + opacity:.42; + box-shadow:none; + position:relative; + z-index:2; } button.ms1-ui-chooser-item-side:hover{ - filter:brightness(.97); + opacity:.8; } -.ms1-ui-chooser-item-side--face.is-selected{ +.ms1-ui-chooser-item-side--face{ background:#5B9BD5; border-color:#3d7eae; color:#fff; } -.ms1-ui-chooser-item-side--endos.is-selected{ +.ms1-ui-chooser-item-side--endos{ background:#C00000; border-color:#8a0000; color:#fff; } -.ms1-ui-chooser-item-side--info.is-selected{ +.ms1-ui-chooser-item-side--info{ background:#fff; border-color:#6c757d; color:#212529; box-shadow:inset 0 0 0 1px #adb5bd; } +.ms1-ui-chooser-selected-item .ms1-ui-chooser-item-side.is-selected{ + opacity:1; + outline:2px solid #1a3a6b; + outline-offset:1px; +} +.ms1-ui-chooser-selected-item{ + flex-wrap:wrap; +} .ms1-ui-chooser[data-side-locked="1"] .ms1-ui-chooser-item-side, .ms1-ui-chooser[data-locked="1"] .ms1-ui-chooser-item-side{ cursor:default; - opacity:.9; + opacity:1; + outline:none; } .ms1-ui-chooser-ghost{ diff --git a/js/v2/bib-assignments.js b/js/v2/bib-assignments.js index 76784db..e915834 100644 --- a/js/v2/bib-assignments.js +++ b/js/v2/bib-assignments.js @@ -595,7 +595,9 @@ // MSIN-4546 — Charger Production seulement quand on ouvre cet espace. if (workspaceId === 'production') { - bibEnsureProductionPanelLoaded(); + bibEnsureProductionPanelLoaded().then(function () { + initBibProductionFieldsChooser(moduleBib); + }); } // MSIN-4475 — Modes d'assignation : afficher les choix tout de suite (sans clic tuile). @@ -618,13 +620,15 @@ if (!panel) { return Promise.resolve(); } - // Déjà chargé (mount remplacé par le HTML production). + // Déjà chargé (HTML production en place) → juste s’assurer que le chooser est vivant. if (panel.getAttribute('data-production-loaded') === '1' || !mount) { - return Promise.resolve(); + initBibProductionFieldsChooser(panel); + return Promise.resolve(panel); } if (panel.getAttribute('data-production-deferred') !== '1' && mount.getAttribute('data-production-deferred') !== '1') { - return Promise.resolve(); + initBibProductionFieldsChooser(panel); + return Promise.resolve(panel); } if (bibProductionLoadPromise) { return bibProductionLoadPromise; @@ -642,7 +646,14 @@ if (!data || !data.success || !data.html) { throw new Error((data && data.message) || bibJs('jsErrGeneric') || 'Erreur'); } - mount.outerHTML = data.html; + // Garder #bib-production-mount (innerHTML) pour ne pas perdre la cible d’erreur. + var liveMount = document.getElementById('bib-production-mount'); + if (liveMount) { + liveMount.innerHTML = data.html; + liveMount.removeAttribute('data-production-deferred'); + } else { + panel.innerHTML = data.html; + } panel.removeAttribute('data-production-deferred'); panel.setAttribute('data-production-loaded', '1'); initModuleBibTippy(panel); @@ -651,9 +662,12 @@ }) .catch(function (err) { var msg = (err && err.message) ? err.message : (bibJs('jsServerError') || 'Erreur serveur'); - mount.innerHTML = '
' - + bibTippyEscapeHtml(msg) - + '
'; + var errHost = document.getElementById('bib-production-mount') || panel; + if (errHost) { + errHost.innerHTML = '
' + + bibTippyEscapeHtml(msg) + + '
'; + } }) .finally(function () { bibProductionLoadPromise = null; @@ -2032,24 +2046,33 @@ }, 0); } - /** MSIN-4515 — mockup A + Face/Endos/Info via MS1UiChooser. */ + /** MSIN-4515 — mockup A + Face/Endos/Info via MS1UiChooser (réessaie si script pas prêt). */ function initBibProductionFieldsChooser(root) { - root = root || moduleBib; - var panel = root.querySelector - ? root.querySelector('.bib-production-fields') - : null; - if (!panel) { - panel = document.querySelector('#module-bib .bib-production-fields'); + var scope = (root && root.querySelector) ? root : moduleBib; + var fieldsPanel = scope.querySelector('.bib-production-fields') + || moduleBib.querySelector('.bib-production-fields'); + if (!fieldsPanel) { + return false; } - if (!panel || panel.getAttribute('data-bib-prod-fields-init') === '1') { - return; + var chooser = fieldsPanel.querySelector('.ms1-ui-chooser[data-ms1-ui-screen="bib-prod-fields"]') + || fieldsPanel.querySelector('.ms1-ui-chooser'); + if (!chooser) { + return false; } - var chooser = panel.querySelector('.ms1-ui-chooser[data-ms1-ui-screen="bib-prod-fields"]') - || panel.querySelector('.ms1-ui-chooser'); - if (!chooser || !window.MS1UiChooser || typeof window.MS1UiChooser.init !== 'function') { - return; + // Déjà branché et vivant. + if (chooser._ms1UiChooser) { + return true; + } + if (!window.MS1UiChooser || typeof window.MS1UiChooser.init !== 'function') { + if (!fieldsPanel.getAttribute('data-bib-prod-chooser-retry')) { + fieldsPanel.setAttribute('data-bib-prod-chooser-retry', '1'); + window.setTimeout(function () { + fieldsPanel.removeAttribute('data-bib-prod-chooser-retry'); + initBibProductionFieldsChooser(scope); + }, 120); + } + return false; } - panel.setAttribute('data-bib-prod-fields-init', '1'); var sideOptions = []; try { @@ -2058,28 +2081,39 @@ sideOptions = []; } - window.MS1UiChooser.init(chooser, { - // Pas de budget Excel (colonnes illimitées). - getBudget: function () { return null; }, - labelRemove: panel.getAttribute('data-label-remove') || '', - sideOptions: sideOptions, - defaultSide: chooser.getAttribute('data-default-side') || 'face', - sideLocked: panel.getAttribute('data-locked') === '1' - || chooser.getAttribute('data-side-locked') === '1', - onChange: function (_root, reason) { - if (panel.getAttribute('data-locked') === '1') { - return; + var blnLocked = fieldsPanel.getAttribute('data-locked') === '1' + || chooser.getAttribute('data-locked') === '1' + || chooser.getAttribute('data-side-locked') === '1'; + + try { + chooser.removeAttribute('data-ms1-ui-chooser-init'); + chooser._ms1UiChooser = null; + window.MS1UiChooser.init(chooser, { + getBudget: function () { return null; }, + labelRemove: fieldsPanel.getAttribute('data-label-remove') || '', + sideOptions: sideOptions, + defaultSide: chooser.getAttribute('data-default-side') || 'face', + sideLocked: blnLocked, + onChange: function (_root, reason) { + if (fieldsPanel.getAttribute('data-locked') === '1') { + return; + } + if (reason === 'apply') { + bibProdFieldsSaveNow(bibProdFieldsLabel( + 'data-label-apply-done', + 'Choix de la 1re épreuve appliqués aux autres.' + )); + return; + } + bibProdFieldsSaveSoon(); } - if (reason === 'apply') { - bibProdFieldsSaveNow(bibProdFieldsLabel( - 'data-label-apply-done', - 'Choix de la 1re épreuve appliqués aux autres.' - )); - return; - } - bibProdFieldsSaveSoon(); - } - }); + }); + } catch (errInit) { + chooser.removeAttribute('data-ms1-ui-chooser-init'); + chooser._ms1UiChooser = null; + return false; + } + return !!chooser._ms1UiChooser; } moduleBib.addEventListener('submit', function (e) { diff --git a/js/v2/ms1-ui-chooser.js b/js/v2/ms1-ui-chooser.js index ba5d3c7..d21ecca 100644 --- a/js/v2/ms1-ui-chooser.js +++ b/js/v2/ms1-ui-chooser.js @@ -102,8 +102,9 @@ } function isChooserLocked(root) { - return !!(root && (root.getAttribute('data-locked') === '1' - || root.getAttribute('data-side-locked') === '1')); + // Verrouillage interaction = data-locked seulement (impression Excel). + // data-side-locked sert au rendu pastilles lecture seule, pas à bloquer tout le kit. + return !!(root && root.getAttribute('data-locked') === '1'); } function notifyChange(root, opts, reason) { @@ -357,7 +358,42 @@ }, 4500); } - function addFromChip(section, chip, opts, root) { + function clearSidePick(section) { + if (!section) { + return; + } + section.removeAttribute('data-pending-key'); + section.removeAttribute('data-pending-label'); + section.removeAttribute('data-pending-width'); + section.querySelectorAll('.ms1-ui-chooser-chip.is-pending').forEach(function (chip) { + chip.classList.remove('is-pending'); + }); + var pick = section.querySelector('.ms1-ui-chooser-side-pick'); + if (pick) { + pick.hidden = true; + } + } + + function openSidePick(section, chip) { + if (!section || !chip) { + return; + } + clearSidePick(section); + var key = chip.getAttribute('data-item-key') || ''; + if (!key) { + return; + } + section.setAttribute('data-pending-key', key); + section.setAttribute('data-pending-label', chip.getAttribute('data-item-label') || key); + section.setAttribute('data-pending-width', chip.getAttribute('data-item-width') || '0'); + chip.classList.add('is-pending'); + var pick = section.querySelector('.ms1-ui-chooser-side-pick'); + if (pick) { + pick.hidden = false; + } + } + + function addFromChip(section, chip, opts, root, sideOverride) { var order = orderList(section); if (!order || !chip) { return { ok: false, reason: 'missing' }; @@ -368,6 +404,7 @@ } if (order.querySelector('.ms1-ui-chooser-selected-item[data-item-key="' + cssEscape(key) + '"]')) { setChipUsed(section, key, true); + clearSidePick(section); return { ok: true }; } var width = parseInt(chip.getAttribute('data-item-width') || '0', 10) || 0; @@ -378,6 +415,7 @@ } } var label = chip.getAttribute('data-item-label') || key; + var buildOpts = itemBuildOpts(root, opts, sideOverride); order.appendChild(buildSelectedItem( order, key, @@ -385,13 +423,30 @@ width, opts.labelRemove || root.getAttribute('data-label-remove') || '×', false, - itemBuildOpts(root, opts) + buildOpts )); setChipUsed(section, key, true); syncEmpty(section); + clearSidePick(section); return { ok: true }; } + function addPendingWithSide(section, side, opts, root) { + if (!section || !side) { + return { ok: false, reason: 'missing' }; + } + var key = section.getAttribute('data-pending-key') || ''; + if (!key) { + return { ok: false, reason: 'missing' }; + } + var chip = section.querySelector('.ms1-ui-chooser-chip[data-item-key="' + cssEscape(key) + '"]'); + if (!chip) { + clearSidePick(section); + return { ok: false, reason: 'missing' }; + } + return addFromChip(section, chip, opts, root, side); + } + function removeKey(section, key) { var order = orderList(section); if (!order || !key) { @@ -512,18 +567,25 @@ if (order._ms1Sortable) { return order._ms1Sortable; } - order._ms1Sortable = global.Sortable.create(order, { - animation: 150, - handle: '.ms1-ui-chooser-drag-handle', - draggable: '.ms1-ui-chooser-selected-item', - ghostClass: 'ms1-ui-chooser-ghost', - chosenClass: 'ms1-ui-chooser-chosen', - dragClass: 'ms1-ui-chooser-drag', - onEnd: function () { - updateFitUi(root, opts); - notifyChange(root, opts, 'sort'); - } - }); + try { + order._ms1Sortable = global.Sortable.create(order, { + animation: 150, + handle: '.ms1-ui-chooser-drag-handle', + filter: '.ms1-ui-chooser-item-side, .ms1-ui-chooser-selected-remove, .ms1-ui-chooser-item-sides, .ms1-ui-chooser-side-pick', + preventOnFilter: false, + draggable: '.ms1-ui-chooser-selected-item', + ghostClass: 'ms1-ui-chooser-ghost', + chosenClass: 'ms1-ui-chooser-chosen', + dragClass: 'ms1-ui-chooser-drag', + onEnd: function () { + updateFitUi(root, opts); + notifyChange(root, opts, 'sort'); + } + }); + } catch (err) { + order._ms1Sortable = null; + return null; + } return order._ms1Sortable; } @@ -560,20 +622,30 @@ if (!root) { return null; } - if (root.getAttribute('data-ms1-ui-chooser-init') === '1') { - return root._ms1UiChooser || null; + // Déjà vivant → ne pas rebrancher. + if (root._ms1UiChooser) { + return root._ms1UiChooser; } - root.setAttribute('data-ms1-ui-chooser-init', '1'); + // Flag orphelin (planté avant les listeners) → on réessaie. + root.removeAttribute('data-ms1-ui-chooser-init'); var opts = options || {}; var radioSelector = opts.radioSelector || '.ms1-ui-chooser-radio'; root.querySelectorAll('.ms1-ui-chooser-section').forEach(function (sec) { - syncEmpty(sec); - initSortable(sec, opts, root); + try { + syncEmpty(sec); + initSortable(sec, opts, root); + } catch (errInit) { + // Ne pas bloquer les clics chip / épreuve si Sortable échoue. + } }); - syncSegActive(root); - updateFitUi(root, opts); + try { + syncSegActive(root); + updateFitUi(root, opts); + } catch (errFit) { + // ignore + } root.addEventListener('change', function (ev) { var el = ev.target; @@ -624,14 +696,32 @@ : null; if (sideBtn) { ev.preventDefault(); - var itemSide = sideBtn.closest('.ms1-ui-chooser-selected-item'); - if (!itemSide) { - return; - } + ev.stopPropagation(); var newSide = sideBtn.getAttribute('data-side') || ''; if (!newSide) { return; } + + // MSIN-4515 — Choix Face/Endos/Info après clic chip (panneau disponible). + var pickWrap = sideBtn.closest('.ms1-ui-chooser-side-pick'); + if (pickWrap) { + var secPick = sideBtn.closest('.ms1-ui-chooser-section'); + var pickResult = addPendingWithSide(secPick, newSide, opts, root); + if (!pickResult.ok) { + if (pickResult.reason === 'full') { + flashFitFull(root, opts, secPick); + } + return; + } + updateFitUi(root, opts); + notifyChange(root, opts, 'add'); + return; + } + + var itemSide = sideBtn.closest('.ms1-ui-chooser-selected-item'); + if (!itemSide) { + return; + } itemSide.setAttribute('data-item-side', newSide); itemSide.querySelectorAll('button.ms1-ui-chooser-item-side[data-side]').forEach(function (btn) { var on = btn.getAttribute('data-side') === newSide; @@ -648,6 +738,12 @@ if (chip && !chip.classList.contains('is-used') && !chip.hidden) { ev.preventDefault(); var secChip = chip.closest('.ms1-ui-chooser-section'); + var sideOpts = parseSideOptions(root, opts); + // Mode production : chip → choisir Face/Endos/Info (visuel validé), puis ajouter. + if (sideOpts.length && secChip && secChip.querySelector('.ms1-ui-chooser-side-pick')) { + openSidePick(secChip, chip); + return; + } var result = addFromChip(secChip, chip, opts, root); if (!result.ok) { if (result.reason === 'full') { @@ -734,6 +830,7 @@ } }; root._ms1UiChooser = api; + root.setAttribute('data-ms1-ui-chooser-init', '1'); return api; } diff --git a/php/inc_fx_ms1_ui.php b/php/inc_fx_ms1_ui.php index c3b85d5..d55b3e7 100644 --- a/php/inc_fx_ms1_ui.php +++ b/php/inc_fx_ms1_ui.php @@ -250,6 +250,7 @@ if (!function_exists('fxMs1UiEsc')) { $tabSideOptions = is_array($tabCfg['side_options'] ?? null) ? $tabCfg['side_options'] : []; $blnSideLocked = !empty($tabCfg['side_locked']); $blnHideRemove = !empty($tabCfg['hide_remove']); + $strSidePickLabel = (string)($tabCfg['side_pick_label'] ?? ''); $tabSelectedKeys = []; foreach ($tabSelected as $opt) { @@ -332,6 +333,28 @@ if (!function_exists('fxMs1UiEsc')) { } ?> + + +
diff --git a/php/inc_fx_promoteur.php b/php/inc_fx_promoteur.php index f5bd6ea..062a4fd 100644 --- a/php/inc_fx_promoteur.php +++ b/php/inc_fx_promoteur.php @@ -5708,6 +5708,7 @@ function renderBibProductionPreview(array $tabEpreuves, $strLangue = 'fr', array 'side_options' => $tabSideOptions, 'side_locked' => $blnProductionLocked, 'hide_remove' => $blnProductionLocked, + 'side_pick_label' => fxBibTexte('bib_v5_production_fields_pick', 0), ], ]); } diff --git a/php/inc_settings.php b/php/inc_settings.php index 59a42a1..3cebb0d 100644 --- a/php/inc_settings.php +++ b/php/inc_settings.php @@ -7,7 +7,7 @@ * Constantes * * **************/ -define('_VERSION_CODE', '4.72.942'); // MSIN-4515 — production champs = UI kit chooser + Face/Endos/Info +define('_VERSION_CODE', '4.72.945'); // MSIN-4515 — init chooser production fiable (plus d’écran fumée) define('_DATE_CODE', '2026-08-03'); //MSIN-4290 define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe');