diff --git a/.gitignore b/.gitignore index 2d042e3e..2931b040 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,6 @@ public/userarea/schemi_base_response.json public/userarea/cache/ public/userarea/error_log.txt + +# PDF/QR generati dai template (userarea) +/public/userarea/pdftemplates/ \ No newline at end of file diff --git a/public/userarea/gridRenderer.js b/public/userarea/gridRenderer.js index d14177d0..288dbc59 100644 --- a/public/userarea/gridRenderer.js +++ b/public/userarea/gridRenderer.js @@ -895,6 +895,76 @@ updateDirtyIndicator(); } + // Aggiorna in-place SOLO le celle di una colonna in tutte le righe visibili, + // senza distruggere/ricostruire l'intera griglia (evita il freeze in propagazione). + function updateColumnCells(colKey, colType) { + if (!rowContainer) return; + const cells = rowContainer.querySelectorAll( + `.grid-cell[data-col="${CSS.escape(String(colKey))}"][data-col-type="${colType}"]`, + ); + cells.forEach((cell) => { + const rowIndex = parseInt(cell.dataset.row, 10); + if (Number.isNaN(rowIndex) || !data[rowIndex]) return; + + // Ricava il valore corrente dal modello dati + let value = ""; + if (colType === "detail" || colType === "main_field") { + value = data[rowIndex].details?.[String(colKey)] ?? ""; + } else if (colType === "fixed") { + value = data[rowIndex].fixedFields?.[colKey] ?? ""; + } else if (colType === "cliente_fornitore_id") { + value = data[rowIndex].cliente_fornitore_id ?? ""; + } + + const input = cell.querySelector(".cell-input"); + if (!input) return; + + if ($(input).hasClass("select2-hidden-accessible")) { + // Assicura che l'opzione selezionata esista, poi imposta il valore + if ( + value && + !input.querySelector( + `option[value="${CSS.escape(String(value))}"]`, + ) + ) { + const label = + clientNameCache[value] || + (input.dataset?.fieldId + ? dropdownNameCache[ + input.dataset.fieldId + "_" + value + ] + : null) || + value; + input.add(new Option(label, value, true, true)); + } + $(input) + .val(value ? String(value) : "") + .trigger("change.select2"); + } else if (input.tagName === "SELECT") { + // Select nativo: aggiungi opzione se manca + if ( + value && + !input.querySelector( + `option[value="${CSS.escape(String(value))}"]`, + ) + ) { + const label = + (input.dataset?.fieldId + ? dropdownNameCache[ + input.dataset.fieldId + "_" + value + ] + : null) || value; + input.add(new Option(label, value, true, true)); + } + input.value = value; + } else { + input.value = value; + } + + cell.classList.add("cell-changed"); + }); + } + function renderSingleRow(rowIndex) { const existing = rowContainer.querySelector( `.grid-row[data-id="${data[rowIndex].iddatadb}"]`, @@ -1546,7 +1616,10 @@ row._dirty = true; }); - renderVisibleRows(); + updateColumnCells( + "cliente_fornitore_id", + "cliente_fornitore_id", + ); updateDirtyIndicator(); return; } @@ -1565,7 +1638,7 @@ row._dirty = true; }); - renderVisibleRows(); + updateColumnCells(fixedKey, "fixed"); updateDirtyIndicator(); return; } @@ -1595,7 +1668,15 @@ row._dirty = true; }); - renderVisibleRows(); + updateColumnCells(col.key, col.type); + // Se esiste una colonna auto-copiata (es. Arrival→Unlock), aggiorna anche quella + if (autoPair) { + const pairCol = columns.find( + (c) => String(c.key) === String(autoPair.toKey), + ); + if (pairCol) updateColumnCells(pairCol.key, pairCol.type); + } + updateDirtyIndicator(); return; } diff --git a/public/userarea/imported.php b/public/userarea/imported.php index 50c9d9dd..11c74624 100644 --- a/public/userarea/imported.php +++ b/public/userarea/imported.php @@ -1417,6 +1417,8 @@ $gridMeta = [ /* posizione verticale barra azioni: abbassa/alza questo */ --actions-height: 58px; /* altezza reale del card-header azioni */ + --scrollbar-height: 18px; + /* altezza della .top-scrollbar */ } .card-header.sticky-actions { @@ -1431,6 +1433,22 @@ $gridMeta = [ top: calc(var(--actions-top) + var(--actions-height)) !important; } + /* Wrapper del grid-top: sticky verticale + scroll orizzontale proprio */ + .grid-top-scroller { + position: sticky; + top: calc(var(--actions-top) + var(--actions-height) + var(--scrollbar-height)); + z-index: 25; + background: #fff; + overflow-x: auto; + overflow-y: hidden; + /* nasconde la barra di scroll di questo wrapper: usiamo la top-scrollbar e la griglia */ + scrollbar-width: none; + } + + .grid-top-scroller::-webkit-scrollbar { + display: none; + } + /* Tastino toggle sticky */ .sticky-toggle-btn { border: 1px solid #ced4da; @@ -1460,12 +1478,17 @@ $gridMeta = [ transform: rotate(45deg); } - /* Quando la classe è tolta dal card-header, non è più sticky */ + /* Quando sbloccato, card-header e grid-top non sono più sticky verticalmente */ .card-header.actions-unpinned { position: static !important; top: auto !important; } + .grid-top-scroller.actions-unpinned { + position: static !important; + top: auto !important; + } + = 2): ?>.grid-top .grid-cell:nth-child(3) { position: sticky !important; left: 360px; @@ -1594,8 +1617,11 @@ $gridMeta = [
-