filter on imported
This commit is contained in:
@@ -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 = `<input type="checkbox" class="filter-row-checkbox" ${selected.has(id) ? "checked" : ""}>`;
|
||||||
|
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 = `<label class="filter-cb-wrap" style="display:inline-flex;" title="Seleziona tutti i filtrati"><input type="checkbox" id="filterSelectAll"></label>`;
|
||||||
|
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 = `<input type="text" class="filter-col-input" data-col-key="${col.key}" placeholder="🔍 ${col.label || ""}" value="${colFilters[col.key] || ""}" style="width:100%;padding:3px 6px;font-size:12px;border:1px solid #ced4da;border-radius:4px;">`;
|
||||||
|
}
|
||||||
|
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
|
||||||
|
? '<i class="fas fa-eye"></i> Mostra tutti'
|
||||||
|
: '<i class="fas fa-compress"></i> 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 = `<i class="fas fa-spinner fa-spin"></i> ${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 = '<i class="fas fa-filter"></i> 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 = `
|
||||||
|
<span style="font-size:12px;color:#555;">Selezionate: <strong id="filterSelCount">0</strong></span>
|
||||||
|
<button type="button" id="filterRestrictBtn" class="btn btn-outline-primary btn-sm"><i class="fas fa-compress"></i> Restringi selezione</button>
|
||||||
|
<button type="button" id="filterDeleteBtn" class="btn btn-outline-danger btn-sm"><i class="fas fa-trash"></i> Elimina selezionati</button>
|
||||||
|
`;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -1388,6 +1388,11 @@
|
|||||||
|
|
||||||
if (column === "idclient") {
|
if (column === "idclient") {
|
||||||
data.forEach((row) => {
|
data.forEach((row) => {
|
||||||
|
if (
|
||||||
|
window.isTargetGridId &&
|
||||||
|
!window.isTargetGridId(row.iddatadb)
|
||||||
|
)
|
||||||
|
return;
|
||||||
const oldClientId = row.idclient || "";
|
const oldClientId = row.idclient || "";
|
||||||
|
|
||||||
row.idclient = value;
|
row.idclient = value;
|
||||||
@@ -1421,6 +1426,11 @@
|
|||||||
|
|
||||||
if (column === "cliente_fornitore_id") {
|
if (column === "cliente_fornitore_id") {
|
||||||
data.forEach((row) => {
|
data.forEach((row) => {
|
||||||
|
if (
|
||||||
|
window.isTargetGridId &&
|
||||||
|
!window.isTargetGridId(row.iddatadb)
|
||||||
|
)
|
||||||
|
return;
|
||||||
row.cliente_fornitore_id = value;
|
row.cliente_fornitore_id = value;
|
||||||
row._dirty = true;
|
row._dirty = true;
|
||||||
});
|
});
|
||||||
@@ -1434,6 +1444,11 @@
|
|||||||
const fixedKey = column.replace("fixed_", "");
|
const fixedKey = column.replace("fixed_", "");
|
||||||
|
|
||||||
data.forEach((row) => {
|
data.forEach((row) => {
|
||||||
|
if (
|
||||||
|
window.isTargetGridId &&
|
||||||
|
!window.isTargetGridId(row.iddatadb)
|
||||||
|
)
|
||||||
|
return;
|
||||||
if (!row.fixedFields) row.fixedFields = {};
|
if (!row.fixedFields) row.fixedFields = {};
|
||||||
row.fixedFields[fixedKey] = value;
|
row.fixedFields[fixedKey] = value;
|
||||||
row._dirty = true;
|
row._dirty = true;
|
||||||
@@ -1446,6 +1461,11 @@
|
|||||||
|
|
||||||
if (col && (col.type === "detail" || col.type === "main_field")) {
|
if (col && (col.type === "detail" || col.type === "main_field")) {
|
||||||
data.forEach((row) => {
|
data.forEach((row) => {
|
||||||
|
if (
|
||||||
|
window.isTargetGridId &&
|
||||||
|
!window.isTargetGridId(row.iddatadb)
|
||||||
|
)
|
||||||
|
return;
|
||||||
if (!row.details) row.details = {};
|
if (!row.details) row.details = {};
|
||||||
row.details[col.key] = value;
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|||||||
@@ -1328,6 +1328,117 @@ $gridMeta = [
|
|||||||
outline: 2px solid #dc3545 !important;
|
outline: 2px solid #dc3545 !important;
|
||||||
outline-offset: -2px;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
<?php if (isset($mainFieldMappings) && count($mainFieldMappings) >= 2): ?>.grid-top .grid-cell:nth-child(3) {
|
||||||
|
position: sticky !important;
|
||||||
|
left: 360px;
|
||||||
|
z-index: 7;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
<?php endif; ?>.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
<?php if (isset($mainFieldMappings) && count($mainFieldMappings) >= 2): ?>.grid-filter-row .grid-cell:nth-child(3) {
|
||||||
|
position: sticky !important;
|
||||||
|
left: 360px;
|
||||||
|
z-index: 7;
|
||||||
|
background: #eef6f9 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
</style>
|
</style>
|
||||||
<title>Edit Imported Data - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
<title>Edit Imported Data - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||||
</head>
|
</head>
|
||||||
@@ -1456,6 +1567,7 @@ $gridMeta = [
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||||
<script src="tracking.js"></script>
|
<script src="tracking.js"></script>
|
||||||
<script src="gridRenderer.js"></script>
|
<script src="gridRenderer.js"></script>
|
||||||
|
<script src="gridFilter.js"></script>
|
||||||
<script src="saveAll.js"></script>
|
<script src="saveAll.js"></script>
|
||||||
<script src="exportLims_gridData.js"></script>
|
<script src="exportLims_gridData.js"></script>
|
||||||
<script src="modals_gridData.js"></script>
|
<script src="modals_gridData.js"></script>
|
||||||
|
|||||||
@@ -103,30 +103,31 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ===== KPI ===== -->
|
<!-- ===== KPI ===== -->
|
||||||
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-6 g-3 kpi-row">
|
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-5 g-3 kpi-row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card radius-10 stat-card stat-card-extra h-100">
|
<div class="card radius-10 stat-card stat-card-extra h-100">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="stat-label">Export mese <span id="kpiMonthLabel"></span></div>
|
<div class="stat-label">Campioni mese <span id="kpiMonthLabel"></span></div>
|
||||||
<div class="stat-value text-primary" id="kpiExportMonth">0</div>
|
<div class="stat-value" id="kpiPartsMonth">0</div>
|
||||||
<small class="text-muted">Dati dal 16/07/2026</small>
|
<small class="text-muted">Commesse: <span id="kpiExportMonth">0</span> · dal 16/07/2026</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card radius-10 stat-card stat-card-extra h-100">
|
<div class="card radius-10 stat-card stat-card-extra h-100">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="stat-label">Export anno <span id="kpiYearLabel"></span></div>
|
<div class="stat-label">Campioni anno <span id="kpiYearLabel"></span></div>
|
||||||
<div class="stat-value text-dark" id="kpiExportYear">0</div>
|
<div class="stat-value" id="kpiPartsYear">0</div>
|
||||||
<small class="text-muted">Dati dal 16/07/2026</small>
|
<small class="text-muted">Commesse: <span id="kpiExportYear">0</span> · dal 16/07/2026</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card radius-10 stat-card h-100">
|
<div class="card radius-10 stat-card h-100">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="stat-label">Export giorno (status L)</div>
|
<div class="stat-label">Campioni esportati (giorno)</div>
|
||||||
<div class="stat-value text-primary" id="kpiExport">0</div>
|
<div class="stat-value text-primary" id="kpiParts">0</div>
|
||||||
|
<small class="text-muted">Commesse: <span id="kpiExport">0</span></small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -146,14 +147,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
|
||||||
<div class="card radius-10 stat-card h-100">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="stat-label">Clienti distinti</div>
|
|
||||||
<div class="stat-value text-warning" id="kpiClienti">0</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ===== DATO GIORNALIERO CAMPIONI LIMS ===== -->
|
<!-- ===== DATO GIORNALIERO CAMPIONI LIMS ===== -->
|
||||||
@@ -222,7 +215,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>ID Cliente</th>
|
<th>ID Cliente</th>
|
||||||
<th>Cliente</th>
|
<th>Cliente</th>
|
||||||
<th>Export</th>
|
<th>Campioni</th>
|
||||||
|
<th>Commesse</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody></tbody>
|
<tbody></tbody>
|
||||||
@@ -243,7 +237,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>ID Utente</th>
|
<th>ID Utente</th>
|
||||||
<th>Utente</th>
|
<th>Utente</th>
|
||||||
<th>Export</th>
|
<th>Campioni</th>
|
||||||
|
<th>Commesse</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody></tbody>
|
<tbody></tbody>
|
||||||
@@ -295,6 +290,10 @@
|
|||||||
data: 'clientname',
|
data: 'clientname',
|
||||||
defaultContent: '—'
|
defaultContent: '—'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
data: 'parts',
|
||||||
|
className: 'text-end'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
data: 'tot',
|
data: 'tot',
|
||||||
className: 'text-end'
|
className: 'text-end'
|
||||||
@@ -318,6 +317,10 @@
|
|||||||
data: 'username',
|
data: 'username',
|
||||||
defaultContent: '—'
|
defaultContent: '—'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
data: 'parts',
|
||||||
|
className: 'text-end'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
data: 'tot',
|
data: 'tot',
|
||||||
className: 'text-end'
|
className: 'text-end'
|
||||||
@@ -428,11 +431,13 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// KPI
|
// KPI — dato principale = parti/campioni, sotto le commesse
|
||||||
$('#kpiExport').text(res.totals.export);
|
$('#kpiParts').text(res.totals.parts ?? 0);
|
||||||
$('#kpiExportMonth').text(res.totalsMonth ? res.totalsMonth.export : 0);
|
$('#kpiExport').text(res.totals.export ?? 0);
|
||||||
$('#kpiExportYear').text(res.totalsYear ? res.totalsYear.export : 0);
|
$('#kpiPartsMonth').text(res.totalsMonth ? (res.totalsMonth.parts ?? 0) : 0);
|
||||||
$('#kpiClienti').text(res.byClient.length);
|
$('#kpiExportMonth').text(res.totalsMonth ? (res.totalsMonth.export ?? 0) : 0);
|
||||||
|
$('#kpiPartsYear').text(res.totalsYear ? (res.totalsYear.parts ?? 0) : 0);
|
||||||
|
$('#kpiExportYear').text(res.totalsYear ? (res.totalsYear.export ?? 0) : 0);
|
||||||
$('#kpiLimsSamples').text(res.limsSamples ?? 0);
|
$('#kpiLimsSamples').text(res.limsSamples ?? 0);
|
||||||
$('#limsSamplesInput').val(res.limsSamples ?? '');
|
$('#limsSamplesInput').val(res.limsSamples ?? '');
|
||||||
|
|
||||||
@@ -452,25 +457,26 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const limsTot = parseInt(res.limsSamples ?? 0, 10);
|
const limsTot = parseInt(res.limsSamples ?? 0, 10);
|
||||||
|
const partsTot = parseInt(res.totals.parts ?? 0, 10);
|
||||||
if (limsTot > 0) {
|
if (limsTot > 0) {
|
||||||
const perc = (res.totals.export / limsTot) * 100;
|
const perc = (partsTot / limsTot) * 100;
|
||||||
$('#kpiPerc').text(perc.toFixed(1) + '%');
|
$('#kpiPerc').text(perc.toFixed(1) + '%');
|
||||||
} else {
|
} else {
|
||||||
$('#kpiPerc').text('—');
|
$('#kpiPerc').text('—');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Torta esportati vs totale
|
// Torta parti esportate vs totale inserito nel LIMS
|
||||||
renderPie(res.totals.export, limsTot);
|
renderPie(partsTot, limsTot);
|
||||||
|
|
||||||
// Grafici
|
// Grafici — valori = campioni/parti
|
||||||
const clientTop = topN(res.byClient);
|
const clientTop = topN(res.byClient);
|
||||||
const userTop = topN(res.byUser);
|
const userTop = topN(res.byUser);
|
||||||
chartClienti = renderBarChart(chartClienti, 'chartClienti',
|
chartClienti = renderBarChart(chartClienti, 'chartClienti',
|
||||||
clientTop.map(r => r.clientname ? (r.clientname + ' (' + r.idclient + ')') : ('ID ' + r.idclient)),
|
clientTop.map(r => r.clientname ? (r.clientname + ' (' + r.idclient + ')') : ('ID ' + r.idclient)),
|
||||||
clientTop.map(r => r.tot), 'Export');
|
clientTop.map(r => r.parts), 'Campioni');
|
||||||
chartUtenti = renderBarChart(chartUtenti, 'chartUtenti',
|
chartUtenti = renderBarChart(chartUtenti, 'chartUtenti',
|
||||||
userTop.map(r => r.username ? (r.username + ' (' + r.user_id + ')') : ('ID ' + r.user_id)),
|
userTop.map(r => r.username ? (r.username + ' (' + r.user_id + ')') : ('ID ' + r.user_id)),
|
||||||
userTop.map(r => r.tot), 'Export');
|
userTop.map(r => r.parts), 'Campioni');
|
||||||
|
|
||||||
// Tabelle
|
// Tabelle
|
||||||
dtClienti.clear().rows.add(res.byClient).draw();
|
dtClienti.clear().rows.add(res.byClient).draw();
|
||||||
|
|||||||
@@ -15,14 +15,17 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Export per cliente (status = 'l', giornata su export_date) ---
|
// --- Export per cliente (status = 'l', giornata su export_date) ---
|
||||||
|
// tot = numero commesse, parts = numero parti/campioni
|
||||||
$stmtC = $pdo->prepare("
|
$stmtC = $pdo->prepare("
|
||||||
SELECT d.idclient AS idclient,
|
SELECT d.idclient AS idclient,
|
||||||
COUNT(*) AS tot
|
COUNT(DISTINCT d.iddatadb) AS tot,
|
||||||
|
COUNT(ip.id) AS parts
|
||||||
FROM datadb d
|
FROM datadb d
|
||||||
|
LEFT JOIN identification_parts ip ON ip.iddatadb = d.iddatadb
|
||||||
WHERE d.status = 'l'
|
WHERE d.status = 'l'
|
||||||
AND DATE(d.export_date) = :d
|
AND DATE(d.export_date) = :d
|
||||||
GROUP BY d.idclient
|
GROUP BY d.idclient
|
||||||
ORDER BY tot DESC
|
ORDER BY parts DESC
|
||||||
");
|
");
|
||||||
$stmtC->execute(['d' => $date]);
|
$stmtC->execute(['d' => $date]);
|
||||||
$byClient = $stmtC->fetchAll(PDO::FETCH_ASSOC);
|
$byClient = $stmtC->fetchAll(PDO::FETCH_ASSOC);
|
||||||
@@ -38,16 +41,19 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Export per utente (status = 'l', giornata su export_date) ---
|
// --- Export per utente (status = 'l', giornata su export_date) ---
|
||||||
|
// tot = numero commesse, parts = numero parti/campioni
|
||||||
$stmtU = $pdo->prepare("
|
$stmtU = $pdo->prepare("
|
||||||
SELECT d.user_id AS user_id,
|
SELECT d.user_id AS user_id,
|
||||||
TRIM(CONCAT(COALESCE(u.first_name,''),' ',COALESCE(u.last_name,''))) AS username,
|
TRIM(CONCAT(COALESCE(u.first_name,''),' ',COALESCE(u.last_name,''))) AS username,
|
||||||
COUNT(*) AS tot
|
COUNT(DISTINCT d.iddatadb) AS tot,
|
||||||
|
COUNT(ip.id) AS parts
|
||||||
FROM datadb d
|
FROM datadb d
|
||||||
LEFT JOIN auth_users u ON u.id = d.user_id
|
LEFT JOIN auth_users u ON u.id = d.user_id
|
||||||
|
LEFT JOIN identification_parts ip ON ip.iddatadb = d.iddatadb
|
||||||
WHERE d.status = 'l'
|
WHERE d.status = 'l'
|
||||||
AND DATE(d.export_date) = :d
|
AND DATE(d.export_date) = :d
|
||||||
GROUP BY d.user_id, username
|
GROUP BY d.user_id, username
|
||||||
ORDER BY tot DESC
|
ORDER BY parts DESC
|
||||||
");
|
");
|
||||||
$stmtU->execute(['d' => $date]);
|
$stmtU->execute(['d' => $date]);
|
||||||
$byUser = $stmtU->fetchAll(PDO::FETCH_ASSOC);
|
$byUser = $stmtU->fetchAll(PDO::FETCH_ASSOC);
|
||||||
@@ -56,6 +62,7 @@ try {
|
|||||||
foreach ($byClient as &$r) {
|
foreach ($byClient as &$r) {
|
||||||
$r['idclient'] = $r['idclient'] !== null ? (int)$r['idclient'] : null;
|
$r['idclient'] = $r['idclient'] !== null ? (int)$r['idclient'] : null;
|
||||||
$r['tot'] = (int)$r['tot'];
|
$r['tot'] = (int)$r['tot'];
|
||||||
|
$r['parts'] = (int)$r['parts'];
|
||||||
$nm = $clientNames[$r['idclient']] ?? '';
|
$nm = $clientNames[$r['idclient']] ?? '';
|
||||||
$r['clientname'] = $nm !== '' ? $nm : null;
|
$r['clientname'] = $nm !== '' ? $nm : null;
|
||||||
}
|
}
|
||||||
@@ -63,6 +70,7 @@ try {
|
|||||||
foreach ($byUser as &$r) {
|
foreach ($byUser as &$r) {
|
||||||
$r['user_id'] = $r['user_id'] !== null ? (int)$r['user_id'] : null;
|
$r['user_id'] = $r['user_id'] !== null ? (int)$r['user_id'] : null;
|
||||||
$r['tot'] = (int)$r['tot'];
|
$r['tot'] = (int)$r['tot'];
|
||||||
|
$r['parts'] = (int)$r['parts'];
|
||||||
if (isset($r['username'])) $r['username'] = $r['username'] !== null && $r['username'] !== '' ? $r['username'] : null;
|
if (isset($r['username'])) $r['username'] = $r['username'] !== null && $r['username'] !== '' ? $r['username'] : null;
|
||||||
}
|
}
|
||||||
unset($r);
|
unset($r);
|
||||||
@@ -70,7 +78,19 @@ try {
|
|||||||
// --- Totale export della giornata ---
|
// --- Totale export della giornata ---
|
||||||
$totExport = array_sum(array_column($byClient, 'tot'));
|
$totExport = array_sum(array_column($byClient, 'tot'));
|
||||||
|
|
||||||
// --- Totale export del MESE della data selezionata (status = 'l') ---
|
// --- Totale PARTI/CAMPIONI della giornata (righe identification_parts legate
|
||||||
|
// ai datadb esportati status='l' in questa giornata) ---
|
||||||
|
$stmtP = $pdo->prepare("
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM identification_parts ip
|
||||||
|
INNER JOIN datadb d ON d.iddatadb = ip.iddatadb
|
||||||
|
WHERE d.status = 'l'
|
||||||
|
AND DATE(d.export_date) = :d
|
||||||
|
");
|
||||||
|
$stmtP->execute(['d' => $date]);
|
||||||
|
$totParts = (int)$stmtP->fetchColumn();
|
||||||
|
|
||||||
|
// --- Totale COMMESSE del MESE (status = 'l') ---
|
||||||
$stmtM = $pdo->prepare("
|
$stmtM = $pdo->prepare("
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM datadb d
|
FROM datadb d
|
||||||
@@ -80,7 +100,18 @@ try {
|
|||||||
$stmtM->execute(['m' => $d->format('Y-m')]);
|
$stmtM->execute(['m' => $d->format('Y-m')]);
|
||||||
$totExportMonth = (int)$stmtM->fetchColumn();
|
$totExportMonth = (int)$stmtM->fetchColumn();
|
||||||
|
|
||||||
// --- Totale export dell'ANNO della data selezionata (status = 'l') ---
|
// --- Totale PARTI del MESE (status = 'l') ---
|
||||||
|
$stmtMP = $pdo->prepare("
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM identification_parts ip
|
||||||
|
INNER JOIN datadb d ON d.iddatadb = ip.iddatadb
|
||||||
|
WHERE d.status = 'l'
|
||||||
|
AND DATE_FORMAT(d.export_date, '%Y-%m') = :m
|
||||||
|
");
|
||||||
|
$stmtMP->execute(['m' => $d->format('Y-m')]);
|
||||||
|
$totPartsMonth = (int)$stmtMP->fetchColumn();
|
||||||
|
|
||||||
|
// --- Totale COMMESSE dell'ANNO (status = 'l') ---
|
||||||
$stmtY = $pdo->prepare("
|
$stmtY = $pdo->prepare("
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM datadb d
|
FROM datadb d
|
||||||
@@ -90,6 +121,17 @@ try {
|
|||||||
$stmtY->execute(['y' => $d->format('Y')]);
|
$stmtY->execute(['y' => $d->format('Y')]);
|
||||||
$totExportYear = (int)$stmtY->fetchColumn();
|
$totExportYear = (int)$stmtY->fetchColumn();
|
||||||
|
|
||||||
|
// --- Totale PARTI dell'ANNO (status = 'l') ---
|
||||||
|
$stmtYP = $pdo->prepare("
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM identification_parts ip
|
||||||
|
INNER JOIN datadb d ON d.iddatadb = ip.iddatadb
|
||||||
|
WHERE d.status = 'l'
|
||||||
|
AND YEAR(d.export_date) = :y
|
||||||
|
");
|
||||||
|
$stmtYP->execute(['y' => $d->format('Y')]);
|
||||||
|
$totPartsYear = (int)$stmtYP->fetchColumn();
|
||||||
|
|
||||||
// --- Campioni totali inseriti nel LIMS per la giornata (tabella lims_daily_samples) ---
|
// --- Campioni totali inseriti nel LIMS per la giornata (tabella lims_daily_samples) ---
|
||||||
$stmtL = $pdo->prepare("SELECT total_samples FROM lims_daily_samples WHERE sample_date = :d LIMIT 1");
|
$stmtL = $pdo->prepare("SELECT total_samples FROM lims_daily_samples WHERE sample_date = :d LIMIT 1");
|
||||||
$stmtL->execute(['d' => $date]);
|
$stmtL->execute(['d' => $date]);
|
||||||
@@ -101,9 +143,9 @@ try {
|
|||||||
'date' => $date,
|
'date' => $date,
|
||||||
'month' => $d->format('Y-m'),
|
'month' => $d->format('Y-m'),
|
||||||
'year' => (int)$d->format('Y'),
|
'year' => (int)$d->format('Y'),
|
||||||
'totals' => ['export' => (int)$totExport],
|
'totals' => ['export' => (int)$totExport, 'parts' => $totParts],
|
||||||
'totalsMonth' => ['export' => $totExportMonth],
|
'totalsMonth' => ['export' => $totExportMonth, 'parts' => $totPartsMonth],
|
||||||
'totalsYear' => ['export' => $totExportYear],
|
'totalsYear' => ['export' => $totExportYear, 'parts' => $totPartsYear],
|
||||||
'byClient' => $byClient,
|
'byClient' => $byClient,
|
||||||
'byUser' => $byUser,
|
'byUser' => $byUser,
|
||||||
'limsSamples' => $limsSamples,
|
'limsSamples' => $limsSamples,
|
||||||
|
|||||||
Reference in New Issue
Block a user