fixed export and save to selection

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