diff --git a/public/userarea/class/VisualLimsApiClient.class.php b/public/userarea/class/VisualLimsApiClient.class.php index 369d5857..a040e851 100644 --- a/public/userarea/class/VisualLimsApiClient.class.php +++ b/public/userarea/class/VisualLimsApiClient.class.php @@ -43,6 +43,7 @@ class VisualLimsApiClient { $ch = curl_init("{$this->baseUrl}/api/authentication/authenticate"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_USERAGENT, 'VisualLims-Client/1.0'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'Username' => $this->username, @@ -105,6 +106,7 @@ class VisualLimsApiClient $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_USERAGENT, 'VisualLims-Client/1.0'); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Authorization: Bearer {$token}", "Accept: application/json" @@ -144,6 +146,7 @@ class VisualLimsApiClient $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_USERAGENT, 'VisualLims-Client/1.0'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); curl_setopt($ch, CURLOPT_HTTPHEADER, [ @@ -177,6 +180,7 @@ class VisualLimsApiClient $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_USERAGENT, 'VisualLims-Client/1.0'); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH"); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); curl_setopt($ch, CURLOPT_HTTPHEADER, [ @@ -225,6 +229,7 @@ class VisualLimsApiClient $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_USERAGENT, 'VisualLims-Client/1.0'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_HTTPHEADER, [ @@ -268,6 +273,7 @@ class VisualLimsApiClient $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_USERAGENT, 'VisualLims-Client/1.0'); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Authorization: Bearer {$token}", "Accept: */*" diff --git a/public/userarea/export_to_lims.js b/public/userarea/export_to_lims.js index cf28fad0..0f043bb3 100644 --- a/public/userarea/export_to_lims.js +++ b/public/userarea/export_to_lims.js @@ -43,17 +43,23 @@ document.addEventListener("DOMContentLoaded", () => { * 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"); + 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"); }); - 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(); } @@ -90,16 +96,24 @@ document.addEventListener("DOMContentLoaded", () => { } }); if (targetIndex) { - const rowIndex = row.querySelector(".grid-cell[data-row]")?.getAttribute("data-row"); + 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}"]`); + 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"); + 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}"]`); + cell = row.querySelector( + `.grid-cell[data-col="${err.field}"][data-row="${rowIndex}"]`, + ); } } @@ -194,8 +208,9 @@ document.addEventListener("DOMContentLoaded", () => { * Show the result of a single export in the response modal. */ function showExportResult(data) { - const responseModalElement = - document.getElementById("exportResponseModal"); + const responseModalElement = document.getElementById( + "exportResponseModal", + ); if (!responseModalElement) { alert("Errore: Modale di risposta non trovato"); return; @@ -276,20 +291,28 @@ document.addEventListener("DOMContentLoaded", () => { errorEl.className = "batch-error-msg"; errorEl.textContent = "⚠ Errore — clicca per dettagli"; errorEl.addEventListener("click", () => { - document.getElementById("exportResponseMessage").innerHTML = message.replace(/\n/g, "
"); - document.getElementById("exportResponseModalLabel").textContent = "Errore Validazione (id: " + iddatadb + ")"; - new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show(); + document.getElementById("exportResponseMessage").innerHTML = + message.replace(/\n/g, "
"); + 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(); - }); + 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() { @@ -333,15 +356,23 @@ document.addEventListener("DOMContentLoaded", () => { // Show validating state on the row 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: parseInt(rowIndex) }]) + validateRows([ + { iddatadb: parseInt(iddatadb), index: parseInt(rowIndex) }, + ]) .then((validationData) => { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); if (!validationData.success) { - showExportResult({ success: false, message: validationData.message || "Errore di validazione" }); + showExportResult({ + success: false, + message: + validationData.message || "Errore di validazione", + }); return; } @@ -360,7 +391,10 @@ document.addEventListener("DOMContentLoaded", () => { gridRow.classList.remove("batch-disabled"); console.error("Validation error:", error); - showExportResult({ success: false, message: "Errore di validazione: " + error.message }); + showExportResult({ + success: false, + message: "Errore di validazione: " + error.message, + }); }); } @@ -402,7 +436,11 @@ document.addEventListener("DOMContentLoaded", () => { gridRow.classList.remove("batch-disabled"); if (!data.success) { - showRowError(gridRow, iddatadb, data.message || "Errore sconosciuto"); + showRowError( + gridRow, + iddatadb, + data.message || "Errore sconosciuto", + ); } showExportResult(data); } catch (error) { @@ -577,8 +615,9 @@ document.addEventListener("DOMContentLoaded", () => { document.getElementById("exportResponseMessage").innerHTML = `Nessuna riga valida per l'esportazione.
` + `${invalidCount} righe con errori di validazione.`; - document.getElementById("exportResponseModalLabel").textContent = - "Validazione Fallita"; + document.getElementById( + "exportResponseModalLabel", + ).textContent = "Validazione Fallita"; new bootstrap.Modal( document.getElementById("exportResponseModal"), { keyboard: false }, @@ -595,27 +634,38 @@ document.addEventListener("DOMContentLoaded", () => { if (invalidCount > 0) { countText += ` (${invalidCount} escluse per errori di validazione)`; } - document.getElementById("exportBatchCount").textContent = countText; + document.getElementById("exportBatchCount").textContent = + countText; confirmModal.show(); - const confirmBtn = document.getElementById("exportBatchConfirmBtn"); + const confirmBtn = document.getElementById( + "exportBatchConfirmBtn", + ); if (pendingBatchConfirmHandler) { - confirmBtn.removeEventListener("click", 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) => { showValidationSpinner(false); console.error("Batch validation error:", error); document.getElementById("exportResponseMessage").textContent = "Errore di validazione: " + error.message; - document.getElementById("exportResponseModalLabel").textContent = - "Errore Validazione"; + document.getElementById( + "exportResponseModalLabel", + ).textContent = "Errore Validazione"; new bootstrap.Modal( document.getElementById("exportResponseModal"), { keyboard: false }, @@ -635,54 +685,51 @@ document.addEventListener("DOMContentLoaded", () => { ); unsavedModal.show(); - document - .getElementById("batchSaveAndExportBtn") - .addEventListener( - "click", - () => { - unsavedModal.hide(); - // Trigger Save All, then proceed - const saveAllEl = - document.querySelector(".save-all-btn"); - if (!saveAllEl) return; + 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) { + // 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( - "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); + "exportResponseModal", + ), + { keyboard: false }, + ).show(); + return; } - }); - observer.observe( - document.querySelector(".grid-container"), - { - subtree: true, - attributes: true, - attributeFilter: ["class"], - }, - ); + showBatchConfirm(eligibleRows); + } + }); + observer.observe( + document.querySelector(".grid-container"), + { + subtree: true, + attributes: true, + attributeFilter: ["class"], + }, + ); - saveAllEl.click(); - }, - { once: true }, - ); + saveAllEl.click(); + }, + { once: true }, + ); return; } @@ -754,7 +801,11 @@ document.addEventListener("DOMContentLoaded", () => { succeeded++; } else { failed++; - showRowError(row, iddatadb, data.message || "Errore sconosciuto"); + showRowError( + row, + iddatadb, + data.message || "Errore sconosciuto", + ); } } catch (error) { processed++; @@ -794,7 +845,9 @@ document.addEventListener("DOMContentLoaded", () => { const modalEl = document.getElementById("exportResponseModal"); const modal = new bootstrap.Modal(modalEl, { keyboard: false }); modal.show(); - modalEl.addEventListener("hidden.bs.modal", cleanupBackdrop, { once: true }); + modalEl.addEventListener("hidden.bs.modal", cleanupBackdrop, { + once: true, + }); })(); } });