MSIN-4515 — Refine UI kit for production fields by enhancing side option selection and updating related styles and scripts. Introduce new side pick functionality and improve interaction handling. Adjust version code to 4.72.945 to reflect these enhancements.

This commit is contained in:
2026-08-03 13:29:39 -04:00
parent bb70347581
commit de820e8d1b
6 changed files with 279 additions and 82 deletions

View File

@ -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{

View File

@ -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 sassurer 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 derreur.
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 = '<div class="text-danger small" style="padding:12px;">'
+ bibTippyEscapeHtml(msg)
+ '</div>';
var errHost = document.getElementById('bib-production-mount') || panel;
if (errHost) {
errHost.innerHTML = '<div class="text-danger small" style="padding:12px;">'
+ bibTippyEscapeHtml(msg)
+ '</div>';
}
})
.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) {

View File

@ -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;
}

View File

@ -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')) {
}
?>
</div>
<?php if (!empty($tabSideOptions) && !$blnSideLocked) { ?>
<div class="ms1-ui-chooser-side-pick" hidden>
<?php if ($strSidePickLabel !== '') { ?>
<p class="ms1-ui-chooser-side-pick-title"><?php echo fxMs1UiEsc($strSidePickLabel); ?></p>
<?php } ?>
<span class="ms1-ui-chooser-item-sides ms1-ui-chooser-side-pick-btns" role="group">
<?php foreach ($tabSideOptions as $tabSideOpt) {
$strVal = (string)($tabSideOpt['value'] ?? '');
$strSideLab = (string)($tabSideOpt['label'] ?? $strVal);
if ($strVal === '') {
continue;
}
?>
<button type="button"
class="ms1-ui-chooser-item-side ms1-ui-chooser-item-side--<?php echo fxMs1UiEsc($strVal); ?> is-selected"
data-side="<?php echo fxMs1UiEsc($strVal); ?>">
<?php echo fxMs1UiEsc($strSideLab); ?>
</button>
<?php } ?>
</span>
</div>
<?php } ?>
</div>
<div class="ms1-ui-chooser-selected">
<div class="ms1-ui-chooser-panel-title"><?php echo fxMs1UiEsc($strSelectedTitle); ?></div>

View File

@ -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),
],
]);
}

View File

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