diff --git a/css/style.css b/css/style.css index 5fe4879..b7a36cc 100644 --- a/css/style.css +++ b/css/style.css @@ -1083,6 +1083,10 @@ ul.ms1-menu-compte li a:hover, ul.ms1-menu-compte li a:active { padding-top:12px; border-top:1px solid #d8e3ef; } +.bib-global-batch--unavailable{ + background:#f8f9fa; + border-style:dashed; +} .bib-global-epr{ margin-bottom:12px; padding:8px 10px; diff --git a/js/v2/bib-assignments.js b/js/v2/bib-assignments.js index 26fe958..ba8d29b 100644 --- a/js/v2/bib-assignments.js +++ b/js/v2/bib-assignments.js @@ -89,6 +89,12 @@ } } + /** MSIN-4436 — Force la fermeture du loader (batch global : évite overlay bloqué). */ + function bibBatchProgressForceHide() { + bibLoaderDepth = 0; + bibBatchProgressHide(); + } + function bibAfterBatchGoSuccess(row, data, isResetMode) { let existing = document.querySelector('.epr-row-view'); if (existing) { @@ -102,9 +108,10 @@ bibAfterBatchGoRefreshOnly(row, isResetMode); } - function bibAfterBatchGoRefreshOnly(row, isResetMode) { + function bibAfterBatchGoRefreshOnly(row, isResetMode, options) { + options = options || {}; if (!row) { - return; + return Promise.resolve(); } if (isResetMode) { @@ -139,7 +146,10 @@ syncBibUiState(row); - bibFetch('/ajax_bib_range.php', { + var useLoader = options.loader !== false; + var skipAnomalies = options.skipAnomalies === true; + + return bibFetch('/ajax_bib_range.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: @@ -147,15 +157,25 @@ + '&epr_id=' + encodeURIComponent(row.dataset.eprId) + '&mode=assign' + bibAjaxLangSuffix() - }) - .then(res => res.json()) - .then(refresh => { + }, { loader: useLoader }) + .then(function (res) { + if (!res.ok) { + throw new Error(bibJs('jsErrGeneric')); + } + return res.json(); + }) + .then(function (refresh) { - if (!refresh.success) return; + if (!refresh.success) { + return; + } - let container = setBibContainerHtml(row, refresh.html); - if (!container) return; - bibFetch('/ajax_bib_range.php', { + let containerRef = setBibContainerHtml(row, refresh.html, { skipAnomalies: skipAnomalies }); + if (!containerRef) { + return; + } + + return bibFetch('/ajax_bib_range.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' @@ -164,11 +184,13 @@ 'action=batch_analysis' + '&epr_id=' + encodeURIComponent(row.dataset.eprId) + bibAjaxLangSuffix() - }) - .then(res => res.json()) - .then(stats => { + }, { loader: false }) + .then(function (res) { return res.json(); }) + .then(function (stats) { - if (!stats.success) return; + if (!stats.success) { + return; + } bibSyncEprBibStats(row, stats.info); syncBibUiState(row); @@ -192,30 +214,50 @@ method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: body - }).then(function (res) { return res.json(); }); + }) + .then(function (res) { + if (!res.ok) { + throw new Error(bibJs('jsErrGeneric')); + } + return res.json(); + }) + .catch(function (err) { + if (err && err.message) { + throw err; + } + throw new Error(bibJs('jsErrGeneric')); + }); } function bibRunBatchApplyChunks(row, selected, ba_id, options) { options = options || {}; var showResult = options.showResult !== false; var manageLoader = options.manageLoader !== false; + var deferUiRefresh = options.deferUiRefresh === true; var onProgress = options.onProgress; var totalInitial = options.totalInitial > 0 ? options.totalInitial : bibRowPendingBibs(row); var batchReset = true; + var chunkGuard = 0; + var maxChunks = Math.max(50, Math.ceil((totalInitial || 1) / BIB_BATCH_CHUNK_SIZE) + 5); if (manageLoader) { bibBatchProgressShow(0, totalInitial > 0 ? totalInitial : 1); } function runChunk() { + chunkGuard++; + if (chunkGuard > maxChunks) { + return Promise.reject(new Error(bibJs('jsErrGeneric'))); + } + 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 || !data.success) { + throw new Error((data && data.message) || bibJs('jsErrGeneric')); } if (data.total_initial) { @@ -233,8 +275,8 @@ if (data.complete) { if (showResult) { bibAfterBatchGoSuccess(row, data, false); - } else { - bibAfterBatchGoRefreshOnly(row, false); + } else if (!deferUiRefresh) { + bibAfterBatchGoRefreshOnly(row, false, { loader: false, skipAnomalies: true }); } return data; } @@ -260,6 +302,25 @@ // MSIN-4436 — Batch global (épreuves solo). var bibGlobalBatchPanel = document.getElementById('bib-global-batch'); + function bibSetGlobalBatchInfo(infoEl, text, state) { + if (!infoEl) { + return; + } + + infoEl.textContent = text || ''; + infoEl.classList.remove('text-muted', 'text-danger', 'text-warning', 'text-success'); + + if (state === 'error') { + infoEl.classList.add('text-danger'); + } else if (state === 'warning') { + infoEl.classList.add('text-warning'); + } else if (state === 'ok') { + infoEl.classList.add('text-success'); + } else { + infoEl.classList.add('text-muted'); + } + } + function bibCollectGlobalBatchPlans() { if (!bibGlobalBatchPanel) { return []; @@ -304,17 +365,25 @@ } if (plans.length === 0) { - infoEl.textContent = bibJs('jsGlobalNoEpr') || bibJs('jsBatchNone'); - return Promise.resolve(); + bibSetGlobalBatchInfo( + infoEl, + bibJs('jsGlobalNoEpr') || bibJs('jsBatchNone'), + 'error' + ); + return Promise.resolve(null); } var hasRange = plans.some(function (p) { return p.ranges.length > 0; }); if (!hasRange) { - infoEl.textContent = bibJs('jsGlobalNoSeq') || bibJs('jsNoSeqSelected'); - return Promise.resolve(); + bibSetGlobalBatchInfo( + infoEl, + bibJs('jsGlobalNoSeq') || bibJs('jsNoSeqSelected'), + 'error' + ); + return Promise.resolve(null); } - infoEl.textContent = bibJs('jsLoaderWait') || '…'; + bibSetGlobalBatchInfo(infoEl, bibJs('jsLoaderWait') || '…', 'normal'); return fetch('/ajax_bib_range.php', { method: 'POST', @@ -329,13 +398,21 @@ .then(function (res) { return res.json(); }) .then(function (data) { if (!data.success) { - infoEl.textContent = data.message || bibJs('jsErrGeneric'); - return; + bibSetGlobalBatchInfo( + infoEl, + data.message || bibJs('jsErrGeneric'), + 'error' + ); + return data; } - infoEl.textContent = data.info || ''; + + var state = (data.stats && data.stats.nb_manque > 0) ? 'warning' : 'normal'; + bibSetGlobalBatchInfo(infoEl, data.info || '', state); + return data; }) .catch(function () { - infoEl.textContent = bibJs('jsErrGeneric'); + bibSetGlobalBatchInfo(infoEl, bibJs('jsErrGeneric'), 'error'); + return null; }); } @@ -369,16 +446,22 @@ + '&plans=' + encodeURIComponent(JSON.stringify(plans)) + bibAjaxLangSuffix() }) - .then(function (res) { return res.json(); }) + .then(function (res) { + if (!res.ok) { + throw new Error(bibJs('jsErrGeneric')); + } + return res.json(); + }) .then(function (analysis) { - if (!analysis.success) { - throw new Error(analysis.message || bibJs('jsErrGeneric')); + if (!analysis || !analysis.success) { + throw new Error((analysis && analysis.message) || bibJs('jsErrGeneric')); } var globalTotal = (analysis.stats && analysis.stats.nb_participants) ? analysis.stats.nb_participants : 1; var completedOffset = 0; + var runResults = []; bibBatchProgressShow(0, globalTotal); @@ -388,7 +471,7 @@ chain = chain.then(function () { var row = moduleBib.querySelector('.epr-row[data-epr-id="' + plan.epr_id + '"]'); if (!row) { - return null; + throw new Error(bibJs('jsErrGeneric')); } var pendingBefore = bibRowPendingBibs(row); @@ -397,11 +480,17 @@ return bibRunBatchApplyChunks(row, plan.ranges, ba_id, { showResult: false, manageLoader: false, + deferUiRefresh: true, totalInitial: pendingBefore, onProgress: function (processed) { bibBatchProgressShow(offset + processed, globalTotal); } - }).then(function () { + }).then(function (data) { + runResults.push({ + epr_id: plan.epr_id, + assigned: data.assigned_total || 0, + total: data.total_initial || pendingBefore + }); completedOffset = offset + pendingBefore; }); }); @@ -409,17 +498,62 @@ return chain.then(function () { bibBatchProgressShow(globalTotal, globalTotal); - if (typeof refreshBibAnomaliesPanel === 'function') { - refreshBibAnomaliesPanel(); - } - return updateGlobalBatchAnalysis(); + + var refreshChain = Promise.resolve(); + runResults.forEach(function (result) { + refreshChain = refreshChain.then(function () { + var row = moduleBib.querySelector('.epr-row[data-epr-id="' + result.epr_id + '"]'); + if (!row) { + return null; + } + return bibAfterBatchGoRefreshOnly(row, false, { + loader: false, + skipAnomalies: true + }); + }); + }); + + return refreshChain.then(function () { + if (typeof refreshBibAnomaliesPanel === 'function') { + refreshBibAnomaliesPanel(); + } + + var infoEl = bibGlobalBatchPanel.querySelector('.bib-global-batch__info'); + var totalAssigned = 0; + var totalExpected = 0; + + runResults.forEach(function (result) { + totalAssigned += result.assigned; + totalExpected += result.total; + }); + + var remaining = Math.max(0, totalExpected - totalAssigned); + + return updateGlobalBatchAnalysis().then(function () { + if (totalAssigned === 0 && totalExpected > 0) { + var msgNone = bibJs('jsAssignNone') || bibJs('jsErrGeneric'); + bibSetGlobalBatchInfo(infoEl, msgNone, 'error'); + alert(msgNone); + } else if (remaining > 0) { + var msgPartial = bibJsFmt('jsAssignPartial', totalAssigned, remaining); + bibSetGlobalBatchInfo(infoEl, msgPartial, 'warning'); + alert(msgPartial); + } else if (totalAssigned > 0) { + bibSetGlobalBatchInfo( + infoEl, + bibJsFmt('jsAssignFull', totalAssigned), + 'ok' + ); + } + }); + }); }); }) .catch(function (err) { alert(err.message || bibJs('jsErrGeneric')); }) .finally(function () { - bibBatchProgressHide(); + bibBatchProgressForceHide(); }); } @@ -714,7 +848,8 @@ }); } - function setBibContainerHtml(row, html) { + function setBibContainerHtml(row, html, opts) { + opts = opts || {}; let container = getBibContainer(row); if (!container) return null; // renderBibRanges renvoie un bloc .epr-table.bib-container complet. @@ -729,7 +864,9 @@ initBibRangeRows(container); } syncBibUiState(row); - refreshBibAnomaliesPanel(); + if (!opts.skipAnomalies) { + refreshBibAnomaliesPanel(); + } return container; } diff --git a/php/inc_fx_promoteur.php b/php/inc_fx_promoteur.php index fe74316..da12254 100644 --- a/php/inc_fx_promoteur.php +++ b/php/inc_fx_promoteur.php @@ -2901,6 +2901,11 @@ function fxBibStaticFallback($clef) { 'bib_v4_anomalies_order_fmt' => ['fr' => 'Commande %s', 'en' => 'Order %s'], 'bib_v4_anomalies_assign_link' => ['fr' => 'Assigner un dossard', 'en' => 'Assign a bib'], 'bib_v4_js_loader_wait' => ['fr' => 'Chargement...', 'en' => 'Loading...'], + 'bib_v4_js_global_no_epr' => ['fr' => 'Sélectionnez au moins une épreuve solo.', 'en' => 'Select at least one solo race.'], + 'bib_v4_js_global_no_seq' => ['fr' => 'Cochez au moins une séquence pour les épreuves sélectionnées.', 'en' => 'Check at least one sequence for the selected races.'], + 'bib_v4_global_batch_plan_invalid' => ['fr' => 'Sélection invalide : choisissez au moins une épreuve solo et des séquences.', 'en' => 'Invalid selection: pick at least one solo race and sequences.'], + 'bib_v4_global_batch_need_two' => ['fr' => 'L''assignation globale nécessite au moins deux épreuves solo avec des séquences disponibles.', 'en' => 'Global assignment requires at least two solo races with available sequences.'], + 'bib_v4_ajax_global_sim_summary' => ['fr' => '%d épreuve(s) · %d dispo / %d à assigner → %d assignables (%d manque)', 'en' => '%d race(s) · %d avail. / %d to assign → %d assignable (%d short)'], ]; return $tab[$clef][$lng] ?? ''; @@ -3109,6 +3114,9 @@ function fxBibModuleJsDataAttrs() { 'batch-progress' => 'bib_v4_js_batch_progress', 'global-no-epr' => 'bib_v4_js_global_no_epr', 'global-no-seq' => 'bib_v4_js_global_no_seq', + 'assign-full' => 'bib_v4_ajax_assign_full', + 'assign-partial' => 'bib_v4_ajax_assign_partial', + 'assign-none' => 'bib_v4_ajax_assign_none', ]; $html = ''; foreach ($map as $attr => $clef) { @@ -3908,7 +3916,19 @@ function renderBibGlobalBatchPanel($int_eve_id, $tabEpreuves, $tabBibAssignments } if (count($tabSolo) < 2) { - return ''; + if (empty($tabSolo)) { + return ''; + } + + ob_start(); + ?> +
+

+ +

+
+