diff --git a/public/userarea/gridFilter.js b/public/userarea/gridFilter.js new file mode 100644 index 0000000..b6774a9 --- /dev/null +++ b/public/userarea/gridFilter.js @@ -0,0 +1,423 @@ +/** + * gridFilter.js — Selezione righe + filtri colonna a scomparsa per imported.php + * Dipende da window.gridRenderer (gridRenderer.js). Includere DOPO gridRenderer.js. + */ +(function () { + "use strict"; + + let filtersActive = false; + let restricted = false; + const selected = new Set(); // iddatadb selezionati + const colFilters = {}; // colKey -> testo filtro + let restrictedIds = null; // Set di iddatadb visibili quando ristretto + + function R() { + return window.gridRenderer; + } + function data() { + return R().getData(); + } + function meta() { + return R().getMeta(); + } + + // ── Filtro: decide se una riga passa i filtri colonna ── + function rowMatchesFilters(row) { + for (const [key, term] of Object.entries(colFilters)) { + if (!term) continue; + const t = term.toLowerCase(); + const val = getRowColValue(row, key); + if (!String(val).toLowerCase().includes(t)) return false; + } + return true; + } + + function getRowColValue(row, key) { + const cols = meta().columns || []; + const col = cols.find((c) => String(c.key) === String(key)); + if (!col) return ""; + switch (col.type) { + case "detail": + case "main_field": + return row.details?.[String(key)] ?? ""; + case "fixed": + return row.fixedFields?.[key] ?? ""; + case "idclient": + return row.idclient ?? ""; + case "cliente_fornitore_id": + return row.cliente_fornitore_id ?? ""; + case "tested_component": + return row.tested_component ?? ""; + case "static": + return row[key] ?? ""; + case "status": + return row.status ?? ""; + default: + return row[key] ?? ""; + } + } + + // ── Applica visibilità righe (filtri + restrizione) ── + function applyVisibility() { + const rows = document.querySelectorAll("#gridRowContainer .grid-row"); + rows.forEach((rowEl) => { + const id = parseInt(rowEl.dataset.id, 10); + const row = data().find((r) => String(r.iddatadb) === String(id)); + let show = true; + if (row && !rowMatchesFilters(row)) show = false; + if (restricted && restrictedIds && !restrictedIds.has(id)) + show = false; + rowEl.style.display = show ? "" : "none"; + }); + window.dispatchEvent(new Event("resize")); // ricalcola scrollbar top + } + + // ── Lista iddatadb attualmente visibili (per propagazione/export ecc.) ── + window.getVisibleGridIds = function () { + const ids = []; + document + .querySelectorAll("#gridRowContainer .grid-row") + .forEach((el) => { + if (el.style.display !== "none") + ids.push(parseInt(el.dataset.id, 10)); + }); + return ids.filter(Boolean); + }; + + // ── Righe target per operazioni bulk (propaga/export/clona/save) ── + // Regola: filtri attivi + selezione → solo selezionate; + // filtri attivi senza selezione → solo visibili; + // filtri non attivi → null (= tutte, comportamento originale). + window.getTargetGridIds = function () { + if (!filtersActive) return null; // null = nessun vincolo + if (selected.size > 0) return new Set([...selected]); + return new Set(window.getVisibleGridIds()); + }; + + // Helper: true se l'id è tra i target (o se non c'è vincolo) + window.isTargetGridId = function (id) { + const t = window.getTargetGridIds(); + if (t === null) return true; + return t.has(parseInt(id, 10)); + }; + + // ── Toggle selezione riga ── + function toggleRow(id, on) { + id = parseInt(id, 10); + if (on) selected.add(id); + else selected.delete(id); + const rowEl = document.querySelector( + `#gridRowContainer .grid-row[data-id="${id}"]`, + ); + if (rowEl) { + rowEl.classList.toggle("row-selected", selected.has(id)); + const cb = rowEl.querySelector(".filter-row-checkbox"); + if (cb) cb.checked = selected.has(id); + } + updateToolbar(); + } + + // ── Inietta checkbox nelle righe visibili ── + // ── Inietta checkbox dentro la cella Actions (già sticky) ── + function injectCheckboxes() { + document + .querySelectorAll("#gridRowContainer .grid-row") + .forEach((rowEl) => { + const btnCell = rowEl.querySelector(".button-cell"); + if (!btnCell) return; + if (btnCell.querySelector(".filter-row-checkbox")) return; + const id = parseInt(rowEl.dataset.id, 10); + const wrap = document.createElement("label"); + wrap.className = "filter-cb-wrap"; + wrap.innerHTML = ``; + btnCell.insertBefore(wrap, btnCell.firstChild); + if (selected.has(id)) rowEl.classList.add("row-selected"); + }); + } + + function removeCheckboxes() { + document + .querySelectorAll(".filter-cb-wrap") + .forEach((el) => el.remove()); + document + .querySelectorAll(".grid-row.row-selected") + .forEach((el) => el.classList.remove("row-selected")); + } + + // ── Riga input filtri colonna (allineata alle larghezze reali) ── + // ── Riga input filtri colonna (allineata alle larghezze reali) ── + function injectFilterRow() { + if (document.getElementById("gridFilterRow")) return; + const top = document.getElementById("gridTopContainer"); + const header = document.getElementById("gridHeaderContainer"); + if (!top || !header) return; + + const fr = document.createElement("div"); + fr.className = "grid-row grid-filter-row"; + fr.id = "gridFilterRow"; + + // cella allineata alla colonna Actions (sticky, legge larghezza reale) + const actHeader = header.querySelector(".button-header"); + const actCell = document.createElement("div"); + actCell.className = "grid-cell button-cell filter-actions-cell"; + const aw = actHeader ? actHeader.offsetWidth : 220; + actCell.style.flex = `0 0 ${aw}px`; + actCell.style.minWidth = `${aw}px`; + actCell.innerHTML = ``; + fr.appendChild(actCell); + + // una cella per colonna, larghezza letta dall'header reale + const headerCells = header.querySelectorAll( + ".grid-header:not(.button-header)", + ); + (meta().columns || []).forEach((col, i) => { + const hc = headerCells[i]; + const w = hc ? hc.offsetWidth : col.width || 150; + const cell = document.createElement("div"); + cell.className = "grid-cell"; + cell.style.flex = `0 0 ${w}px`; + if (col.type === "tracking" || col.type === "awb") { + // niente filtro + } else { + cell.innerHTML = ``; + } + fr.appendChild(cell); + }); + + top.parentNode.insertBefore(fr, top); + } + + function removeFilterRow() { + const fr = document.getElementById("gridFilterRow"); + if (fr) fr.remove(); + } + + // ── Toolbar (bottoni contestuali) ── + function updateToolbar() { + const bar = document.getElementById("filterActionBar"); + if (!bar) return; + const count = selected.size; + bar.style.display = filtersActive ? "inline-flex" : "none"; + const cntEl = document.getElementById("filterSelCount"); + if (cntEl) cntEl.textContent = count; + document.getElementById("filterDeleteBtn").disabled = count === 0; + document.getElementById("filterRestrictBtn").disabled = + count === 0 && !restricted; + document.getElementById("filterRestrictBtn").innerHTML = restricted + ? ' Mostra tutti' + : ' Restringi selezione'; + } + + // ── Main toggle ── + function toggleFilters() { + filtersActive = !filtersActive; + const grid = document.getElementById("gridContainer"); + const btn = document.getElementById("filtersToggleBtn"); + if (filtersActive) { + grid.classList.add("filters-on"); + btn.classList.add("active"); + injectCheckboxes(); + injectFilterRow(); + } else { + grid.classList.remove("filters-on"); + btn.classList.remove("active"); + removeCheckboxes(); + removeFilterRow(); + } + updateToolbar(); + } + + // ── Batch delete ── + async function batchDelete() { + const ids = [...selected]; + if (!ids.length) return; + if ( + !confirm( + `Eliminare ${ids.length} righe dal database? Operazione irreversibile.`, + ) + ) + return; + + const btn = document.getElementById("filterDeleteBtn"); + btn.disabled = true; + const original = btn.innerHTML; + + let ok = 0, + fail = 0; + for (const id of ids) { + try { + btn.innerHTML = ` ${ok + fail + 1}/${ids.length}`; + const resp = await fetch("delete_record.php", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ id: id }), + }); + const json = await resp.json(); + if (json.success) { + ok++; + // rimuovi da gridData + const arr = data(); + const idx = arr.findIndex( + (r) => String(r.iddatadb) === String(id), + ); + if (idx !== -1) arr.splice(idx, 1); + selected.delete(id); + if (restrictedIds) restrictedIds.delete(id); + } else fail++; + } catch (e) { + fail++; + } + } + + btn.innerHTML = original; + R().renderVisibleRows(); + if (filtersActive) { + injectCheckboxes(); + } + applyVisibility(); + updateToolbar(); + alert(`Eliminate: ${ok}${fail ? " — Falliti: " + fail : ""}`); + } + + // ── Restringi / mostra tutti ── + function toggleRestrict() { + if (!restricted) { + if (!selected.size) return; + restrictedIds = new Set([...selected]); + restricted = true; + } else { + restricted = false; + restrictedIds = null; + } + applyVisibility(); + updateToolbar(); + } + + // ── Costruzione UI toolbar (bottoni in cima) ── + function buildUI() { + const addBtn = document.getElementById("addRowBtn"); + if (!addBtn) return; + + // bottone Filters + const fBtn = document.createElement("button"); + fBtn.type = "button"; + fBtn.id = "filtersToggleBtn"; + fBtn.className = "btn btn-outline-info btn-sm"; + fBtn.style.flexShrink = "0"; + fBtn.innerHTML = ' Filters'; + addBtn.parentNode.insertBefore(fBtn, addBtn.nextSibling); + + // barra azioni contestuali + const bar = document.createElement("span"); + bar.id = "filterActionBar"; + bar.style.cssText = + "display:none;align-items:center;gap:8px;flex-shrink:0;"; + bar.innerHTML = ` + Selezionate: 0 + + + `; + fBtn.parentNode.insertBefore(bar, fBtn.nextSibling); + + fBtn.addEventListener("click", toggleFilters); + document + .getElementById("filterDeleteBtn") + .addEventListener("click", batchDelete); + document + .getElementById("filterRestrictBtn") + .addEventListener("click", toggleRestrict); + } + + // ── Event delegation ── + function attachEvents() { + // checkbox click + document.addEventListener("change", function (e) { + if (e.target.classList.contains("filter-row-checkbox")) { + const rowEl = e.target.closest(".grid-row"); + toggleRow(rowEl.dataset.id, e.target.checked); + } + }); + + // Seleziona / deseleziona tutti i FILTRATI visibili + document.addEventListener("change", function (e) { + if (e.target.id !== "filterSelectAll") return; + const on = e.target.checked; + document + .querySelectorAll("#gridRowContainer .grid-row") + .forEach((rowEl) => { + if (rowEl.style.display === "none") return; + const id = parseInt(rowEl.dataset.id, 10); + if (!id) return; + if (on) selected.add(id); + else selected.delete(id); + rowEl.classList.toggle("row-selected", on); + const cb = rowEl.querySelector(".filter-row-checkbox"); + if (cb) cb.checked = on; + }); + updateToolbar(); + }); + + // click su QUALSIASI punto della riga (tranne campi interattivi) = seleziona + document.addEventListener("click", function (e) { + if (!filtersActive) return; + const rowEl = e.target.closest("#gridRowContainer .grid-row"); + if (!rowEl) return; + + // ignora click su elementi interattivi + if ( + e.target.closest( + "input, select, textarea, button, a, .select2-container, .action-btn, .propagate-btn, .add-part-btn", + ) + ) { + // ma se ho cliccato proprio la checkbox, lascia fare al change + return; + } + + const cb = rowEl.querySelector(".filter-row-checkbox"); + if (!cb) return; + cb.checked = !cb.checked; + toggleRow(rowEl.dataset.id, cb.checked); + }); + + // filtri colonna live (debounce) + let t = null; + document.addEventListener("input", function (e) { + if (!e.target.classList.contains("filter-col-input")) return; + const key = e.target.dataset.colKey; + colFilters[key] = e.target.value; + clearTimeout(t); + t = setTimeout(applyVisibility, 200); + }); + } + + // ── Re-inietta dopo ogni re-render della griglia ── + function hookRerender() { + const orig = R().renderVisibleRows; + R().renderVisibleRows = function () { + orig.apply(this, arguments); + if (filtersActive) { + injectCheckboxes(); + applyVisibility(); + } + }; + } + + function init() { + if (!window.gridRenderer) { + console.error("[gridFilter] gridRenderer non trovato"); + return; + } + buildUI(); + attachEvents(); + hookRerender(); + } + + // parte dopo che gridRenderer ha finito init + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", () => + setTimeout(init, 300), + ); + } else { + setTimeout(init, 300); + } +})(); diff --git a/public/userarea/gridRenderer.js b/public/userarea/gridRenderer.js index 4048a94..24463da 100644 --- a/public/userarea/gridRenderer.js +++ b/public/userarea/gridRenderer.js @@ -1388,6 +1388,11 @@ if (column === "idclient") { data.forEach((row) => { + if ( + window.isTargetGridId && + !window.isTargetGridId(row.iddatadb) + ) + return; const oldClientId = row.idclient || ""; row.idclient = value; @@ -1421,6 +1426,11 @@ if (column === "cliente_fornitore_id") { data.forEach((row) => { + if ( + window.isTargetGridId && + !window.isTargetGridId(row.iddatadb) + ) + return; row.cliente_fornitore_id = value; row._dirty = true; }); @@ -1434,6 +1444,11 @@ const fixedKey = column.replace("fixed_", ""); data.forEach((row) => { + if ( + window.isTargetGridId && + !window.isTargetGridId(row.iddatadb) + ) + return; if (!row.fixedFields) row.fixedFields = {}; row.fixedFields[fixedKey] = value; row._dirty = true; @@ -1446,6 +1461,11 @@ if (col && (col.type === "detail" || col.type === "main_field")) { data.forEach((row) => { + if ( + window.isTargetGridId && + !window.isTargetGridId(row.iddatadb) + ) + return; if (!row.details) row.details = {}; row.details[col.key] = value; @@ -1760,3 +1780,127 @@ }, }; })(); +// =================== +// NAVIGAZIONE GRIGLIA CON FRECCE TASTIERA +// =================== +(function () { + // Naviga solo tra input di testo/numero/time e date-picker. + // I Select2 (client, fornitore, SceltaMultipla) sono esclusi: + // lì le frecce servono al dropdown. + function isNavigable(el) { + if (!el || !el.classList) return false; + if (!el.classList.contains("cell-input")) return false; + // Escludi i select (nativi o Select2) + if (el.tagName === "SELECT") return false; + if (el.classList.contains("select2-hidden-accessible")) return false; + return true; + } + + // Tutte le celle navigabili della riga, ordinate per data-index + function navCellsInRow($row) { + return $row.find(".grid-cell[data-index]").filter(function () { + const input = this.querySelector(".cell-input"); + return isNavigable(input); + }); + } + + function focusCellInput($cell) { + if (!$cell || !$cell.length) return; + const input = $cell.get(0).querySelector(".cell-input"); + if (!input) return; + input.focus(); + if (input.type !== "date" && typeof input.select === "function") { + try { + input.select(); + } catch (e) {} + } + } + + function atStart(el) { + if (el.type === "date" || el.type === "number" || el.type === "time") + return true; + return el.selectionStart === 0 && el.selectionEnd === 0; + } + + function atEnd(el) { + if (el.type === "date" || el.type === "number" || el.type === "time") + return true; + const len = (el.value || "").length; + return el.selectionStart === len && el.selectionEnd === len; + } + + $(document).on("keydown", "#gridRowContainer .cell-input", function (e) { + const el = this; + if (!isNavigable(el)) return; + + // Ignora se un dropdown Select2 è aperto + if ($(".select2-container--open").length) return; + + const key = e.key; + if ( + key !== "ArrowUp" && + key !== "ArrowDown" && + key !== "ArrowLeft" && + key !== "ArrowRight" + ) { + return; + } + + const $cell = $(el).closest(".grid-cell"); + const $row = $cell.closest(".grid-row"); + if (!$cell.length || !$row.length) return; + + const colIndex = $cell.attr("data-index"); + + // --- SU / GIÙ: stessa colonna (data-index), riga adiacente --- + if (key === "ArrowUp" || key === "ArrowDown") { + const $targetRow = + key === "ArrowUp" + ? $row.prev(".grid-row") + : $row.next(".grid-row"); + if (!$targetRow.length) return; + + e.preventDefault(); + + // Cella stessa colonna nella riga di destinazione + let $target = $targetRow.find( + `.grid-cell[data-index="${colIndex}"]`, + ); + + // Se quella colonna non è navigabile (es. è un select), + // cerca la più vicina navigabile nella riga + if ( + !$target.length || + !isNavigable($target.get(0).querySelector(".cell-input")) + ) { + const $cells = navCellsInRow($targetRow); + if (!$cells.length) return; + $target = $cells.first(); + } + + focusCellInput($target); + return; + } + + // --- SINISTRA / DESTRA: cella navigabile precedente/successiva nella riga --- + const $cells = navCellsInRow($row); + const curPos = $cells.index($cell); + if (curPos === -1) return; + + if (key === "ArrowLeft") { + if (!atStart(el)) return; + if (curPos <= 0) return; + e.preventDefault(); + focusCellInput($cells.eq(curPos - 1)); + return; + } + + if (key === "ArrowRight") { + if (!atEnd(el)) return; + if (curPos >= $cells.length - 1) return; + e.preventDefault(); + focusCellInput($cells.eq(curPos + 1)); + return; + } + }); +})(); diff --git a/public/userarea/imported.php b/public/userarea/imported.php index c728f3b..39ae9f8 100644 --- a/public/userarea/imported.php +++ b/public/userarea/imported.php @@ -1328,6 +1328,117 @@ $gridMeta = [ outline: 2px solid #dc3545 !important; outline-offset: -2px; } + + /* ── Filtri / selezione righe ── */ + #filtersToggleBtn.active { + background-color: #0dcaf0 !important; + color: #fff !important; + border-color: #0dcaf0 !important; + } + + /* checkbox dentro la cella Actions */ + .filter-cb-wrap { + display: none; + align-items: center; + justify-content: center; + margin-right: 6px; + flex-shrink: 0; + } + + .grid-container.filters-on .filter-cb-wrap { + display: inline-flex; + } + + .filter-cb-wrap input { + width: 18px !important; + height: 18px !important; + cursor: pointer; + } + + .grid-container.filters-on .button-cell { + display: flex; + align-items: center; + } + + .grid-row.row-selected { + background-color: #cfe9ff !important; + } + + .grid-row.row-selected .button-cell, + .grid-row.row-selected .grid-cell:nth-child(2), + .grid-row.row-selected .grid-cell:nth-child(3) { + background-color: #cfe9ff !important; + } + + .grid-filter-row { + background: #eef6f9 !important; + border-bottom: 2px solid #0dcaf0; + } + + .grid-filter-row .grid-cell { + padding: 4px 8px; + } + + .grid-filter-row .filter-actions-cell { + background: #eef6f9 !important; + } + + .grid-container.filters-on .grid-row { + cursor: pointer; + } + + .grid-container.filters-on .grid-row input, + .grid-container.filters-on .grid-row select, + .grid-container.filters-on .grid-row textarea, + .grid-container.filters-on .grid-row button, + .grid-container.filters-on .grid-row a { + cursor: auto; + } + + /* Sticky su riga propagazione e filtri (fix scroll orizzontale) */ + .grid-top .grid-cell.save-all-cell { + position: sticky !important; + left: 0; + z-index: 9; + background: #fff; + } + + .grid-top .grid-cell:nth-child(2) { + position: sticky !important; + left: 210px; + z-index: 8; + background: #fff; + } + + = 2): ?>.grid-top .grid-cell:nth-child(3) { + position: sticky !important; + left: 360px; + z-index: 7; + background: #fff; + } + + .grid-filter-row .filter-actions-cell { + position: sticky !important; + left: 0; + z-index: 9; + background: #eef6f9 !important; + } + + .grid-filter-row .grid-cell:nth-child(2) { + position: sticky !important; + left: 210px; + z-index: 8; + background: #eef6f9 !important; + } + + = 2): ?>.grid-filter-row .grid-cell:nth-child(3) { + position: sticky !important; + left: 360px; + z-index: 7; + background: #eef6f9 !important; + } + +