fixed export and save to selection
This commit is contained in:
@@ -14,6 +14,7 @@ $targetList = $data['target_iddatadb_list'] ?? [];
|
|||||||
// (comportamento di default richiesto, coerente con la checkbox pre-spuntata)
|
// (comportamento di default richiesto, coerente con la checkbox pre-spuntata)
|
||||||
$cloneNotes = array_key_exists('clone_notes', $data) ? !empty($data['clone_notes']) : true;
|
$cloneNotes = array_key_exists('clone_notes', $data) ? !empty($data['clone_notes']) : true;
|
||||||
$cloneAnalyses = !empty($data['clone_analyses']);
|
$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) {
|
$targetIds = array_values(array_unique(array_filter(array_map('intval', (array)$targetList), function ($v) use ($sourceIddatadb) {
|
||||||
return $v > 0 && $v !== $sourceIddatadb;
|
return $v > 0 && $v !== $sourceIddatadb;
|
||||||
@@ -93,6 +94,28 @@ try {
|
|||||||
$details = [];
|
$details = [];
|
||||||
$totalClonedParts = 0;
|
$totalClonedParts = 0;
|
||||||
$totalClonedAnalyses = 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
|
// 3. Clone source parts to each target record
|
||||||
foreach ($targetIds as $targetIddatadb) {
|
foreach ($targetIds as $targetIddatadb) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* Single export + batch export (Export All) with validation.
|
* Single export + batch export (Export All) with validation.
|
||||||
*/
|
*/
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
let pendingConfirmHandler = null;
|
let pendingConfirmHandler = null;
|
||||||
let batchRunning = false;
|
let batchRunning = false;
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
// ── Helpers ──────────────────────────────────────────────────────────
|
// ── Helpers ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function cleanupBackdrop() {
|
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.classList.remove("modal-open");
|
||||||
document.body.style.paddingRight = "";
|
document.body.style.paddingRight = "";
|
||||||
const overlay = document.querySelector(".overlay.toggle-icon");
|
const overlay = document.querySelector(".overlay.toggle-icon");
|
||||||
@@ -29,7 +29,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getRowIndexByIddatadb(iddatadb) {
|
function getRowIndexByIddatadb(iddatadb) {
|
||||||
return (window.gridData || []).findIndex(r => String(r.iddatadb) === String(iddatadb));
|
return (window.gridData || []).findIndex(
|
||||||
|
(r) => String(r.iddatadb) === String(iddatadb),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Validation ──────────────────────────────────────────────────────
|
// ── Validation ──────────────────────────────────────────────────────
|
||||||
@@ -40,21 +42,31 @@
|
|||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ rows: rowsToValidate }),
|
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();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearValidationErrors() {
|
function clearValidationErrors() {
|
||||||
// Clear from gridData
|
// Clear from gridData
|
||||||
(window.gridData || []).forEach(row => { delete row._validationErrors; delete row._exportError; });
|
(window.gridData || []).forEach((row) => {
|
||||||
|
delete row._validationErrors;
|
||||||
document.querySelectorAll(".grid-cell.validation-error").forEach(cell => {
|
delete row._exportError;
|
||||||
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"));
|
|
||||||
|
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();
|
clearAllRowErrors();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +79,7 @@
|
|||||||
gridRow.classList.add("validation-row-error");
|
gridRow.classList.add("validation-row-error");
|
||||||
const messages = [];
|
const messages = [];
|
||||||
|
|
||||||
errors.forEach(err => {
|
errors.forEach((err) => {
|
||||||
messages.push(err.message);
|
messages.push(err.message);
|
||||||
if (!err.field) return;
|
if (!err.field) return;
|
||||||
|
|
||||||
@@ -76,19 +88,26 @@
|
|||||||
const label = err.field.substring("field_label:".length);
|
const label = err.field.substring("field_label:".length);
|
||||||
const headers = document.querySelectorAll(".grid-header");
|
const headers = document.querySelectorAll(".grid-header");
|
||||||
let targetIndex = null;
|
let targetIndex = null;
|
||||||
headers.forEach(h => {
|
headers.forEach((h) => {
|
||||||
if (h.textContent.trim() === label) targetIndex = h.getAttribute("data-index");
|
if (h.textContent.trim() === label)
|
||||||
|
targetIndex = h.getAttribute("data-index");
|
||||||
});
|
});
|
||||||
if (targetIndex) {
|
if (targetIndex) {
|
||||||
cell = gridRow.querySelector(`.grid-cell[data-index="${targetIndex}"]`);
|
cell = gridRow.querySelector(
|
||||||
|
`.grid-cell[data-index="${targetIndex}"]`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cell = gridRow.querySelector(`.grid-cell[data-col="${err.field}"]`);
|
cell = gridRow.querySelector(
|
||||||
|
`.grid-cell[data-col="${err.field}"]`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cell) {
|
if (cell) {
|
||||||
cell.classList.add("validation-error");
|
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");
|
let tooltip = cell.querySelector(".validation-tooltip");
|
||||||
if (!tooltip) {
|
if (!tooltip) {
|
||||||
tooltip = document.createElement("div");
|
tooltip = document.createElement("div");
|
||||||
@@ -109,34 +128,43 @@
|
|||||||
formData.append("iddatadb", iddatadb);
|
formData.append("iddatadb", iddatadb);
|
||||||
if (batchUuid) formData.append("batch_uuid", batchUuid);
|
if (batchUuid) formData.append("batch_uuid", batchUuid);
|
||||||
|
|
||||||
const response = await fetch("export_to_lims.php", { method: "POST", body: formData });
|
const response = await fetch("export_to_lims.php", {
|
||||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
if (!response.ok)
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
// Update gridData
|
// Update gridData
|
||||||
const idx = getRowIndexByIddatadb(iddatadb);
|
const idx = getRowIndexByIddatadb(iddatadb);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
window.gridData[idx].status = 'l';
|
window.gridData[idx].status = "l";
|
||||||
window.gridData[idx].commessaweb = data.commessaweb;
|
window.gridData[idx].commessaweb = data.commessaweb;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update visible DOM row
|
// Update visible DOM row
|
||||||
const gridRow = getGridRow(iddatadb);
|
const gridRow = getGridRow(iddatadb);
|
||||||
if (gridRow) {
|
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) {
|
if (statusBadge) {
|
||||||
statusBadge.classList.remove("status-i", "status-P");
|
statusBadge.classList.remove("status-i", "status-P");
|
||||||
statusBadge.classList.add("status-l");
|
statusBadge.classList.add("status-l");
|
||||||
statusBadge.textContent = "To LIMS";
|
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) {
|
if (statusCell && data.commessaweb) {
|
||||||
let cwSpan = statusCell.querySelector(".commessaweb-code");
|
let cwSpan = statusCell.querySelector(".commessaweb-code");
|
||||||
if (!cwSpan) {
|
if (!cwSpan) {
|
||||||
cwSpan = document.createElement("span");
|
cwSpan = document.createElement("span");
|
||||||
cwSpan.className = "commessaweb-code";
|
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);
|
statusCell.appendChild(cwSpan);
|
||||||
}
|
}
|
||||||
cwSpan.textContent = data.commessaweb;
|
cwSpan.textContent = data.commessaweb;
|
||||||
@@ -164,7 +192,8 @@
|
|||||||
const label = document.getElementById("exportResponseModalLabel");
|
const label = document.getElementById("exportResponseModalLabel");
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
msg.innerHTML = `${data.message.replace(/\n/g, "<br>")}` +
|
msg.innerHTML =
|
||||||
|
`${data.message.replace(/\n/g, "<br>")}` +
|
||||||
`<br>ID CommessaWeb: ${data.idcommessaweb}` +
|
`<br>ID CommessaWeb: ${data.idcommessaweb}` +
|
||||||
`<br>Codice CommessaWeb: ${data.commessaweb}` +
|
`<br>Codice CommessaWeb: ${data.commessaweb}` +
|
||||||
(data.totalPhotos > 0 ? `<br>Foto: ${data.totalPhotos}` : "");
|
(data.totalPhotos > 0 ? `<br>Foto: ${data.totalPhotos}` : "");
|
||||||
@@ -186,10 +215,14 @@
|
|||||||
row.classList.remove("batch-disabled");
|
row.classList.remove("batch-disabled");
|
||||||
row.classList.add("batch-exporting");
|
row.classList.add("batch-exporting");
|
||||||
if (btnCell) {
|
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");
|
const spinner = document.createElement("span");
|
||||||
spinner.className = "batch-row-spinner";
|
spinner.className = "batch-row-spinner";
|
||||||
spinner.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Exporting...';
|
spinner.innerHTML =
|
||||||
|
'<i class="fas fa-spinner fa-spin"></i> Exporting...';
|
||||||
btnCell.appendChild(spinner);
|
btnCell.appendChild(spinner);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -197,7 +230,10 @@
|
|||||||
if (btnCell) {
|
if (btnCell) {
|
||||||
const spinner = btnCell.querySelector(".batch-row-spinner");
|
const spinner = btnCell.querySelector(".batch-row-spinner");
|
||||||
if (spinner) spinner.remove();
|
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.className = "batch-error-msg";
|
||||||
errorEl.textContent = "Warning — click for details";
|
errorEl.textContent = "Warning — click for details";
|
||||||
errorEl.addEventListener("click", () => {
|
errorEl.addEventListener("click", () => {
|
||||||
document.getElementById("exportResponseMessage").innerHTML = message.replace(/\n/g, "<br>");
|
document.getElementById("exportResponseMessage").innerHTML =
|
||||||
document.getElementById("exportResponseModalLabel").textContent = "Error (id: " + iddatadb + ")";
|
message.replace(/\n/g, "<br>");
|
||||||
new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show();
|
document.getElementById(
|
||||||
|
"exportResponseModalLabel",
|
||||||
|
).textContent = "Error (id: " + iddatadb + ")";
|
||||||
|
new bootstrap.Modal(
|
||||||
|
document.getElementById("exportResponseModal"),
|
||||||
|
{ keyboard: false },
|
||||||
|
).show();
|
||||||
});
|
});
|
||||||
btnCell.appendChild(errorEl);
|
btnCell.appendChild(errorEl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearAllRowErrors() {
|
function clearAllRowErrors() {
|
||||||
document.querySelectorAll(".grid-row.batch-row-error").forEach(row => {
|
document
|
||||||
row.classList.remove("batch-row-error");
|
.querySelectorAll(".grid-row.batch-row-error")
|
||||||
const msg = row.querySelector(".batch-error-msg");
|
.forEach((row) => {
|
||||||
if (msg) msg.remove();
|
row.classList.remove("batch-row-error");
|
||||||
});
|
const msg = row.querySelector(".batch-error-msg");
|
||||||
|
if (msg) msg.remove();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableAllRowButtons() {
|
function disableAllRowButtons() {
|
||||||
document.querySelectorAll(".grid-row[data-id]").forEach(row => row.classList.add("batch-disabled"));
|
document
|
||||||
const toggle = document.querySelector(".actions-dropdown .dropdown-toggle");
|
.querySelectorAll(".grid-row[data-id]")
|
||||||
if (toggle) { toggle.disabled = true; toggle.style.opacity = "0.5"; toggle.style.pointerEvents = "none"; }
|
.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() {
|
function enableAllRowButtons() {
|
||||||
document.querySelectorAll(".grid-row[data-id]").forEach(row => row.classList.remove("batch-disabled"));
|
document
|
||||||
const toggle = document.querySelector(".actions-dropdown .dropdown-toggle");
|
.querySelectorAll(".grid-row[data-id]")
|
||||||
if (toggle) { toggle.disabled = false; toggle.style.opacity = ""; toggle.style.pointerEvents = ""; }
|
.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 ────────────────────
|
// ── Single row export: validate → confirm → send ────────────────────
|
||||||
@@ -250,40 +310,61 @@
|
|||||||
if (gridRow) {
|
if (gridRow) {
|
||||||
setRowExporting(gridRow, true);
|
setRowExporting(gridRow, true);
|
||||||
const spinner = gridRow.querySelector(".batch-row-spinner");
|
const spinner = gridRow.querySelector(".batch-row-spinner");
|
||||||
if (spinner) spinner.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Validating...';
|
if (spinner)
|
||||||
|
spinner.innerHTML =
|
||||||
|
'<i class="fas fa-spinner fa-spin"></i> Validating...';
|
||||||
}
|
}
|
||||||
|
|
||||||
validateRows([{ iddatadb: parseInt(iddatadb), index: rowIndex }])
|
validateRows([{ iddatadb: parseInt(iddatadb), index: rowIndex }])
|
||||||
.then(validationData => {
|
.then((validationData) => {
|
||||||
if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); }
|
if (gridRow) {
|
||||||
|
setRowExporting(gridRow, false);
|
||||||
|
gridRow.classList.remove("batch-disabled");
|
||||||
|
}
|
||||||
|
|
||||||
if (!validationData.success) {
|
if (!validationData.success) {
|
||||||
showExportResult({ success: false, message: validationData.message || "Validation error" });
|
showExportResult({
|
||||||
|
success: false,
|
||||||
|
message: validationData.message || "Validation error",
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const result = validationData.results[rowIndex];
|
const result = validationData.results[rowIndex];
|
||||||
if (result && !result.valid) {
|
if (result && !result.valid) {
|
||||||
if (gridRow) showValidationErrors(gridRow, iddatadb, result.errors);
|
if (gridRow)
|
||||||
|
showValidationErrors(gridRow, iddatadb, result.errors);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showConfirmAndExport(iddatadb, rowIndex);
|
showConfirmAndExport(iddatadb, rowIndex);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); }
|
if (gridRow) {
|
||||||
showExportResult({ success: false, message: "Validation error: " + error.message });
|
setRowExporting(gridRow, false);
|
||||||
|
gridRow.classList.remove("batch-disabled");
|
||||||
|
}
|
||||||
|
showExportResult({
|
||||||
|
success: false,
|
||||||
|
message: "Validation error: " + error.message,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showConfirmAndExport(iddatadb, rowIndex) {
|
function showConfirmAndExport(iddatadb, rowIndex) {
|
||||||
const confirmModalElement = document.getElementById("exportConfirmModal");
|
const confirmModalElement =
|
||||||
|
document.getElementById("exportConfirmModal");
|
||||||
if (!confirmModalElement) return;
|
if (!confirmModalElement) return;
|
||||||
|
|
||||||
const confirmModal = new bootstrap.Modal(confirmModalElement, { keyboard: false });
|
const confirmModal = new bootstrap.Modal(confirmModalElement, {
|
||||||
|
keyboard: false,
|
||||||
|
});
|
||||||
document.getElementById("exportIddatadb").textContent = iddatadb;
|
document.getElementById("exportIddatadb").textContent = iddatadb;
|
||||||
confirmModal.show();
|
confirmModal.show();
|
||||||
|
|
||||||
const confirmBtn = document.getElementById("exportConfirmBtn");
|
const confirmBtn = document.getElementById("exportConfirmBtn");
|
||||||
if (!confirmBtn) { confirmModal.hide(); return; }
|
if (!confirmBtn) {
|
||||||
|
confirmModal.hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const confirmHandler = async () => {
|
const confirmHandler = async () => {
|
||||||
pendingConfirmHandler = null;
|
pendingConfirmHandler = null;
|
||||||
@@ -294,24 +375,36 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await sendExport(iddatadb);
|
const data = await sendExport(iddatadb);
|
||||||
if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); }
|
if (gridRow) {
|
||||||
if (!data.success) showRowError(gridRow, iddatadb, data.message || "Unknown error");
|
setRowExporting(gridRow, false);
|
||||||
|
gridRow.classList.remove("batch-disabled");
|
||||||
|
}
|
||||||
|
if (!data.success)
|
||||||
|
showRowError(
|
||||||
|
gridRow,
|
||||||
|
iddatadb,
|
||||||
|
data.message || "Unknown error",
|
||||||
|
);
|
||||||
showExportResult(data);
|
showExportResult(data);
|
||||||
} catch (error) {
|
} 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);
|
showRowError(gridRow, iddatadb, error.message);
|
||||||
showExportResult({ success: false, message: error.message });
|
showExportResult({ success: false, message: error.message });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (pendingConfirmHandler) confirmBtn.removeEventListener("click", pendingConfirmHandler);
|
if (pendingConfirmHandler)
|
||||||
|
confirmBtn.removeEventListener("click", pendingConfirmHandler);
|
||||||
pendingConfirmHandler = confirmHandler;
|
pendingConfirmHandler = confirmHandler;
|
||||||
confirmBtn.addEventListener("click", confirmHandler, { once: true });
|
confirmBtn.addEventListener("click", confirmHandler, { once: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Single row click (event delegation) ─────────────────────────────
|
// ── Single row click (event delegation) ─────────────────────────────
|
||||||
|
|
||||||
$(document).on('click', '.export-lims-btn', function (e) {
|
$(document).on("click", ".export-lims-btn", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (batchRunning) return;
|
if (batchRunning) return;
|
||||||
|
|
||||||
@@ -322,24 +415,34 @@
|
|||||||
// Check unsaved changes for this row
|
// Check unsaved changes for this row
|
||||||
const dataRow = window.gridData?.[rowIndex];
|
const dataRow = window.gridData?.[rowIndex];
|
||||||
if (dataRow && dataRow._dirty) {
|
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();
|
unsavedModal.show();
|
||||||
|
|
||||||
document.getElementById("saveAndExportBtn")?.addEventListener("click", () => {
|
document.getElementById("saveAndExportBtn")?.addEventListener(
|
||||||
unsavedModal.hide();
|
"click",
|
||||||
// Save first, then export
|
() => {
|
||||||
const formData = window.buildSavePayload(rowIndex);
|
unsavedModal.hide();
|
||||||
fetch('save_edited_row.php', { method: 'POST', body: formData })
|
// Save first, then export
|
||||||
.then(r => r.json())
|
const formData = window.buildSavePayload(rowIndex);
|
||||||
.then(result => {
|
fetch("save_edited_row.php", {
|
||||||
if (result.success) {
|
method: "POST",
|
||||||
dataRow._dirty = false;
|
body: formData,
|
||||||
startExportConfirmFlow(iddatadb, rowIndex);
|
})
|
||||||
} else {
|
.then((r) => r.json())
|
||||||
alert('Save failed: ' + result.message);
|
.then((result) => {
|
||||||
}
|
if (result.success) {
|
||||||
});
|
dataRow._dirty = false;
|
||||||
}, { once: true });
|
startExportConfirmFlow(iddatadb, rowIndex);
|
||||||
|
} else {
|
||||||
|
alert("Save failed: " + result.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ once: true },
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,18 +452,33 @@
|
|||||||
// ── Batch export (Export All) ───────────────────────────────────────
|
// ── Batch export (Export All) ───────────────────────────────────────
|
||||||
|
|
||||||
function collectEligibleRows() {
|
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 = [];
|
const eligible = [];
|
||||||
(window.gridData || []).forEach((row, index) => {
|
(window.gridData || []).forEach((row, index) => {
|
||||||
if (row.status !== 'l') {
|
if (row.status === "l") return;
|
||||||
eligible.push({ iddatadb: row.iddatadb, index, row: getGridRow(row.iddatadb) });
|
if (target && !target.has(parseInt(row.iddatadb, 10))) return;
|
||||||
}
|
eligible.push({
|
||||||
|
iddatadb: row.iddatadb,
|
||||||
|
index,
|
||||||
|
row: getGridRow(row.iddatadb),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
return eligible;
|
return eligible;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasUnsavedChanges() {
|
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) {
|
async function validateAndFilter(eligibleRows) {
|
||||||
@@ -369,7 +487,8 @@
|
|||||||
index,
|
index,
|
||||||
}));
|
}));
|
||||||
const validationData = await validateRows(rowsToValidate);
|
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 = [];
|
const validRows = [];
|
||||||
let invalidCount = 0;
|
let invalidCount = 0;
|
||||||
@@ -377,7 +496,12 @@
|
|||||||
for (const item of eligibleRows) {
|
for (const item of eligibleRows) {
|
||||||
const result = validationData.results[item.index];
|
const result = validationData.results[item.index];
|
||||||
if (result && !result.valid) {
|
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++;
|
invalidCount++;
|
||||||
} else {
|
} else {
|
||||||
validRows.push(item);
|
validRows.push(item);
|
||||||
@@ -410,54 +534,92 @@
|
|||||||
if (validRows.length === 0) {
|
if (validRows.length === 0) {
|
||||||
document.getElementById("exportResponseMessage").innerHTML =
|
document.getElementById("exportResponseMessage").innerHTML =
|
||||||
`No valid rows for export.<br><strong>${invalidCount}</strong> rows with validation errors.`;
|
`No valid rows for export.<br><strong>${invalidCount}</strong> rows with validation errors.`;
|
||||||
document.getElementById("exportResponseModalLabel").textContent = "Validation Failed";
|
document.getElementById(
|
||||||
new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show();
|
"exportResponseModalLabel",
|
||||||
|
).textContent = "Validation Failed";
|
||||||
|
new bootstrap.Modal(
|
||||||
|
document.getElementById("exportResponseModal"),
|
||||||
|
{ keyboard: false },
|
||||||
|
).show();
|
||||||
return;
|
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);
|
let countText = String(validRows.length);
|
||||||
if (invalidCount > 0) countText += ` (${invalidCount} excluded due to errors)`;
|
if (invalidCount > 0)
|
||||||
document.getElementById("exportBatchCount").textContent = countText;
|
countText += ` (${invalidCount} excluded due to errors)`;
|
||||||
|
document.getElementById("exportBatchCount").textContent =
|
||||||
|
countText;
|
||||||
confirmModal.show();
|
confirmModal.show();
|
||||||
|
|
||||||
const confirmBtn = document.getElementById("exportBatchConfirmBtn");
|
const confirmBtn = document.getElementById(
|
||||||
if (pendingBatchConfirmHandler) confirmBtn.removeEventListener("click", pendingBatchConfirmHandler);
|
"exportBatchConfirmBtn",
|
||||||
|
);
|
||||||
|
if (pendingBatchConfirmHandler)
|
||||||
|
confirmBtn.removeEventListener(
|
||||||
|
"click",
|
||||||
|
pendingBatchConfirmHandler,
|
||||||
|
);
|
||||||
pendingBatchConfirmHandler = () => {
|
pendingBatchConfirmHandler = () => {
|
||||||
pendingBatchConfirmHandler = null;
|
pendingBatchConfirmHandler = null;
|
||||||
confirmModal.hide();
|
confirmModal.hide();
|
||||||
startBatchExport(validRows);
|
startBatchExport(validRows);
|
||||||
};
|
};
|
||||||
confirmBtn.addEventListener("click", pendingBatchConfirmHandler, { once: true });
|
confirmBtn.addEventListener(
|
||||||
|
"click",
|
||||||
|
pendingBatchConfirmHandler,
|
||||||
|
{ once: true },
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
showValidationSpinner(false);
|
showValidationSpinner(false);
|
||||||
document.getElementById("exportResponseMessage").textContent = "Validation error: " + error.message;
|
document.getElementById("exportResponseMessage").textContent =
|
||||||
document.getElementById("exportResponseModalLabel").textContent = "Validation Error";
|
"Validation error: " + error.message;
|
||||||
new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show();
|
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();
|
e.preventDefault();
|
||||||
if (batchRunning) return;
|
if (batchRunning) return;
|
||||||
|
|
||||||
if (hasUnsavedChanges()) {
|
if (hasUnsavedChanges()) {
|
||||||
const unsavedModal = new bootstrap.Modal(document.getElementById("exportBatchUnsavedModal"), { keyboard: false });
|
const unsavedModal = new bootstrap.Modal(
|
||||||
|
document.getElementById("exportBatchUnsavedModal"),
|
||||||
|
{ keyboard: false },
|
||||||
|
);
|
||||||
unsavedModal.show();
|
unsavedModal.show();
|
||||||
document.getElementById("batchSaveAndExportBtn")?.addEventListener("click", () => {
|
document.getElementById("batchSaveAndExportBtn")?.addEventListener(
|
||||||
unsavedModal.hide();
|
"click",
|
||||||
// Trigger save all first — listen for completion
|
() => {
|
||||||
alert("Please Save All first, then Export All.");
|
unsavedModal.hide();
|
||||||
}, { once: true });
|
// Trigger save all first — listen for completion
|
||||||
|
alert("Please Save All first, then Export All.");
|
||||||
|
},
|
||||||
|
{ once: true },
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const eligibleRows = collectEligibleRows();
|
const eligibleRows = collectEligibleRows();
|
||||||
if (eligibleRows.length === 0) {
|
if (eligibleRows.length === 0) {
|
||||||
document.getElementById("exportResponseMessage").textContent = "All rows already exported to LIMS.";
|
document.getElementById("exportResponseMessage").textContent =
|
||||||
document.getElementById("exportResponseModalLabel").textContent = "Export All";
|
"All rows already exported to LIMS.";
|
||||||
new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show();
|
document.getElementById("exportResponseModalLabel").textContent =
|
||||||
|
"Export All";
|
||||||
|
new bootstrap.Modal(
|
||||||
|
document.getElementById("exportResponseModal"),
|
||||||
|
{ keyboard: false },
|
||||||
|
).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showBatchConfirm(eligibleRows);
|
showBatchConfirm(eligibleRows);
|
||||||
@@ -468,7 +630,9 @@
|
|||||||
batchRunning = true;
|
batchRunning = true;
|
||||||
const batchUuid = crypto.randomUUID();
|
const batchUuid = crypto.randomUUID();
|
||||||
const total = eligibleRows.length;
|
const total = eligibleRows.length;
|
||||||
let processed = 0, succeeded = 0, failed = 0;
|
let processed = 0,
|
||||||
|
succeeded = 0,
|
||||||
|
failed = 0;
|
||||||
|
|
||||||
disableAllRowButtons();
|
disableAllRowButtons();
|
||||||
|
|
||||||
@@ -479,18 +643,25 @@
|
|||||||
if (cancelBtn) cancelBtn.disabled = false;
|
if (cancelBtn) cancelBtn.disabled = false;
|
||||||
if (statusEl) statusEl.textContent = `Exporting 0 / ${total}...`;
|
if (statusEl) statusEl.textContent = `Exporting 0 / ${total}...`;
|
||||||
|
|
||||||
cancelBtn?.addEventListener("click", () => {
|
cancelBtn?.addEventListener(
|
||||||
batchCancelled = true;
|
"click",
|
||||||
if (statusEl) statusEl.textContent = "Cancelling... (waiting for current row)";
|
() => {
|
||||||
if (cancelBtn) cancelBtn.disabled = true;
|
batchCancelled = true;
|
||||||
}, { once: true });
|
if (statusEl)
|
||||||
|
statusEl.textContent =
|
||||||
|
"Cancelling... (waiting for current row)";
|
||||||
|
if (cancelBtn) cancelBtn.disabled = true;
|
||||||
|
},
|
||||||
|
{ once: true },
|
||||||
|
);
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
for (let i = 0; i < eligibleRows.length; i++) {
|
for (let i = 0; i < eligibleRows.length; i++) {
|
||||||
if (batchCancelled) break;
|
if (batchCancelled) break;
|
||||||
|
|
||||||
const { iddatadb, row } = eligibleRows[i];
|
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);
|
const gridRow = row || getGridRow(iddatadb);
|
||||||
if (gridRow) setRowExporting(gridRow, true);
|
if (gridRow) setRowExporting(gridRow, true);
|
||||||
@@ -503,18 +674,29 @@
|
|||||||
} else {
|
} else {
|
||||||
failed++;
|
failed++;
|
||||||
const errIdx = getRowIndexByIddatadb(iddatadb);
|
const errIdx = getRowIndexByIddatadb(iddatadb);
|
||||||
if (errIdx >= 0) window.gridData[errIdx]._exportError = data.message || "Unknown error";
|
if (errIdx >= 0)
|
||||||
if (gridRow) showRowError(gridRow, iddatadb, data.message || "Unknown error");
|
window.gridData[errIdx]._exportError =
|
||||||
|
data.message || "Unknown error";
|
||||||
|
if (gridRow)
|
||||||
|
showRowError(
|
||||||
|
gridRow,
|
||||||
|
iddatadb,
|
||||||
|
data.message || "Unknown error",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
processed++;
|
processed++;
|
||||||
failed++;
|
failed++;
|
||||||
const errIdx = getRowIndexByIddatadb(iddatadb);
|
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) 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;
|
batchRunning = false;
|
||||||
@@ -529,7 +711,8 @@
|
|||||||
(window.gridData || []).forEach((row, idx) => {
|
(window.gridData || []).forEach((row, idx) => {
|
||||||
if (row._exportError) {
|
if (row._exportError) {
|
||||||
const gridRow = getGridRow(row.iddatadb);
|
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");
|
const modalEl = document.getElementById("exportResponseModal");
|
||||||
new bootstrap.Modal(modalEl, { keyboard: false }).show();
|
new bootstrap.Modal(modalEl, { keyboard: false }).show();
|
||||||
modalEl.addEventListener("hidden.bs.modal", cleanupBackdrop, { once: true });
|
modalEl.addEventListener("hidden.bs.modal", cleanupBackdrop, {
|
||||||
|
once: true,
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -214,6 +214,12 @@
|
|||||||
Duplica anche le analisi collegate
|
Duplica anche le analisi collegate
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-check mt-2">
|
||||||
|
<input class="form-check-input" type="checkbox" id="cloneOverwriteCheckbox" checked>
|
||||||
|
<label class="form-check-label" for="cloneOverwriteCheckbox">
|
||||||
|
<strong>Sovrascrivi parti esistenti</strong> (elimina le parti già presenti nei record di destinazione prima di inserire)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Annulla</button>
|
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Annulla</button>
|
||||||
|
|||||||
@@ -555,6 +555,14 @@ $(document).ready(function () {
|
|||||||
// ===================
|
// ===================
|
||||||
|
|
||||||
function getVisiblePartsRecordList() {
|
function getVisiblePartsRecordList() {
|
||||||
|
// Se i filtri sono attivi, usa la regola selezione/visibili di gridFilter.
|
||||||
|
if (typeof window.getTargetGridIds === "function") {
|
||||||
|
const target = window.getTargetGridIds();
|
||||||
|
if (target && target.size > 0) {
|
||||||
|
return [...target].map((v) => parseInt(v, 10)).filter(Boolean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const listFromModal = $("#partsModal").data("visible-iddatadb-list");
|
const listFromModal = $("#partsModal").data("visible-iddatadb-list");
|
||||||
|
|
||||||
if (Array.isArray(listFromModal) && listFromModal.length > 0) {
|
if (Array.isArray(listFromModal) && listFromModal.length > 0) {
|
||||||
@@ -2265,8 +2273,11 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
const sourceIddatadb =
|
const sourceIddatadb =
|
||||||
parseInt($("#partsModal").data("iddatadb"), 10) || null;
|
parseInt($("#partsModal").data("iddatadb"), 10) || null;
|
||||||
const visibleListRaw =
|
// Target = selezione/visibili secondo gridFilter, con fallback alla lista del modal
|
||||||
$("#partsModal").data("visible-iddatadb-list") || [];
|
let visibleListRaw =
|
||||||
|
typeof getVisiblePartsRecordList === "function"
|
||||||
|
? getVisiblePartsRecordList()
|
||||||
|
: $("#partsModal").data("visible-iddatadb-list") || [];
|
||||||
|
|
||||||
if (!sourceIddatadb) {
|
if (!sourceIddatadb) {
|
||||||
const errorMsg = $(
|
const errorMsg = $(
|
||||||
@@ -2308,6 +2319,7 @@ $(document).ready(function () {
|
|||||||
);
|
);
|
||||||
$("#cloneNotesCheckbox").prop("checked", true);
|
$("#cloneNotesCheckbox").prop("checked", true);
|
||||||
$("#cloneAnalysesCheckbox").prop("checked", false);
|
$("#cloneAnalysesCheckbox").prop("checked", false);
|
||||||
|
$("#cloneOverwriteCheckbox").prop("checked", true);
|
||||||
|
|
||||||
const modalInstance = new bootstrap.Modal(
|
const modalInstance = new bootstrap.Modal(
|
||||||
document.getElementById("cloneConfirmModal"),
|
document.getElementById("cloneConfirmModal"),
|
||||||
@@ -2323,6 +2335,7 @@ $(document).ready(function () {
|
|||||||
const targetIds = $("#cloneConfirmModal").data("target-ids") || [];
|
const targetIds = $("#cloneConfirmModal").data("target-ids") || [];
|
||||||
const cloneNotes = $("#cloneNotesCheckbox").is(":checked");
|
const cloneNotes = $("#cloneNotesCheckbox").is(":checked");
|
||||||
const cloneAnalyses = $("#cloneAnalysesCheckbox").is(":checked");
|
const cloneAnalyses = $("#cloneAnalysesCheckbox").is(":checked");
|
||||||
|
const cloneOverwrite = $("#cloneOverwriteCheckbox").is(":checked");
|
||||||
|
|
||||||
if (!sourceIddatadb || !targetIds.length) {
|
if (!sourceIddatadb || !targetIds.length) {
|
||||||
return;
|
return;
|
||||||
@@ -2351,6 +2364,7 @@ $(document).ready(function () {
|
|||||||
target_iddatadb_list: targetIds,
|
target_iddatadb_list: targetIds,
|
||||||
clone_notes: cloneNotes,
|
clone_notes: cloneNotes,
|
||||||
clone_analyses: cloneAnalyses,
|
clone_analyses: cloneAnalyses,
|
||||||
|
overwrite: cloneOverwrite,
|
||||||
}),
|
}),
|
||||||
success: function (response) {
|
success: function (response) {
|
||||||
$btn.prop("disabled", false).html(originalHtml);
|
$btn.prop("disabled", false).html(originalHtml);
|
||||||
|
|||||||
+77
-41
@@ -1,8 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* saveAll.js — Save All functionality using gridData
|
* saveAll.js — Save All functionality using gridData
|
||||||
*/
|
*/
|
||||||
(function() {
|
(function () {
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
let saveAllRunning = false;
|
let saveAllRunning = false;
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Save single row ──────────────────────────────────────────────────
|
// ── Save single row ──────────────────────────────────────────────────
|
||||||
$(document).on('click', '.save-btn', async function() {
|
$(document).on("click", ".save-btn", async function () {
|
||||||
const btn = this;
|
const btn = this;
|
||||||
const rowIndex = parseInt(btn.dataset.row);
|
const rowIndex = parseInt(btn.dataset.row);
|
||||||
const row = window.gridData?.[rowIndex];
|
const row = window.gridData?.[rowIndex];
|
||||||
@@ -23,29 +23,44 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const formData = window.buildSavePayload(rowIndex);
|
const formData = window.buildSavePayload(rowIndex);
|
||||||
const resp = await fetch('save_edited_row.php', { method: 'POST', body: formData });
|
const resp = await fetch("save_edited_row.php", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
const result = await resp.json();
|
const result = await resp.json();
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
row._dirty = false;
|
row._dirty = false;
|
||||||
if (window.gridRenderer?.clearDirty) window.gridRenderer.clearDirty(rowIndex);
|
if (window.gridRenderer?.clearDirty)
|
||||||
|
window.gridRenderer.clearDirty(rowIndex);
|
||||||
// Flash success on row without re-rendering (preserves Select2 state)
|
// Flash success on row without re-rendering (preserves Select2 state)
|
||||||
const gridRow = document.querySelector(`.grid-row[data-id="${row.iddatadb}"]`);
|
const gridRow = document.querySelector(
|
||||||
|
`.grid-row[data-id="${row.iddatadb}"]`,
|
||||||
|
);
|
||||||
if (gridRow) {
|
if (gridRow) {
|
||||||
gridRow.classList.remove('row-dirty');
|
gridRow.classList.remove("row-dirty");
|
||||||
gridRow.querySelectorAll('.grid-cell').forEach(cell => {
|
gridRow.querySelectorAll(".grid-cell").forEach((cell) => {
|
||||||
cell.classList.remove('cell-changed');
|
cell.classList.remove("cell-changed");
|
||||||
cell.classList.add('flash-success');
|
cell.classList.add("flash-success");
|
||||||
});
|
});
|
||||||
setTimeout(() => gridRow.querySelectorAll('.flash-success').forEach(c => c.classList.remove('flash-success')), 500);
|
setTimeout(
|
||||||
|
() =>
|
||||||
|
gridRow
|
||||||
|
.querySelectorAll(".flash-success")
|
||||||
|
.forEach((c) =>
|
||||||
|
c.classList.remove("flash-success"),
|
||||||
|
),
|
||||||
|
500,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const toastEl = document.getElementById('saveSuccessToast');
|
const toastEl = document.getElementById("saveSuccessToast");
|
||||||
if (toastEl) bootstrap.Toast.getOrCreateInstance(toastEl).show();
|
if (toastEl)
|
||||||
|
bootstrap.Toast.getOrCreateInstance(toastEl).show();
|
||||||
} else {
|
} else {
|
||||||
alert('Errore: ' + result.message);
|
alert("Errore: " + result.message);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('Errore: ' + e.message);
|
alert("Errore: " + e.message);
|
||||||
} finally {
|
} finally {
|
||||||
btn.innerHTML = origHtml;
|
btn.innerHTML = origHtml;
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
@@ -53,49 +68,68 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ── Save All ─────────────────────────────────────────────────────────
|
// ── Save All ─────────────────────────────────────────────────────────
|
||||||
$(document).on('click', '.save-all-btn', function(e) {
|
$(document).on("click", ".save-all-btn", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (isBusy()) return;
|
if (isBusy()) return;
|
||||||
const modalEl = document.getElementById('saveAllConfirmModal');
|
const modalEl = document.getElementById("saveAllConfirmModal");
|
||||||
if (!modalEl) return;
|
if (!modalEl) return;
|
||||||
new bootstrap.Modal(modalEl, { keyboard: false }).show();
|
new bootstrap.Modal(modalEl, { keyboard: false }).show();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('click', '#saveAllConfirmBtn', async function() {
|
$(document).on("click", "#saveAllConfirmBtn", async function () {
|
||||||
const confirmModal = bootstrap.Modal.getInstance(document.getElementById('saveAllConfirmModal'));
|
const confirmModal = bootstrap.Modal.getInstance(
|
||||||
|
document.getElementById("saveAllConfirmModal"),
|
||||||
|
);
|
||||||
if (confirmModal) confirmModal.hide();
|
if (confirmModal) confirmModal.hide();
|
||||||
saveAllRunning = true;
|
saveAllRunning = true;
|
||||||
|
|
||||||
const bar = document.getElementById('batchExportBar');
|
const bar = document.getElementById("batchExportBar");
|
||||||
const statusEl = document.getElementById('batchExportStatus');
|
const statusEl = document.getElementById("batchExportStatus");
|
||||||
const cancelBtn = document.getElementById('exportBatchCancelBtn');
|
const cancelBtn = document.getElementById("exportBatchCancelBtn");
|
||||||
if (bar) bar.style.display = '';
|
if (bar) bar.style.display = "";
|
||||||
if (cancelBtn) cancelBtn.style.display = 'none';
|
if (cancelBtn) cancelBtn.style.display = "none";
|
||||||
if (statusEl) statusEl.textContent = 'Saving...';
|
if (statusEl) statusEl.textContent = "Saving...";
|
||||||
|
|
||||||
const data = window.gridData || [];
|
const data = window.gridData || [];
|
||||||
const dirtyRows = data.map((r, i) => r._dirty ? i : -1).filter(i => i >= 0);
|
const target =
|
||||||
|
typeof window.getTargetGridIds === "function"
|
||||||
|
? window.getTargetGridIds()
|
||||||
|
: null;
|
||||||
|
const dirtyRows = data
|
||||||
|
.map((r, i) => (r._dirty ? i : -1))
|
||||||
|
.filter((i) => i >= 0)
|
||||||
|
.filter(
|
||||||
|
(i) => !target || target.has(parseInt(data[i].iddatadb, 10)),
|
||||||
|
);
|
||||||
|
|
||||||
if (dirtyRows.length === 0) {
|
if (dirtyRows.length === 0) {
|
||||||
saveAllRunning = false;
|
saveAllRunning = false;
|
||||||
if (bar) bar.style.display = 'none';
|
if (bar) bar.style.display = "none";
|
||||||
const msgEl = document.getElementById('saveAllResultMessage');
|
const msgEl = document.getElementById("saveAllResultMessage");
|
||||||
if (msgEl) msgEl.textContent = 'No changes to save.';
|
if (msgEl) msgEl.textContent = "No changes to save.";
|
||||||
new bootstrap.Modal(document.getElementById('saveAllResultModal')).show();
|
new bootstrap.Modal(
|
||||||
|
document.getElementById("saveAllResultModal"),
|
||||||
|
).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let success = 0, fail = 0;
|
let success = 0,
|
||||||
|
fail = 0;
|
||||||
|
|
||||||
for (const idx of dirtyRows) {
|
for (const idx of dirtyRows) {
|
||||||
if (statusEl) statusEl.textContent = `Saving ${success + fail + 1} / ${dirtyRows.length}...`;
|
if (statusEl)
|
||||||
|
statusEl.textContent = `Saving ${success + fail + 1} / ${dirtyRows.length}...`;
|
||||||
try {
|
try {
|
||||||
const formData = window.buildSavePayload(idx);
|
const formData = window.buildSavePayload(idx);
|
||||||
const resp = await fetch('save_edited_row.php', { method: 'POST', body: formData });
|
const resp = await fetch("save_edited_row.php", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
const result = await resp.json();
|
const result = await resp.json();
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
data[idx]._dirty = false;
|
data[idx]._dirty = false;
|
||||||
if (window.gridRenderer?.clearDirty) window.gridRenderer.clearDirty(idx);
|
if (window.gridRenderer?.clearDirty)
|
||||||
|
window.gridRenderer.clearDirty(idx);
|
||||||
success++;
|
success++;
|
||||||
} else {
|
} else {
|
||||||
fail++;
|
fail++;
|
||||||
@@ -106,22 +140,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveAllRunning = false;
|
saveAllRunning = false;
|
||||||
if (bar) bar.style.display = 'none';
|
if (bar) bar.style.display = "none";
|
||||||
|
|
||||||
const gr = window.gridRenderer;
|
const gr = window.gridRenderer;
|
||||||
if (gr) gr.renderVisibleRows();
|
if (gr) gr.renderVisibleRows();
|
||||||
|
|
||||||
const msg = `Saved: ${success}` + (fail > 0 ? `, Errors: ${fail}` : '');
|
const msg = `Saved: ${success}` + (fail > 0 ? `, Errors: ${fail}` : "");
|
||||||
const msgEl = document.getElementById('saveAllResultMessage');
|
const msgEl = document.getElementById("saveAllResultMessage");
|
||||||
if (msgEl) msgEl.textContent = msg;
|
if (msgEl) msgEl.textContent = msg;
|
||||||
new bootstrap.Modal(document.getElementById('saveAllResultModal')).show();
|
new bootstrap.Modal(
|
||||||
|
document.getElementById("saveAllResultModal"),
|
||||||
|
).show();
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── beforeunload ─────────────────────────────────────────────────────
|
// ── beforeunload ─────────────────────────────────────────────────────
|
||||||
window.addEventListener('beforeunload', function(e) {
|
window.addEventListener("beforeunload", function (e) {
|
||||||
if (window.gridData && window.gridData.some(r => r._dirty)) {
|
if (window.gridData && window.gridData.some((r) => r._dirty)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.returnValue = '';
|
e.returnValue = "";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user