From 574ddbbd32ed8261d5f6bab689677d9e18bd9fbd Mon Sep 17 00:00:00 2001 From: solocla Date: Wed, 13 May 2026 15:12:13 +0200 Subject: [PATCH] fixed propagation --- public/userarea/gridRenderer.js | 245 ++++++++++++++++++++++++++------ 1 file changed, 198 insertions(+), 47 deletions(-) diff --git a/public/userarea/gridRenderer.js b/public/userarea/gridRenderer.js index 87cc0d1..d8c52e4 100644 --- a/public/userarea/gridRenderer.js +++ b/public/userarea/gridRenderer.js @@ -827,6 +827,74 @@ }); } + function syncVisibleRowsToGridData() { + if (!rowContainer) return; + + rowContainer + .querySelectorAll(".grid-cell[data-row]") + .forEach((cell) => { + const rowIndex = parseInt(cell.dataset.row, 10); + const row = data[rowIndex]; + + if (!row) return; + + const colType = cell.dataset.colType; + const colKey = cell.dataset.col; + const input = cell.querySelector(".cell-input"); + + if (!input) return; + + const value = $(input).hasClass("select2-hidden-accessible") + ? $(input).val() || "" + : input.value || ""; + + if (colType === "detail" || colType === "main_field") { + if (!row.details) row.details = {}; + + if ( + String(row.details[String(colKey)] ?? "") !== + String(value) + ) { + row.details[String(colKey)] = value; + + if (colType === "main_field") { + row.mainFieldValue = value; + } + + row._dirty = true; + } + } else if (colType === "fixed") { + if (!row.fixedFields) row.fixedFields = {}; + + if ( + String(row.fixedFields[colKey] ?? "") !== String(value) + ) { + row.fixedFields[colKey] = value; + row._dirty = true; + } + } else if (colType === "idclient") { + if (String(row.idclient ?? "") !== String(value)) { + row.idclient = value; + row._dirty = true; + } + } else if (colType === "cliente_fornitore_id") { + if ( + String(row.cliente_fornitore_id ?? "") !== String(value) + ) { + row.cliente_fornitore_id = value; + row._dirty = true; + } + } else if (colType === "tested_component") { + if (String(row.tested_component ?? "") !== String(value)) { + row.tested_component = value; + row._dirty = true; + } + } + }); + + updateDirtyIndicator(); + } + // ── Headers & Propagate row ──────────────────────────────────────────── function renderHeaders() { @@ -1098,40 +1166,84 @@ const btn = e.target.closest(".propagate-btn"); if (!btn) return; - const colIndex = parseInt(btn.dataset.colIndex); - const column = btn.dataset.column; - if (isNaN(colIndex) && !column) return; + // Before propagating and re-rendering, persist current visible row values into gridData. + syncVisibleRowsToGridData(); - // Get value from the input/select in the same cell - const cell = - btn.closest(".grid-cell") || btn.closest(".grid-top-cell"); + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + + const column = btn.dataset.column || ""; + const colIndex = Number.isNaN(parseInt(btn.dataset.colIndex, 10)) + ? null + : parseInt(btn.dataset.colIndex, 10); + + if (!column && colIndex === null) return; + + // IMPORTANT: + // Read ONLY the input/select inside the same top propagation cell. + // Do not scan other top fields. + const cell = btn.closest(".grid-top-cell"); if (!cell) return; - const input = cell.querySelector("select, input"); - if (!input) return; - const value = $(input).hasClass("select2-hidden-accessible") - ? $(input).val() - : input.value; - // Cache Select2 label so re-render shows name not ID + const input = cell.querySelector(".custom-field"); + if (!input) return; + + const value = $(input).hasClass("select2-hidden-accessible") + ? $(input).val() || "" + : input.value || ""; + + // Do not propagate empty dropdown values. + // This prevents wiping rows when a top Select2 is empty/not fully initialized. + if (input.tagName === "SELECT" && value === "") { + console.warn( + "[gridRenderer] Empty select propagation blocked:", + column, + ); + return; + } + + // Cache selected label so re-render can show text instead of only ID. if (value && $(input).hasClass("select2-hidden-accessible")) { const label = $(input).find("option:selected").text(); + if (label && label !== value) { - clientNameCache[value] = label; - // Also cache for SceltaMultipla + if ( + column === "idclient" || + column === "cliente_fornitore_id" || + input.classList.contains("searchable-client") + ) { + clientNameCache[value] = label; + } + const fieldId = input.dataset?.fieldId; - if (fieldId) + if (fieldId) { dropdownNameCache[fieldId + "_" + value] = label; + } } } - const col = columns[colIndex] || null; + const col = colIndex !== null ? columns[colIndex] : null; + + console.log("[gridRenderer] Propagating ONLY:", { + column: column, + colIndex: colIndex, + value: value, + label: + input.tagName === "SELECT" + ? $(input).find("option:selected").text() + : value, + }); if (column === "idclient") { data.forEach((row) => { + const oldClientId = row.idclient || ""; + row.idclient = value; - // Clear dependent fixed fields when client changes + // Clear ClienteResponsabile only if client really changed. if ( + String(oldClientId) !== String(value) && row.fixedFields && Object.prototype.hasOwnProperty.call( row.fixedFields, @@ -1144,45 +1256,59 @@ row._dirty = true; }); - // Reload ClienteResponsabile options for the propagated client - if (value) { - loadFixedFieldOptions("ClienteResponsabile", value).then( - () => { - refreshTopDependentFixedSelect( - "ClienteResponsabile", - value, - ); - renderVisibleRows(); - updateDirtyIndicator(); - }, + loadFixedFieldOptions("ClienteResponsabile", value).then(() => { + refreshTopDependentFixedSelect( + "ClienteResponsabile", + value, ); - return; - } else { - refreshTopDependentFixedSelect("ClienteResponsabile", ""); - } - } else if (column === "cliente_fornitore_id") { + renderVisibleRows(); + updateDirtyIndicator(); + }); + + return; + } + + if (column === "cliente_fornitore_id") { data.forEach((row) => { row.cliente_fornitore_id = value; row._dirty = true; }); - } else if (column && column.startsWith("fixed_")) { + + renderVisibleRows(); + updateDirtyIndicator(); + return; + } + + if (column && column.startsWith("fixed_")) { const fixedKey = column.replace("fixed_", ""); + data.forEach((row) => { + if (!row.fixedFields) row.fixedFields = {}; row.fixedFields[fixedKey] = value; row._dirty = true; }); - } else if (col) { - if (col.type === "detail" || col.type === "main_field") { - data.forEach((row) => { - row.details[col.key] = value; - if (col.type === "main_field") - row.mainFieldValue = value; - row._dirty = true; - }); - } + + renderVisibleRows(); + updateDirtyIndicator(); + return; } - renderVisibleRows(); + if (col && (col.type === "detail" || col.type === "main_field")) { + data.forEach((row) => { + if (!row.details) row.details = {}; + row.details[col.key] = value; + + if (col.type === "main_field") { + row.mainFieldValue = value; + } + + row._dirty = true; + }); + + renderVisibleRows(); + updateDirtyIndicator(); + return; + } }); // Select2 change events (don't bubble via native addEventListener) @@ -1212,14 +1338,39 @@ updateDirtyIndicator(); }); - // Cache labels on SceltaMultipla change + // Handle SceltaMultipla changes and persist them into gridData. + // Without this, a later renderVisibleRows() can rebuild the row with the old empty value. $(rowContainer).on("change", ".searchable-dropdown", function () { - const val = $(this).val(); + const cell = this.closest(".grid-cell"); + if (!cell || !cell.dataset.row) return; + + const rowIndex = parseInt(cell.dataset.row, 10); + const colType = cell.dataset.colType; + const colKey = cell.dataset.col; + const val = $(this).val() || ""; const fieldId = this.dataset.fieldId; + + // Cache label so re-render shows the text instead of only the ID. if (val && fieldId) { const label = $(this).find("option:selected").text(); - if (label && label !== val) + if (label && label !== val) { dropdownNameCache[fieldId + "_" + val] = label; + } + } + + // Persist value into gridData. + if (colType === "detail" || colType === "main_field") { + if (!data[rowIndex].details) data[rowIndex].details = {}; + + data[rowIndex].details[String(colKey)] = val; + + if (colType === "main_field") { + data[rowIndex].mainFieldValue = val; + } + + data[rowIndex]._dirty = true; + cell.classList.add("cell-changed"); + updateDirtyIndicator(); } });