From be450f9d85f24205249cf1e1775f3eccb36333d6 Mon Sep 17 00:00:00 2001 From: solocla Date: Wed, 15 Jul 2026 16:59:27 +0200 Subject: [PATCH 01/22] parts with save button --- public/userarea/modal_partsTable.php | 3 + public/userarea/partsTable.js | 645 ++++++++------------------- 2 files changed, 179 insertions(+), 469 deletions(-) diff --git a/public/userarea/modal_partsTable.php b/public/userarea/modal_partsTable.php index 143623a..75d945c 100644 --- a/public/userarea/modal_partsTable.php +++ b/public/userarea/modal_partsTable.php @@ -29,6 +29,9 @@
Elenco Parti
+ + + `; + fBtn.parentNode.insertBefore(bar, fBtn.nextSibling); + + fBtn.addEventListener("click", toggleFilters); + document + .getElementById("filterDeleteBtn") + .addEventListener("click", batchDelete); + document + .getElementById("filterRestrictBtn") + .addEventListener("click", toggleRestrict); + } + + // ── Event delegation ── + function attachEvents() { + // checkbox click + document.addEventListener("change", function (e) { + if (e.target.classList.contains("filter-row-checkbox")) { + const rowEl = e.target.closest(".grid-row"); + toggleRow(rowEl.dataset.id, e.target.checked); + } + }); + + // Seleziona / deseleziona tutti i FILTRATI visibili + document.addEventListener("change", function (e) { + if (e.target.id !== "filterSelectAll") return; + const on = e.target.checked; + document + .querySelectorAll("#gridRowContainer .grid-row") + .forEach((rowEl) => { + if (rowEl.style.display === "none") return; + const id = parseInt(rowEl.dataset.id, 10); + if (!id) return; + if (on) selected.add(id); + else selected.delete(id); + rowEl.classList.toggle("row-selected", on); + const cb = rowEl.querySelector(".filter-row-checkbox"); + if (cb) cb.checked = on; + }); + updateToolbar(); + }); + + // click su QUALSIASI punto della riga (tranne campi interattivi) = seleziona + document.addEventListener("click", function (e) { + if (!filtersActive) return; + const rowEl = e.target.closest("#gridRowContainer .grid-row"); + if (!rowEl) return; + + // ignora click su elementi interattivi + if ( + e.target.closest( + "input, select, textarea, button, a, .select2-container, .action-btn, .propagate-btn, .add-part-btn", + ) + ) { + // ma se ho cliccato proprio la checkbox, lascia fare al change + return; + } + + const cb = rowEl.querySelector(".filter-row-checkbox"); + if (!cb) return; + cb.checked = !cb.checked; + toggleRow(rowEl.dataset.id, cb.checked); + }); + + // filtri colonna live (debounce) + let t = null; + document.addEventListener("input", function (e) { + if (!e.target.classList.contains("filter-col-input")) return; + const key = e.target.dataset.colKey; + colFilters[key] = e.target.value; + clearTimeout(t); + t = setTimeout(applyVisibility, 200); + }); + } + + // ── Re-inietta dopo ogni re-render della griglia ── + function hookRerender() { + const orig = R().renderVisibleRows; + R().renderVisibleRows = function () { + orig.apply(this, arguments); + if (filtersActive) { + injectCheckboxes(); + applyVisibility(); + } + }; + } + + function init() { + if (!window.gridRenderer) { + console.error("[gridFilter] gridRenderer non trovato"); + return; + } + buildUI(); + attachEvents(); + hookRerender(); + } + + // parte dopo che gridRenderer ha finito init + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", () => + setTimeout(init, 300), + ); + } else { + setTimeout(init, 300); + } +})(); diff --git a/public/userarea/gridRenderer.js b/public/userarea/gridRenderer.js index 4048a94..24463da 100644 --- a/public/userarea/gridRenderer.js +++ b/public/userarea/gridRenderer.js @@ -1388,6 +1388,11 @@ if (column === "idclient") { data.forEach((row) => { + if ( + window.isTargetGridId && + !window.isTargetGridId(row.iddatadb) + ) + return; const oldClientId = row.idclient || ""; row.idclient = value; @@ -1421,6 +1426,11 @@ if (column === "cliente_fornitore_id") { data.forEach((row) => { + if ( + window.isTargetGridId && + !window.isTargetGridId(row.iddatadb) + ) + return; row.cliente_fornitore_id = value; row._dirty = true; }); @@ -1434,6 +1444,11 @@ const fixedKey = column.replace("fixed_", ""); data.forEach((row) => { + if ( + window.isTargetGridId && + !window.isTargetGridId(row.iddatadb) + ) + return; if (!row.fixedFields) row.fixedFields = {}; row.fixedFields[fixedKey] = value; row._dirty = true; @@ -1446,6 +1461,11 @@ if (col && (col.type === "detail" || col.type === "main_field")) { data.forEach((row) => { + if ( + window.isTargetGridId && + !window.isTargetGridId(row.iddatadb) + ) + return; if (!row.details) row.details = {}; row.details[col.key] = value; @@ -1760,3 +1780,127 @@ }, }; })(); +// =================== +// NAVIGAZIONE GRIGLIA CON FRECCE TASTIERA +// =================== +(function () { + // Naviga solo tra input di testo/numero/time e date-picker. + // I Select2 (client, fornitore, SceltaMultipla) sono esclusi: + // lì le frecce servono al dropdown. + function isNavigable(el) { + if (!el || !el.classList) return false; + if (!el.classList.contains("cell-input")) return false; + // Escludi i select (nativi o Select2) + if (el.tagName === "SELECT") return false; + if (el.classList.contains("select2-hidden-accessible")) return false; + return true; + } + + // Tutte le celle navigabili della riga, ordinate per data-index + function navCellsInRow($row) { + return $row.find(".grid-cell[data-index]").filter(function () { + const input = this.querySelector(".cell-input"); + return isNavigable(input); + }); + } + + function focusCellInput($cell) { + if (!$cell || !$cell.length) return; + const input = $cell.get(0).querySelector(".cell-input"); + if (!input) return; + input.focus(); + if (input.type !== "date" && typeof input.select === "function") { + try { + input.select(); + } catch (e) {} + } + } + + function atStart(el) { + if (el.type === "date" || el.type === "number" || el.type === "time") + return true; + return el.selectionStart === 0 && el.selectionEnd === 0; + } + + function atEnd(el) { + if (el.type === "date" || el.type === "number" || el.type === "time") + return true; + const len = (el.value || "").length; + return el.selectionStart === len && el.selectionEnd === len; + } + + $(document).on("keydown", "#gridRowContainer .cell-input", function (e) { + const el = this; + if (!isNavigable(el)) return; + + // Ignora se un dropdown Select2 è aperto + if ($(".select2-container--open").length) return; + + const key = e.key; + if ( + key !== "ArrowUp" && + key !== "ArrowDown" && + key !== "ArrowLeft" && + key !== "ArrowRight" + ) { + return; + } + + const $cell = $(el).closest(".grid-cell"); + const $row = $cell.closest(".grid-row"); + if (!$cell.length || !$row.length) return; + + const colIndex = $cell.attr("data-index"); + + // --- SU / GIÙ: stessa colonna (data-index), riga adiacente --- + if (key === "ArrowUp" || key === "ArrowDown") { + const $targetRow = + key === "ArrowUp" + ? $row.prev(".grid-row") + : $row.next(".grid-row"); + if (!$targetRow.length) return; + + e.preventDefault(); + + // Cella stessa colonna nella riga di destinazione + let $target = $targetRow.find( + `.grid-cell[data-index="${colIndex}"]`, + ); + + // Se quella colonna non è navigabile (es. è un select), + // cerca la più vicina navigabile nella riga + if ( + !$target.length || + !isNavigable($target.get(0).querySelector(".cell-input")) + ) { + const $cells = navCellsInRow($targetRow); + if (!$cells.length) return; + $target = $cells.first(); + } + + focusCellInput($target); + return; + } + + // --- SINISTRA / DESTRA: cella navigabile precedente/successiva nella riga --- + const $cells = navCellsInRow($row); + const curPos = $cells.index($cell); + if (curPos === -1) return; + + if (key === "ArrowLeft") { + if (!atStart(el)) return; + if (curPos <= 0) return; + e.preventDefault(); + focusCellInput($cells.eq(curPos - 1)); + return; + } + + if (key === "ArrowRight") { + if (!atEnd(el)) return; + if (curPos >= $cells.length - 1) return; + e.preventDefault(); + focusCellInput($cells.eq(curPos + 1)); + return; + } + }); +})(); diff --git a/public/userarea/imported.php b/public/userarea/imported.php index c728f3b..39ae9f8 100644 --- a/public/userarea/imported.php +++ b/public/userarea/imported.php @@ -1328,6 +1328,117 @@ $gridMeta = [ outline: 2px solid #dc3545 !important; outline-offset: -2px; } + + /* ── Filtri / selezione righe ── */ + #filtersToggleBtn.active { + background-color: #0dcaf0 !important; + color: #fff !important; + border-color: #0dcaf0 !important; + } + + /* checkbox dentro la cella Actions */ + .filter-cb-wrap { + display: none; + align-items: center; + justify-content: center; + margin-right: 6px; + flex-shrink: 0; + } + + .grid-container.filters-on .filter-cb-wrap { + display: inline-flex; + } + + .filter-cb-wrap input { + width: 18px !important; + height: 18px !important; + cursor: pointer; + } + + .grid-container.filters-on .button-cell { + display: flex; + align-items: center; + } + + .grid-row.row-selected { + background-color: #cfe9ff !important; + } + + .grid-row.row-selected .button-cell, + .grid-row.row-selected .grid-cell:nth-child(2), + .grid-row.row-selected .grid-cell:nth-child(3) { + background-color: #cfe9ff !important; + } + + .grid-filter-row { + background: #eef6f9 !important; + border-bottom: 2px solid #0dcaf0; + } + + .grid-filter-row .grid-cell { + padding: 4px 8px; + } + + .grid-filter-row .filter-actions-cell { + background: #eef6f9 !important; + } + + .grid-container.filters-on .grid-row { + cursor: pointer; + } + + .grid-container.filters-on .grid-row input, + .grid-container.filters-on .grid-row select, + .grid-container.filters-on .grid-row textarea, + .grid-container.filters-on .grid-row button, + .grid-container.filters-on .grid-row a { + cursor: auto; + } + + /* Sticky su riga propagazione e filtri (fix scroll orizzontale) */ + .grid-top .grid-cell.save-all-cell { + position: sticky !important; + left: 0; + z-index: 9; + background: #fff; + } + + .grid-top .grid-cell:nth-child(2) { + position: sticky !important; + left: 210px; + z-index: 8; + background: #fff; + } + + = 2): ?>.grid-top .grid-cell:nth-child(3) { + position: sticky !important; + left: 360px; + z-index: 7; + background: #fff; + } + + .grid-filter-row .filter-actions-cell { + position: sticky !important; + left: 0; + z-index: 9; + background: #eef6f9 !important; + } + + .grid-filter-row .grid-cell:nth-child(2) { + position: sticky !important; + left: 210px; + z-index: 8; + background: #eef6f9 !important; + } + + = 2): ?>.grid-filter-row .grid-cell:nth-child(3) { + position: sticky !important; + left: 360px; + z-index: 7; + background: #eef6f9 !important; + } + + Edit Imported Data - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?> @@ -1456,6 +1567,7 @@ $gridMeta = [ + diff --git a/public/userarea/stats_export_lims.php b/public/userarea/stats_export_lims.php index 797d13f..a48c10c 100644 --- a/public/userarea/stats_export_lims.php +++ b/public/userarea/stats_export_lims.php @@ -103,30 +103,31 @@
-
+
-
Export mese
-
0
- Dati dal 16/07/2026 +
Campioni mese
+
0
+ Commesse: 0 · dal 16/07/2026
-
Export anno
-
0
- Dati dal 16/07/2026 +
Campioni anno
+
0
+ Commesse: 0 · dal 16/07/2026
-
Export giorno (status L)
-
0
+
Campioni esportati (giorno)
+
0
+ Commesse: 0
@@ -146,14 +147,6 @@
-
-
-
-
Clienti distinti
-
0
-
-
-
@@ -222,7 +215,8 @@ ID Cliente Cliente - Export + Campioni + Commesse @@ -243,7 +237,8 @@ ID Utente Utente - Export + Campioni + Commesse @@ -295,6 +290,10 @@ data: 'clientname', defaultContent: '—' }, + { + data: 'parts', + className: 'text-end' + }, { data: 'tot', className: 'text-end' @@ -318,6 +317,10 @@ data: 'username', defaultContent: '—' }, + { + data: 'parts', + className: 'text-end' + }, { data: 'tot', className: 'text-end' @@ -428,11 +431,13 @@ return; } - // KPI - $('#kpiExport').text(res.totals.export); - $('#kpiExportMonth').text(res.totalsMonth ? res.totalsMonth.export : 0); - $('#kpiExportYear').text(res.totalsYear ? res.totalsYear.export : 0); - $('#kpiClienti').text(res.byClient.length); + // KPI — dato principale = parti/campioni, sotto le commesse + $('#kpiParts').text(res.totals.parts ?? 0); + $('#kpiExport').text(res.totals.export ?? 0); + $('#kpiPartsMonth').text(res.totalsMonth ? (res.totalsMonth.parts ?? 0) : 0); + $('#kpiExportMonth').text(res.totalsMonth ? (res.totalsMonth.export ?? 0) : 0); + $('#kpiPartsYear').text(res.totalsYear ? (res.totalsYear.parts ?? 0) : 0); + $('#kpiExportYear').text(res.totalsYear ? (res.totalsYear.export ?? 0) : 0); $('#kpiLimsSamples').text(res.limsSamples ?? 0); $('#limsSamplesInput').val(res.limsSamples ?? ''); @@ -452,25 +457,26 @@ } const limsTot = parseInt(res.limsSamples ?? 0, 10); + const partsTot = parseInt(res.totals.parts ?? 0, 10); if (limsTot > 0) { - const perc = (res.totals.export / limsTot) * 100; + const perc = (partsTot / limsTot) * 100; $('#kpiPerc').text(perc.toFixed(1) + '%'); } else { $('#kpiPerc').text('—'); } - // Torta esportati vs totale - renderPie(res.totals.export, limsTot); + // Torta parti esportate vs totale inserito nel LIMS + renderPie(partsTot, limsTot); - // Grafici + // Grafici — valori = campioni/parti const clientTop = topN(res.byClient); const userTop = topN(res.byUser); chartClienti = renderBarChart(chartClienti, 'chartClienti', clientTop.map(r => r.clientname ? (r.clientname + ' (' + r.idclient + ')') : ('ID ' + r.idclient)), - clientTop.map(r => r.tot), 'Export'); + clientTop.map(r => r.parts), 'Campioni'); chartUtenti = renderBarChart(chartUtenti, 'chartUtenti', userTop.map(r => r.username ? (r.username + ' (' + r.user_id + ')') : ('ID ' + r.user_id)), - userTop.map(r => r.tot), 'Export'); + userTop.map(r => r.parts), 'Campioni'); // Tabelle dtClienti.clear().rows.add(res.byClient).draw(); diff --git a/public/userarea/stats_export_lims_data.php b/public/userarea/stats_export_lims_data.php index a918633..9191332 100644 --- a/public/userarea/stats_export_lims_data.php +++ b/public/userarea/stats_export_lims_data.php @@ -15,14 +15,17 @@ try { } // --- Export per cliente (status = 'l', giornata su export_date) --- + // tot = numero commesse, parts = numero parti/campioni $stmtC = $pdo->prepare(" SELECT d.idclient AS idclient, - COUNT(*) AS tot + COUNT(DISTINCT d.iddatadb) AS tot, + COUNT(ip.id) AS parts FROM datadb d + LEFT JOIN identification_parts ip ON ip.iddatadb = d.iddatadb WHERE d.status = 'l' AND DATE(d.export_date) = :d GROUP BY d.idclient - ORDER BY tot DESC + ORDER BY parts DESC "); $stmtC->execute(['d' => $date]); $byClient = $stmtC->fetchAll(PDO::FETCH_ASSOC); @@ -38,16 +41,19 @@ try { } // --- Export per utente (status = 'l', giornata su export_date) --- + // tot = numero commesse, parts = numero parti/campioni $stmtU = $pdo->prepare(" SELECT d.user_id AS user_id, TRIM(CONCAT(COALESCE(u.first_name,''),' ',COALESCE(u.last_name,''))) AS username, - COUNT(*) AS tot + COUNT(DISTINCT d.iddatadb) AS tot, + COUNT(ip.id) AS parts FROM datadb d LEFT JOIN auth_users u ON u.id = d.user_id + LEFT JOIN identification_parts ip ON ip.iddatadb = d.iddatadb WHERE d.status = 'l' AND DATE(d.export_date) = :d GROUP BY d.user_id, username - ORDER BY tot DESC + ORDER BY parts DESC "); $stmtU->execute(['d' => $date]); $byUser = $stmtU->fetchAll(PDO::FETCH_ASSOC); @@ -56,6 +62,7 @@ try { foreach ($byClient as &$r) { $r['idclient'] = $r['idclient'] !== null ? (int)$r['idclient'] : null; $r['tot'] = (int)$r['tot']; + $r['parts'] = (int)$r['parts']; $nm = $clientNames[$r['idclient']] ?? ''; $r['clientname'] = $nm !== '' ? $nm : null; } @@ -63,6 +70,7 @@ try { foreach ($byUser as &$r) { $r['user_id'] = $r['user_id'] !== null ? (int)$r['user_id'] : null; $r['tot'] = (int)$r['tot']; + $r['parts'] = (int)$r['parts']; if (isset($r['username'])) $r['username'] = $r['username'] !== null && $r['username'] !== '' ? $r['username'] : null; } unset($r); @@ -70,7 +78,19 @@ try { // --- Totale export della giornata --- $totExport = array_sum(array_column($byClient, 'tot')); - // --- Totale export del MESE della data selezionata (status = 'l') --- + // --- Totale PARTI/CAMPIONI della giornata (righe identification_parts legate + // ai datadb esportati status='l' in questa giornata) --- + $stmtP = $pdo->prepare(" + SELECT COUNT(*) + FROM identification_parts ip + INNER JOIN datadb d ON d.iddatadb = ip.iddatadb + WHERE d.status = 'l' + AND DATE(d.export_date) = :d + "); + $stmtP->execute(['d' => $date]); + $totParts = (int)$stmtP->fetchColumn(); + + // --- Totale COMMESSE del MESE (status = 'l') --- $stmtM = $pdo->prepare(" SELECT COUNT(*) FROM datadb d @@ -80,7 +100,18 @@ try { $stmtM->execute(['m' => $d->format('Y-m')]); $totExportMonth = (int)$stmtM->fetchColumn(); - // --- Totale export dell'ANNO della data selezionata (status = 'l') --- + // --- Totale PARTI del MESE (status = 'l') --- + $stmtMP = $pdo->prepare(" + SELECT COUNT(*) + FROM identification_parts ip + INNER JOIN datadb d ON d.iddatadb = ip.iddatadb + WHERE d.status = 'l' + AND DATE_FORMAT(d.export_date, '%Y-%m') = :m + "); + $stmtMP->execute(['m' => $d->format('Y-m')]); + $totPartsMonth = (int)$stmtMP->fetchColumn(); + + // --- Totale COMMESSE dell'ANNO (status = 'l') --- $stmtY = $pdo->prepare(" SELECT COUNT(*) FROM datadb d @@ -90,6 +121,17 @@ try { $stmtY->execute(['y' => $d->format('Y')]); $totExportYear = (int)$stmtY->fetchColumn(); + // --- Totale PARTI dell'ANNO (status = 'l') --- + $stmtYP = $pdo->prepare(" + SELECT COUNT(*) + FROM identification_parts ip + INNER JOIN datadb d ON d.iddatadb = ip.iddatadb + WHERE d.status = 'l' + AND YEAR(d.export_date) = :y + "); + $stmtYP->execute(['y' => $d->format('Y')]); + $totPartsYear = (int)$stmtYP->fetchColumn(); + // --- Campioni totali inseriti nel LIMS per la giornata (tabella lims_daily_samples) --- $stmtL = $pdo->prepare("SELECT total_samples FROM lims_daily_samples WHERE sample_date = :d LIMIT 1"); $stmtL->execute(['d' => $date]); @@ -101,9 +143,9 @@ try { 'date' => $date, 'month' => $d->format('Y-m'), 'year' => (int)$d->format('Y'), - 'totals' => ['export' => (int)$totExport], - 'totalsMonth' => ['export' => $totExportMonth], - 'totalsYear' => ['export' => $totExportYear], + 'totals' => ['export' => (int)$totExport, 'parts' => $totParts], + 'totalsMonth' => ['export' => $totExportMonth, 'parts' => $totPartsMonth], + 'totalsYear' => ['export' => $totExportYear, 'parts' => $totPartsYear], 'byClient' => $byClient, 'byUser' => $byUser, 'limsSamples' => $limsSamples, From ff676376ab52b448236ef22d7c616636ec5562fb Mon Sep 17 00:00:00 2001 From: solocla Date: Fri, 17 Jul 2026 09:19:23 +0200 Subject: [PATCH 04/22] fixed filter --- public/userarea/gridFilter.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/public/userarea/gridFilter.js b/public/userarea/gridFilter.js index b6774a9..389a194 100644 --- a/public/userarea/gridFilter.js +++ b/public/userarea/gridFilter.js @@ -390,15 +390,24 @@ }); } + // ── Ripristina stato filtri dopo un re-render (checkbox + restrizione) ── + function restoreFilterState() { + if (!filtersActive) return; + injectCheckboxes(); + applyVisibility(); // ri-applica sia filtri colonna sia restringi selezione + } + // ── Re-inietta dopo ogni re-render della griglia ── function hookRerender() { const orig = R().renderVisibleRows; R().renderVisibleRows = function () { - orig.apply(this, arguments); - if (filtersActive) { - injectCheckboxes(); - applyVisibility(); - } + const ret = orig.apply(this, arguments); + // sincrono + restoreFilterState(); + // e di nuovo dopo il tick, per coprire re-render dentro .then() async + setTimeout(restoreFilterState, 0); + setTimeout(restoreFilterState, 50); + return ret; }; } From 86b1c8420d29167270064a9caa9eec89af0b3525 Mon Sep 17 00:00:00 2001 From: solocla Date: Fri, 17 Jul 2026 09:30:55 +0200 Subject: [PATCH 05/22] fixed rebuild grid --- public/userarea/gridFilter.js | 40 +++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/public/userarea/gridFilter.js b/public/userarea/gridFilter.js index 389a194..201c61d 100644 --- a/public/userarea/gridFilter.js +++ b/public/userarea/gridFilter.js @@ -391,24 +391,36 @@ } // ── Ripristina stato filtri dopo un re-render (checkbox + restrizione) ── + let _restoring = false; function restoreFilterState() { - if (!filtersActive) return; - injectCheckboxes(); - applyVisibility(); // ri-applica sia filtri colonna sia restringi selezione + if (!filtersActive || _restoring) return; + _restoring = true; // evita loop con l'observer mentre iniettiamo/nascondiamo + try { + injectCheckboxes(); + applyVisibility(); // ri-applica filtri colonna E restringi selezione + } finally { + // rilascia dopo il tick così le nostre mutazioni non ci ri-triggerano subito + setTimeout(() => { + _restoring = false; + }, 0); + } } - // ── Re-inietta dopo ogni re-render della griglia ── + // ── Observer: qualunque cosa ricrei le righe (propagazione, save, ecc.) + // ripristina automaticamente checkbox + restrizione. Robusto perché + // non dipende da chi chiama renderVisibleRows. ── function hookRerender() { - const orig = R().renderVisibleRows; - R().renderVisibleRows = function () { - const ret = orig.apply(this, arguments); - // sincrono - restoreFilterState(); - // e di nuovo dopo il tick, per coprire re-render dentro .then() async - setTimeout(restoreFilterState, 0); - setTimeout(restoreFilterState, 50); - return ret; - }; + const rowC = document.getElementById("gridRowContainer"); + if (!rowC) return; + + const obs = new MutationObserver(() => { + if (!filtersActive || _restoring) return; + // debounce leggero: aspetta che il render finisca + clearTimeout(hookRerender._t); + hookRerender._t = setTimeout(restoreFilterState, 30); + }); + + obs.observe(rowC, { childList: true }); } function init() { From c2fd2dc470bcfa89b02acb327a4a070c6f4e4eb3 Mon Sep 17 00:00:00 2001 From: solocla Date: Fri, 17 Jul 2026 10:46:33 +0200 Subject: [PATCH 06/22] fixed export and save to selection --- public/userarea/clone_parts_to_visible.php | 23 ++ public/userarea/exportLims_gridData.js | 417 +++++++++++++++------ public/userarea/modal_partsTable.php | 6 + public/userarea/partsTable.js | 18 +- public/userarea/saveAll.js | 118 ++++-- 5 files changed, 423 insertions(+), 159 deletions(-) diff --git a/public/userarea/clone_parts_to_visible.php b/public/userarea/clone_parts_to_visible.php index 23a4f3a..a2a9222 100644 --- a/public/userarea/clone_parts_to_visible.php +++ b/public/userarea/clone_parts_to_visible.php @@ -14,6 +14,7 @@ $targetList = $data['target_iddatadb_list'] ?? []; // (comportamento di default richiesto, coerente con la checkbox pre-spuntata) $cloneNotes = array_key_exists('clone_notes', $data) ? !empty($data['clone_notes']) : true; $cloneAnalyses = !empty($data['clone_analyses']); +$overwrite = !empty($data['overwrite']); $targetIds = array_values(array_unique(array_filter(array_map('intval', (array)$targetList), function ($v) use ($sourceIddatadb) { return $v > 0 && $v !== $sourceIddatadb; @@ -93,6 +94,28 @@ try { $details = []; $totalClonedParts = 0; $totalClonedAnalyses = 0; + $totalDeletedParts = 0; + + // 2b. Se overwrite: elimina le parti esistenti dei target (e i loro figli) + if ($overwrite) { + $stmtSelectExisting = $pdo->prepare("SELECT id FROM identification_parts WHERE iddatadb = ?"); + $stmtDeleteCFByPart = $pdo->prepare("DELETE FROM identification_parts_customfields WHERE part_id = ?"); + $stmtDeleteAnByPart = $pdo->prepare("DELETE FROM identification_parts_analyses WHERE part_id = ?"); + $stmtDeletePart = $pdo->prepare("DELETE FROM identification_parts WHERE id = ?"); + + foreach ($targetIds as $targetIddatadb) { + $stmtSelectExisting->execute([$targetIddatadb]); + $existingIds = $stmtSelectExisting->fetchAll(PDO::FETCH_COLUMN); + + foreach ($existingIds as $existingPartId) { + $existingPartId = (int)$existingPartId; + $stmtDeleteCFByPart->execute([$existingPartId]); + $stmtDeleteAnByPart->execute([$existingPartId]); + $stmtDeletePart->execute([$existingPartId]); + $totalDeletedParts++; + } + } + } // 3. Clone source parts to each target record foreach ($targetIds as $targetIddatadb) { diff --git a/public/userarea/exportLims_gridData.js b/public/userarea/exportLims_gridData.js index 3c1730a..44bb57a 100644 --- a/public/userarea/exportLims_gridData.js +++ b/public/userarea/exportLims_gridData.js @@ -5,7 +5,7 @@ * Single export + batch export (Export All) with validation. */ (function () { - 'use strict'; + "use strict"; let pendingConfirmHandler = null; let batchRunning = false; @@ -17,7 +17,7 @@ // ── Helpers ────────────────────────────────────────────────────────── function cleanupBackdrop() { - document.querySelectorAll(".modal-backdrop").forEach(b => b.remove()); + document.querySelectorAll(".modal-backdrop").forEach((b) => b.remove()); document.body.classList.remove("modal-open"); document.body.style.paddingRight = ""; const overlay = document.querySelector(".overlay.toggle-icon"); @@ -29,7 +29,9 @@ } function getRowIndexByIddatadb(iddatadb) { - return (window.gridData || []).findIndex(r => String(r.iddatadb) === String(iddatadb)); + return (window.gridData || []).findIndex( + (r) => String(r.iddatadb) === String(iddatadb), + ); } // ── Validation ────────────────────────────────────────────────────── @@ -40,21 +42,31 @@ headers: { "Content-Type": "application/json" }, body: JSON.stringify({ rows: rowsToValidate }), }); - if (!response.ok) throw new Error(`Validation HTTP error: ${response.status}`); + if (!response.ok) + throw new Error(`Validation HTTP error: ${response.status}`); return response.json(); } function clearValidationErrors() { // Clear from gridData - (window.gridData || []).forEach(row => { delete row._validationErrors; delete row._exportError; }); - - document.querySelectorAll(".grid-cell.validation-error").forEach(cell => { - cell.classList.remove("validation-error"); - cell.querySelectorAll(".input-validation-error").forEach(el => el.classList.remove("input-validation-error")); - const tooltip = cell.querySelector(".validation-tooltip"); - if (tooltip) tooltip.remove(); + (window.gridData || []).forEach((row) => { + delete row._validationErrors; + delete row._exportError; }); - document.querySelectorAll(".grid-row.validation-row-error").forEach(row => row.classList.remove("validation-row-error")); + + document + .querySelectorAll(".grid-cell.validation-error") + .forEach((cell) => { + cell.classList.remove("validation-error"); + cell.querySelectorAll(".input-validation-error").forEach((el) => + el.classList.remove("input-validation-error"), + ); + const tooltip = cell.querySelector(".validation-tooltip"); + if (tooltip) tooltip.remove(); + }); + document + .querySelectorAll(".grid-row.validation-row-error") + .forEach((row) => row.classList.remove("validation-row-error")); clearAllRowErrors(); } @@ -67,7 +79,7 @@ gridRow.classList.add("validation-row-error"); const messages = []; - errors.forEach(err => { + errors.forEach((err) => { messages.push(err.message); if (!err.field) return; @@ -76,19 +88,26 @@ const label = err.field.substring("field_label:".length); const headers = document.querySelectorAll(".grid-header"); let targetIndex = null; - headers.forEach(h => { - if (h.textContent.trim() === label) targetIndex = h.getAttribute("data-index"); + headers.forEach((h) => { + if (h.textContent.trim() === label) + targetIndex = h.getAttribute("data-index"); }); if (targetIndex) { - cell = gridRow.querySelector(`.grid-cell[data-index="${targetIndex}"]`); + cell = gridRow.querySelector( + `.grid-cell[data-index="${targetIndex}"]`, + ); } } else { - cell = gridRow.querySelector(`.grid-cell[data-col="${err.field}"]`); + cell = gridRow.querySelector( + `.grid-cell[data-col="${err.field}"]`, + ); } if (cell) { cell.classList.add("validation-error"); - cell.querySelectorAll("input, select").forEach(el => el.classList.add("input-validation-error")); + cell.querySelectorAll("input, select").forEach((el) => + el.classList.add("input-validation-error"), + ); let tooltip = cell.querySelector(".validation-tooltip"); if (!tooltip) { tooltip = document.createElement("div"); @@ -109,34 +128,43 @@ formData.append("iddatadb", iddatadb); if (batchUuid) formData.append("batch_uuid", batchUuid); - const response = await fetch("export_to_lims.php", { method: "POST", body: formData }); - if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); + const response = await fetch("export_to_lims.php", { + method: "POST", + body: formData, + }); + if (!response.ok) + throw new Error(`HTTP error! status: ${response.status}`); const data = await response.json(); if (data.success) { // Update gridData const idx = getRowIndexByIddatadb(iddatadb); if (idx >= 0) { - window.gridData[idx].status = 'l'; + window.gridData[idx].status = "l"; window.gridData[idx].commessaweb = data.commessaweb; } // Update visible DOM row const gridRow = getGridRow(iddatadb); if (gridRow) { - const statusBadge = gridRow.querySelector('.grid-cell[data-col="status"] .status-badge'); + const statusBadge = gridRow.querySelector( + '.grid-cell[data-col="status"] .status-badge', + ); if (statusBadge) { statusBadge.classList.remove("status-i", "status-P"); statusBadge.classList.add("status-l"); statusBadge.textContent = "To LIMS"; } - const statusCell = gridRow.querySelector('.grid-cell[data-col="status"]'); + const statusCell = gridRow.querySelector( + '.grid-cell[data-col="status"]', + ); if (statusCell && data.commessaweb) { let cwSpan = statusCell.querySelector(".commessaweb-code"); if (!cwSpan) { cwSpan = document.createElement("span"); cwSpan.className = "commessaweb-code"; - cwSpan.style.cssText = "display:block; font-size:0.75em; color:#555; margin-top:2px;"; + cwSpan.style.cssText = + "display:block; font-size:0.75em; color:#555; margin-top:2px;"; statusCell.appendChild(cwSpan); } cwSpan.textContent = data.commessaweb; @@ -164,7 +192,8 @@ const label = document.getElementById("exportResponseModalLabel"); if (data.success) { - msg.innerHTML = `${data.message.replace(/\n/g, "
")}` + + msg.innerHTML = + `${data.message.replace(/\n/g, "
")}` + `
ID CommessaWeb: ${data.idcommessaweb}` + `
Codice CommessaWeb: ${data.commessaweb}` + (data.totalPhotos > 0 ? `
Foto: ${data.totalPhotos}` : ""); @@ -186,10 +215,14 @@ row.classList.remove("batch-disabled"); row.classList.add("batch-exporting"); if (btnCell) { - btnCell.querySelectorAll(".action-btn").forEach(b => { b.dataset.prevDisplay = b.style.display; b.style.display = "none"; }); + btnCell.querySelectorAll(".action-btn").forEach((b) => { + b.dataset.prevDisplay = b.style.display; + b.style.display = "none"; + }); const spinner = document.createElement("span"); spinner.className = "batch-row-spinner"; - spinner.innerHTML = ' Exporting...'; + spinner.innerHTML = + ' Exporting...'; btnCell.appendChild(spinner); } } else { @@ -197,7 +230,10 @@ if (btnCell) { const spinner = btnCell.querySelector(".batch-row-spinner"); if (spinner) spinner.remove(); - btnCell.querySelectorAll(".action-btn").forEach(b => { b.style.display = b.dataset.prevDisplay || ""; delete b.dataset.prevDisplay; }); + btnCell.querySelectorAll(".action-btn").forEach((b) => { + b.style.display = b.dataset.prevDisplay || ""; + delete b.dataset.prevDisplay; + }); } } } @@ -213,32 +249,56 @@ errorEl.className = "batch-error-msg"; errorEl.textContent = "Warning — click for details"; errorEl.addEventListener("click", () => { - document.getElementById("exportResponseMessage").innerHTML = message.replace(/\n/g, "
"); - document.getElementById("exportResponseModalLabel").textContent = "Error (id: " + iddatadb + ")"; - new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show(); + document.getElementById("exportResponseMessage").innerHTML = + message.replace(/\n/g, "
"); + document.getElementById( + "exportResponseModalLabel", + ).textContent = "Error (id: " + iddatadb + ")"; + new bootstrap.Modal( + document.getElementById("exportResponseModal"), + { keyboard: false }, + ).show(); }); btnCell.appendChild(errorEl); } } function clearAllRowErrors() { - document.querySelectorAll(".grid-row.batch-row-error").forEach(row => { - row.classList.remove("batch-row-error"); - const msg = row.querySelector(".batch-error-msg"); - if (msg) msg.remove(); - }); + document + .querySelectorAll(".grid-row.batch-row-error") + .forEach((row) => { + row.classList.remove("batch-row-error"); + const msg = row.querySelector(".batch-error-msg"); + if (msg) msg.remove(); + }); } function disableAllRowButtons() { - document.querySelectorAll(".grid-row[data-id]").forEach(row => row.classList.add("batch-disabled")); - const toggle = document.querySelector(".actions-dropdown .dropdown-toggle"); - if (toggle) { toggle.disabled = true; toggle.style.opacity = "0.5"; toggle.style.pointerEvents = "none"; } + document + .querySelectorAll(".grid-row[data-id]") + .forEach((row) => row.classList.add("batch-disabled")); + const toggle = document.querySelector( + ".actions-dropdown .dropdown-toggle", + ); + if (toggle) { + toggle.disabled = true; + toggle.style.opacity = "0.5"; + toggle.style.pointerEvents = "none"; + } } function enableAllRowButtons() { - document.querySelectorAll(".grid-row[data-id]").forEach(row => row.classList.remove("batch-disabled")); - const toggle = document.querySelector(".actions-dropdown .dropdown-toggle"); - if (toggle) { toggle.disabled = false; toggle.style.opacity = ""; toggle.style.pointerEvents = ""; } + document + .querySelectorAll(".grid-row[data-id]") + .forEach((row) => row.classList.remove("batch-disabled")); + const toggle = document.querySelector( + ".actions-dropdown .dropdown-toggle", + ); + if (toggle) { + toggle.disabled = false; + toggle.style.opacity = ""; + toggle.style.pointerEvents = ""; + } } // ── Single row export: validate → confirm → send ──────────────────── @@ -250,40 +310,61 @@ if (gridRow) { setRowExporting(gridRow, true); const spinner = gridRow.querySelector(".batch-row-spinner"); - if (spinner) spinner.innerHTML = ' Validating...'; + if (spinner) + spinner.innerHTML = + ' Validating...'; } validateRows([{ iddatadb: parseInt(iddatadb), index: rowIndex }]) - .then(validationData => { - if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); } + .then((validationData) => { + if (gridRow) { + setRowExporting(gridRow, false); + gridRow.classList.remove("batch-disabled"); + } if (!validationData.success) { - showExportResult({ success: false, message: validationData.message || "Validation error" }); + showExportResult({ + success: false, + message: validationData.message || "Validation error", + }); return; } const result = validationData.results[rowIndex]; if (result && !result.valid) { - if (gridRow) showValidationErrors(gridRow, iddatadb, result.errors); + if (gridRow) + showValidationErrors(gridRow, iddatadb, result.errors); return; } showConfirmAndExport(iddatadb, rowIndex); }) - .catch(error => { - if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); } - showExportResult({ success: false, message: "Validation error: " + error.message }); + .catch((error) => { + if (gridRow) { + setRowExporting(gridRow, false); + gridRow.classList.remove("batch-disabled"); + } + showExportResult({ + success: false, + message: "Validation error: " + error.message, + }); }); } function showConfirmAndExport(iddatadb, rowIndex) { - const confirmModalElement = document.getElementById("exportConfirmModal"); + const confirmModalElement = + document.getElementById("exportConfirmModal"); if (!confirmModalElement) return; - const confirmModal = new bootstrap.Modal(confirmModalElement, { keyboard: false }); + const confirmModal = new bootstrap.Modal(confirmModalElement, { + keyboard: false, + }); document.getElementById("exportIddatadb").textContent = iddatadb; confirmModal.show(); const confirmBtn = document.getElementById("exportConfirmBtn"); - if (!confirmBtn) { confirmModal.hide(); return; } + if (!confirmBtn) { + confirmModal.hide(); + return; + } const confirmHandler = async () => { pendingConfirmHandler = null; @@ -294,24 +375,36 @@ try { const data = await sendExport(iddatadb); - if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); } - if (!data.success) showRowError(gridRow, iddatadb, data.message || "Unknown error"); + if (gridRow) { + setRowExporting(gridRow, false); + gridRow.classList.remove("batch-disabled"); + } + if (!data.success) + showRowError( + gridRow, + iddatadb, + data.message || "Unknown error", + ); showExportResult(data); } catch (error) { - if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); } + if (gridRow) { + setRowExporting(gridRow, false); + gridRow.classList.remove("batch-disabled"); + } showRowError(gridRow, iddatadb, error.message); showExportResult({ success: false, message: error.message }); } }; - if (pendingConfirmHandler) confirmBtn.removeEventListener("click", pendingConfirmHandler); + if (pendingConfirmHandler) + confirmBtn.removeEventListener("click", pendingConfirmHandler); pendingConfirmHandler = confirmHandler; confirmBtn.addEventListener("click", confirmHandler, { once: true }); } // ── Single row click (event delegation) ───────────────────────────── - $(document).on('click', '.export-lims-btn', function (e) { + $(document).on("click", ".export-lims-btn", function (e) { e.preventDefault(); if (batchRunning) return; @@ -322,24 +415,34 @@ // Check unsaved changes for this row const dataRow = window.gridData?.[rowIndex]; if (dataRow && dataRow._dirty) { - const unsavedModal = new bootstrap.Modal(document.getElementById("exportUnsavedModal"), { keyboard: false }); + const unsavedModal = new bootstrap.Modal( + document.getElementById("exportUnsavedModal"), + { keyboard: false }, + ); unsavedModal.show(); - document.getElementById("saveAndExportBtn")?.addEventListener("click", () => { - unsavedModal.hide(); - // Save first, then export - const formData = window.buildSavePayload(rowIndex); - fetch('save_edited_row.php', { method: 'POST', body: formData }) - .then(r => r.json()) - .then(result => { - if (result.success) { - dataRow._dirty = false; - startExportConfirmFlow(iddatadb, rowIndex); - } else { - alert('Save failed: ' + result.message); - } - }); - }, { once: true }); + document.getElementById("saveAndExportBtn")?.addEventListener( + "click", + () => { + unsavedModal.hide(); + // Save first, then export + const formData = window.buildSavePayload(rowIndex); + fetch("save_edited_row.php", { + method: "POST", + body: formData, + }) + .then((r) => r.json()) + .then((result) => { + if (result.success) { + dataRow._dirty = false; + startExportConfirmFlow(iddatadb, rowIndex); + } else { + alert("Save failed: " + result.message); + } + }); + }, + { once: true }, + ); return; } @@ -349,18 +452,33 @@ // ── Batch export (Export All) ─────────────────────────────────────── function collectEligibleRows() { - // Read from gridData, not DOM + // Read from gridData, not DOM. Se i filtri sono attivi, limita al target set. + const target = + typeof window.getTargetGridIds === "function" + ? window.getTargetGridIds() + : null; const eligible = []; (window.gridData || []).forEach((row, index) => { - if (row.status !== 'l') { - eligible.push({ iddatadb: row.iddatadb, index, row: getGridRow(row.iddatadb) }); - } + if (row.status === "l") return; + if (target && !target.has(parseInt(row.iddatadb, 10))) return; + eligible.push({ + iddatadb: row.iddatadb, + index, + row: getGridRow(row.iddatadb), + }); }); return eligible; } function hasUnsavedChanges() { - return (window.gridData || []).some(r => r._dirty); + const target = + typeof window.getTargetGridIds === "function" + ? window.getTargetGridIds() + : null; + return (window.gridData || []).some( + (r) => + r._dirty && (!target || target.has(parseInt(r.iddatadb, 10))), + ); } async function validateAndFilter(eligibleRows) { @@ -369,7 +487,8 @@ index, })); const validationData = await validateRows(rowsToValidate); - if (!validationData.success) throw new Error(validationData.message || "Validation error"); + if (!validationData.success) + throw new Error(validationData.message || "Validation error"); const validRows = []; let invalidCount = 0; @@ -377,7 +496,12 @@ for (const item of eligibleRows) { const result = validationData.results[item.index]; if (result && !result.valid) { - if (item.row) showValidationErrors(item.row, item.iddatadb, result.errors); + if (item.row) + showValidationErrors( + item.row, + item.iddatadb, + result.errors, + ); invalidCount++; } else { validRows.push(item); @@ -410,54 +534,92 @@ if (validRows.length === 0) { document.getElementById("exportResponseMessage").innerHTML = `No valid rows for export.
${invalidCount} rows with validation errors.`; - document.getElementById("exportResponseModalLabel").textContent = "Validation Failed"; - new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show(); + document.getElementById( + "exportResponseModalLabel", + ).textContent = "Validation Failed"; + new bootstrap.Modal( + document.getElementById("exportResponseModal"), + { keyboard: false }, + ).show(); return; } - const confirmModal = new bootstrap.Modal(document.getElementById("exportBatchConfirmModal"), { keyboard: false }); + const confirmModal = new bootstrap.Modal( + document.getElementById("exportBatchConfirmModal"), + { keyboard: false }, + ); let countText = String(validRows.length); - if (invalidCount > 0) countText += ` (${invalidCount} excluded due to errors)`; - document.getElementById("exportBatchCount").textContent = countText; + if (invalidCount > 0) + countText += ` (${invalidCount} excluded due to errors)`; + document.getElementById("exportBatchCount").textContent = + countText; confirmModal.show(); - const confirmBtn = document.getElementById("exportBatchConfirmBtn"); - if (pendingBatchConfirmHandler) confirmBtn.removeEventListener("click", pendingBatchConfirmHandler); + const confirmBtn = document.getElementById( + "exportBatchConfirmBtn", + ); + if (pendingBatchConfirmHandler) + confirmBtn.removeEventListener( + "click", + pendingBatchConfirmHandler, + ); pendingBatchConfirmHandler = () => { pendingBatchConfirmHandler = null; confirmModal.hide(); startBatchExport(validRows); }; - confirmBtn.addEventListener("click", pendingBatchConfirmHandler, { once: true }); + confirmBtn.addEventListener( + "click", + pendingBatchConfirmHandler, + { once: true }, + ); }) - .catch(error => { + .catch((error) => { showValidationSpinner(false); - document.getElementById("exportResponseMessage").textContent = "Validation error: " + error.message; - document.getElementById("exportResponseModalLabel").textContent = "Validation Error"; - new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show(); + document.getElementById("exportResponseMessage").textContent = + "Validation error: " + error.message; + document.getElementById( + "exportResponseModalLabel", + ).textContent = "Validation Error"; + new bootstrap.Modal( + document.getElementById("exportResponseModal"), + { keyboard: false }, + ).show(); }); } - $(document).on('click', '.export-all-lims-btn', function (e) { + $(document).on("click", ".export-all-lims-btn", function (e) { e.preventDefault(); if (batchRunning) return; if (hasUnsavedChanges()) { - const unsavedModal = new bootstrap.Modal(document.getElementById("exportBatchUnsavedModal"), { keyboard: false }); + const unsavedModal = new bootstrap.Modal( + document.getElementById("exportBatchUnsavedModal"), + { keyboard: false }, + ); unsavedModal.show(); - document.getElementById("batchSaveAndExportBtn")?.addEventListener("click", () => { - unsavedModal.hide(); - // Trigger save all first — listen for completion - alert("Please Save All first, then Export All."); - }, { once: true }); + document.getElementById("batchSaveAndExportBtn")?.addEventListener( + "click", + () => { + unsavedModal.hide(); + // Trigger save all first — listen for completion + alert("Please Save All first, then Export All."); + }, + { once: true }, + ); return; } const eligibleRows = collectEligibleRows(); if (eligibleRows.length === 0) { - document.getElementById("exportResponseMessage").textContent = "All rows already exported to LIMS."; - document.getElementById("exportResponseModalLabel").textContent = "Export All"; - new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show(); + document.getElementById("exportResponseMessage").textContent = + "All rows already exported to LIMS."; + document.getElementById("exportResponseModalLabel").textContent = + "Export All"; + new bootstrap.Modal( + document.getElementById("exportResponseModal"), + { keyboard: false }, + ).show(); return; } showBatchConfirm(eligibleRows); @@ -468,7 +630,9 @@ batchRunning = true; const batchUuid = crypto.randomUUID(); const total = eligibleRows.length; - let processed = 0, succeeded = 0, failed = 0; + let processed = 0, + succeeded = 0, + failed = 0; disableAllRowButtons(); @@ -479,18 +643,25 @@ if (cancelBtn) cancelBtn.disabled = false; if (statusEl) statusEl.textContent = `Exporting 0 / ${total}...`; - cancelBtn?.addEventListener("click", () => { - batchCancelled = true; - if (statusEl) statusEl.textContent = "Cancelling... (waiting for current row)"; - if (cancelBtn) cancelBtn.disabled = true; - }, { once: true }); + cancelBtn?.addEventListener( + "click", + () => { + batchCancelled = true; + if (statusEl) + statusEl.textContent = + "Cancelling... (waiting for current row)"; + if (cancelBtn) cancelBtn.disabled = true; + }, + { once: true }, + ); (async () => { for (let i = 0; i < eligibleRows.length; i++) { if (batchCancelled) break; const { iddatadb, row } = eligibleRows[i]; - if (statusEl) statusEl.textContent = `Exporting ${processed + 1} / ${total} (id: ${iddatadb})...`; + if (statusEl) + statusEl.textContent = `Exporting ${processed + 1} / ${total} (id: ${iddatadb})...`; const gridRow = row || getGridRow(iddatadb); if (gridRow) setRowExporting(gridRow, true); @@ -503,18 +674,29 @@ } else { failed++; const errIdx = getRowIndexByIddatadb(iddatadb); - if (errIdx >= 0) window.gridData[errIdx]._exportError = data.message || "Unknown error"; - if (gridRow) showRowError(gridRow, iddatadb, data.message || "Unknown error"); + if (errIdx >= 0) + window.gridData[errIdx]._exportError = + data.message || "Unknown error"; + if (gridRow) + showRowError( + gridRow, + iddatadb, + data.message || "Unknown error", + ); } } catch (error) { processed++; failed++; const errIdx = getRowIndexByIddatadb(iddatadb); - if (errIdx >= 0) window.gridData[errIdx]._exportError = error.message; + if (errIdx >= 0) + window.gridData[errIdx]._exportError = error.message; if (gridRow) showRowError(gridRow, iddatadb, error.message); } - if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); } + if (gridRow) { + setRowExporting(gridRow, false); + gridRow.classList.remove("batch-disabled"); + } } batchRunning = false; @@ -529,7 +711,8 @@ (window.gridData || []).forEach((row, idx) => { if (row._exportError) { const gridRow = getGridRow(row.iddatadb); - if (gridRow) showRowError(gridRow, row.iddatadb, row._exportError); + if (gridRow) + showRowError(gridRow, row.iddatadb, row._exportError); } }); @@ -549,7 +732,9 @@ const modalEl = document.getElementById("exportResponseModal"); new bootstrap.Modal(modalEl, { keyboard: false }).show(); - modalEl.addEventListener("hidden.bs.modal", cleanupBackdrop, { once: true }); + modalEl.addEventListener("hidden.bs.modal", cleanupBackdrop, { + once: true, + }); })(); } })(); diff --git a/public/userarea/modal_partsTable.php b/public/userarea/modal_partsTable.php index 75d945c..0157a2c 100644 --- a/public/userarea/modal_partsTable.php +++ b/public/userarea/modal_partsTable.php @@ -214,6 +214,12 @@ Duplica anche le analisi collegate +
+ + +