1,2,3,4,5,6 points of milestone
This commit is contained in:
+686
-134
@@ -4,13 +4,13 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const exportButtons = document.querySelectorAll(".export-lims-btn");
|
||||
console.log(`Found ${exportButtons.length} export-lims-btn buttons`);
|
||||
|
||||
if (exportButtons.length === 0) {
|
||||
console.warn("No .export-lims-btn buttons found in the DOM");
|
||||
return;
|
||||
}
|
||||
|
||||
// Tracks the active confirm handler so it can be replaced on re-open
|
||||
let pendingConfirmHandler = null;
|
||||
let batchRunning = false;
|
||||
// Expose for Save All to check
|
||||
Object.defineProperty(window, "batchRunning", {
|
||||
get: () => batchRunning,
|
||||
});
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -22,10 +22,340 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
if (overlay) overlay.style.display = "none";
|
||||
}
|
||||
|
||||
// ── Step 2: show export-confirm modal, send on "Conferma" ────────────────
|
||||
// ── Validation ───────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Call the validation endpoint for an array of { iddatadb, index } objects.
|
||||
* Returns the parsed JSON response.
|
||||
*/
|
||||
async function validateRows(rowsToValidate) {
|
||||
const response = await fetch("validate_export.php", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ rows: rowsToValidate }),
|
||||
});
|
||||
if (!response.ok)
|
||||
throw new Error(`Validation HTTP error: ${response.status}`);
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all validation-error highlights from the grid.
|
||||
*/
|
||||
function clearValidationErrors() {
|
||||
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");
|
||||
});
|
||||
// Also clear batch-row-error that came from validation
|
||||
clearAllRowErrors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight specific cells on a row based on validation errors.
|
||||
* Each error has { field, message }.
|
||||
* field can be: a data-col value, "parts", "field_label:SomeLabel", or null (row-level).
|
||||
*/
|
||||
function showValidationErrors(row, iddatadb, errors) {
|
||||
row.classList.add("validation-row-error");
|
||||
|
||||
const messages = [];
|
||||
|
||||
errors.forEach((err) => {
|
||||
messages.push(err.message);
|
||||
|
||||
if (!err.field) return; // row-level error, no specific cell
|
||||
|
||||
let cell = null;
|
||||
|
||||
if (err.field === "parts") {
|
||||
// No specific cell to highlight, but we highlight the parts button area
|
||||
// just add to messages
|
||||
return;
|
||||
} else if (err.field.startsWith("field_label:")) {
|
||||
// Match by field_label text — find the header with this label, get its index
|
||||
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");
|
||||
}
|
||||
});
|
||||
if (targetIndex) {
|
||||
const rowIndex = row.querySelector(".grid-cell[data-row]")?.getAttribute("data-row");
|
||||
if (rowIndex !== null) {
|
||||
cell = row.querySelector(`.grid-cell[data-row="${rowIndex}"][data-index="${targetIndex}"]`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Direct data-col match
|
||||
const rowIndex = row.querySelector(".grid-cell[data-row]")?.getAttribute("data-row");
|
||||
if (rowIndex !== null) {
|
||||
cell = row.querySelector(`.grid-cell[data-col="${err.field}"][data-row="${rowIndex}"]`);
|
||||
}
|
||||
}
|
||||
|
||||
if (cell) {
|
||||
cell.classList.add("validation-error");
|
||||
// Mark the input/select inside the cell
|
||||
cell.querySelectorAll("input, select").forEach((el) => {
|
||||
el.classList.add("input-validation-error");
|
||||
});
|
||||
// Add tooltip with error message
|
||||
let tooltip = cell.querySelector(".validation-tooltip");
|
||||
if (!tooltip) {
|
||||
tooltip = document.createElement("div");
|
||||
tooltip.className = "validation-tooltip";
|
||||
cell.appendChild(tooltip);
|
||||
}
|
||||
tooltip.textContent = err.message;
|
||||
}
|
||||
});
|
||||
|
||||
// Show aggregated error on the row using existing mechanism
|
||||
showRowError(row, iddatadb, messages.join("\n"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a single export request and update the row UI on success.
|
||||
* Returns the parsed JSON response.
|
||||
*/
|
||||
async function sendExport(iddatadb, gridRow, batchUuid = null) {
|
||||
const formData = new FormData();
|
||||
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 data = await response.json();
|
||||
|
||||
if (data.success && gridRow) {
|
||||
// Update 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";
|
||||
}
|
||||
|
||||
// Insert/update CommessaWeb code span
|
||||
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.title = "CommessaWeb";
|
||||
const hiddenInput = statusCell.querySelector(
|
||||
'input[type="hidden"]',
|
||||
);
|
||||
hiddenInput
|
||||
? statusCell.insertBefore(cwSpan, hiddenInput)
|
||||
: statusCell.appendChild(cwSpan);
|
||||
}
|
||||
cwSpan.textContent = data.commessaweb;
|
||||
}
|
||||
|
||||
// Disable export button for this row
|
||||
const exportBtn = gridRow.querySelector(".export-lims-btn");
|
||||
if (exportBtn) {
|
||||
exportBtn.disabled = true;
|
||||
exportBtn.style.background = "#ccc";
|
||||
exportBtn.style.cursor = "not-allowed";
|
||||
exportBtn.style.opacity = "0.5";
|
||||
exportBtn.title = "Già esportato";
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the result of a single export in the response modal.
|
||||
*/
|
||||
function showExportResult(data) {
|
||||
const responseModalElement =
|
||||
document.getElementById("exportResponseModal");
|
||||
if (!responseModalElement) {
|
||||
alert("Errore: Modale di risposta non trovato");
|
||||
return;
|
||||
}
|
||||
|
||||
const responseModal = new bootstrap.Modal(responseModalElement, {
|
||||
keyboard: false,
|
||||
});
|
||||
const responseMessage = document.getElementById(
|
||||
"exportResponseMessage",
|
||||
);
|
||||
|
||||
if (data.success) {
|
||||
responseMessage.innerHTML =
|
||||
`${data.message.replace(/\n/g, "<br>")}` +
|
||||
`<br>ID CommessaWeb: ${data.idcommessaweb}` +
|
||||
`<br>Codice CommessaWeb: ${data.commessaweb}` +
|
||||
(data.totalPhotos > 0
|
||||
? `<br>Foto trovate: ${data.totalPhotos}`
|
||||
: "");
|
||||
document.getElementById("exportResponseModalLabel").textContent =
|
||||
"Esportazione Completata";
|
||||
} else {
|
||||
responseMessage.textContent = `Errore durante la generazione dei payload: ${data.message}`;
|
||||
document.getElementById("exportResponseModalLabel").textContent =
|
||||
"Errore Esportazione";
|
||||
}
|
||||
|
||||
responseModal.show();
|
||||
responseModalElement.addEventListener(
|
||||
"hidden.bs.modal",
|
||||
cleanupBackdrop,
|
||||
{ once: true },
|
||||
);
|
||||
}
|
||||
|
||||
// ── Row button helpers (disable/enable during batch) ────────────────────
|
||||
|
||||
function setRowExporting(row, active) {
|
||||
const btnCell = row.querySelector(".button-cell");
|
||||
if (active) {
|
||||
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";
|
||||
});
|
||||
const spinner = document.createElement("span");
|
||||
spinner.className = "batch-row-spinner";
|
||||
spinner.innerHTML =
|
||||
'<i class="fas fa-spinner fa-spin"></i> Exporting...';
|
||||
btnCell.appendChild(spinner);
|
||||
}
|
||||
} else {
|
||||
row.classList.remove("batch-exporting");
|
||||
row.classList.add("batch-disabled");
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showRowError(row, iddatadb, message) {
|
||||
row.classList.add("batch-row-error");
|
||||
const btnCell = row.querySelector(".button-cell");
|
||||
if (btnCell) {
|
||||
// Remove existing error msg
|
||||
const old = btnCell.querySelector(".batch-error-msg");
|
||||
if (old) old.remove();
|
||||
|
||||
const errorEl = document.createElement("div");
|
||||
errorEl.className = "batch-error-msg";
|
||||
errorEl.textContent = "⚠ Errore — clicca per dettagli";
|
||||
errorEl.addEventListener("click", () => {
|
||||
document.getElementById("exportResponseMessage").innerHTML = message.replace(/\n/g, "<br>");
|
||||
document.getElementById("exportResponseModalLabel").textContent = "Errore Validazione (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();
|
||||
});
|
||||
}
|
||||
|
||||
function disableAllRowButtons() {
|
||||
document.querySelectorAll(".grid-row[data-id]").forEach((row) => {
|
||||
row.classList.add("batch-disabled");
|
||||
});
|
||||
// Disable Actions dropdown
|
||||
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 = "";
|
||||
}
|
||||
}
|
||||
|
||||
// ── Single row export: validate, confirm modal, then send ───────────────
|
||||
|
||||
function startExportConfirmFlow(iddatadb, btn) {
|
||||
const confirmModalElement = document.getElementById("exportConfirmModal");
|
||||
const gridRow = btn.closest(".grid-row");
|
||||
const rowIndex = btn.dataset.row;
|
||||
|
||||
// Validate first
|
||||
clearValidationErrors();
|
||||
|
||||
validateRows([{ iddatadb: parseInt(iddatadb), index: parseInt(rowIndex) }])
|
||||
.then((validationData) => {
|
||||
if (!validationData.success) {
|
||||
showExportResult({ success: false, message: validationData.message || "Errore di validazione" });
|
||||
return;
|
||||
}
|
||||
|
||||
const result = validationData.results[rowIndex];
|
||||
if (result && !result.valid) {
|
||||
// Show validation errors on the row
|
||||
showValidationErrors(gridRow, iddatadb, result.errors);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validation passed — show confirm modal
|
||||
showConfirmAndExport(iddatadb, btn);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Validation error:", error);
|
||||
showExportResult({ success: false, message: "Errore di validazione: " + error.message });
|
||||
});
|
||||
}
|
||||
|
||||
function showConfirmAndExport(iddatadb, btn) {
|
||||
const confirmModalElement =
|
||||
document.getElementById("exportConfirmModal");
|
||||
if (!confirmModalElement) {
|
||||
alert("Errore: Modale di conferma non trovato");
|
||||
return;
|
||||
@@ -49,118 +379,17 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
console.log(`Confirmed export for iddatadb: ${iddatadb}`);
|
||||
confirmModal.hide();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("iddatadb", iddatadb);
|
||||
|
||||
try {
|
||||
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();
|
||||
|
||||
const gridRow = btn.closest(".grid-row");
|
||||
const data = await sendExport(iddatadb, gridRow);
|
||||
console.log("Export response:", data);
|
||||
|
||||
const responseModalElement =
|
||||
document.getElementById("exportResponseModal");
|
||||
if (!responseModalElement) {
|
||||
alert("Errore: Modale di risposta non trovato");
|
||||
return;
|
||||
}
|
||||
|
||||
const responseModal = new bootstrap.Modal(
|
||||
responseModalElement,
|
||||
{ keyboard: false },
|
||||
);
|
||||
const responseMessage = document.getElementById(
|
||||
"exportResponseMessage",
|
||||
);
|
||||
|
||||
if (data.success) {
|
||||
responseMessage.innerHTML =
|
||||
`${data.message.replace(/\n/g, "<br>")}` +
|
||||
`<br>ID CommessaWeb: ${data.idcommessaweb}` +
|
||||
`<br>Codice CommessaWeb: ${data.commessaweb}` +
|
||||
(data.totalPhotos > 0
|
||||
? `<br>Foto trovate: ${data.totalPhotos}`
|
||||
: "");
|
||||
document.getElementById(
|
||||
"exportResponseModalLabel",
|
||||
).textContent = "Esportazione Completata";
|
||||
responseModal.show();
|
||||
|
||||
// Update status badge
|
||||
const gridRow = btn.closest(".grid-row");
|
||||
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";
|
||||
}
|
||||
|
||||
// Insert/update CommessaWeb code span
|
||||
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.title = "CommessaWeb";
|
||||
const hiddenInput = statusCell.querySelector(
|
||||
'input[type="hidden"]',
|
||||
);
|
||||
hiddenInput
|
||||
? statusCell.insertBefore(cwSpan, hiddenInput)
|
||||
: statusCell.appendChild(cwSpan);
|
||||
}
|
||||
cwSpan.textContent = data.commessaweb;
|
||||
}
|
||||
} else {
|
||||
responseMessage.textContent = `Errore durante la generazione dei payload: ${data.message}`;
|
||||
document.getElementById(
|
||||
"exportResponseModalLabel",
|
||||
).textContent = "Errore Esportazione";
|
||||
responseModal.show();
|
||||
}
|
||||
|
||||
responseModalElement.addEventListener(
|
||||
"hidden.bs.modal",
|
||||
cleanupBackdrop,
|
||||
{ once: true },
|
||||
);
|
||||
showExportResult(data);
|
||||
} catch (error) {
|
||||
console.error("Export error:", error);
|
||||
const responseModalElement =
|
||||
document.getElementById("exportResponseModal");
|
||||
if (!responseModalElement) {
|
||||
alert("Errore: Modale di risposta non trovato");
|
||||
return;
|
||||
}
|
||||
const responseModal = new bootstrap.Modal(
|
||||
responseModalElement,
|
||||
{ keyboard: false },
|
||||
);
|
||||
document.getElementById(
|
||||
"exportResponseMessage",
|
||||
).textContent = `Errore: ${error.message}`;
|
||||
document.getElementById(
|
||||
"exportResponseModalLabel",
|
||||
).textContent = "Errore Esportazione";
|
||||
responseModal.show();
|
||||
responseModalElement.addEventListener(
|
||||
"hidden.bs.modal",
|
||||
cleanupBackdrop,
|
||||
{ once: true },
|
||||
);
|
||||
showExportResult({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -171,11 +400,13 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
confirmBtn.addEventListener("click", confirmHandler, { once: true });
|
||||
}
|
||||
|
||||
// ── Step 1: check unsaved changes, save if needed, then export ───────────
|
||||
// ── Single row click handler ────────────────────────────────────────────
|
||||
|
||||
exportButtons.forEach((btn) => {
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
if (batchRunning) return;
|
||||
|
||||
const rowIndex = btn.dataset.row;
|
||||
const iddatadb = btn.dataset.iddatadb;
|
||||
console.log(
|
||||
@@ -191,31 +422,352 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
);
|
||||
unsavedModal.show();
|
||||
|
||||
document.getElementById("saveAndExportBtn").addEventListener("click", () => {
|
||||
unsavedModal.hide();
|
||||
const saveBtn = gridRow.querySelector(".save-btn");
|
||||
if (!saveBtn) return;
|
||||
document.getElementById("saveAndExportBtn").addEventListener(
|
||||
"click",
|
||||
() => {
|
||||
unsavedModal.hide();
|
||||
const saveBtn = gridRow.querySelector(".save-btn");
|
||||
if (!saveBtn) return;
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
if (!gridRow.querySelector(".cell-changed")) {
|
||||
observer.disconnect();
|
||||
startExportConfirmFlow(iddatadb, btn);
|
||||
}
|
||||
});
|
||||
observer.observe(gridRow, {
|
||||
subtree: true,
|
||||
attributes: true,
|
||||
attributeFilter: ["class"],
|
||||
});
|
||||
const observer = new MutationObserver(() => {
|
||||
if (!gridRow.querySelector(".cell-changed")) {
|
||||
observer.disconnect();
|
||||
startExportConfirmFlow(iddatadb, btn);
|
||||
}
|
||||
});
|
||||
observer.observe(gridRow, {
|
||||
subtree: true,
|
||||
attributes: true,
|
||||
attributeFilter: ["class"],
|
||||
});
|
||||
|
||||
saveBtn.click();
|
||||
}, { once: true });
|
||||
saveBtn.click();
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// No unsaved changes — go straight to export confirm
|
||||
// No unsaved changes — go straight to validate + export confirm
|
||||
startExportConfirmFlow(iddatadb, btn);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Batch export (Export All) ───────────────────────────────────────────
|
||||
|
||||
const exportAllBtn = document.querySelector(".export-all-lims-btn");
|
||||
if (!exportAllBtn) return;
|
||||
|
||||
let batchCancelled = false;
|
||||
let pendingBatchConfirmHandler = null;
|
||||
|
||||
function collectEligibleRows() {
|
||||
const allRows = document.querySelectorAll(".grid-row[data-id]");
|
||||
const eligible = [];
|
||||
allRows.forEach((row) => {
|
||||
const statusBadge = row.querySelector(
|
||||
'.grid-cell[data-col="status"] .status-badge',
|
||||
);
|
||||
if (statusBadge && !statusBadge.classList.contains("status-l")) {
|
||||
const iddatadb = row.dataset.id;
|
||||
if (iddatadb) {
|
||||
eligible.push({ iddatadb, row });
|
||||
}
|
||||
}
|
||||
});
|
||||
return eligible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data-row index for a grid row element.
|
||||
*/
|
||||
function getRowIndex(row) {
|
||||
const cell = row.querySelector(".grid-cell[data-row]");
|
||||
return cell ? parseInt(cell.getAttribute("data-row")) : null;
|
||||
}
|
||||
|
||||
function hasUnsavedChanges() {
|
||||
return !!document.querySelector(".grid-row[data-id] .cell-changed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate all eligible rows, show errors, and return only the valid ones.
|
||||
*/
|
||||
async function validateAndFilter(eligibleRows) {
|
||||
const rowsToValidate = eligibleRows.map(({ iddatadb, row }) => ({
|
||||
iddatadb: parseInt(iddatadb),
|
||||
index: getRowIndex(row),
|
||||
}));
|
||||
|
||||
const validationData = await validateRows(rowsToValidate);
|
||||
|
||||
if (!validationData.success) {
|
||||
throw new Error(validationData.message || "Errore di validazione");
|
||||
}
|
||||
|
||||
const validRows = [];
|
||||
let invalidCount = 0;
|
||||
|
||||
for (const { iddatadb, row } of eligibleRows) {
|
||||
const rowIdx = getRowIndex(row);
|
||||
const result = validationData.results[rowIdx];
|
||||
|
||||
if (result && !result.valid) {
|
||||
showValidationErrors(row, iddatadb, result.errors);
|
||||
invalidCount++;
|
||||
} else {
|
||||
validRows.push({ iddatadb, row });
|
||||
}
|
||||
}
|
||||
|
||||
return { validRows, invalidCount };
|
||||
}
|
||||
|
||||
function showValidationSpinner(show) {
|
||||
const bar = document.getElementById("batchExportBar");
|
||||
const statusEl = document.getElementById("batchExportStatus");
|
||||
const cancelBtn = document.getElementById("exportBatchCancelBtn");
|
||||
if (show) {
|
||||
bar.style.display = "";
|
||||
statusEl.textContent = "Validazione in corso...";
|
||||
cancelBtn.style.display = "none";
|
||||
} else {
|
||||
bar.style.display = "none";
|
||||
cancelBtn.style.display = "";
|
||||
}
|
||||
}
|
||||
|
||||
function showBatchConfirm(eligibleRows) {
|
||||
clearValidationErrors();
|
||||
showValidationSpinner(true);
|
||||
|
||||
// Validate before showing confirm
|
||||
validateAndFilter(eligibleRows)
|
||||
.then(({ validRows, invalidCount }) => {
|
||||
showValidationSpinner(false);
|
||||
if (validRows.length === 0) {
|
||||
document.getElementById("exportResponseMessage").innerHTML =
|
||||
`Nessuna riga valida per l'esportazione.<br>` +
|
||||
`<strong>${invalidCount}</strong> righe con errori di validazione.`;
|
||||
document.getElementById("exportResponseModalLabel").textContent =
|
||||
"Validazione Fallita";
|
||||
new bootstrap.Modal(
|
||||
document.getElementById("exportResponseModal"),
|
||||
{ keyboard: false },
|
||||
).show();
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmModal = new bootstrap.Modal(
|
||||
document.getElementById("exportBatchConfirmModal"),
|
||||
{ keyboard: false },
|
||||
);
|
||||
|
||||
let countText = String(validRows.length);
|
||||
if (invalidCount > 0) {
|
||||
countText += ` (${invalidCount} escluse per errori di validazione)`;
|
||||
}
|
||||
document.getElementById("exportBatchCount").textContent = countText;
|
||||
confirmModal.show();
|
||||
|
||||
const confirmBtn = document.getElementById("exportBatchConfirmBtn");
|
||||
if (pendingBatchConfirmHandler) {
|
||||
confirmBtn.removeEventListener("click", pendingBatchConfirmHandler);
|
||||
}
|
||||
pendingBatchConfirmHandler = () => {
|
||||
pendingBatchConfirmHandler = null;
|
||||
confirmModal.hide();
|
||||
startBatchExport(validRows);
|
||||
};
|
||||
confirmBtn.addEventListener("click", pendingBatchConfirmHandler, { once: true });
|
||||
})
|
||||
.catch((error) => {
|
||||
showValidationSpinner(false);
|
||||
console.error("Batch validation error:", error);
|
||||
document.getElementById("exportResponseMessage").textContent =
|
||||
"Errore di validazione: " + error.message;
|
||||
document.getElementById("exportResponseModalLabel").textContent =
|
||||
"Errore Validazione";
|
||||
new bootstrap.Modal(
|
||||
document.getElementById("exportResponseModal"),
|
||||
{ keyboard: false },
|
||||
).show();
|
||||
});
|
||||
}
|
||||
|
||||
exportAllBtn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
if (batchRunning) return;
|
||||
|
||||
// Check unsaved changes first
|
||||
if (hasUnsavedChanges()) {
|
||||
const unsavedModal = new bootstrap.Modal(
|
||||
document.getElementById("exportBatchUnsavedModal"),
|
||||
{ keyboard: false },
|
||||
);
|
||||
unsavedModal.show();
|
||||
|
||||
document
|
||||
.getElementById("batchSaveAndExportBtn")
|
||||
.addEventListener(
|
||||
"click",
|
||||
() => {
|
||||
unsavedModal.hide();
|
||||
// Trigger Save All, then proceed
|
||||
const saveAllEl =
|
||||
document.querySelector(".save-all-btn");
|
||||
if (!saveAllEl) return;
|
||||
|
||||
// Watch for all .cell-changed to disappear
|
||||
const observer = new MutationObserver(() => {
|
||||
if (!hasUnsavedChanges()) {
|
||||
observer.disconnect();
|
||||
const eligibleRows = collectEligibleRows();
|
||||
if (eligibleRows.length === 0) {
|
||||
document.getElementById(
|
||||
"exportResponseMessage",
|
||||
).textContent =
|
||||
"Tutte le righe sono già state esportate al LIMS.";
|
||||
document.getElementById(
|
||||
"exportResponseModalLabel",
|
||||
).textContent = "Export All";
|
||||
new bootstrap.Modal(
|
||||
document.getElementById(
|
||||
"exportResponseModal",
|
||||
),
|
||||
{ keyboard: false },
|
||||
).show();
|
||||
return;
|
||||
}
|
||||
showBatchConfirm(eligibleRows);
|
||||
}
|
||||
});
|
||||
observer.observe(
|
||||
document.querySelector(".grid-container"),
|
||||
{
|
||||
subtree: true,
|
||||
attributes: true,
|
||||
attributeFilter: ["class"],
|
||||
},
|
||||
);
|
||||
|
||||
saveAllEl.click();
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const eligibleRows = collectEligibleRows();
|
||||
|
||||
if (eligibleRows.length === 0) {
|
||||
document.getElementById("exportResponseMessage").textContent =
|
||||
"Tutte le righe sono già state esportate al LIMS.";
|
||||
document.getElementById("exportResponseModalLabel").textContent =
|
||||
"Export All";
|
||||
const modal = new bootstrap.Modal(
|
||||
document.getElementById("exportResponseModal"),
|
||||
{ keyboard: false },
|
||||
);
|
||||
modal.show();
|
||||
return;
|
||||
}
|
||||
|
||||
showBatchConfirm(eligibleRows);
|
||||
});
|
||||
|
||||
function startBatchExport(eligibleRows) {
|
||||
batchCancelled = false;
|
||||
batchRunning = true;
|
||||
// Don't clear validation errors — they should stay visible for invalid rows
|
||||
const batchUuid = crypto.randomUUID();
|
||||
const total = eligibleRows.length;
|
||||
let processed = 0;
|
||||
let succeeded = 0;
|
||||
let failed = 0;
|
||||
|
||||
// Disable all row buttons
|
||||
disableAllRowButtons();
|
||||
|
||||
// Show inline status bar
|
||||
const bar = document.getElementById("batchExportBar");
|
||||
const statusEl = document.getElementById("batchExportStatus");
|
||||
const cancelBtn = document.getElementById("exportBatchCancelBtn");
|
||||
bar.style.display = "";
|
||||
cancelBtn.disabled = false;
|
||||
statusEl.textContent = `Esportazione 0 / ${total}...`;
|
||||
|
||||
// Cancel handler
|
||||
cancelBtn.addEventListener(
|
||||
"click",
|
||||
() => {
|
||||
batchCancelled = true;
|
||||
statusEl.textContent =
|
||||
"Annullamento... (attendi riga corrente)";
|
||||
cancelBtn.disabled = true;
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
|
||||
(async () => {
|
||||
for (let i = 0; i < eligibleRows.length; i++) {
|
||||
if (batchCancelled) break;
|
||||
|
||||
const { iddatadb, row } = eligibleRows[i];
|
||||
statusEl.textContent = `Esportazione ${processed + 1} / ${total} (id: ${iddatadb})...`;
|
||||
|
||||
// Highlight current row
|
||||
setRowExporting(row, true);
|
||||
|
||||
try {
|
||||
const data = await sendExport(iddatadb, row, batchUuid);
|
||||
processed++;
|
||||
if (data.success) {
|
||||
succeeded++;
|
||||
} else {
|
||||
failed++;
|
||||
showRowError(row, iddatadb, data.message || "Errore sconosciuto");
|
||||
}
|
||||
} catch (error) {
|
||||
processed++;
|
||||
failed++;
|
||||
showRowError(row, iddatadb, error.message);
|
||||
}
|
||||
|
||||
// Remove highlight from current row
|
||||
setRowExporting(row, false);
|
||||
}
|
||||
|
||||
// Finished
|
||||
batchRunning = false;
|
||||
enableAllRowButtons();
|
||||
bar.style.display = "none";
|
||||
|
||||
// Show result modal
|
||||
const msgEl = document.getElementById("exportResponseMessage");
|
||||
const labelEl = document.getElementById("exportResponseModalLabel");
|
||||
|
||||
if (batchCancelled) {
|
||||
labelEl.textContent = "Export All — Annullato";
|
||||
msgEl.innerHTML =
|
||||
`Esportate: <strong>${succeeded}</strong><br>` +
|
||||
`Errori: <strong>${failed}</strong><br>` +
|
||||
`Non processate: <strong>${total - processed}</strong>`;
|
||||
} else if (failed === 0) {
|
||||
labelEl.textContent = "Export All — Completato";
|
||||
msgEl.innerHTML = `Tutte le <strong>${succeeded}</strong> righe esportate con successo.`;
|
||||
} else {
|
||||
labelEl.textContent = "Export All — Completato con errori";
|
||||
msgEl.innerHTML =
|
||||
`Esportate: <strong>${succeeded}</strong><br>` +
|
||||
`Errori: <strong>${failed}</strong>`;
|
||||
}
|
||||
|
||||
const modalEl = document.getElementById("exportResponseModal");
|
||||
const modal = new bootstrap.Modal(modalEl, { keyboard: false });
|
||||
modal.show();
|
||||
modalEl.addEventListener("hidden.bs.modal", cleanupBackdrop, { once: true });
|
||||
})();
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user