diff --git a/public/userarea/matrici.php b/public/userarea/matrici.php index ad6d9db4..5d1cdc44 100644 --- a/public/userarea/matrici.php +++ b/public/userarea/matrici.php @@ -64,6 +64,7 @@ function revisionLabel($rev) + @@ -125,6 +126,57 @@ function revisionLabel($rev) text-align: center; } + /* paginazione tabella: pulsanti distanziati e cliccabili */ + .dataTables_wrapper .dataTables_paginate { + margin-top: 12px; + } + + .dataTables_wrapper .dataTables_paginate .pagination { + flex-wrap: wrap; + gap: 4px; + justify-content: flex-end; + } + + .dataTables_wrapper .dataTables_paginate .page-link, + .dataTables_wrapper .dataTables_paginate a.paginate_button { + display: inline-block; + min-width: 40px; + padding: 8px 12px; + margin: 0 2px; + border-radius: 10px !important; + border: 1px solid #dee2e6; + text-align: center; + cursor: pointer; + user-select: none; + } + + .dataTables_wrapper .dataTables_paginate .page-item.active .page-link, + .dataTables_wrapper .dataTables_paginate a.paginate_button.current { + background: #198754; + border-color: #198754; + color: #fff; + } + + .dataTables_wrapper .dataTables_paginate .page-item.disabled .page-link, + .dataTables_wrapper .dataTables_paginate a.paginate_button.disabled { + opacity: .5; + cursor: default; + } + + @keyframes rowFlash { + from { + background-color: #fde68a; + } + + to { + background-color: transparent; + } + } + + #tabellaMatrici tbody tr.row-flash>td { + animation: rowFlash 1.8s ease-out; + } + .action-btn { border: none; background: none; @@ -1371,10 +1423,109 @@ function revisionLabel($rev) $("#filePreviewContainer").html(html); - const modal = new bootstrap.Modal(document.getElementById('filePreviewModal')); + const modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('filePreviewModal')); modal.show(); } + /* ---------- aggiornamento tabella senza ricaricare la pagina ---------- + * Dopo un salvataggio rilegge la pagina in background e aggiorna solo le righe cambiate: + * ricerca, pagina e ordinamento restano dove l'utente li aveva lasciati. */ + function rowId(tr) { + const b = tr && tr.querySelector('.action-btn.edit[data-id]'); + return b ? String(b.getAttribute('data-id')) : null; + } + + function hideTooltips() { + document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => { + const t = bootstrap.Tooltip.getInstance(el); + if (t) t.hide(); + }); + document.querySelectorAll('.tooltip').forEach(el => el.remove()); + } + + function toastOk(msg) { + Swal.fire({ + toast: true, + position: 'top-end', + icon: 'success', + title: msg || 'Salvato', + showConfirmButton: false, + timer: 1600 + }); + } + + function flashRow(tr) { + if (!tr) return; + tr.classList.remove('row-flash'); + void tr.offsetWidth; + tr.classList.add('row-flash'); + } + + // focusId: riga da evidenziare; se è nuova e non visibile con il filtro attuale, cerco il suo nome + function refreshTable(focusId) { + const dt = $('#tabellaMatrici').DataTable(); + return fetch(location.href, { + cache: 'no-store', + credentials: 'same-origin' + }) + .then(r => r.text()) + .then(html => { + const doc = new DOMParser().parseFromString(html, 'text/html'); + const fresh = {}; + doc.querySelectorAll('#tabellaMatrici tbody tr').forEach(tr => { + const id = rowId(tr); + if (id) fresh[id] = tr; + }); + if (!Object.keys(fresh).length) return location.reload(); // risposta inattesa: ricarico come prima + + hideTooltips(); + const seen = {}; + const added = []; + const gone = []; + dt.rows().every(function() { + const tr = this.node(); + const id = rowId(tr); + if (!id || !fresh[id]) { // eliminata (o riga "Nessuna matrice presente") + gone.push(tr); + return; + } + seen[id] = true; + const nw = fresh[id]; + if (tr.innerHTML !== nw.innerHTML) { + Array.from(nw.cells).forEach((td, i) => { + const old = tr.cells[i]; + if (!old) return; + old.innerHTML = td.innerHTML; + if (td.hasAttribute('title')) old.setAttribute('title', td.getAttribute('title')); + }); + this.invalidate('dom'); + flashRow(tr); + } + }); + gone.forEach(tr => dt.row(tr).remove()); + Object.keys(fresh).forEach(id => { + if (!seen[id]) { + const node = document.importNode(fresh[id], true); + dt.row.add(node); + added.push(node); + } + }); + dt.draw(false); + added.forEach(flashRow); + + // matrice nuova: la mostro (filtrando per il suo nome se non è in pagina) + if (focusId && fresh[focusId]) { + const node = dt.rows().nodes().toArray().find(tr => rowId(tr) === String(focusId)); + if (node && !document.body.contains(node)) { + const nome = (fresh[focusId].cells[1] && fresh[focusId].cells[1].textContent.trim()) || ''; + if (nome) dt.search(nome).draw(); + } + flashRow(node); + } + }) + .catch(() => location.reload()); + } + $(document).ready(function() { $('#tabellaMatrici').DataTable({ @@ -1477,12 +1628,24 @@ function revisionLabel($rev) }) .then(r => r.json()) .then(data => { - Swal.fire({ - icon: data.success ? "success" : "error", - title: data.success ? "Salvato!" : "Errore", - text: data.message || "" - }).then(() => location.reload()); - }); + if (!data.success) { + Swal.fire({ + icon: "error", + title: "Errore", + text: data.message || "" + }); + return; + } + bootstrap.Modal.getOrCreateInstance(document.getElementById('addMatriceModal')).hide(); + toastOk(data.message || "Matrice salvata"); + // id della matrice nuova (se il salvataggio lo restituisce), altrimenti quella modificata + refreshTable(data.id || data.idmatrice || id || null); + }) + .catch(() => Swal.fire({ + icon: "error", + title: "Errore", + text: "Errore di comunicazione con il server" + })); }); $(document).on("click", ".delete", function() { @@ -1500,17 +1663,26 @@ function revisionLabel($rev) fetch("delete_matrice.php?id=" + encodeURIComponent(id)) .then(r => r.json()) .then(data => { - Swal.fire({ - icon: data.success ? "success" : "error", - title: data.success ? "Eliminata!" : "Errore", - text: data.message || "" - }).then(() => location.reload()); + if (!data.success) { + Swal.fire({ + icon: "error", + title: "Errore", + text: data.message || "" + }); + return; + } + toastOk(data.message || "Matrice eliminata"); + refreshTable(); }); } }); }); - document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => new bootstrap.Tooltip(el)); + new bootstrap.Tooltip(document.body, { + selector: '[data-bs-toggle="tooltip"]', + trigger: 'hover' + }); + document.addEventListener('show.bs.modal', hideTooltips); $('#ml_linee').select2({ theme: 'bootstrap-5', @@ -1547,7 +1719,7 @@ function revisionLabel($rev) }); $sel.val((data.selected_ids || []).map(String)).trigger("change"); - new bootstrap.Modal(document.getElementById('matriceLineeModal')).show(); + bootstrap.Modal.getOrCreateInstance(document.getElementById('matriceLineeModal')).show(); }); }); @@ -1565,16 +1737,17 @@ function revisionLabel($rev) }) .then(r => r.json()) .then(data => { - Swal.fire({ - icon: data.success ? "success" : "error", - title: data.success ? "Salvato!" : "Errore", - text: data.message || "" - }).then(() => { - if (data.success) { - bootstrap.Modal.getInstance(document.getElementById('matriceLineeModal')).hide(); - location.reload(); - } - }); + if (!data.success) { + Swal.fire({ + icon: "error", + title: "Errore", + text: data.message || "" + }); + return; + } + bootstrap.Modal.getOrCreateInstance(document.getElementById('matriceLineeModal')).hide(); + toastOk(data.message || "Linee salvate"); + refreshTable(idmatrice); }); }); @@ -1599,9 +1772,7 @@ function revisionLabel($rev) const loadingOption = new Option("Caricamento mescole...", "", false, false); $sel.append(loadingOption).trigger("change"); - const modalEl = document.getElementById('matriceMescoleModal'); - const modal = new bootstrap.Modal(modalEl); - modal.show(); + bootstrap.Modal.getOrCreateInstance(document.getElementById('matriceMescoleModal')).show(); fetch("get_matrice_mescole.php?id=" + encodeURIComponent(idmatrice)) .then(r => { @@ -1652,16 +1823,17 @@ function revisionLabel($rev) }) .then(r => r.json()) .then(data => { - Swal.fire({ - icon: data.success ? "success" : "error", - title: data.success ? "Salvato!" : "Errore", - text: data.message || "" - }).then(() => { - if (data.success) { - bootstrap.Modal.getInstance(document.getElementById('matriceMescoleModal')).hide(); - location.reload(); - } - }); + if (!data.success) { + Swal.fire({ + icon: "error", + title: "Errore", + text: data.message || "" + }); + return; + } + bootstrap.Modal.getOrCreateInstance(document.getElementById('matriceMescoleModal')).hide(); + toastOk(data.message || "Mescole salvate"); + refreshTable(idmatrice); }); }); @@ -1678,7 +1850,7 @@ function revisionLabel($rev) $("#mlm_list").html(items.map(x => `${escapeHtml(x)}`).join('')); } - new bootstrap.Modal(document.getElementById('mescoleListModal')).show(); + bootstrap.Modal.getOrCreateInstance(document.getElementById('mescoleListModal')).show(); }); $(document).on("click", ".files", function() { @@ -1692,7 +1864,7 @@ function revisionLabel($rev) renderSelectedAttachments(); loadExistingAttachments(idmatrice); - new bootstrap.Modal(document.getElementById('matriceFilesModal')).show(); + bootstrap.Modal.getOrCreateInstance(document.getElementById('matriceFilesModal')).show(); }); $("#mf_pick_files_btn, #attachmentsDropzone").on("click", function() {