diff --git a/.cursor/rules/ms1-v2.mdc b/.cursor/rules/ms1-v2.mdc
new file mode 100644
index 0000000..0bf36fd
--- /dev/null
+++ b/.cursor/rules/ms1-v2.mdc
@@ -0,0 +1,31 @@
+---
+description: MS1 modules v2 — bib v4 et gestion inscriptions (hors legacy)
+globs: js/v2/**,php/inc_v2_assets.php,inc_tableau_gestion_epreuves.php,inc_tableau_inscriptions_gestion.php,ajax_bib_range.php,ajax_inscr_gestion.php,php/inc_fx_promoteur.php,php/inc_fx_inscriptions_gestion.php
+alwaysApply: false
+---
+
+# Modules v2 MS1
+
+## Périmètre v2 (non prod, développement actif)
+
+- **Bib assignments v4** — `#module-bib`, `fxShowBibTool4`, `ajax_bib_range.php`
+- **Gestion inscriptions v2** — `#module-inscr-gestion`, `inc_fx_inscriptions_gestion.php`, `ajax_inscr_gestion.php`
+
+## Règle JS
+
+- **Ne jamais** ajouter de code v2 dans `inc_footer_scripts.php`.
+- Nouveau JS v2 → `js/v2/` (fichier existant ou nouveau selon le module).
+- Enregistrer le script sur la page concernée : `fxV2RegisterScript('bib-assignments')` ou `'inscr-gestion'`.
+- Config PHP → JS : `data-*` sur le module (bib) ou `window.MS1_V2_INSCR_GESTION` (inscr).
+
+## Legacy (ne pas toucher sauf pont minimal)
+
+- `.btn_bib` jQuery, `fxShowBibTool2/3`, `inc_tableau_dossards.php`
+
+## Fichiers de référence
+
+| Rôle | Fichier |
+|------|---------|
+| Enregistrement / rendu scripts | `php/inc_v2_assets.php` |
+| Bib v4 client | `js/v2/bib-assignments.js` |
+| Inscr gestion client | `js/v2/inscr-gestion.js` |
diff --git a/compte.php b/compte.php
index 14a3b25..d34d2a1 100644
--- a/compte.php
+++ b/compte.php
@@ -22,6 +22,7 @@ require_once('superadm/php/inc_fx_excell.php');
require_once('php/inc_fx_liste_attente.php');
require_once('php/inc_fx_inscriptions_gestion.php');
+require_once('php/inc_v2_assets.php');
require_once('php/inc_fx_eve_acces.php');
require_once('php/inc_fx_promoteur_hub.php');
global $objDatabase, $vPage, $vDomaine;
diff --git a/inc_footer.php b/inc_footer.php
index fcf288f..371dc8b 100644
--- a/inc_footer.php
+++ b/inc_footer.php
@@ -369,6 +369,10 @@ if (isset($tabEvenement) && $tabEvenement['general']['eve_commanditaires'] == "1
+
diff --git a/inc_footer_scripts.php b/inc_footer_scripts.php
index 369daa7..d64e7e2 100644
--- a/inc_footer_scripts.php
+++ b/inc_footer_scripts.php
@@ -1,4 +1,4 @@
-
+
- if ($('#frm_inscr_gestion_login').length) {
- $('#frm_inscr_gestion_login').validate({
- messages: {
- txt_login: "",
- txt_password: ""
- },
- submitHandler: function (form) {
- if (window.Ms1Loader) {
- Ms1Loader.show();
- } else {
- $preloader.show();
- }
- form.submit();
- }
- });
- }
$('.btn_update_prod').click(function () {
var $id = $(this).data('id');
@@ -1299,249 +1283,6 @@ if ($strLangue == 'fr') {
}
});
- (function () {
- var pendingStatutXhr = null;
- var lastStatutParId = null;
-
- $('.inscr-gestion-statut-select').each(function () {
- $(this).data('prev', $(this).val());
- });
-
- $body.on('change', '.inscr-gestion-statut-select', function () {
- var $select = $(this);
- var $block = $select.closest('.inscr-gestion-field-block--statut');
- var intParId = $select.data('par_id');
- var strCode = $select.val();
- var strPrev = $select.data('prev');
-
- if (!intParId || !strCode) {
- return;
- }
-
- if (pendingStatutXhr && lastStatutParId === intParId) {
- pendingStatutXhr.abort();
- }
- lastStatutParId = intParId;
- $block.removeClass('inscr-gestion-statut--saved inscr-gestion-statut--error');
-
- $preloader_text.html('');
- $preloader.show();
-
- pendingStatutXhr = $.post('/ajax_inscr_gestion.php', {
- a: 'par_statut_course',
- par_id: intParId,
- par_statut_course: strCode,
- lang: ''
- }, function (result) {
- pendingStatutXhr = null;
- $preloader.fadeOut('slow');
- if (result && result.state === 'success') {
- $select.data('prev', strCode);
- $block.addClass('inscr-gestion-statut--saved');
- window.setTimeout(function () {
- $block.removeClass('inscr-gestion-statut--saved');
- }, 1200);
- } else {
- $select.val(strPrev);
- $block.addClass('inscr-gestion-statut--error');
- }
- }, 'json').fail(function () {
- pendingStatutXhr = null;
- $preloader.fadeOut('slow');
- $select.val(strPrev);
- $block.addClass('inscr-gestion-statut--error');
- });
- });
- })();
-
- // ===== Remboursement PayPal (fiche de gestion) =====
- // Bouton bascule : ouvre/ferme le panneau repliable et charge le formulaire en AJAX a la 1ere ouverture.
- (function () {
- // Texte explicatif affiche apres un remboursement reussi (bouton Annuler inline dans le bandeau).
- var refundCancelHint = ;
-
- // Comportement post-succes : recalcul du restant, re-emission du jeton, mise en evidence du bouton Annuler.
- function onRefundSuccess(form, json) {
- var $form = $(form);
-
- // 1) Re-emission du jeton anti double-clic (le serveur l'a consomme) -> autorise un 2e remboursement partiel.
- if (json && json.new_token) {
- $form.find('input[name="refund_token"]').val(json.new_token);
- }
-
- // 2) Recalcul du montant restant (remboursement partiel) cote client : ancien max - montant rembourse.
- var amount = parseFloat($form.find('input[name="refund_amount"]').val()) || 0;
- var $max = $('#pp_ref_max');
- var oldMax = parseFloat(String($max.text() || '0').replace(',', '.')) || 0;
- var newMax = Math.max(0, oldMax - amount);
- $max.text(newMax.toFixed(2));
-
- var $amountInput = $('#pp_ref_amount');
- $amountInput.attr('max', newMax.toFixed(2));
- if (newMax <= 0) {
- // Plus rien a rembourser : on verrouille le formulaire (le solde affiche reste a 0).
- $amountInput.val('0.00').prop('disabled', true);
- $form.find('button[type="submit"]').prop('disabled', true);
- } else {
- $amountInput.val(newMax.toFixed(2));
- }
-
- // 3) Bandeau explicatif avec bouton Annuler inline (le bouton du haut est masque pour eviter le doublon).
- var $cancelBtn = $('#annuler_fiche');
- if ($cancelBtn.length && $cancelBtn.is(':visible')) {
- if (!$('#inscr-gestion-refund-cancel-hint').length) {
- var $hint = $('
');
- $hint.append($('').text(refundCancelHint));
- $hint.append(
- $('')
- .text($.trim($cancelBtn.text()))
- .on('click', function () { $cancelBtn.trigger('click'); })
- );
- $hint.insertAfter($('#inscr-gestion-refund-panel'));
- }
- $cancelBtn.addClass('d-none');
- }
- }
-
- $(document).on('click', '.btn_refund_toggle', function () {
- var $btn = $(this);
- var $panel = $($btn.attr('data-refund-target') || '#inscr-gestion-refund-panel');
- if (!$panel.length) { return; }
-
- // Si deja ouvert -> on replie.
- if (!$panel.prop('hidden')) {
- $panel.prop('hidden', true);
- $btn.attr('aria-expanded', 'false');
- return;
- }
-
- // Sinon -> on deplie.
- $panel.prop('hidden', false);
- $btn.attr('aria-expanded', 'true');
-
- // Amene le panneau de remboursement dans le viewport et place le focus sur le champ
- // montant : utile quand le detail de la transaction est deja affiche au-dessus/dessous.
- function focusRefundPanel() {
- if ($panel[0] && $panel[0].scrollIntoView) {
- $panel[0].scrollIntoView({ behavior: 'smooth', block: 'start' });
- }
- var $amount = $panel.find('#pp_ref_amount');
- if ($amount.length && !$amount.prop('disabled')) {
- $amount.trigger('focus').trigger('select');
- }
- }
-
- // Deja charge ? on ne recharge pas (le serveur recalcule a chaque (re)chargement reel).
- if ($panel.attr('data-loaded') === '1') { focusRefundPanel(); return; }
-
- $panel.html(ms1LoaderInlineHtml());
- $.post('/ajax_inscr_gestion.php', {
- a: 'refund_form',
- no_panier: $btn.data('no_panier'),
- eve_id: $btn.data('eve_id'),
- lang: ''
- }, function (result) {
- if (result && result.state === 'ok' && result.html) {
- $panel.html(result.html);
- $panel.attr('data-loaded', '1');
- focusRefundPanel();
- } else {
- $panel.html('\u26a0\ufe0f ' + ((result && result.message) || 'Erreur de chargement.') + '
');
- }
- }, 'json').fail(function () {
- $panel.html('\u26a0\ufe0f Erreur reseau.
');
- });
- });
-
- // Bascule "Afficher la transaction" (MSIN) : meme phenomene que le remboursement,
- // mais panneau independant accessible a tout moment. Charge le resume en AJAX (transaction_resume).
- $(document).on('click', '.btn_transaction_toggle', function () {
- var $btn = $(this);
- var $panel = $($btn.attr('data-transaction-target') || '#inscr-gestion-transaction-panel');
- if (!$panel.length) { return; }
-
- var strShow = $btn.data('label-show');
- var strHide = $btn.data('label-hide');
-
- // Si deja ouvert -> on replie.
- if (!$panel.prop('hidden')) {
- $panel.prop('hidden', true);
- $btn.attr('aria-expanded', 'false');
- if (strShow) { $btn.text(strShow); }
- return;
- }
-
- // Sinon -> on deplie.
- $panel.prop('hidden', false);
- $btn.attr('aria-expanded', 'true');
- if (strHide) { $btn.text(strHide); }
-
- // Deja charge ? on ne recharge pas.
- if ($panel.attr('data-loaded') === '1') { return; }
-
- $panel.html(ms1LoaderInlineHtml());
- $.post('/ajax_inscr_gestion.php', {
- a: 'transaction_resume',
- no_panier: $btn.data('no_panier'),
- lang: ''
- }, function (result) {
- if (result && result.state === 'ok' && result.html) {
- $panel.html(result.html);
- $panel.attr('data-loaded', '1');
- } else {
- $panel.html('\u26a0\ufe0f ' + ((result && result.message) || 'Erreur de chargement.') + '
');
- }
- }, 'json').fail(function () {
- $panel.html('\u26a0\ufe0f Erreur reseau.
');
- });
- });
-
- // Handler delegue du formulaire injecte (le
diff --git a/inc_tableau_gestion_epreuves.php b/inc_tableau_gestion_epreuves.php
index 935f2d7..78c4b7f 100644
--- a/inc_tableau_gestion_epreuves.php
+++ b/inc_tableau_gestion_epreuves.php
@@ -4,6 +4,7 @@
include_once("php/inc_fx_modifierinscriptions.php");
include_once("php/inc_fx_memberships.php");
include_once("php/inc_fx_promoteur.php");
+require_once('php/inc_v2_assets.php');
require_once('php/inc_fx_eve_acces.php');
require_once('superadm/php/inc_manager.php');
@@ -63,6 +64,7 @@ if (isset($_POST['btn_cancel_event_promo'])) {
diff --git a/inc_tableau_inscriptions_gestion.php b/inc_tableau_inscriptions_gestion.php
index 1ce289f..904795a 100644
--- a/inc_tableau_inscriptions_gestion.php
+++ b/inc_tableau_inscriptions_gestion.php
@@ -5,6 +5,7 @@ include_once('php/inc_fx_modifierinscriptions.php');
include_once('php/inc_fx_memberships.php');
include_once('php/inc_fx_promoteur.php');
include_once('php/inc_fx_inscriptions_gestion.php');
+require_once('php/inc_v2_assets.php');
require_once('superadm/php/inc_manager.php');
global $objDatabase, $vDomaine, $strLangue;
@@ -24,5 +25,6 @@ if ($intEveId === 0) {
}
echo '';
+fxV2RegisterScript('inscr-gestion');
fxInscrGestionRun($intEveId, $strLangue);
echo '
';
diff --git a/js/v2/bib-assignments.js b/js/v2/bib-assignments.js
new file mode 100644
index 0000000..d7b3b9e
--- /dev/null
+++ b/js/v2/bib-assignments.js
@@ -0,0 +1,1994 @@
+/**
+ * MSIN-4435 — Bib assignments v4 (#module-bib)
+ * Chargé uniquement sur inc_tableau_gestion_epreuves.
+ */
+(function () {
+ 'use strict';
+ const moduleBib = document.getElementById('module-bib');
+
+
+ if (moduleBib) {
+
+ function bibAjaxLangSuffix() {
+ var lang = moduleBib.dataset.lang || 'fr';
+ return '&lang=' + encodeURIComponent(lang)
+ + '&csrf_token=' + encodeURIComponent(bibCsrfToken());
+ }
+
+ function bibJs(attr) {
+ return moduleBib.dataset[attr] || '';
+ }
+
+ function bibJsConfirm(attr) {
+ return bibJs(attr).replace(/\\n/g, '\n');
+ }
+
+ function bibJsFmt(attr) {
+ var tpl = bibJs(attr);
+ var args = Array.prototype.slice.call(arguments, 1);
+ var i = 0;
+ return tpl.replace(/%d/g, function () {
+ return String(args[i++] ?? 0);
+ });
+ }
+
+ var bibLoaderDepth = 0;
+
+ function bibLoaderShow() {
+ if (!window.Ms1Loader) {
+ return;
+ }
+ bibLoaderDepth++;
+ if (bibLoaderDepth === 1) {
+ var msg = bibJs('jsLoaderWait');
+ Ms1Loader.show(msg || undefined);
+ }
+ }
+
+ function bibLoaderHide() {
+ if (!window.Ms1Loader) {
+ return;
+ }
+ bibLoaderDepth = Math.max(0, bibLoaderDepth - 1);
+ if (bibLoaderDepth === 0) {
+ Ms1Loader.hide();
+ }
+ }
+
+ /** fetch ajax_bib_range.php — loader plein ecran par defaut (compteur si appels imbriques). */
+ function bibFetch(url, options, settings) {
+ settings = settings || {};
+ var useLoader = settings.loader !== false;
+ if (useLoader) {
+ bibLoaderShow();
+ }
+ return fetch(url, options).finally(function () {
+ if (useLoader) {
+ bibLoaderHide();
+ }
+ });
+ }
+
+ var BIB_BATCH_CHUNK_SIZE = 50;
+
+ function bibBatchProgressShow(processed, total) {
+ var intTotal = total > 0 ? total : 1;
+ var msg = bibJsFmt('jsBatchProgress', processed, intTotal);
+ if (window.Ms1Loader && Ms1Loader.showProgress) {
+ Ms1Loader.showProgress(processed, intTotal, msg);
+ } else {
+ bibLoaderShow();
+ }
+ }
+
+ function bibBatchProgressHide() {
+ if (window.Ms1Loader && Ms1Loader.hide) {
+ Ms1Loader.hide();
+ } else {
+ bibLoaderHide();
+ }
+ }
+
+ function bibAfterBatchGoSuccess(row, data, isResetMode) {
+ let existing = document.querySelector('.epr-row-view');
+ if (existing) {
+ existing.remove();
+ }
+
+ if (data.html) {
+ row.insertAdjacentHTML('afterend', data.html);
+ }
+
+ if (isResetMode) {
+ row.classList.remove('reset-mode');
+ row.dataset.mode = '';
+
+ let btnReset = row.querySelector('.btn-reset');
+ if (btnReset) {
+ btnReset.textContent = row.dataset.resetLabel || btnReset.textContent;
+ }
+ }
+ row.classList.remove('batch-mode');
+
+ let container = row.querySelector('.bib-container');
+ if (container) {
+ container.classList.remove('batch-mode');
+ }
+
+ let btnBatch = row.querySelector('.btn-assign-batch');
+ if (btnBatch) {
+ btnBatch.textContent = row.dataset.batchLabel || btnBatch.textContent;
+ }
+
+ row.querySelectorAll('.bib-start, .bib-qty, .bib-end').forEach(input => {
+ input.removeAttribute('readonly');
+ });
+
+ row.querySelectorAll('.batch-select-range').forEach(cb => {
+ cb.checked = false;
+ cb.disabled = false;
+ });
+
+ syncBibUiState(row);
+
+ bibFetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+ body:
+ 'action=refresh_ranges'
+ + '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ + '&mode=assign'
+ + bibAjaxLangSuffix()
+ })
+ .then(res => res.json())
+ .then(refresh => {
+
+ if (!refresh.success) return;
+
+ let container = setBibContainerHtml(row, refresh.html);
+ if (!container) return;
+ bibFetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body:
+ 'action=batch_analysis'
+ + '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ + bibAjaxLangSuffix()
+ })
+ .then(res => res.json())
+ .then(stats => {
+
+ if (!stats.success) return;
+
+ bibSyncEprBibStats(row, stats.info);
+ syncBibUiState(row);
+ });
+ });
+ }
+
+ function bibFetchBatchApplyChunk(row, selected, ba_id, opts) {
+ opts = opts || {};
+ var body =
+ 'action=batch_apply_chunk'
+ + '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ + '&ranges=' + encodeURIComponent(JSON.stringify(selected))
+ + '&ba_id=' + encodeURIComponent(ba_id)
+ + '&chunk_size=' + BIB_BATCH_CHUNK_SIZE
+ + '&total_initial=' + (opts.totalInitial || 0)
+ + '&batch_reset=' + (opts.batchReset ? 1 : 0)
+ + bibAjaxLangSuffix();
+
+ return fetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+ body: body
+ }).then(function (res) { return res.json(); });
+ }
+
+ function bibRunBatchApplyChunks(row, selected, ba_id) {
+ var totalInitial = bibRowPendingBibs(row);
+ var batchReset = true;
+
+ bibBatchProgressShow(0, totalInitial > 0 ? totalInitial : 1);
+
+ function runChunk() {
+ return bibFetchBatchApplyChunk(row, selected, ba_id, {
+ batchReset: batchReset,
+ totalInitial: totalInitial
+ }).then(function (data) {
+ if (!data.success) {
+ throw new Error(data.message || bibJs('jsErrGeneric'));
+ }
+
+ if (data.total_initial) {
+ totalInitial = data.total_initial;
+ }
+ batchReset = false;
+
+ var processed = data.processed || 0;
+ bibBatchProgressShow(processed, totalInitial);
+
+ if (data.complete) {
+ bibAfterBatchGoSuccess(row, data, false);
+ return;
+ }
+
+ return runChunk();
+ });
+ }
+
+ runChunk()
+ .catch(function (err) {
+ alert(err.message || bibJs('jsErrGeneric'));
+ })
+ .finally(function () {
+ bibBatchProgressHide();
+ });
+ }
+
+ function bibRowPendingBibs(row) {
+ if (!row) return 0;
+ return parseInt(row.dataset.bibAAssigner || '0', 10) || 0;
+ }
+
+ function bibSyncEprBibStats(row, info) {
+ if (!row || !info) return;
+
+ if (info.total !== undefined) {
+ let totalEl = row.querySelector('.bib-total');
+ if (totalEl) totalEl.textContent = info.total;
+ }
+ if (info.avec_bib !== undefined) {
+ let avecEl = row.querySelector('.bib-avec');
+ if (avecEl) avecEl.textContent = info.avec_bib;
+ }
+
+ let pending = (info.a_assigner !== undefined && info.a_assigner !== null)
+ ? info.a_assigner
+ : (info.sans_bib || 0);
+ row.dataset.bibAAssigner = String(parseInt(pending, 10) || 0);
+ }
+
+ function bibAlertAutoPendingBibs(row) {
+ alert(bibJsFmt('jsAutoPendingBibs', bibRowPendingBibs(row)).replace(/\\n/g, '\n'));
+ }
+
+ /** Bloque l'entrée en mode auto si des inscriptions n'ont pas encore de dossard. */
+ function bibBlockAutoActivation(row) {
+ if (!row || row.dataset.autoActive === '1') {
+ return false;
+ }
+ if (bibRowPendingBibs(row) > 0) {
+ bibAlertAutoPendingBibs(row);
+ return true;
+ }
+ return false;
+ }
+
+ // MSIN-4379 — Réduire / développer les blocs gestion et assignation (en-tête seul visible).
+ moduleBib.addEventListener('click', function (e) {
+ let btnToggle = e.target.closest('.epr-block-toggle');
+ if (!btnToggle || !moduleBib.contains(btnToggle)) {
+ return;
+ }
+ let block = btnToggle.closest('.epr-block');
+ if (!block) {
+ return;
+ }
+ let collapsed = block.classList.toggle('is-collapsed');
+ btnToggle.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
+ let label = collapsed
+ ? (btnToggle.getAttribute('data-label-expand') || '')
+ : (btnToggle.getAttribute('data-label-collapse') || '');
+ if (label) {
+ btnToggle.setAttribute('aria-label', label);
+ btnToggle.setAttribute('title', label);
+ }
+ let icon = btnToggle.querySelector('.fa');
+ if (icon) {
+ icon.classList.toggle('fa-chevron-up', !collapsed);
+ icon.classList.toggle('fa-chevron-down', collapsed);
+ }
+ });
+
+ // ============================================================
+ // MSIN-4379 — Tippy.js (module #module-bib uniquement)
+ // info_texte → data-tippy-content (tip court)
+ // info_aide → data-tippy-content sur .btn-aide-bib (pop-up long)
+ // Ré-init après chaque refresh AJAX des séquences.
+ // ============================================================
+
+ function getBibContainer(row) {
+ if (!row) return null;
+ return row.querySelector('.epr-block-assign .epr-table.bib-container')
+ || row.querySelector('.epr-table.bib-container')
+ || row.querySelector('.epr-block-assign .bib-container')
+ || row.querySelector('.bib-container');
+ }
+
+ function bibTippyEscapeHtml(str) {
+ return String(str)
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"');
+ }
+
+ function bibTippyAideContent(reference) {
+ let span = reference.querySelector('.bib-aide-text');
+ if (!span) {
+ return '';
+ }
+ // Mini-formatage : retours à la ligne →
; double saut → paragraphe.
+ let lines = span.textContent.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim().split('\n');
+ let html = '';
+ let para = [];
+ lines.forEach(function (line) {
+ if (line.trim() === '') {
+ if (para.length) {
+ html += '' + para.join('
') + '
';
+ para = [];
+ }
+ } else {
+ para.push(bibTippyEscapeHtml(line));
+ }
+ });
+ if (para.length) {
+ html += '' + para.join('
') + '
';
+ }
+ return html;
+ }
+
+ function initModuleBibTippy(root) {
+ if (typeof tippy === 'undefined') return;
+ let scope = root || moduleBib;
+ scope.querySelectorAll('[data-tippy-content], .bib-aide-trigger').forEach(function (el) {
+ if (el._tippy) {
+ el._tippy.destroy();
+ }
+ });
+
+ tippy(scope.querySelectorAll('[data-tippy-content]'), {
+ theme: 'ms1-bib',
+ animation: 'fade',
+ allowHTML: false,
+ maxWidth: 280,
+ interactive: false
+ });
+
+ tippy(scope.querySelectorAll('.bib-aide-trigger'), {
+ theme: 'ms1-bib ms1-bib-aide',
+ animation: 'fade',
+ allowHTML: true,
+ maxWidth: 420,
+ interactive: true,
+ content: bibTippyAideContent
+ });
+ }
+
+ /** Visibilité UI v4 — bib-ui-hidden sur le conteneur (bouton + ? ensemble). */
+ function bibQuery(row, selector) {
+ return (row || moduleBib).querySelector(selector);
+ }
+
+ function bibSetVisible(target, show, row) {
+ let el = target;
+ if (typeof target === 'string') {
+ el = bibQuery(row, target);
+ } else if (el && el.nodeType === 1
+ && !el.classList.contains('epr-action-group')
+ && !el.classList.contains('batch-config-container')
+ && !el.classList.contains('auto-config-container')
+ && !el.classList.contains('epr-add-range-row')) {
+ el = el.closest('.epr-action-group') || el;
+ }
+ if (el) {
+ el.classList.toggle('bib-ui-hidden', !show);
+ }
+ }
+
+ function bibRowModes(row) {
+ return {
+ batch: row.classList.contains('batch-mode'),
+ auto: row.classList.contains('auto-mode'),
+ reset: row.classList.contains('reset-mode'),
+ };
+ }
+
+ function bibHasSavedSequences(row) {
+ return Array.from(row.querySelectorAll('.bib-range[data-id]')).some(function (line) {
+ return parseInt(line.getAttribute('data-id') || '0', 10) > 0;
+ });
+ }
+
+ function bibCountSelectedRanges(row) {
+ return row.querySelectorAll('.batch-select-range:checked').length;
+ }
+
+ /** MSIN-4379 — Rafraîchit le dashboard anomalies (chien de garde). */
+ function refreshBibAnomaliesPanel() {
+ let mount = document.getElementById('bib-anomalies-mount');
+ if (!mount || !moduleBib) {
+ return;
+ }
+
+ let eveId = moduleBib.dataset.eveId || '';
+ if (!eveId) {
+ return;
+ }
+
+ bibFetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body: 'action=anomalies&eve_id=' + encodeURIComponent(eveId) + bibAjaxLangSuffix()
+ }, { loader: false })
+ .then(function (res) { return res.json(); })
+ .then(function (data) {
+ if (!data.success || !data.html) {
+ return;
+ }
+ mount.innerHTML = data.html;
+ initModuleBibTippy(mount);
+ });
+ }
+
+ function setBibContainerHtml(row, html) {
+ let container = getBibContainer(row);
+ if (!container) return null;
+ // renderBibRanges renvoie un bloc .epr-table.bib-container complet.
+ if (html.indexOf('bib-container') !== -1) {
+ container.outerHTML = html;
+ container = getBibContainer(row);
+ } else {
+ container.innerHTML = html;
+ }
+ if (container) {
+ initModuleBibTippy(row);
+ initBibRangeRows(container);
+ }
+ syncBibUiState(row);
+ refreshBibAnomaliesPanel();
+ return container;
+ }
+
+ // MSIN-4429 — Séquence : Début puis Qté ou Fin (pilote / calculé).
+ function bibRangeParseInt(val) {
+ var n = parseInt(String(val || '').trim(), 10);
+ return isNaN(n) ? 0 : n;
+ }
+
+ function bibRangeGetFields(row) {
+ if (!row) return null;
+ return {
+ start: row.querySelector('.bib-start'),
+ qty: row.querySelector('.bib-qty'),
+ end: row.querySelector('.bib-end')
+ };
+ }
+
+ function bibRangeGetErrorEl(row) {
+ var el = row && row.nextElementSibling;
+ if (el && el.classList.contains('bib-error')) {
+ return el;
+ }
+ return null;
+ }
+
+ function bibRangeClearRangeError(row) {
+ var errEl = bibRangeGetErrorEl(row);
+ if (!errEl) return;
+
+ var msg = errEl.querySelector('.bib-range-validate-msg');
+ if (msg) {
+ msg.remove();
+ }
+
+ if (!errEl.querySelector('div:not(.bib-range-validate-msg)') && !errEl.textContent.trim()) {
+ errEl.style.display = 'none';
+ }
+ }
+
+ function bibRangeClearFieldClasses(row) {
+ var fields = bibRangeGetFields(row);
+ if (!fields) return;
+ [fields.start, fields.qty, fields.end].forEach(function (el) {
+ if (!el) return;
+ el.classList.remove('bib-field--idle', 'bib-field--pick', 'bib-field--calc', 'bib-field--active');
+ });
+ }
+
+ 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');
+ }
+ });
+
+ bibRangeClearRangeError(row);
+ }
+
+ /** 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) {
+ return;
+ }
+
+ bibRangeClearValidationUi(row);
+
+ if (!validation) {
+ return;
+ }
+
+ var target = validation.field === 'qty' ? fields.qty : fields.end;
+ if (target) {
+ target.classList.add('bib-field--invalid');
+ }
+
+ var errEl = bibRangeGetErrorEl(row);
+ if (!errEl) {
+ return;
+ }
+
+ var msgEl = errEl.querySelector('.bib-range-validate-msg');
+ if (!msgEl) {
+ msgEl = document.createElement('div');
+ msgEl.className = 'bib-range-validate-msg';
+ errEl.insertBefore(msgEl, errEl.firstChild);
+ }
+ msgEl.textContent = bibRangeValidationMessage(validation);
+ errEl.style.display = 'block';
+ }
+
+ function bibRangeValidateOnBlur(row) {
+ applyBibRangeFieldState(row);
+ bibRangeShowValidation(row, bibRangeValidate(row));
+ }
+
+ function applyBibRangeFieldState(row) {
+ var fields = bibRangeGetFields(row);
+ if (!fields || !fields.start || !fields.qty || !fields.end) return;
+
+ if (row.dataset.locked === '1') {
+ bibRangeClearFieldClasses(row);
+ return;
+ }
+
+ var start = bibRangeParseInt(fields.start.value);
+ var pilot = row.dataset.rangePilot || '';
+
+ bibRangeClearFieldClasses(row);
+
+ if (start <= 0) {
+ fields.qty.classList.add('bib-field--idle');
+ fields.end.classList.add('bib-field--idle');
+ fields.qty.readOnly = true;
+ fields.end.readOnly = true;
+ fields.qty.value = '';
+ fields.end.value = '';
+ row.dataset.rangePilot = '';
+ return;
+ }
+
+ if (!pilot) {
+ fields.qty.classList.add('bib-field--pick');
+ fields.end.classList.add('bib-field--pick');
+ fields.qty.readOnly = true;
+ fields.end.readOnly = true;
+ return;
+ }
+
+ if (pilot === 'qty') {
+ var qty = bibRangeParseInt(fields.qty.value);
+ fields.qty.classList.add('bib-field--active');
+ fields.end.classList.add('bib-field--calc');
+ fields.qty.readOnly = false;
+ fields.end.readOnly = true;
+ if (qty > 0) {
+ fields.end.value = String(start + qty - 1);
+ } else {
+ fields.end.value = '';
+ }
+ } else if (pilot === 'end') {
+ var end = bibRangeParseInt(fields.end.value);
+ fields.end.classList.add('bib-field--active');
+ fields.qty.classList.add('bib-field--calc');
+ fields.end.readOnly = false;
+ fields.qty.readOnly = true;
+ if (end >= start) {
+ fields.qty.value = String(end - start + 1);
+ } else {
+ fields.qty.value = '';
+ }
+ }
+ }
+
+ function onBibRangeFieldPick(row, pilot) {
+ if (!row || row.dataset.locked === '1') return;
+
+ var fields = bibRangeGetFields(row);
+ if (!fields || !fields.start) return;
+
+ if (bibRangeParseInt(fields.start.value) <= 0) {
+ fields.start.focus();
+ return;
+ }
+
+ row.dataset.rangePilot = pilot;
+ applyBibRangeFieldState(row);
+
+ var target = pilot === 'qty' ? fields.qty : fields.end;
+ if (target) {
+ target.focus();
+ target.select();
+ }
+ }
+
+ function onBibRangeStartChange(row) {
+ var fields = bibRangeGetFields(row);
+ if (!fields || !fields.start) return;
+
+ var start = bibRangeParseInt(fields.start.value);
+ var lastStart = bibRangeParseInt(row.dataset.rangeLastStart || '0');
+
+ if (start !== lastStart && !row.dataset.rangePilot) {
+ fields.qty.value = '';
+ fields.end.value = '';
+ row.dataset.hasValues = '0';
+ }
+
+ row.dataset.rangeLastStart = String(start);
+
+ if (start <= 0) {
+ row.dataset.rangePilot = '';
+ }
+
+ applyBibRangeFieldState(row);
+ }
+
+ function bindBibRangeRow(row) {
+ if (!row || row.dataset.bibRangeBound === '1') return;
+ row.dataset.bibRangeBound = '1';
+
+ var fields = bibRangeGetFields(row);
+ if (!fields || !fields.start || !fields.qty || !fields.end) return;
+
+ 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');
+ });
+
+ fields.end.addEventListener('click', function () {
+ onBibRangeFieldPick(row, 'end');
+ });
+
+ fields.qty.addEventListener('focus', function () {
+ onBibRangeFieldPick(row, 'qty');
+ });
+
+ fields.end.addEventListener('focus', function () {
+ onBibRangeFieldPick(row, 'end');
+ });
+
+ applyBibRangeFieldState(row);
+
+ if (row.dataset.id === '0' && !fields.start.value) {
+ fields.start.focus();
+ }
+ }
+
+ function initBibRangeRows(root) {
+ if (!root) return;
+ root.querySelectorAll('.bib-range').forEach(bindBibRangeRow);
+ }
+
+ function bibCsrfToken() {
+ return moduleBib ? (moduleBib.dataset.csrfToken || '') : '';
+ }
+
+ initModuleBibTippy(moduleBib);
+ initBibRangeRows(moduleBib);
+
+ // ============================================================
+ // MSIN-4379 — Étape 2 : assignation automatique (interface JS)
+ // Prochain tour : étape 6 (fxAssignAutoBibForParticipant), étape 7 (fxMajCommande).
+ // ============================================================
+
+ /** MSIN-4379 — Compte les séquences cochées et met à jour le texte d'aide. */
+ function updateAutoSelectionInfo(row) {
+
+ if (!row) return;
+
+ let count = row.querySelectorAll('.batch-select-range:checked').length;
+ let info = row.querySelector('.auto-info-text');
+ if (!info) return;
+
+ if (count === 0) {
+ info.textContent = bibJs('jsAutoSelectHint');
+ } else {
+ info.textContent = bibJsFmt('jsAutoSelectCount', count);
+ }
+ }
+
+ function syncAutoUiState(row, isActive) {
+ if (!row) return;
+
+ let btnAuto = row.querySelector('.btn-auto');
+ row.dataset.autoActive = isActive ? '1' : '0';
+ row.classList.toggle('auto-enabled', isActive);
+
+ if (btnAuto) {
+ btnAuto.textContent = isActive ? row.dataset.autoLabelConfig : row.dataset.autoLabel;
+ btnAuto.classList.toggle('btn-bib-auto--active', isActive);
+ let autoAide = bibQuery(row, '.epr-action-auto-group .epr-btn-aide-segment .bib-aide-trigger');
+ if (autoAide) {
+ autoAide.classList.add('btn-aide-tone-auto');
+ autoAide.classList.remove('btn-aide-tone-success', 'btn-aide-tone-secondary');
+ }
+ }
+ }
+
+ /** MSIN-4379 — Quitte le mode édition auto sans sauver ; recharge les plages (mode assign). */
+ function exitAutoMode(row) {
+
+ if (!row) return;
+
+ row.classList.remove('auto-mode');
+
+ let container = row.querySelector('.bib-container');
+ if (container) {
+ container.classList.remove('batch-mode');
+ }
+
+ row.querySelectorAll('.bib-start, .bib-qty, .bib-end').forEach(input => {
+ input.removeAttribute('readonly');
+ });
+
+ bibFetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body:
+ 'action=refresh_ranges'
+ + '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ + '&mode=assign'
+ + bibAjaxLangSuffix()
+ })
+ .then(res => res.json())
+ .then(data => {
+
+ if (!data.success) return;
+
+ let bibContainer = setBibContainerHtml(row, data.html);
+ });
+ }
+
+ /** MSIN-4379 — Ouvre le mode édition : checkboxes, bloc Accepter/Annuler, refresh mode=auto. */
+ function enterAutoMode(row) {
+
+ if (!row) return;
+
+ if (row.classList.contains('batch-mode') || row.classList.contains('reset-mode')) {
+ return;
+ }
+
+ if (bibBlockAutoActivation(row)) {
+ return;
+ }
+
+ row.classList.add('auto-mode');
+
+ let container = row.querySelector('.bib-container');
+ if (container) {
+ container.classList.add('batch-mode');
+ }
+
+ row.querySelectorAll('.bib-start, .bib-qty, .bib-end').forEach(input => {
+ input.setAttribute('readonly', true);
+ });
+
+ syncBibUiState(row);
+
+ bibFetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body:
+ 'action=refresh_ranges'
+ + '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ + '&mode=auto'
+ + bibAjaxLangSuffix()
+ })
+ .then(res => res.json())
+ .then(data => {
+
+ if (!data.success) return;
+
+ let bibContainer = setBibContainerHtml(row, data.html);
+ if (!bibContainer) return;
+
+ bibContainer.classList.add('batch-mode');
+
+ row.querySelectorAll('.bib-start, .bib-qty, .bib-end').forEach(input => {
+ input.setAttribute('readonly', true);
+ });
+
+ updateAutoSelectionInfo(row);
+ syncBibUiState(row);
+ });
+ }
+
+ function syncBibUiState(row) {
+ if (!row) return;
+
+ let modes = bibRowModes(row);
+ let avecBib = parseInt(bibQuery(row, '.bib-avec')?.textContent || '0', 10) || 0;
+ let hasSequences = bibHasSavedSequences(row);
+ let hasSelection = bibCountSelectedRanges(row) > 0;
+ let inBatchFlow = modes.batch || modes.reset;
+ let inAutoFlow = modes.auto;
+ let inPickFlow = inBatchFlow || inAutoFlow;
+
+ row.querySelectorAll('.team-mode-box input[type="radio"]').forEach(function (radio) {
+ if (!radio.closest('.team-mode-option.disabled')) {
+ radio.disabled = avecBib > 0;
+ }
+ });
+
+ let showReset = hasSequences && (avecBib > 0 || modes.reset)
+ && !modes.auto
+ && (!modes.batch || modes.reset);
+ bibSetVisible('.epr-action-batch-group',
+ hasSequences && ((!modes.reset && !modes.auto) || modes.batch), row);
+ bibSetVisible('.epr-action-reset-group', showReset, row);
+ bibSetVisible('.epr-action-auto-group',
+ hasSequences && !modes.reset && !modes.auto && !modes.batch, row);
+
+ if (hasSequences) {
+ syncAutoUiState(row, row.dataset.autoActive === '1');
+ }
+
+ bibSetVisible('.epr-add-range-row', !inPickFlow, row);
+ bibSetVisible('.batch-config-container', inBatchFlow, row);
+ bibSetVisible('.auto-config-container', inAutoFlow, row);
+
+ let showPick = inPickFlow && !hasSelection;
+ let showBatchReady = inBatchFlow && hasSelection;
+ let showAutoReady = inAutoFlow && hasSelection;
+ bibSetVisible('.bib-seq-pick-prompt', showPick, row);
+ bibSetVisible('.batch-config-ready', showBatchReady, row);
+ bibSetVisible('.auto-config-ready', showAutoReady, row);
+
+ let bibContainer = getBibContainer(row);
+ if (bibContainer) {
+ bibContainer.classList.toggle('batch-awaiting-selection', showPick);
+ }
+
+ bibSetVisible('.epr-action-desactiver-group',
+ hasSequences && row.dataset.autoActive === '1' && inAutoFlow, row);
+ bibSetVisible('.epr-action-cancel-auto-group', inAutoFlow, row);
+ bibSetVisible('.epr-action-accept-auto-group', showAutoReady, row);
+ bibSetVisible('.epr-auto-info-group', showAutoReady, row);
+ bibSetVisible('.batch-reset-warning', showBatchReady && modes.reset, row);
+ bibSetVisible('.epr-batch-order-wrap', showBatchReady && !modes.reset, row);
+ bibSetVisible('.epr-action-sim-group', showBatchReady && !modes.reset, row);
+ bibSetVisible('.epr-action-go-group', showBatchReady, row);
+ }
+
+ function updateBatchAnalysis(row) {
+
+ if (!row) return;
+
+ let totalDispo = 0;
+ let totalReset = 0;
+
+ // =====================
+ // TOTAL DISPO
+ // Source officielle = PHP
+ // JS ne recalcule PAS les disponibilités.
+ // Il additionne seulement les valeurs affichées dans .bib-dispo.
+ // =====================
+ row.querySelectorAll('.batch-select-range:checked').forEach(cb => {
+
+ let line = cb.closest('.epr-line');
+ if (!line) return;
+
+ let usedDiv = line.querySelector('.bib-used');
+ let dispoDiv = line.querySelector('.bib-dispo');
+
+ let used = parseInt((usedDiv?.textContent || '0'), 10);
+ let dispo = parseInt((dispoDiv?.textContent || '0'), 10);
+
+ if (isNaN(used)) {
+ used = 0;
+ }
+
+ if (isNaN(dispo)) {
+ dispo = 0;
+ }
+
+ totalReset += used;
+ totalDispo += dispo;
+ });
+
+ bibFetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body: 'action=batch_analysis&epr_id=' + encodeURIComponent(row.dataset.eprId) + bibAjaxLangSuffix()
+ }, { loader: false })
+ .then(res => res.json())
+ .then(data => {
+
+ if (!data.success) return;
+
+ bibSyncEprBibStats(row, data.info);
+ syncBibUiState(row);
+ let sansBib = bibRowPendingBibs(row);
+ let isResetMode = row.dataset.mode === 'reset';
+ let message = '';
+
+ if (isResetMode) {
+
+ if (totalReset === 0) {
+ message = bibJs('jsBatchNone');
+ } else {
+ message = bibJsFmt('jsBatchResetCount', totalReset);
+ }
+
+ } else {
+
+ if (totalDispo === 0) {
+ message = bibJs('jsBatchNone');
+ } else if (totalDispo < sansBib) {
+ message = bibJsFmt('jsBatchSummaryShort', totalDispo, sansBib, sansBib - totalDispo);
+ } else {
+ message = bibJsFmt('jsBatchSummaryOk', totalDispo, sansBib);
+ }
+
+ }
+
+ let info = row.querySelector('.epr-actions .batch-info-text');
+ if (info && bibCountSelectedRanges(row) > 0) {
+ info.textContent = message;
+ }
+
+ syncBibUiState(row);
+ });
+ }
+
+// =====================
+// BATCH CHECKBOX CHANGE (PROPRE)
+// =====================
+ moduleBib.addEventListener('change', function (e) {
+
+ // =====================
+ // TEAM MODE
+ // =====================
+ let teamRadio = e.target.closest('.team-mode-box input[type="radio"]');
+
+ if (teamRadio) {
+
+ if (teamRadio.disabled) return;
+
+ let box = teamRadio.closest('.team-mode-box');
+ if (!box) return;
+
+ bibFetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body:
+ 'action=save_team_mode'
+ + '&epr_id=' + encodeURIComponent(box.dataset.eprId)
+ + '&team_mode=' + encodeURIComponent(teamRadio.value)
+ + bibAjaxLangSuffix()
+ }, { loader: false });
+
+ return;
+ }
+
+ let cb = e.target.closest('.batch-select-range');
+ if (!cb) return;
+
+ let row = cb.closest('.epr-row');
+ if (!row) return;
+
+ // MSIN-4379 — Mode auto : même sélection de séquences que le batch, sans GO/simulation.
+ if (row.classList.contains('auto-mode')) {
+ updateAutoSelectionInfo(row);
+ syncBibUiState(row);
+ return;
+ }
+
+ if (row.dataset.loading === '1') return;
+ let existing = document.querySelector('.epr-row-view');
+ if (existing) existing.remove();
+ row.dataset.loading = '1';
+
+ // 1. garder sélection
+ let selected = [];
+ row.querySelectorAll('.batch-select-range:checked').forEach(el => {
+ selected.push(el.dataset.rangeId);
+ });
+
+ // 2. analyse gauche/droite
+ updateBatchAnalysis(row);
+
+ // 3. sortir du cycle du change
+ setTimeout(() => {
+
+ bibFetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body:
+ 'action=refresh_ranges'
+ + '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ + '&mode=' + encodeURIComponent(row.dataset.mode || 'assign')
+ + bibAjaxLangSuffix()
+ }, { loader: false })
+ .then(res => res.json())
+ .then(data => {
+
+ if (!data.success) {
+ row.dataset.loading = '0';
+ return;
+ }
+
+ setBibContainerHtml(row, data.html);
+ let container = getBibContainer(row);
+ if (!container) {
+ row.dataset.loading = '0';
+ return;
+ }
+
+ // 4. refresh complet serveur
+ // Re-sync après refresh serveur
+
+ container.classList.add('batch-mode');
+
+ // 5. remettre readonly
+ container.querySelectorAll('.bib-start, .bib-qty, .bib-end').forEach(input => {
+ input.setAttribute('readonly', true);
+ });
+
+ // 6. restaurer checkbox
+ container.querySelectorAll('.batch-select-range').forEach(el => {
+ if (selected.includes(el.dataset.rangeId)) {
+ el.checked = true;
+ }
+ });
+ updateBatchAnalysis(row);
+
+ row.dataset.loading = '0';
+
+ })
+ .catch(() => {
+ row.dataset.loading = '0';
+ });
+
+ }, 0);
+ });
+
+ moduleBib.querySelectorAll('.epr-row').forEach(syncBibUiState);
+
+ moduleBib.addEventListener('click', function (e) {
+
+ let btnGotoAssign = e.target.closest('.bib-anomaly-goto-assign');
+ if (btnGotoAssign) {
+ e.preventDefault();
+
+ let eprId = btnGotoAssign.dataset.eprId;
+ if (!eprId) {
+ return;
+ }
+
+ let row = moduleBib.querySelector('.epr-row[data-epr-id="' + eprId + '"]');
+ if (row) {
+ row.scrollIntoView({ behavior: 'smooth', block: 'start' });
+ let btnView = row.querySelector('.btn-view-range[data-range-id="0"]');
+ if (btnView) {
+ btnView.click();
+ }
+ }
+ return;
+ }
+
+ let btnTrad = e.target.closest('.btn-admin-trad, .btn-bib-trad');
+ if (btnTrad) {
+ e.preventDefault();
+ e.stopPropagation();
+ let url = btnTrad.getAttribute('data-trad-url');
+ if (!url) {
+ return;
+ }
+ let clef = btnTrad.getAttribute('data-info-clef') || 'bib_trad';
+ let popup = window.open(
+ url,
+ 'bib_trad_' + clef,
+ 'width=960,height=640,scrollbars=yes,resizable=yes'
+ );
+ if (popup) {
+ let timer = setInterval(function () {
+ if (popup.closed) {
+ clearInterval(timer);
+ window.location.reload();
+ }
+ }, 400);
+ }
+ return;
+ }
+
+ // =====================
+ // LOCK / UNLOCK SEQUENCE
+ // =====================
+ let btnLock = e.target.closest('.btn-bib-lock');
+ if (btnLock) {
+ e.preventDefault();
+
+ let row = btnLock.closest('.epr-row');
+ if (!row) return;
+
+ let isLocked = btnLock.dataset.locked === '1';
+ let confirmMsg = isLocked
+ ? bibJsConfirm('jsUnlockConfirm')
+ : bibJsConfirm('jsLockConfirm');
+
+ if (!confirm(confirmMsg)) {
+ return;
+ }
+
+ bibFetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body:
+ 'action=toggle_lock'
+ + '&epr_bib_id=' + encodeURIComponent(btnLock.dataset.rangeId)
+ + '&epr_id=' + encodeURIComponent(btnLock.dataset.eprId || row.dataset.eprId)
+ + '&locked=' + encodeURIComponent(isLocked ? '0' : '1')
+ + bibAjaxLangSuffix()
+ })
+ .then(res => res.json())
+ .then(data => {
+
+ if (!data.success) {
+ alert(data.message || bibJs('jsErrGeneric'));
+ return;
+ }
+
+ if (typeof data.auto_active !== 'undefined') {
+ row.dataset.autoActive = data.auto_active ? '1' : '0';
+ syncAutoUiState(row, data.auto_active);
+ syncBibUiState(row);
+ }
+
+ if (data.html) {
+ setBibContainerHtml(row, data.html);
+ }
+ });
+
+ return;
+ }
+
+ // =====================
+ // DELETE
+ // =====================
+ let btnDelete = e.target.closest('.btn-delete-range');
+ if (btnDelete) {
+
+
+ e.preventDefault();
+
+ let container = btnDelete.closest('.bib-container');
+ let errorDiv = container.querySelector('.bib-error-global');
+
+ // reset erreur
+ if (errorDiv) {
+ errorDiv.style.display = 'none';
+ errorDiv.innerText = '';
+ }
+
+ let id = btnDelete.dataset.id;
+ if (!id) return;
+
+ bibFetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body: 'action=delete&id=' + encodeURIComponent(id) + bibAjaxLangSuffix()
+ })
+ .then(res => res.text())
+ .then(raw => {
+
+ let data;
+ try {
+ data = JSON.parse(raw);
+ } catch (err) {
+ if (errorDiv) {
+ errorDiv.innerText = bibJs('jsServerError');
+ errorDiv.style.display = 'block';
+ }
+ return;
+ }
+
+ if (!data.success) {
+ if (errorDiv) {
+ errorDiv.innerText = data.message || bibJs('jsErrGeneric');
+ errorDiv.style.display = 'block';
+ }
+ return;
+ }
+
+ setBibContainerHtml(btnDelete.closest('.epr-row'), data.html);
+ });
+
+ return;
+ }
+
+ // =====================
+ // SAVE
+ // =====================
+ let btnSave = e.target.closest('.btn-save-range');
+ if (btnSave) {
+ e.preventDefault();
+
+ let row = btnSave.closest('.bib-range');
+ let errorDiv = row.nextElementSibling;
+ let epr_id = btnSave.dataset.eprId;
+
+ // reset
+ if (errorDiv) {
+ errorDiv.style.display = 'none';
+ errorDiv.innerText = '';
+ }
+
+ applyBibRangeFieldState(row);
+
+ 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) {
+ row.remove();
+ if (errorDiv && errorDiv.classList.contains('bib-error')) {
+ errorDiv.remove();
+ }
+ return;
+ }
+
+ if (start <= 0 || end <= 0) {
+ validation = validation || { key: 'incomplete' };
+ }
+
+ if (!validation && end < start) {
+ validation = { key: 'invalidEnd', field: 'end', start: start };
+ }
+
+ if (validation) {
+ bibRangeShowValidation(row, validation);
+ if (errorDiv) {
+ var msgEl = errorDiv.querySelector('.bib-range-validate-msg');
+ if (!msgEl) {
+ 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;
+ }
+
+ bibFetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body:
+ 'action=save'
+ + '&id=' + encodeURIComponent(id)
+ + '&epr_id=' + encodeURIComponent(epr_id)
+ + '&start=' + encodeURIComponent(start)
+ + '&end=' + encodeURIComponent(end)
+ + bibAjaxLangSuffix()
+ })
+ .then(res => res.text())
+ .then(raw => {
+
+ let data;
+ try {
+ data = JSON.parse(raw);
+ } catch (err) {
+ errorDiv.innerText = bibJs('jsServerError');
+ errorDiv.style.display = 'block';
+ return;
+ }
+
+ if (!data.success) {
+ errorDiv.innerText = data.message || bibJs('jsErrGeneric');
+ errorDiv.style.display = 'block';
+ return;
+ }
+
+ setBibContainerHtml(btnSave.closest('.epr-row'), data.html);
+ });
+
+ return;
+ }
+// =====================
+// VIEW
+// =====================
+ let btnView = e.target.closest('.btn-view-range');
+ if (btnView) {
+ e.preventDefault();
+
+ let epr_id = btnView.dataset.eprId;
+ let range_id = btnView.dataset.rangeId;
+
+ console.log('epr_id:', epr_id);
+ console.log('range_id:', range_id);
+ if (!epr_id || typeof range_id === 'undefined') return;
+
+ let container = btnView.closest('.epr-block');
+
+ // Vérifier si une vue est déjà ouverte
+ let existing = document.querySelector('.epr-row-view');
+
+ // Toggle : si c'est le même œil qui a ouvert la vue, on la ferme et on s'arrête
+ if (existing
+ && existing.dataset.eprId === epr_id
+ && existing.dataset.rangeId === range_id
+ ) {
+ existing.remove();
+ return;
+ }
+
+ // Un autre œil a été cliqué : fermer la vue précédente avant d'ouvrir la nouvelle
+ if (existing) {
+ existing.remove();
+ }
+
+ bibFetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body:
+ 'action=view'
+ + '&epr_id=' + encodeURIComponent(epr_id)
+ + '&range_id=' + encodeURIComponent(range_id)
+ + bibAjaxLangSuffix()
+ })
+ .then(res => res.text())
+ .then(raw => {
+
+ let data;
+ try {
+ data = JSON.parse(raw);
+ } catch (err) {
+ console.log(bibJs('jsServerError'));
+ return;
+ }
+
+ if (!data.success) {
+ console.log(data.message || bibJs('jsErrGeneric'));
+ return;
+ }
+
+ let row = btnView.closest('.epr-row');
+
+ // Injecter la nouvelle vue sous la ligne du bouton cliqué,
+ // en mémorisant epr_id et range_id pour le toggle suivant
+ row.insertAdjacentHTML('afterend', data.html);
+
+ // Stocker les identifiants sur la vue injectée pour pouvoir détecter
+ // le 2e clic sur le même œil
+ let newView = row.nextElementSibling;
+ if (newView && newView.classList.contains('epr-row-view')) {
+ newView.dataset.eprId = epr_id;
+ newView.dataset.rangeId = range_id;
+ }
+ });
+
+ return;
+ }
+
+
+ // =====================
+// CLOSE VIEW
+// =====================
+ let btnClose = e.target.closest('.btn-close-view');
+ if (btnClose) {
+ e.preventDefault();
+
+ let view = btnClose.closest('.epr-row-view');
+ if (view) {
+ view.remove();
+ }
+
+ return;
+ }
+// =====================
+// ASSIGN BATCH
+// =====================
+// Active/désactive le mode batch pour UNE épreuve.
+// - Affiche les checkbox
+// - Bloque Début/Fin
+// - Affiche le bloc de configuration
+// - Recalcule l’analyse initiale
+// =====================
+ let btnBatch = e.target.closest('.btn-assign-batch');
+ if (btnBatch) {
+
+ e.preventDefault();
+
+ let row = btnBatch.closest('.epr-row');
+ if (!row) return;
+
+ // MSIN-4379 — Batch et auto sont mutuellement exclusifs.
+ if (row.classList.contains('auto-mode')) {
+ return;
+ }
+
+ let container = row.querySelector('.bib-container');
+ if (!container) return;
+
+ // OFF
+ if (row.classList.contains('batch-mode')) {
+
+ row.classList.remove('batch-mode');
+ row.querySelectorAll('.batch-select-range').forEach(cb => {
+ cb.disabled = false;
+ });
+ container.classList.remove('batch-mode');
+
+ row.querySelectorAll('.bib-start, .bib-qty, .bib-end').forEach(input => {
+ input.removeAttribute('readonly');
+ });
+
+ btnBatch.textContent = row.dataset.batchLabel || btnBatch.textContent;
+
+ let goBtn = row.querySelector('.btn-go-batch');
+ if (goBtn) {
+ goBtn.textContent = row.dataset.goLabel || goBtn.textContent;
+ }
+ row.querySelectorAll('.batch-select-range').forEach(cb => {
+ cb.checked = false;
+ });
+ let info = row.querySelector('.epr-actions .batch-info-text');
+ if (info) info.textContent = '';
+ syncBibUiState(row);
+ return;
+ }
+
+ // ON
+ row.classList.add('batch-mode');
+ container.classList.add('batch-mode');
+
+ row.querySelectorAll('.bib-start, .bib-qty, .bib-end').forEach(input => {
+ input.setAttribute('readonly', true);
+ });
+
+ btnBatch.textContent = row.dataset.batchLabelCancel || btnBatch.textContent;
+ let goBtn = row.querySelector('.btn-go-batch');
+ if (goBtn) {
+ goBtn.textContent = row.dataset.goLabel || goBtn.textContent;
+ }
+ if (typeof updateBatchAnalysis === 'function') {
+ updateBatchAnalysis(row);
+ }
+ syncBibUiState(row);
+
+ return;
+ }
+// =====================
+// SIMULER BATCH
+// =====================
+ let btnSim = e.target.closest('.btn-simuler-batch');
+ if (btnSim) {
+
+ e.preventDefault();
+
+ let row = btnSim.closest('.epr-row');
+ if (!row) return;
+
+ let selected = [];
+
+ row.querySelectorAll('.batch-select-range:checked').forEach(cb => {
+ selected.push(cb.dataset.rangeId);
+ });
+
+ if (selected.length === 0) {
+ alert(bibJs('jsNoSeqSelected'));
+ return;
+ }
+ let isResetMode = row.dataset.mode === 'reset';
+ let ba_id = row.querySelector('.batch-order').value;
+
+ bibFetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body:
+ (isResetMode ? 'action=reset_preview' : 'action=batch_go')
+ + '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ + '&ranges=' + encodeURIComponent(JSON.stringify(selected))
+ + '&ba_id=' + encodeURIComponent(ba_id)
+ + bibAjaxLangSuffix()
+ })
+ .then(res => res.json())
+ .then(data => {
+
+ if (!data.success) {
+ alert(data.message || bibJs('jsErrGeneric'));
+ return;
+ }
+
+ let existing = document.querySelector('.epr-row-view');
+ if (existing) {
+ existing.remove();
+ }
+
+ row.insertAdjacentHTML('afterend', data.html);
+
+
+ });
+
+ return;
+ }
+
+// =====================
+// RESET MODE
+// =====================
+ let btnReset = e.target.closest('.btn-reset');
+ if (btnReset) {
+
+ e.preventDefault();
+
+ let row = btnReset.closest('.epr-row');
+ if (!row) return;
+
+ // MSIN-4379 — Reset et auto sont mutuellement exclusifs.
+ if (row.classList.contains('auto-mode')) {
+ return;
+ }
+
+ let container = row.querySelector('.bib-container');
+ if (!container) return;
+
+ // =====================
+ // OFF
+ // =====================
+ if (row.classList.contains('reset-mode')) {
+
+ row.classList.remove('reset-mode');
+ btnReset.textContent = row.dataset.resetLabel || btnReset.textContent;
+ container.classList.remove('batch-mode');
+
+ row.querySelectorAll('.bib-start, .bib-qty, .bib-end').forEach(input => {
+ input.removeAttribute('readonly');
+ });
+
+ row.querySelectorAll('.batch-select-range').forEach(cb => {
+ cb.checked = false;
+ });
+
+ let goBtn = row.querySelector('.btn-go-batch');
+ if (goBtn) {
+ goBtn.textContent = row.dataset.goLabel || goBtn.textContent;
+ }
+
+ let info = row.querySelector('.epr-actions .batch-info-text');
+ if (info) {
+ info.innerHTML = '';
+ }
+ row.dataset.mode = '';
+ syncBibUiState(row);
+ return;
+ }
+
+ // =====================
+ // ON
+ // =====================
+ row.classList.add('reset-mode');
+ btnReset.textContent = row.dataset.resetCancelLabel || btnReset.textContent;
+ row.dataset.mode = 'reset';
+ container.classList.add('batch-mode');
+
+ row.querySelectorAll('.bib-start, .bib-qty, .bib-end').forEach(input => {
+ input.setAttribute('readonly', true);
+ });
+
+ syncBibUiState(row);
+
+ let goBtn = row.querySelector('.btn-go-batch');
+ if (goBtn) {
+ goBtn.textContent = row.dataset.resetLabel || goBtn.textContent;
+ }
+
+ bibFetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body:
+ 'action=refresh_ranges'
+ + '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ + '&mode=reset'
+ + bibAjaxLangSuffix()
+ })
+ .then(res => res.json())
+ .then(data => {
+
+ if (!data.success) return;
+
+ let container = setBibContainerHtml(row, data.html);
+ if (!container) return;
+
+ container.classList.add('batch-mode');
+
+ row.querySelectorAll('.bib-start, .bib-qty, .bib-end').forEach(input => {
+ input.setAttribute('readonly', true);
+ });
+
+ syncBibUiState(row);
+ });
+
+ return;
+ }
+
+
+
+
+// =====================
+// GO BATCH
+// =====================
+ let btnGo = e.target.closest('.btn-go-batch');
+ if (btnGo) {
+
+ e.preventDefault();
+
+ let row = btnGo.closest('.epr-row');
+ if (!row) return;
+
+ let selected = [];
+
+ row.querySelectorAll('.batch-select-range:checked').forEach(cb => {
+ selected.push(cb.dataset.rangeId);
+ });
+
+ if (selected.length === 0) {
+ alert(bibJs('jsNoSeqSelected'));
+ return;
+ }
+ let isResetMode = row.dataset.mode === 'reset';
+ let ba_id = row.querySelector('.batch-order').value;
+
+ if (isResetMode) {
+ bibFetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body:
+ 'action=reset_apply'
+ + '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ + '&ranges=' + encodeURIComponent(JSON.stringify(selected))
+ + '&ba_id=' + encodeURIComponent(ba_id)
+ + bibAjaxLangSuffix()
+ })
+ .then(res => res.json())
+ .then(data => {
+
+ if (!data.success) {
+ alert(data.message || bibJs('jsErrGeneric'));
+ return;
+ }
+
+ bibAfterBatchGoSuccess(row, data, true);
+ });
+
+ return;
+ }
+
+ bibRunBatchApplyChunks(row, selected, ba_id);
+
+ return;
+ }
+
+// =====================
+// MSIN-4379 — Étape 2 : AUTO DOSSARD (clics Accepter / Annuler / Désactiver / Activer)
+// Prochain tour : étape 6–7 (assignation PHP à l'inscription).
+// =====================
+ let btnAccepterAuto = e.target.closest('.btn-accepter-auto');
+ if (btnAccepterAuto) {
+
+ e.preventDefault();
+
+ let row = btnAccepterAuto.closest('.epr-row');
+ if (!row) return;
+
+ let selected = [];
+
+ row.querySelectorAll('.batch-select-range:checked').forEach(cb => {
+ selected.push(cb.dataset.rangeId);
+ });
+
+ if (selected.length === 0) {
+ alert(bibJs('jsNoSeqSelected'));
+ return;
+ }
+
+ if (bibRowPendingBibs(row) > 0) {
+ bibAlertAutoPendingBibs(row);
+ return;
+ }
+
+ // MSIN-4379 — Persiste epr_bib_auto=1 sur les séquences choisies (save_auto_config).
+ bibFetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body:
+ 'action=save_auto_config'
+ + '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ + '&active=1'
+ + '&ranges=' + encodeURIComponent(JSON.stringify(selected))
+ + bibAjaxLangSuffix()
+ })
+ .then(res => res.json())
+ .then(data => {
+
+ if (!data.success) {
+ alert(data.message || bibJs('jsErrGeneric'));
+ return;
+ }
+
+ syncAutoUiState(row, true);
+ exitAutoMode(row);
+ syncBibUiState(row);
+ });
+
+ return;
+ }
+
+ // MSIN-4379 — Annule l'édition sans appeler save_auto_config.
+ let btnAnnulerAuto = e.target.closest('.btn-annuler-auto');
+ if (btnAnnulerAuto) {
+
+ e.preventDefault();
+
+ let row = btnAnnulerAuto.closest('.epr-row');
+ if (!row) return;
+
+ exitAutoMode(row);
+ return;
+ }
+
+ // MSIN-4379 — Désactive l'auto en base (active=0, toutes les séquences à 0).
+ let btnDesactiverAuto = e.target.closest('.btn-desactiver-auto');
+ if (btnDesactiverAuto) {
+
+ e.preventDefault();
+
+ let row = btnDesactiverAuto.closest('.epr-row');
+ if (!row) return;
+
+ if (!confirm(bibJs('jsAutoDisableConfirm'))) {
+ return;
+ }
+
+ bibFetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body:
+ 'action=save_auto_config'
+ + '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ + '&active=0'
+ + '&ranges=' + encodeURIComponent('[]')
+ + bibAjaxLangSuffix()
+ })
+ .then(res => res.json())
+ .then(data => {
+
+ if (!data.success) {
+ alert(data.message || bibJs('jsErrGeneric'));
+ return;
+ }
+
+ syncAutoUiState(row, false);
+ exitAutoMode(row);
+ syncBibUiState(row);
+ });
+
+ return;
+ }
+
+ // MSIN-4379 — Entrée dans le mode configuration auto.
+ let btnAuto = e.target.closest('.btn-auto');
+ if (btnAuto) {
+
+ e.preventDefault();
+
+ let row = btnAuto.closest('.epr-row');
+ if (!row) return;
+
+ if (row.classList.contains('batch-mode') || row.classList.contains('reset-mode')) {
+ return;
+ }
+
+ enterAutoMode(row);
+ return;
+ }
+
+ // =====================
+ // CREATE
+ // =====================
+
+ let btn = e.target.closest('.btn-add-range');
+ if (!btn) return;
+
+ e.preventDefault();
+
+ let container = btn.closest('.bib-container');
+ let errorDiv = container.querySelector('.bib-error-global');
+
+ // reset
+ if (errorDiv) {
+ errorDiv.style.display = 'none';
+ errorDiv.innerText = '';
+ }
+
+ let epr_id = btn.dataset.eprId;
+ if (!epr_id) return;
+
+ bibFetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body: 'action=create&epr_id=' + encodeURIComponent(epr_id) + bibAjaxLangSuffix()
+ })
+ .then(res => res.text())
+ .then(raw => {
+
+ let data;
+ try {
+ data = JSON.parse(raw);
+ } catch (err) {
+ if (errorDiv) {
+ errorDiv.innerText = bibJs('jsServerError');
+ errorDiv.style.display = 'block';
+ }
+ return;
+ }
+
+ if (!data.success) {
+ console.log('CREATE ERROR:', data);
+ console.log('container:', container);
+ console.log('errorDiv:', errorDiv);
+ if (errorDiv) {
+ errorDiv.innerText = data.message || bibJs('jsErrGeneric');
+ errorDiv.style.display = 'block';
+ }
+ return;
+ }
+
+ setBibContainerHtml(btn.closest('.epr-row'), data.html);
+ });
+ });
+ }
+})();
diff --git a/js/v2/inscr-gestion.js b/js/v2/inscr-gestion.js
new file mode 100644
index 0000000..fdf1642
--- /dev/null
+++ b/js/v2/inscr-gestion.js
@@ -0,0 +1,895 @@
+/**
+ * MSIN-4435 — Gestion inscriptions v2
+ * Chargé sur inc_tableau_inscriptions_gestion et mobile.php (entrée gestion).
+ */
+(function () {
+ 'use strict';
+
+ var cfg = window.MS1_V2_INSCR_GESTION || {};
+
+ function ms1LoaderInlineHtml() {
+ if (window.Ms1Loader) {
+ return Ms1Loader.inlineHtml(cfg.preloaderWait || undefined);
+ }
+ return '…
';
+ }
+
+ if (typeof jQuery !== 'undefined') {
+ jQuery(function ($) {
+ if ($('#frm_inscr_gestion_login').length && cfg.loginMessages) {
+ $('#frm_inscr_gestion_login').validate({
+ messages: {
+ txt_login: cfg.loginMessages.txt_login || '',
+ txt_password: cfg.loginMessages.txt_password || ''
+ },
+ submitHandler: function (form) {
+ if (window.Ms1Loader) {
+ Ms1Loader.show(cfg.preloaderWait || undefined);
+ } else {
+ $('#preloader').show();
+ }
+ form.submit();
+ }
+ });
+ }
+
+ var $body = $('body');
+ var $preloader = $('#preloader');
+ var $preloader_text = $('#preloader_text');
+
+ (function () {
+ var pendingStatutXhr = null;
+ var lastStatutParId = null;
+
+ $('.inscr-gestion-statut-select').each(function () {
+ $(this).data('prev', $(this).val());
+ });
+
+ $body.on('change', '.inscr-gestion-statut-select', function () {
+ var $select = $(this);
+ var $block = $select.closest('.inscr-gestion-field-block--statut');
+ var intParId = $select.data('par_id');
+ var strCode = $select.val();
+ var strPrev = $select.data('prev');
+
+ if (!intParId || !strCode) {
+ return;
+ }
+
+ if (pendingStatutXhr && lastStatutParId === intParId) {
+ pendingStatutXhr.abort();
+ }
+ lastStatutParId = intParId;
+ $block.removeClass('inscr-gestion-statut--saved inscr-gestion-statut--error');
+
+ $preloader_text.html(cfg.preloaderWait || '');
+ $preloader.show();
+
+ pendingStatutXhr = $.post((cfg.domain || '') + '/ajax_inscr_gestion.php', {
+ a: 'par_statut_course',
+ par_id: intParId,
+ par_statut_course: strCode,
+ lang: cfg.lang || 'fr'
+ }, function (result) {
+ pendingStatutXhr = null;
+ $preloader.fadeOut('slow');
+ if (result && result.state === 'success') {
+ $select.data('prev', strCode);
+ $block.addClass('inscr-gestion-statut--saved');
+ window.setTimeout(function () {
+ $block.removeClass('inscr-gestion-statut--saved');
+ }, 1200);
+ } else {
+ $select.val(strPrev);
+ $block.addClass('inscr-gestion-statut--error');
+ }
+ }, 'json').fail(function () {
+ pendingStatutXhr = null;
+ $preloader.fadeOut('slow');
+ $select.val(strPrev);
+ $block.addClass('inscr-gestion-statut--error');
+ });
+ });
+ })();
+
+ (function () {
+ var refundCancelHint = cfg.refundCancelHint || '';
+
+ function onRefundSuccess(form, json) {
+ var $form = $(form);
+
+ if (json && json.new_token) {
+ $form.find('input[name="refund_token"]').val(json.new_token);
+ }
+
+ var amount = parseFloat($form.find('input[name="refund_amount"]').val()) || 0;
+ var $max = $('#pp_ref_max');
+ var oldMax = parseFloat(String($max.text() || '0').replace(',', '.')) || 0;
+ var newMax = Math.max(0, oldMax - amount);
+ $max.text(newMax.toFixed(2));
+
+ var $amountInput = $('#pp_ref_amount');
+ $amountInput.attr('max', newMax.toFixed(2));
+ if (newMax <= 0) {
+ $amountInput.val('0.00').prop('disabled', true);
+ $form.find('button[type="submit"]').prop('disabled', true);
+ } else {
+ $amountInput.val(newMax.toFixed(2));
+ }
+
+ var $cancelBtn = $('#annuler_fiche');
+ if ($cancelBtn.length && $cancelBtn.is(':visible')) {
+ if (!$('#inscr-gestion-refund-cancel-hint').length) {
+ var $hint = $('');
+ $hint.append($('').text(refundCancelHint));
+ $hint.append(
+ $('')
+ .text($.trim($cancelBtn.text()))
+ .on('click', function () { $cancelBtn.trigger('click'); })
+ );
+ $hint.insertAfter($('#inscr-gestion-refund-panel'));
+ }
+ $cancelBtn.addClass('d-none');
+ }
+ }
+
+ $(document).on('click', '.btn_refund_toggle', function () {
+ var $btn = $(this);
+ var $panel = $($btn.attr('data-refund-target') || '#inscr-gestion-refund-panel');
+ if (!$panel.length) { return; }
+
+ if (!$panel.prop('hidden')) {
+ $panel.prop('hidden', true);
+ $btn.attr('aria-expanded', 'false');
+ return;
+ }
+
+ $panel.prop('hidden', false);
+ $btn.attr('aria-expanded', 'true');
+
+ function focusRefundPanel() {
+ if ($panel[0] && $panel[0].scrollIntoView) {
+ $panel[0].scrollIntoView({ behavior: 'smooth', block: 'start' });
+ }
+ var $amount = $panel.find('#pp_ref_amount');
+ if ($amount.length && !$amount.prop('disabled')) {
+ $amount.trigger('focus').trigger('select');
+ }
+ }
+
+ if ($panel.attr('data-loaded') === '1') { focusRefundPanel(); return; }
+
+ $panel.html(ms1LoaderInlineHtml());
+ $.post((cfg.domain || '') + '/ajax_inscr_gestion.php', {
+ a: 'refund_form',
+ no_panier: $btn.data('no_panier'),
+ eve_id: $btn.data('eve_id'),
+ lang: cfg.lang || 'fr'
+ }, function (result) {
+ if (result && result.state === 'ok' && result.html) {
+ $panel.html(result.html);
+ $panel.attr('data-loaded', '1');
+ focusRefundPanel();
+ } else {
+ $panel.html('\u26a0\ufe0f ' + ((result && result.message) || 'Erreur de chargement.') + '
');
+ }
+ }, 'json').fail(function () {
+ $panel.html('\u26a0\ufe0f Erreur reseau.
');
+ });
+ });
+
+ $(document).on('click', '.btn_transaction_toggle', function () {
+ var $btn = $(this);
+ var $panel = $($btn.attr('data-transaction-target') || '#inscr-gestion-transaction-panel');
+ if (!$panel.length) { return; }
+
+ var strShow = $btn.data('label-show');
+ var strHide = $btn.data('label-hide');
+
+ if (!$panel.prop('hidden')) {
+ $panel.prop('hidden', true);
+ $btn.attr('aria-expanded', 'false');
+ if (strShow) { $btn.text(strShow); }
+ return;
+ }
+
+ $panel.prop('hidden', false);
+ $btn.attr('aria-expanded', 'true');
+ if (strHide) { $btn.text(strHide); }
+
+ if ($panel.attr('data-loaded') === '1') { return; }
+
+ $panel.html(ms1LoaderInlineHtml());
+ $.post((cfg.domain || '') + '/ajax_inscr_gestion.php', {
+ a: 'transaction_resume',
+ no_panier: $btn.data('no_panier'),
+ lang: cfg.lang || 'fr'
+ }, function (result) {
+ if (result && result.state === 'ok' && result.html) {
+ $panel.html(result.html);
+ $panel.attr('data-loaded', '1');
+ } else {
+ $panel.html('\u26a0\ufe0f ' + ((result && result.message) || 'Erreur de chargement.') + '
');
+ }
+ }, 'json').fail(function () {
+ $panel.html('\u26a0\ufe0f Erreur reseau.
');
+ });
+ });
+
+ $(document).on('submit', '#refund-form', function (e) {
+ e.preventDefault();
+ var form = this;
+ var url = form.getAttribute('data-refund-url');
+ var $msg = $('#refund-msg');
+
+ function showMsg(text, ok) {
+ $msg.css({
+ display: 'block',
+ background: ok ? '#e9f8ef' : '#feecec',
+ border: ok ? '1px solid #b8e3c5' : '1px solid #f2b8b5'
+ }).text(text);
+ }
+
+ showMsg('\u23f3 Traitement en cours...', false);
+ if (window.Ms1Loader) {
+ Ms1Loader.show(cfg.preloaderWait || undefined);
+ }
+
+ fetch(url, { method: 'POST', body: new FormData(form) })
+ .then(function (res) {
+ if (!res.ok) { throw new Error('network'); }
+ return res.json();
+ })
+ .then(function (json) {
+ if (window.Ms1Loader) {
+ Ms1Loader.hide();
+ }
+ if (json && json.success) {
+ showMsg('\u2705 ' + (json.message || 'Remboursement effectue.'), true);
+ onRefundSuccess(form, json);
+ } else {
+ showMsg('\u26a0\ufe0f ' + ((json && json.message) || 'Erreur remboursement.'), false);
+ }
+ })
+ .catch(function () {
+ if (window.Ms1Loader) {
+ Ms1Loader.hide();
+ }
+ showMsg('\u26a0\ufe0f Erreur reseau pendant le remboursement.', false);
+ });
+ });
+ })();
+ });
+ }
+ (function () {
+ var qrPanel = document.getElementById('inscr-gestion-qr-panel');
+ if (!qrPanel) {
+ return;
+ }
+
+ var qrReaderEl = document.getElementById('inscr-gestion-qr-reader');
+ var qrFeedback = document.getElementById('inscr-gestion-qr-feedback');
+ var qrCamera = null;
+ var qrStarting = false;
+ var lastQrRaw = '';
+ var qrResolvePending = false;
+ var qrLibPromise = null;
+
+ function loadHtml5Qrcode() {
+ if (window.Html5Qrcode) {
+ return Promise.resolve();
+ }
+ if (qrLibPromise) {
+ return qrLibPromise;
+ }
+ qrLibPromise = new Promise(function (resolve, reject) {
+ var script = document.createElement('script');
+ script.src = 'https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js';
+ script.onload = function () { resolve(); };
+ script.onerror = function () {
+ qrLibPromise = null;
+ reject(new Error('load'));
+ };
+ document.head.appendChild(script);
+ });
+ return qrLibPromise;
+ }
+
+ function showQrFeedback(type, message) {
+ if (!qrFeedback) {
+ return;
+ }
+ qrFeedback.className = 'alert mb-2 alert-' + type;
+ qrFeedback.textContent = message;
+ qrFeedback.classList.remove('d-none');
+ }
+
+ function hideQrFeedback() {
+ if (!qrFeedback) {
+ return;
+ }
+ qrFeedback.classList.add('d-none');
+ qrFeedback.textContent = '';
+ }
+
+ function stopQrScanner() {
+ if (!qrCamera) {
+ return Promise.resolve();
+ }
+ var instance = qrCamera;
+ qrCamera = null;
+ return instance.stop().catch(function () {
+ return null;
+ }).then(function () {
+ try {
+ instance.clear();
+ } catch (e) {
+ /* ignore */
+ }
+ });
+ }
+
+ function startQrScanner() {
+ if (qrStarting || qrCamera || !qrReaderEl || !qrPanel.open) {
+ return;
+ }
+ qrStarting = true;
+ hideQrFeedback();
+ lastQrRaw = '';
+
+ loadHtml5Qrcode().then(function () {
+ if (!qrPanel.open) {
+ qrStarting = false;
+ return;
+ }
+ var config = window.Html5QrcodeSupportedFormats
+ ? {formatsToSupport: [Html5QrcodeSupportedFormats.QR_CODE]}
+ : undefined;
+ qrCamera = new Html5Qrcode('inscr-gestion-qr-reader', config);
+ return qrCamera.start(
+ {facingMode: 'environment'},
+ {fps: 8, qrbox: {width: 220, height: 220}},
+ onQrDecoded
+ );
+ }).catch(function () {
+ showQrFeedback('danger', qrPanel.getAttribute('data-qr-msg-camera') || '');
+ }).then(function () {
+ qrStarting = false;
+ });
+ }
+
+ function onQrDecoded(decodedText) {
+ if (!decodedText || decodedText === lastQrRaw || qrResolvePending) {
+ return;
+ }
+ lastQrRaw = decodedText;
+ qrResolvePending = true;
+
+ var resolveUrl = qrPanel.getAttribute('data-qr-resolve-url');
+ var eveId = qrPanel.getAttribute('data-qr-eve-id');
+ var lang = qrPanel.getAttribute('data-qr-lang') || 'fr';
+ if (!resolveUrl || !eveId) {
+ qrResolvePending = false;
+ return;
+ }
+
+ var body = new URLSearchParams();
+ body.set('qr', decodedText);
+ body.set('promoteur_eve_id', eveId);
+ body.set('lang', lang);
+
+ fetch(resolveUrl, {
+ method: 'POST',
+ headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
+ body: body.toString(),
+ credentials: 'same-origin'
+ }).then(function (response) {
+ return response.json();
+ }).then(function (data) {
+ qrResolvePending = false;
+ if (!data || !data.state) {
+ showQrFeedback('danger', qrPanel.getAttribute('data-qr-msg-invalid') || '');
+ window.setTimeout(function () { lastQrRaw = ''; }, 2000);
+ return;
+ }
+ if (data.state === 'success' && data.pec_id) {
+ stopQrScanner().then(function () {
+ var url = new URL(window.location.href);
+ url.searchParams.set('pec_id', String(data.pec_id));
+ url.searchParams.set('from_qr', '1');
+ window.location.href = url.toString();
+ });
+ return;
+ }
+ if (data.state === 'wrong_event') {
+ var wrongTpl = qrPanel.getAttribute('data-qr-msg-wrong') || '';
+ showQrFeedback('warning', data.message || wrongTpl.replace('%s', data.eve_nom || ''));
+ window.setTimeout(function () { lastQrRaw = ''; }, 2500);
+ return;
+ }
+ showQrFeedback('danger', data.message || qrPanel.getAttribute('data-qr-msg-invalid') || '');
+ window.setTimeout(function () { lastQrRaw = ''; }, 2000);
+ }).catch(function () {
+ qrResolvePending = false;
+ showQrFeedback('danger', qrPanel.getAttribute('data-qr-msg-invalid') || '');
+ window.setTimeout(function () { lastQrRaw = ''; }, 2000);
+ });
+ }
+
+ qrPanel.addEventListener('toggle', function () {
+ if (qrPanel.open) {
+ startQrScanner();
+ } else {
+ stopQrScanner();
+ hideQrFeedback();
+ lastQrRaw = '';
+ qrResolvePending = false;
+ }
+ });
+ })();
+
+
+ (function () {
+ var BIB_MAX_LEN = 6;
+ var OCR_MAX_WIDTH = 320;
+ var tesseractPromise = null;
+ var globalOcrWorker = null;
+ var globalOcrWorkerPromise = null;
+ var activeScan = null;
+
+ function loadTesseract() {
+ if (window.Tesseract) {
+ return Promise.resolve();
+ }
+ if (tesseractPromise) {
+ return tesseractPromise;
+ }
+ tesseractPromise = new Promise(function (resolve, reject) {
+ var script = document.createElement('script');
+ script.src = 'https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js';
+ script.onload = function () { resolve(); };
+ script.onerror = function () {
+ tesseractPromise = null;
+ reject(new Error('load'));
+ };
+ document.head.appendChild(script);
+ });
+ return tesseractPromise;
+ }
+
+ function getGlobalOcrWorker() {
+ if (globalOcrWorker) {
+ return Promise.resolve(globalOcrWorker);
+ }
+ if (globalOcrWorkerPromise) {
+ return globalOcrWorkerPromise;
+ }
+ globalOcrWorkerPromise = loadTesseract().then(function () {
+ return Tesseract.createWorker('eng');
+ }).then(function (worker) {
+ globalOcrWorker = worker;
+ var params = {tessedit_char_whitelist: '0123456789'};
+ if (window.Tesseract && Tesseract.PSM && Tesseract.PSM.SINGLE_LINE) {
+ params.tessedit_pageseg_mode = Tesseract.PSM.SINGLE_LINE;
+ }
+ return worker.setParameters(params).then(function () {
+ return globalOcrWorker;
+ });
+ }).catch(function (err) {
+ globalOcrWorkerPromise = null;
+ throw err;
+ });
+ return globalOcrWorkerPromise;
+ }
+
+ function preloadOcrWorker() {
+ return getGlobalOcrWorker().catch(function () {
+ /* ignore preload errors */
+ });
+ }
+
+ function extractBibNumber(raw) {
+ var matches = String(raw || '').match(/\d+/g);
+ if (!matches || !matches.length) {
+ return '';
+ }
+ if (matches.length > 1) {
+ return '';
+ }
+ var num = matches[0];
+ if (num.length < 1 || num.length > BIB_MAX_LEN) {
+ return '';
+ }
+ return num;
+ }
+
+ function captureCenterFrame(videoEl) {
+ if (!videoEl || !videoEl.videoWidth) {
+ return null;
+ }
+ var w = videoEl.videoWidth;
+ var h = videoEl.videoHeight;
+ var cropW = Math.round(w * 0.55);
+ var cropH = Math.round(h * 0.3);
+ var sx = Math.round((w - cropW) / 2);
+ var sy = Math.round((h - cropH) / 2);
+ var canvas = document.createElement('canvas');
+ var outW = cropW;
+ var outH = cropH;
+ if (cropW > OCR_MAX_WIDTH) {
+ var scale = OCR_MAX_WIDTH / cropW;
+ outW = OCR_MAX_WIDTH;
+ outH = Math.max(1, Math.round(cropH * scale));
+ }
+ canvas.width = outW;
+ canvas.height = outH;
+ canvas.getContext('2d').drawImage(videoEl, sx, sy, cropW, cropH, 0, 0, outW, outH);
+ return canvas;
+ }
+
+ function BibScanSession(block) {
+ this.block = block;
+ this.panel = block.querySelector('.inscr-gestion-bib-scan-panel');
+ this.toggleBtn = block.querySelector('.inscr-gestion-bib-scan-toggle');
+ this.statusEl = this.panel ? this.panel.querySelector('.inscr-gestion-bib-scan-status') : null;
+ this.captureEl = this.panel ? this.panel.querySelector('.inscr-gestion-bib-scan-capture') : null;
+ this.videoWrap = this.captureEl ? this.captureEl.querySelector('.inscr-gestion-bib-scan-video') : null;
+ this.captureLabel = this.captureEl ? this.captureEl.querySelector('.inscr-gestion-bib-scan-capture-label') : null;
+ this.inputId = this.panel ? this.panel.getAttribute('data-bib-input-id') : '';
+ this.stream = null;
+ this.video = null;
+ this.ocrBusy = false;
+ this.workerReady = false;
+ this.onCaptureClick = null;
+ }
+
+ BibScanSession.prototype.getInput = function () {
+ return this.inputId ? document.getElementById(this.inputId) : null;
+ };
+
+ BibScanSession.prototype.setStatus = function (text, active) {
+ if (!this.statusEl) {
+ return;
+ }
+ this.statusEl.textContent = text || '';
+ this.statusEl.classList.toggle('is-active', !!active);
+ };
+
+ BibScanSession.prototype.setCaptureReady = function (ready) {
+ if (this.captureEl) {
+ this.captureEl.classList.toggle('is-ready', !!ready);
+ this.captureEl.setAttribute('aria-disabled', ready ? 'false' : 'true');
+ }
+ };
+
+ // Detecte une clef i18n brute (label absent de la table info) quel que
+ // soit le prefixe (inscr_gestion_, ancien inscr_mobile_, etc.) : une vraie
+ // traduction contient des espaces/accents, jamais ce motif "cle_technique".
+ function isRawI18nKey(str) {
+ return typeof str === 'string' && /^[a-z][a-z0-9_]*$/.test(str) && str.indexOf('_') !== -1;
+ }
+
+ // Renvoie la valeur de l'attribut si elle est traduite, sinon le texte
+ // integre (bilingue) — evite d'afficher une clef brute a l'usager.
+ function scanMsg(panel, attr, frText, enText) {
+ var value = panel ? panel.getAttribute(attr) : '';
+ if (value && !isRawI18nKey(value)) {
+ return value;
+ }
+ return (document.documentElement.lang === 'en') ? enText : frText;
+ }
+
+ BibScanSession.prototype.showReady = function () {
+ this.setStatus(scanMsg(this.panel, 'data-msg-ready', 'Cadrez le numéro, puis touchez l\'image', 'Frame the number, then tap the image'), false);
+ this.setCaptureReady(true);
+ if (this.captureLabel) {
+ this.captureLabel.hidden = false;
+ }
+ };
+
+ BibScanSession.prototype.unbindCapture = function () {
+ if (this.captureEl && this.onCaptureClick) {
+ this.captureEl.removeEventListener('click', this.onCaptureClick);
+ this.captureEl.removeEventListener('keydown', this.onCaptureKey);
+ }
+ this.onCaptureClick = null;
+ this.onCaptureKey = null;
+ };
+
+ BibScanSession.prototype.bindCapture = function () {
+ var self = this;
+ self.unbindCapture();
+ if (!self.captureEl) {
+ return;
+ }
+ self.onCaptureClick = function () {
+ if (!self.workerReady || self.ocrBusy || activeScan !== self) {
+ return;
+ }
+ self.runOcr();
+ };
+ self.onCaptureKey = function (e) {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ self.onCaptureClick();
+ }
+ };
+ self.captureEl.addEventListener('click', self.onCaptureClick);
+ self.captureEl.addEventListener('keydown', self.onCaptureKey);
+ };
+
+ BibScanSession.prototype.stop = function () {
+ this.unbindCapture();
+ if (this.stream) {
+ this.stream.getTracks().forEach(function (track) { track.stop(); });
+ this.stream = null;
+ }
+ if (this.video) {
+ this.video.srcObject = null;
+ this.video = null;
+ }
+ if (this.videoWrap) {
+ this.videoWrap.innerHTML = '';
+ }
+ this.ocrBusy = false;
+ this.workerReady = false;
+ this.setCaptureReady(false);
+ this.setStatus('');
+ if (this.panel) {
+ this.panel.hidden = true;
+ }
+ if (this.toggleBtn) {
+ this.toggleBtn.setAttribute('aria-expanded', 'false');
+ this.toggleBtn.classList.remove('is-active');
+ }
+ if (activeScan === this) {
+ activeScan = null;
+ }
+ };
+
+ BibScanSession.prototype.isSearchContext = function () {
+ var form = this.block ? this.block.closest('form.inscr-gestion-form') : null;
+ return !!(form && !form.closest('.inscr-gestion-fiche'));
+ };
+
+ BibScanSession.prototype.submitSearchForm = function (form) {
+ if (!form) {
+ return;
+ }
+ var submitBtn = form.querySelector('button[type="submit"][name="btn_recherche"], input[type="submit"][name="btn_recherche"]');
+ if (submitBtn) {
+ submitBtn.click();
+ return;
+ }
+ var hidden = document.createElement('input');
+ hidden.type = 'hidden';
+ hidden.name = 'btn_recherche';
+ hidden.value = '1';
+ form.appendChild(hidden);
+ form.submit();
+ };
+
+ BibScanSession.prototype.applyNumber = function (strNum) {
+ var input = this.getInput();
+ if (!input) {
+ return;
+ }
+ input.value = strNum;
+ input.classList.add('inscr-gestion-bib--proposed');
+ var blnSearch = this.isSearchContext();
+ if (blnSearch) {
+ var tplSearch = scanMsg(this.panel, 'data-msg-found-search', 'Numéro %s détecté — recherche...', 'Number %s detected — searching...');
+ this.setStatus(tplSearch.replace('%s', strNum), true);
+ this.stop();
+ var form = this.block.closest('form.inscr-gestion-form');
+ window.setTimeout(function () {
+ BibScanSession.prototype.submitSearchForm(form);
+ }, 350);
+ return;
+ }
+ input.focus();
+ var tpl = scanMsg(this.panel, 'data-msg-found', 'Numéro %s détecté — validez avec OK.', 'Number %s detected — press OK.');
+ this.setStatus(tpl.replace('%s', strNum), false);
+ this.stop();
+ };
+
+ BibScanSession.prototype.runOcr = function () {
+ var self = this;
+ if (self.ocrBusy || !self.video || activeScan !== self || !self.workerReady) {
+ return;
+ }
+ var canvas = captureCenterFrame(self.video);
+ if (!canvas) {
+ return;
+ }
+ self.ocrBusy = true;
+ self.setCaptureReady(false);
+ if (self.captureLabel) {
+ self.captureLabel.hidden = true;
+ }
+ self.setStatus(scanMsg(self.panel, 'data-msg-scanning', 'Lecture en cours...', 'Reading...'), true);
+ getGlobalOcrWorker().then(function (worker) {
+ return worker.recognize(canvas);
+ }).then(function (result) {
+ var rawText = result && result.data ? String(result.data.text || '') : '';
+ var strNum = extractBibNumber(rawText);
+ if (strNum) {
+ self.applyNumber(strNum);
+ return;
+ }
+ self.setStatus(scanMsg(self.panel, 'data-msg-invalid', 'Numéro illisible. Cadrez un seul numéro.', 'Unreadable number. Frame a single number.'), false);
+ window.setTimeout(function () {
+ if (activeScan === self) {
+ self.showReady();
+ }
+ }, 1800);
+ }).catch(function () {
+ self.setStatus(scanMsg(self.panel, 'data-msg-invalid', 'Numéro illisible. Cadrez un seul numéro.', 'Unreadable number. Frame a single number.'), false);
+ window.setTimeout(function () {
+ if (activeScan === self) {
+ self.showReady();
+ }
+ }, 1800);
+ }).then(function () {
+ self.ocrBusy = false;
+ });
+ };
+
+ BibScanSession.prototype.start = function () {
+ var self = this;
+ if (!self.panel || !navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
+ self.setStatus(scanMsg(self.panel, 'data-msg-camera', 'Impossible d\'accéder à la caméra.', 'Unable to access the camera.'), false);
+ return;
+ }
+ if (activeScan && activeScan !== self) {
+ activeScan.stop();
+ }
+ activeScan = self;
+ self.panel.hidden = false;
+ if (self.toggleBtn) {
+ self.toggleBtn.setAttribute('aria-expanded', 'true');
+ self.toggleBtn.classList.add('is-active');
+ }
+ self.setStatus(scanMsg(self.panel, 'data-msg-loading', 'Initialisation (1re fois)...', 'Initializing (first time)...'), true);
+
+ var cameraPromise = navigator.mediaDevices.getUserMedia({
+ video: {facingMode: 'environment'},
+ audio: false
+ });
+ var workerPromise = getGlobalOcrWorker();
+
+ Promise.all([cameraPromise, workerPromise]).then(function (results) {
+ if (activeScan !== self) {
+ results[0].getTracks().forEach(function (track) { track.stop(); });
+ return;
+ }
+ self.workerReady = true;
+ self.stream = results[0];
+ self.video = document.createElement('video');
+ self.video.setAttribute('playsinline', 'true');
+ self.video.setAttribute('autoplay', 'true');
+ self.video.muted = true;
+ self.video.srcObject = self.stream;
+ self.videoWrap.innerHTML = '';
+ self.videoWrap.appendChild(self.video);
+ return self.video.play();
+ }).then(function () {
+ if (activeScan !== self || !self.workerReady) {
+ return;
+ }
+ self.bindCapture();
+ self.showReady();
+ }).catch(function () {
+ self.setStatus(scanMsg(self.panel, 'data-msg-camera', 'Impossible d\'accéder à la caméra.', 'Unable to access the camera.'), false);
+ self.stop();
+ });
+ };
+
+ if (document.querySelector('.inscr-gestion-bib-scan-toggle')) {
+ preloadOcrWorker();
+ }
+
+ document.querySelectorAll('.inscr-gestion-field-block[data-inscr-field="bib"]').forEach(function (block) {
+ var toggleBtn = block.querySelector('.inscr-gestion-bib-scan-toggle');
+ var panel = block.querySelector('.inscr-gestion-bib-scan-panel');
+ if (!toggleBtn || !panel) {
+ return;
+ }
+ var session = new BibScanSession(block);
+ toggleBtn.addEventListener('click', function () {
+ if (activeScan === session && !panel.hidden) {
+ session.stop();
+ return;
+ }
+ session.start();
+ });
+ var input = session.getInput();
+ if (input) {
+ input.addEventListener('input', function () {
+ input.classList.remove('inscr-gestion-bib--proposed');
+ });
+ }
+ });
+
+ var ficheFromQr = document.querySelector('.inscr-gestion-fiche[data-from-qr="1"]');
+ if (ficheFromQr) {
+ var editableSection = ficheFromQr.querySelector('.inscr-gestion-fiche-section--editable');
+ if (editableSection) {
+ window.setTimeout(function () {
+ editableSection.scrollIntoView({behavior: 'smooth', block: 'start'});
+ }, 80);
+ }
+ }
+ })();
+
+
+ (function () {
+ var moduleInscr = document.getElementById('module-inscr-gestion');
+ if (!moduleInscr) {
+ return;
+ }
+
+ var params = new URLSearchParams(window.location.search);
+ if (params.get('pec_id')) {
+ moduleInscr.scrollIntoView({block: 'start'});
+ } else if (params.get('pg') && parseInt(params.get('pg'), 10) > 1) {
+ var listAnchor = document.getElementById('inscr-gestion-list');
+ if (listAnchor) {
+ listAnchor.scrollIntoView({block: 'start'});
+ }
+ }
+ moduleInscr.addEventListener('click', function (e) {
+ var btnToggle = e.target.closest('.epr-block-toggle');
+ if (!btnToggle || !moduleInscr.contains(btnToggle)) {
+ return;
+ }
+ var block = btnToggle.closest('.epr-block');
+ if (!block) {
+ return;
+ }
+ var collapsed = block.classList.toggle('is-collapsed');
+ btnToggle.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
+ var label = collapsed
+ ? (btnToggle.getAttribute('data-label-expand') || '')
+ : (btnToggle.getAttribute('data-label-collapse') || '');
+ if (label) {
+ btnToggle.setAttribute('aria-label', label);
+ btnToggle.setAttribute('title', label);
+ }
+ var icon = btnToggle.querySelector('.fa');
+ if (icon) {
+ icon.classList.toggle('fa-chevron-up', !collapsed);
+ icon.classList.toggle('fa-chevron-down', collapsed);
+ }
+ });
+
+ var inscrLoaderMsg = cfg.preloaderWait || '';
+
+ moduleInscr.addEventListener('click', function (e) {
+ var factureLink = e.target.closest('a[href*="facture.php"]');
+ if (!factureLink || !moduleInscr.contains(factureLink)) {
+ return;
+ }
+ if (window.Ms1Loader) {
+ Ms1Loader.show(inscrLoaderMsg);
+ window.setTimeout(function () {
+ Ms1Loader.hide();
+ }, 3000);
+ }
+ });
+
+ moduleInscr.addEventListener('submit', function (e) {
+ var form = e.target;
+ if (!form || !moduleInscr.contains(form)) {
+ return;
+ }
+ if (!form.classList.contains('inscr-gestion-form')) {
+ return;
+ }
+ if (window.Ms1Loader) {
+ Ms1Loader.show(inscrLoaderMsg);
+ }
+ });
+ })();
+
+})();
diff --git a/mobile.php b/mobile.php
index 23b16ed..9961151 100644
--- a/mobile.php
+++ b/mobile.php
@@ -8,6 +8,7 @@ require_once('php/inc_fx_messages.php');
require_once('php/inc_fx_compte.php');
require_once('php/inc_fx_promoteur.php');
require_once('php/inc_fx_inscriptions_gestion.php');
+require_once('php/inc_v2_assets.php');
global $objDatabase, $vDomaine, $vPage, $vtexte_page;
@@ -18,6 +19,7 @@ if (!empty($_GET['lang'])) {
}
fxInscrGestionInitTextes($strLangue);
+fxV2RegisterScript('inscr-gestion');
$vPage = 'mobile.php';
$strCode = 'mobile';
diff --git a/php/inc_settings.php b/php/inc_settings.php
index 34350cd..b34c344 100644
--- a/php/inc_settings.php
+++ b/php/inc_settings.php
@@ -7,7 +7,7 @@
* Constantes *
*
**************/
-define('_VERSION_CODE', '4.72.733');
+define('_VERSION_CODE', '4.72.734');
define('_DATE_CODE', '2026-07-07');
//MSIN-4290
define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe');
diff --git a/php/inc_v2_assets.php b/php/inc_v2_assets.php
new file mode 100644
index 0000000..f534ec5
--- /dev/null
+++ b/php/inc_v2_assets.php
@@ -0,0 +1,72 @@
+ $vDomaine,
+ 'lang' => $strLangue,
+ 'preloaderWait' => afficheTexte('preloader_wait', 0, 0, 1),
+ 'refundCancelHint' => ($strLangue === 'en')
+ ? 'Your transaction has been refunded. If you also want to cancel it in the registration system:'
+ : "Votre transaction a été remboursée. Si vous voulez aussi l'annuler dans le système d'inscription :",
+ 'loginMessages' => [
+ 'txt_login' => ($strLangue === 'fr')
+ ? "Veuillez entrer votre nom d'usager"
+ : 'Please enter your username',
+ 'txt_password' => ($strLangue === 'fr')
+ ? 'Veuillez entrer votre mot de passe'
+ : 'Please enter your password',
+ ],
+ ];
+ }
+
+ /** À appeler dans inc_footer.php après inc_footer_scripts.php */
+ function fxV2RenderFooterScripts() {
+ global $MS1_V2_SCRIPTS, $vDomaine, $strLangue;
+
+ if (empty($MS1_V2_SCRIPTS) || !is_array($MS1_V2_SCRIPTS)) {
+ return;
+ }
+
+ $mapFiles = [
+ 'bib-assignments' => '/js/v2/bib-assignments.js',
+ 'inscr-gestion' => '/js/v2/inscr-gestion.js',
+ ];
+
+ if (in_array('inscr-gestion', $MS1_V2_SCRIPTS, true)) {
+ $cfg = fxV2InscrGestionConfig($strLangue ?? 'fr');
+ echo '' . "\n";
+ }
+
+ foreach ($MS1_V2_SCRIPTS as $strName) {
+ if (!isset($mapFiles[$strName])) {
+ continue;
+ }
+ $strSrc = htmlspecialchars($vDomaine . $mapFiles[$strName], ENT_QUOTES, 'UTF-8');
+ echo '' . "\n";
+ }
+ }
+}