Files
ms1inscription-v5/js/v2/inscr-gestion.js

1065 lines
44 KiB
JavaScript

/**
* 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 || {};
// MSIN-4401 — Session expiree en AJAX : recharger → ecran de login (pas un message mort).
function fxInscrRedirectIfSessionExpired(result) {
if (!result) {
return false;
}
if (result.code === 'session' || result.message === 'session') {
window.location.reload();
return true;
}
return false;
}
function ms1LoaderInlineHtml() {
if (window.Ms1Loader) {
return Ms1Loader.inlineHtml(cfg.preloaderWait || undefined);
}
return '<div class="inscr-gestion-muted" style="padding:10px;">…</div>';
}
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 (fxInscrRedirectIfSessionExpired(result)) {
return;
}
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 = $('<div id="inscr-gestion-refund-cancel-hint" style="margin-top:8px;padding:10px;border:1px solid #ffe1a6;background:#fff8e6;border-radius:8px;font-size:13px;display:flex;align-items:center;flex-wrap:wrap;gap:10px;"></div>');
$hint.append($('<span></span>').text(refundCancelHint));
$hint.append(
$('<button type="button" class="btn btn-danger btn-sm rounded-0"></button>')
.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 (fxInscrRedirectIfSessionExpired(result)) {
return;
}
if (result && result.state === 'ok' && result.html) {
$panel.html(result.html);
$panel.attr('data-loaded', '1');
focusRefundPanel();
} else {
$panel.html('<div style="background:#feecec;border:1px solid #f2b8b5;padding:10px;border-radius:8px;">\u26a0\ufe0f ' + ((result && result.message) || 'Erreur de chargement.') + '</div>');
}
}, 'json').fail(function () {
$panel.html('<div style="background:#feecec;border:1px solid #f2b8b5;padding:10px;border-radius:8px;">\u26a0\ufe0f Erreur reseau.</div>');
});
});
$(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 (fxInscrRedirectIfSessionExpired(result)) {
return;
}
if (result && result.state === 'ok' && result.html) {
$panel.html(result.html);
$panel.attr('data-loaded', '1');
} else {
$panel.html('<div style="background:#feecec;border:1px solid #f2b8b5;padding:10px;border-radius:8px;">\u26a0\ufe0f ' + ((result && result.message) || 'Erreur de chargement.') + '</div>');
}
}, 'json').fail(function () {
$panel.html('<div style="background:#feecec;border:1px solid #f2b8b5;padding:10px;border-radius:8px;">\u26a0\ufe0f Erreur reseau.</div>');
});
});
$(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 (fxInscrRedirectIfSessionExpired(data)) {
return;
}
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 = 640;
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;
// Chiffres seulement + bloc (dossard type "1020", pas une ligne unique fragile).
var params = {tessedit_char_whitelist: '0123456789'};
if (window.Tesseract && Tesseract.PSM && Tesseract.PSM.SINGLE_BLOCK) {
params.tessedit_pageseg_mode = Tesseract.PSM.SINGLE_BLOCK;
}
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 */
});
}
// MSIN-4401 — Extraire UN numero de dossard :
// - manuscrit "5 2 1" → coller les chiffres isoles → 521
// - dossard "1 KM" + "1020" → garder le plus long groupe → 1020 (pas 11020)
function extractBibNumber(raw) {
var matches = String(raw || '').match(/\d+/g);
if (!matches || !matches.length) {
return '';
}
var i;
var allSingles = true;
for (i = 0; i < matches.length; i++) {
if (matches[i].length !== 1) {
allSingles = false;
break;
}
}
if (allSingles) {
var joined = matches.join('');
return (joined.length >= 1 && joined.length <= BIB_MAX_LEN) ? joined : '';
}
var best = '';
for (i = 0; i < matches.length; i++) {
if (matches[i].length <= BIB_MAX_LEN && matches[i].length > best.length) {
best = matches[i];
}
}
return best;
}
// Zone video visible avec object-fit:cover (alignee sur le carre a l'ecran).
function getCoverVisibleRect(videoEl, displayEl) {
var vw = videoEl.videoWidth;
var vh = videoEl.videoHeight;
var dw = displayEl.clientWidth || 1;
var dh = displayEl.clientHeight || 1;
var videoRatio = vw / vh;
var displayRatio = dw / dh;
var renderedW;
var renderedH;
var offsetX;
var offsetY;
if (videoRatio > displayRatio) {
renderedH = vh;
renderedW = vh * displayRatio;
offsetX = (vw - renderedW) / 2;
offsetY = 0;
} else {
renderedW = vw;
renderedH = vw / displayRatio;
offsetX = 0;
offsetY = (vh - renderedH) / 2;
}
return {
offsetX: offsetX,
offsetY: offsetY,
renderedW: renderedW,
renderedH: renderedH
};
}
// Renforce le contraste pour l'OCR (dossard blanc sur fond photo).
function preprocessOcrCanvas(srcCanvas) {
var w = srcCanvas.width;
var h = srcCanvas.height;
var scale = (w < 280) ? 2 : 1;
var out = document.createElement('canvas');
out.width = Math.max(1, Math.round(w * scale));
out.height = Math.max(1, Math.round(h * scale));
var ctx = out.getContext('2d');
ctx.imageSmoothingEnabled = true;
ctx.drawImage(srcCanvas, 0, 0, out.width, out.height);
var img = ctx.getImageData(0, 0, out.width, out.height);
var d = img.data;
var j;
for (j = 0; j < d.length; j += 4) {
var gray = (0.299 * d[j] + 0.587 * d[j + 1] + 0.114 * d[j + 2]);
// Contraste fort : pousse vers noir/blanc (chiffres blancs sur fond sombre).
gray = (gray - 128) * 1.6 + 128;
if (gray < 0) { gray = 0; }
if (gray > 255) { gray = 255; }
d[j] = d[j + 1] = d[j + 2] = gray;
}
ctx.putImageData(img, 0, 0);
return out;
}
// Crop = cadre blanc (~55% x 30%) sur la zone VISIBLE (cover), pas le flux brut.
function captureCenterFrame(videoEl, displayEl) {
if (!videoEl || !videoEl.videoWidth) {
return null;
}
var cropRatioW = 0.55;
var cropRatioH = 0.3;
var sx;
var sy;
var cropW;
var cropH;
if (displayEl && displayEl.clientWidth && displayEl.clientHeight) {
var vis = getCoverVisibleRect(videoEl, displayEl);
cropW = Math.max(1, Math.round(vis.renderedW * cropRatioW));
cropH = Math.max(1, Math.round(vis.renderedH * cropRatioH));
sx = Math.round(vis.offsetX + (vis.renderedW - cropW) / 2);
sy = Math.round(vis.offsetY + (vis.renderedH - cropH) / 2);
} else {
var w = videoEl.videoWidth;
var h = videoEl.videoHeight;
cropW = Math.round(w * cropRatioW);
cropH = Math.round(h * cropRatioH);
sx = Math.round((w - cropW) / 2);
sy = Math.round((h - cropH) / 2);
}
var canvas = document.createElement('canvas');
var outW = cropW;
var outH = cropH;
if (cropW > OCR_MAX_WIDTH) {
var scaleDown = OCR_MAX_WIDTH / cropW;
outW = OCR_MAX_WIDTH;
outH = Math.max(1, Math.round(cropH * scaleDown));
}
canvas.width = outW;
canvas.height = outH;
canvas.getContext('2d').drawImage(videoEl, sx, sy, cropW, cropH, 0, 0, outW, outH);
return preprocessOcrCanvas(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.ocrBusy || activeScan !== self) {
return;
}
if (!self.workerReady || !self.video) {
self.setStatus(scanMsg(self.panel, 'data-msg-loading', 'Initialisation (1re fois)...', 'Initializing (first time)...'), true);
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, self.captureEl);
if (!canvas) {
self.setStatus(scanMsg(self.panel, 'data-msg-camera', 'Impossible d\'accéder à la caméra.', 'Unable to access the camera.'), false);
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;
}
// Montre ce que l'OCR a vu (chiffres) pour diagnostiquer sans outil.
var seen = String(rawText || '').replace(/\D/g, '');
var base = scanMsg(self.panel, 'data-msg-invalid', 'Numéro illisible. Cadrez un seul numéro.', 'Unreadable number. Frame a single number.');
if (seen) {
base += ' (' + seen + ')';
}
self.setStatus(base, false);
window.setTimeout(function () {
if (activeScan === self) {
self.showReady();
}
}, 2200);
}).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();
}
}, 2200);
}).then(function () {
self.ocrBusy = false;
});
};
BibScanSession.prototype.scrollCaptureIntoView = function () {
var el = this.captureEl || this.panel;
if (!el || !el.scrollIntoView) {
return;
}
try {
el.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'nearest'});
} catch (eScroll) {
el.scrollIntoView(true);
}
};
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');
}
// MSIN-4401 — Centrer le viseur dans l'ecran (evite la moitie hors viewport).
self.scrollCaptureIntoView();
window.setTimeout(function () {
if (activeScan === self) {
self.scrollCaptureIntoView();
}
}, 120);
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();
// Apres affichage video : recentrer (layout final).
self.scrollCaptureIntoView();
}).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);
}
});
// MSIN-4401 — Libellé du multi-select statut (Tous / N sélectionnés).
(function () {
function updateStatutMultiSummary(details) {
if (!details) {
return;
}
var textEl = details.querySelector('.inscr-gestion-statut-multi__summary-text');
if (!textEl) {
return;
}
var checked = details.querySelectorAll('input[name="sel_rech_statut[]"]:checked');
var n = checked.length;
var labelAll = details.getAttribute('data-label-all') || '';
var labelN = details.getAttribute('data-label-n') || '%d';
textEl.textContent = (n === 0) ? labelAll : labelN.replace('%d', String(n));
}
moduleInscr.addEventListener('change', function (e) {
var input = e.target;
if (!input || input.name !== 'sel_rech_statut[]') {
return;
}
updateStatutMultiSummary(input.closest('.inscr-gestion-statut-multi'));
});
})();
})();
})();