From def73774472c8508d4ba6c1ee9a9c6185ad58743 Mon Sep 17 00:00:00 2001 From: solocla Date: Tue, 8 Sep 2026 09:18:17 +0200 Subject: [PATCH] fixed sticky bars with toggle --- .gitignore | 3 ++ public/userarea/gridRenderer.js | 87 +++++++++++++++++++++++++++++++-- public/userarea/imported.php | 80 ++++++++++++++++++++++++------ 3 files changed, 152 insertions(+), 18 deletions(-) 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 = [
-
+
+
+ +
@@ -1666,21 +1692,43 @@ $gridMeta = [ topScrollbar.scrollLeft = gridContainer.scrollLeft; } + const gridTopScroller = document.getElementById('gridTopScroller'); + let syncing = false; + + function syncScroll(source) { + if (syncing) return; + syncing = true; + const x = source.scrollLeft; + if (source !== topScrollbar) topScrollbar.scrollLeft = x; + if (source !== gridContainer) gridContainer.scrollLeft = x; + if (gridTopScroller && source !== gridTopScroller) gridTopScroller.scrollLeft = x; + syncing = false; + } + topScrollbar.addEventListener('scroll', function() { - if (syncingFromGrid) return; - syncingFromTop = true; - gridContainer.scrollLeft = topScrollbar.scrollLeft; - syncingFromTop = false; + syncScroll(topScrollbar); }); - gridContainer.addEventListener('scroll', function() { - if (syncingFromTop) return; - syncingFromGrid = true; - topScrollbar.scrollLeft = gridContainer.scrollLeft; - syncingFromGrid = false; + syncScroll(gridContainer); }); + if (gridTopScroller) { + gridTopScroller.addEventListener('scroll', function() { + syncScroll(gridTopScroller); + }); + } - window.addEventListener('resize', updateTopScrollbarWidth); + let rafPending = false; + + function scheduleUpdate() { + if (rafPending) return; + rafPending = true; + requestAnimationFrame(function() { + rafPending = false; + updateTopScrollbarWidth(); + }); + } + + window.addEventListener('resize', scheduleUpdate); // Recalculate after JS grid rendering setTimeout(updateTopScrollbarWidth, 100); @@ -1689,13 +1737,13 @@ $gridMeta = [ setTimeout(updateTopScrollbarWidth, 1500); // Recalculate automatically when rows/header/top controls are rendered or changed - const observer = new MutationObserver(updateTopScrollbarWidth); + const observer = new MutationObserver(scheduleUpdate); if (gridContainer) { observer.observe(gridContainer, { childList: true, subtree: true, - attributes: true + attributes: false }); } }); @@ -1872,7 +1920,7 @@ $gridMeta = [