Files
ms1inscription-v5/php/inc_fx_ms1_ui.php

388 lines
17 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* MSIN-4512 — UI kit MS1 (mockup A : Disponibles ↔ Sélectionnés + drag).
* Pas de métier PDF ici — données + names passés par le panneau.
*/
if (!function_exists('fxMs1UiEsc')) {
function fxMs1UiEsc($str) {
return htmlspecialchars((string)$str, ENT_QUOTES, 'UTF-8');
}
/**
* Marqueur kit — root de chaque écran migré.
* Grep : data-ms1-ui-kit | MS1-UI-KIT | data-ms1-ui-screen
*/
function fxMs1UiKitRootAttrs($tabCfg = []) {
$strScreen = trim((string)($tabCfg['screen'] ?? ''));
$strTicket = trim((string)($tabCfg['ticket'] ?? ''));
$mixPatterns = $tabCfg['patterns'] ?? '';
if (is_array($mixPatterns)) {
$strPatterns = implode(',', array_map('strval', $mixPatterns));
} else {
$strPatterns = trim((string)$mixPatterns);
}
$strExtraClass = trim((string)($tabCfg['class'] ?? ''));
$strClass = 'ms1-ui-chooser';
if ($strExtraClass !== '') {
$strClass .= ' ' . $strExtraClass;
}
$str = 'class="' . fxMs1UiEsc($strClass) . '" data-ms1-ui-kit="1"';
if ($strScreen !== '') {
$str .= ' data-ms1-ui-screen="' . fxMs1UiEsc($strScreen) . '"';
}
if ($strPatterns !== '') {
$str .= ' data-ms1-ui-patterns="' . fxMs1UiEsc($strPatterns) . '"';
}
if ($strTicket !== '') {
$str .= ' data-ms1-ui-ticket="' . fxMs1UiEsc($strTicket) . '"';
}
return $str;
}
function fxMs1UiKitPhpMarker($strScreen, $strPatterns, $strTicket = '') {
$str = '// MS1-UI-KIT — ' . $strScreen . ' — ' . $strPatterns;
if ($strTicket !== '') {
$str .= ' — ' . $strTicket;
}
return $str;
}
/** P4 — Appliquer à toutes. */
function fxMs1UiRenderApplyAllButton($tabCfg = []) {
$strLabel = (string)($tabCfg['label'] ?? '');
$strExtra = trim((string)($tabCfg['class'] ?? ''));
$strAttrs = (string)($tabCfg['attrs'] ?? '');
$strClass = 'btn btn-outline-secondary btn-sm ms1-ui-chooser-apply-all';
if ($strExtra !== '') {
$strClass .= ' ' . $strExtra;
}
return '<button type="button" class="' . fxMs1UiEsc($strClass) . '"'
. ($strAttrs !== '' ? ' ' . $strAttrs : '')
. '>' . fxMs1UiEsc($strLabel) . '</button>';
}
/**
* P5 — Orientation / choix exclusif (segmented pills, mockup A).
* options[] : value, label, checked
*/
function fxMs1UiRenderSegmented($tabCfg = []) {
$strName = (string)($tabCfg['name'] ?? 'ms1_ui_seg');
$strLegend = (string)($tabCfg['legend'] ?? '');
$strLegendHtml = (string)($tabCfg['legend_html'] ?? '');
$strHint = (string)($tabCfg['hint'] ?? '');
$strRadioClass = trim((string)($tabCfg['radio_class'] ?? ''));
$tabOptions = is_array($tabCfg['options'] ?? null) ? $tabCfg['options'] : [];
ob_start();
?>
<div class="ms1-ui-chooser-seg-wrap mb-3">
<?php if ($strLegendHtml !== '' || $strLegend !== '') { ?>
<div class="ms1-ui-chooser-seg-legend">
<?php echo $strLegendHtml !== '' ? $strLegendHtml : fxMs1UiEsc($strLegend); ?>
</div>
<?php } ?>
<div class="ms1-ui-chooser-seg" role="radiogroup">
<?php foreach ($tabOptions as $opt) {
$strVal = (string)($opt['value'] ?? '');
$strOptLabel = (string)($opt['label'] ?? $strVal);
$blnChecked = !empty($opt['checked']);
$strCbClass = 'ms1-ui-chooser-radio';
if ($strRadioClass !== '') {
$strCbClass .= ' ' . $strRadioClass;
}
?>
<label class="ms1-ui-chooser-seg-opt<?php echo $blnChecked ? ' is-active' : ''; ?>">
<input type="radio"
class="<?php echo fxMs1UiEsc($strCbClass); ?>"
name="<?php echo fxMs1UiEsc($strName); ?>"
value="<?php echo fxMs1UiEsc($strVal); ?>"
<?php echo $blnChecked ? 'checked' : ''; ?>>
<span><?php echo fxMs1UiEsc($strOptLabel); ?></span>
</label>
<?php } ?>
</div>
<?php if ($strHint !== '') { ?>
<p class="text-muted small mb-0 mt-1"><?php echo fxMs1UiEsc($strHint); ?></p>
<?php } ?>
</div>
<?php
return ob_get_clean();
}
/** Alias legacy → segmented. */
function fxMs1UiRenderRadios($tabCfg = []) {
return fxMs1UiRenderSegmented($tabCfg);
}
/**
* P6 — Fit / budget (barre + textes).
*/
function fxMs1UiRenderFitFeedback($tabCfg = []) {
$strHint = (string)($tabCfg['hint'] ?? '');
$strHintTpl = (string)($tabCfg['hint_tpl'] ?? '');
$intUsed = (int)($tabCfg['used'] ?? 0);
$intBudget = (int)($tabCfg['budget'] ?? 0);
$intPct = ($intBudget > 0) ? (int)min(100, round(100 * $intUsed / $intBudget)) : 0;
ob_start();
?>
<div class="ms1-ui-chooser-fit mb-3">
<div class="ms1-ui-chooser-fit-bar" aria-hidden="true">
<span class="ms1-ui-chooser-fit-bar-fill" style="width:<?php echo $intPct; ?>%;"></span>
</div>
<p class="ms1-ui-chooser-fit-hint text-muted small mb-0"
data-hint-tpl="<?php echo fxMs1UiEsc($strHintTpl !== '' ? $strHintTpl : $strHint); ?>">
<?php echo fxMs1UiEsc($strHint); ?>
</p>
<p class="ms1-ui-chooser-fit-status small mb-0" aria-live="polite"></p>
<p class="ms1-ui-chooser-fit-warn text-danger small mb-0" hidden></p>
</div>
<?php
return ob_get_clean();
}
/** Une ligne sélectionnée (drag + retirer). fixed=true → toujours là (drag OK, pas de ×). */
function fxMs1UiRenderDualSelectedItem($tabCfg = []) {
$strKey = (string)($tabCfg['key'] ?? '');
$strLabel = (string)($tabCfg['label'] ?? $strKey);
$intW = (int)($tabCfg['width'] ?? 0);
$strInputName = (string)($tabCfg['input_name'] ?? '');
$strRemove = (string)($tabCfg['label_remove'] ?? '×');
$blnFixed = !empty($tabCfg['fixed']);
$strLiClass = 'ms1-ui-chooser-selected-item';
if ($blnFixed) {
$strLiClass .= ' is-fixed';
}
ob_start();
?>
<li class="<?php echo fxMs1UiEsc($strLiClass); ?>"
data-item-key="<?php echo fxMs1UiEsc($strKey); ?>"
<?php if ($blnFixed) { ?>data-fixed="1"<?php } ?>
<?php if ($intW > 0) { ?>data-item-width="<?php echo $intW; ?>"<?php } ?>>
<span class="ms1-ui-chooser-drag-handle" aria-hidden="true" title="⋮⋮">
<i class="fa fa-bars" aria-hidden="true"></i>
</span>
<?php if ($blnFixed) { ?>
<span class="ms1-ui-chooser-fixed-lock" aria-hidden="true" title="">
<i class="fa fa-lock" aria-hidden="true"></i>
</span>
<?php } ?>
<span class="ms1-ui-chooser-selected-label"><?php echo fxMs1UiEsc($strLabel); ?></span>
<?php if (!$blnFixed) { ?>
<button type="button"
class="ms1-ui-chooser-selected-remove"
aria-label="<?php echo fxMs1UiEsc($strRemove); ?>"
title="<?php echo fxMs1UiEsc($strRemove); ?>">×</button>
<?php } ?>
<?php if ($strInputName !== '') { ?>
<input type="hidden" name="<?php echo fxMs1UiEsc($strInputName); ?>" value="<?php echo fxMs1UiEsc($strKey); ?>">
<?php } ?>
</li>
<?php
return ob_get_clean();
}
/**
* P2+P3 mockup A — dual panel Disponibles (chips) ↔ Sélectionnés (drag).
*
* available[] / selected[] : key, label, width
* fixed[] optionnel : key, label (toujours en tête de lordre, non soumis)
* groups optionnel : [ ['label'=>, 'keys'=>[...]], … ] pour sous-titres chips
*/
function fxMs1UiRenderDualPanel($tabCfg = []) {
$strSectionId = (string)($tabCfg['section_id'] ?? '');
$strAvailTitle = (string)($tabCfg['available_title'] ?? '');
$strSelectedTitle = (string)($tabCfg['selected_title'] ?? '');
$strSearchPh = (string)($tabCfg['search_placeholder'] ?? '');
$strEmpty = (string)($tabCfg['empty_label'] ?? '');
$strRemove = (string)($tabCfg['label_remove'] ?? '');
$strInputNameTpl = (string)($tabCfg['input_name_tpl'] ?? 'items[%s][]');
$strInputName = sprintf($strInputNameTpl, $strSectionId);
$tabAvailable = is_array($tabCfg['available'] ?? null) ? $tabCfg['available'] : [];
$tabSelected = is_array($tabCfg['selected'] ?? null) ? $tabCfg['selected'] : [];
$tabFixed = is_array($tabCfg['fixed'] ?? null) ? $tabCfg['fixed'] : [];
$tabGroups = is_array($tabCfg['groups'] ?? null) ? $tabCfg['groups'] : [];
$tabSelectedKeys = [];
foreach ($tabSelected as $opt) {
$strK = (string)($opt['key'] ?? '');
if ($strK !== '') {
$tabSelectedKeys[$strK] = true;
}
}
// Index available by key for chip render
$tabAvailByKey = [];
foreach ($tabAvailable as $opt) {
$strK = (string)($opt['key'] ?? '');
if ($strK !== '') {
$tabAvailByKey[$strK] = $opt;
}
}
ob_start();
?>
<div class="ms1-ui-chooser-dual" data-section-id="<?php echo fxMs1UiEsc($strSectionId); ?>">
<div class="ms1-ui-chooser-available">
<div class="ms1-ui-chooser-panel-title"><?php echo fxMs1UiEsc($strAvailTitle); ?></div>
<?php if ($strSearchPh !== '') { ?>
<input type="search"
class="form-control form-control-sm ms1-ui-chooser-search"
placeholder="<?php echo fxMs1UiEsc($strSearchPh); ?>"
autocomplete="off">
<?php } ?>
<div class="ms1-ui-chooser-chips">
<?php
if (!empty($tabGroups)) {
foreach ($tabGroups as $tabG) {
$strGLabel = (string)($tabG['label'] ?? '');
$tabKeys = is_array($tabG['keys'] ?? null) ? $tabG['keys'] : [];
if ($strGLabel !== '') {
echo '<div class="ms1-ui-chooser-chip-group-label">' . fxMs1UiEsc($strGLabel) . '</div>';
}
foreach ($tabKeys as $strK) {
$strK = (string)$strK;
if (!isset($tabAvailByKey[$strK])) {
continue;
}
$opt = $tabAvailByKey[$strK];
$blnUsed = !empty($tabSelectedKeys[$strK]);
$intW = (int)($opt['width'] ?? 0);
$strLab = (string)($opt['label'] ?? $strK);
?>
<button type="button"
class="ms1-ui-chooser-chip<?php echo $blnUsed ? ' is-used' : ''; ?>"
data-item-key="<?php echo fxMs1UiEsc($strK); ?>"
data-item-label="<?php echo fxMs1UiEsc($strLab); ?>"
<?php if ($intW > 0) { ?>data-item-width="<?php echo $intW; ?>"<?php } ?>
<?php echo $blnUsed ? 'hidden' : ''; ?>>
<?php echo fxMs1UiEsc($strLab); ?>
</button>
<?php
}
}
} else {
foreach ($tabAvailable as $opt) {
$strK = (string)($opt['key'] ?? '');
if ($strK === '') {
continue;
}
$blnUsed = !empty($tabSelectedKeys[$strK]);
$intW = (int)($opt['width'] ?? 0);
$strLab = (string)($opt['label'] ?? $strK);
?>
<button type="button"
class="ms1-ui-chooser-chip<?php echo $blnUsed ? ' is-used' : ''; ?>"
data-item-key="<?php echo fxMs1UiEsc($strK); ?>"
data-item-label="<?php echo fxMs1UiEsc($strLab); ?>"
<?php if ($intW > 0) { ?>data-item-width="<?php echo $intW; ?>"<?php } ?>
<?php echo $blnUsed ? 'hidden' : ''; ?>>
<?php echo fxMs1UiEsc($strLab); ?>
</button>
<?php
}
}
?>
</div>
</div>
<div class="ms1-ui-chooser-selected">
<div class="ms1-ui-chooser-panel-title"><?php echo fxMs1UiEsc($strSelectedTitle); ?></div>
<ol class="ms1-ui-chooser-order list-unstyled mb-0"
data-section-id="<?php echo fxMs1UiEsc($strSectionId); ?>"
data-input-name="<?php echo fxMs1UiEsc($strInputName); ?>">
<?php
$blnAnyOptional = false;
foreach ($tabFixed as $opt) {
$strK = (string)($opt['key'] ?? '');
if ($strK === '') {
continue;
}
echo fxMs1UiRenderDualSelectedItem([
'key' => $strK,
'label' => (string)($opt['label'] ?? $strK),
'width' => 0,
'fixed' => true,
'input_name' => $strInputName,
]);
}
foreach ($tabSelected as $opt) {
$strK = (string)($opt['key'] ?? '');
if ($strK === '') {
continue;
}
$blnFixedItem = !empty($opt['fixed']);
if (!$blnFixedItem) {
$blnAnyOptional = true;
}
echo fxMs1UiRenderDualSelectedItem([
'key' => $strK,
'label' => (string)($opt['label'] ?? $strK),
'width' => $blnFixedItem ? 0 : (int)($opt['width'] ?? 0),
'input_name' => $strInputName,
'label_remove' => $strRemove,
'fixed' => $blnFixedItem,
]);
}
?>
</ol>
<p class="ms1-ui-chooser-order-empty text-muted small mb-0"<?php echo $blnAnyOptional ? ' hidden' : ''; ?>>
<?php echo fxMs1UiEsc($strEmpty); ?>
</p>
</div>
</div>
<?php
return ob_get_clean();
}
/**
* Section épreuve : titre + dual panel (+ apply-all optionnel dans le head).
* Pas de boîte « config commune » — apply-all sur lépreuve source.
*/
function fxMs1UiRenderChooserSection($tabCfg = []) {
$strSectionId = (string)($tabCfg['section_id'] ?? '');
$strTitle = (string)($tabCfg['title'] ?? '');
$strTitleSuffix = (string)($tabCfg['title_suffix_html'] ?? '');
$strExtraClass = trim((string)($tabCfg['class'] ?? ''));
$tabApply = $tabCfg['apply_all'] ?? null;
$tabDual = $tabCfg['dual'] ?? null;
$strBodyExtra = (string)($tabCfg['body_html'] ?? '');
$strClass = 'ms1-ui-chooser-section';
if ($strExtraClass !== '') {
$strClass .= ' ' . $strExtraClass;
}
ob_start();
?>
<section class="<?php echo fxMs1UiEsc($strClass); ?>" data-section-id="<?php echo fxMs1UiEsc($strSectionId); ?>">
<div class="ms1-ui-chooser-section-head">
<h3 class="ms1-ui-chooser-section-title">
<?php echo fxMs1UiEsc($strTitle); ?>
<?php echo $strTitleSuffix; ?>
</h3>
<?php
if (is_array($tabApply) && !empty($tabApply['label'])) {
echo fxMs1UiRenderApplyAllButton($tabApply);
}
?>
</div>
<div class="ms1-ui-chooser-section-body">
<?php
if (is_array($tabDual)) {
$tabDual['section_id'] = $strSectionId;
echo fxMs1UiRenderDualPanel($tabDual);
}
echo $strBodyExtra;
?>
</div>
</section>
<?php
return ob_get_clean();
}
}