server side filtering and ui-fixes
This commit is contained in:
@@ -0,0 +1,340 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
require_once __DIR__ . '/include/grid_data_builder.php';
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
const FILTER_ALLOWED_PER_PAGE = [20, 40, 60, 100];
|
||||||
|
const FILTER_ALLIDS_CAP = 5000;
|
||||||
|
|
||||||
|
$body = json_decode(file_get_contents('php://input'), true) ?: [];
|
||||||
|
|
||||||
|
$templateId = (int)($body['template_id'] ?? 0);
|
||||||
|
if ($templateId <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'template_id mancante']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = in_array($body['status'] ?? 'i', ['i', 'P', 'l'], true) ? $body['status'] : 'i';
|
||||||
|
$showAll = !empty($body['all_users']);
|
||||||
|
$importref = trim((string)($body['importref'] ?? '')); // scope su un singolo import
|
||||||
|
$filters = is_array($body['filters'] ?? null) ? $body['filters'] : [];
|
||||||
|
$userId = (int)($iduserlogin ?? 0);
|
||||||
|
$perPage = in_array((int)($body['per_page'] ?? 20), FILTER_ALLOWED_PER_PAGE, true) ? (int)$body['per_page'] : 20;
|
||||||
|
$page = max(1, (int)($body['page'] ?? 1));
|
||||||
|
$wantAllIds = !empty($body['want_all_ids']);
|
||||||
|
|
||||||
|
$cacheDir = __DIR__ . '/cache';
|
||||||
|
|
||||||
|
$fixedAliasMap = [
|
||||||
|
'ClienteResponsabile' => 'cliente_responsabile_id',
|
||||||
|
'ClienteFornitore' => 'cliente_fornitore_id',
|
||||||
|
'ClienteAnalisi' => 'clienteAnalisi',
|
||||||
|
'ClienteFatturazione' => 'ClienteFatturazione',
|
||||||
|
'MoltiplicatorePrezzo' => 'moltiplicatore_prezzo_id',
|
||||||
|
'AnagraficaCertestObject' => 'anagrafica_certest_object_id',
|
||||||
|
'AnagraficaCertestService' => 'anagrafica_certest_service_id',
|
||||||
|
'ConsegnaRichiesta' => 'consegna_richiesta',
|
||||||
|
];
|
||||||
|
$staticCols = ['importreferencecode', 'filename_import', 'importdate', 'commessaweb'];
|
||||||
|
|
||||||
|
// ── Risolutori "etichetta → lista di ID" via cache ────────────────────────
|
||||||
|
function cacheItems(string $file): array
|
||||||
|
{
|
||||||
|
if (!is_file($file)) return [];
|
||||||
|
$data = json_decode(file_get_contents($file), true);
|
||||||
|
if (!is_array($data)) return [];
|
||||||
|
return $data['value'] ?? $data ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clienti: label "Nome - X - id" (come search_clienti.php). */
|
||||||
|
function resolveClientIdsByTerm(string $cacheDir, string $term): array
|
||||||
|
{
|
||||||
|
$term = mb_strtolower(trim($term));
|
||||||
|
if ($term === '') return [];
|
||||||
|
$ids = [];
|
||||||
|
foreach (cacheItems($cacheDir . '/clienti.json') as $c) {
|
||||||
|
$name = trim($c['Nominativo'] ?? '');
|
||||||
|
$id = trim((string)($c['IdCliente'] ?? ''));
|
||||||
|
$code = trim((string)($c['CodiceCliente'] ?? ''));
|
||||||
|
$parts = explode('_', $code);
|
||||||
|
$suffix = trim($parts[1] ?? '');
|
||||||
|
if ($suffix === '' && $code !== '') $suffix = substr($code, 0, 1);
|
||||||
|
if ($suffix === '') $suffix = '--';
|
||||||
|
$label = mb_strtolower($name . ' - ' . $suffix . ' - ' . $id);
|
||||||
|
if (mb_strpos($label, $term) !== false) $ids[] = (int)$c['IdCliente'];
|
||||||
|
}
|
||||||
|
return array_values(array_unique($ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Dropdown generico: matcha il termine su qualsiasi label field, ritorna gli id. */
|
||||||
|
function resolveIdsFromCache(string $file, string $idField, array $labelFields, string $term): array
|
||||||
|
{
|
||||||
|
$term = mb_strtolower(trim($term));
|
||||||
|
if ($term === '') return [];
|
||||||
|
$ids = [];
|
||||||
|
foreach (cacheItems($file) as $it) {
|
||||||
|
$hay = '';
|
||||||
|
foreach ($labelFields as $lf) $hay .= ' ' . ($it[$lf] ?? '');
|
||||||
|
if (mb_strpos(mb_strtolower($hay), $term) !== false && isset($it[$idField])) {
|
||||||
|
$ids[] = (int)$it[$idField];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array_values(array_unique($ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Per un fixed field che memorizza un ID, risolve il termine in lista di id.
|
||||||
|
* Ritorna: array di id (IN), [] se il termine non matcha nulla, null se la colonna
|
||||||
|
* non è risolvibile (→ il chiamante la salta invece di fare LIKE sbagliato).
|
||||||
|
*/
|
||||||
|
function resolveFixedIds(string $cacheDir, string $key, string $term): ?array
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'ClienteAnalisi':
|
||||||
|
case 'ClienteFatturazione':
|
||||||
|
return resolveClientIdsByTerm($cacheDir, $term);
|
||||||
|
case 'MoltiplicatorePrezzo':
|
||||||
|
return resolveIdsFromCache($cacheDir . '/moltiplicatori_prezzo.json', 'IdMoltiplicatorePrezzo', ['Codice', 'Descrizione'], $term);
|
||||||
|
case 'AnagraficaCertestObject':
|
||||||
|
return resolveIdsFromCache($cacheDir . '/anagrafica_certest_object.json', 'IdAnagrafica', ['Codice', 'NomeAnagrafica'], $term);
|
||||||
|
case 'AnagraficaCertestService':
|
||||||
|
return resolveIdsFromCache($cacheDir . '/anagrafica_certest_service.json', 'IdAnagrafica', ['Codice', 'NomeAnagrafica'], $term);
|
||||||
|
default:
|
||||||
|
// ClienteResponsabile (per-cliente) e altri: non risolvibili globalmente
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<array{0:int,1:int}> lista di [idclient, idResponsabile]
|
||||||
|
*/
|
||||||
|
function resolveResponsabilePairs(string $cacheDir, string $term): array
|
||||||
|
{
|
||||||
|
$term = mb_strtolower(trim($term));
|
||||||
|
if ($term === '') return [];
|
||||||
|
$pairs = [];
|
||||||
|
foreach (glob($cacheDir . '/cliente_responsabili_*.json') ?: [] as $file) {
|
||||||
|
if (!preg_match('/cliente_responsabili_(\d+)\.json$/', $file, $m)) continue;
|
||||||
|
$cid = (int)$m[1];
|
||||||
|
$data = json_decode(file_get_contents($file), true);
|
||||||
|
foreach (($data['Responsabili'] ?? []) as $r) {
|
||||||
|
$name = mb_strtolower((string)($r['Nominativo'] ?? ''));
|
||||||
|
if ($name !== '' && mb_strpos($name, $term) !== false && isset($r['IdClienteResponsabile'])) {
|
||||||
|
$pairs[] = [$cid, (int)$r['IdClienteResponsabile']];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $pairs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Meta di un mapping (data_type, field_id), con cache statica. */
|
||||||
|
function mappingMeta(PDO $pdo, int $mappingId): array
|
||||||
|
{
|
||||||
|
static $cache = [];
|
||||||
|
if (!array_key_exists($mappingId, $cache)) {
|
||||||
|
$st = $pdo->prepare("SELECT data_type, field_id FROM template_mapping WHERE id = ?");
|
||||||
|
$st->execute([$mappingId]);
|
||||||
|
$cache[$mappingId] = $st->fetch(PDO::FETCH_ASSOC) ?: ['data_type' => 'Testo', 'field_id' => null];
|
||||||
|
}
|
||||||
|
return $cache[$mappingId];
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveCustomFieldValueIds(string $cacheDir, $fieldId, string $term): array
|
||||||
|
{
|
||||||
|
$fieldId = (int)$fieldId;
|
||||||
|
$term = mb_strtolower(trim($term));
|
||||||
|
if ($fieldId <= 0 || $term === '') return [];
|
||||||
|
$ids = [];
|
||||||
|
foreach (cacheItems($cacheDir . "/customfield_{$fieldId}.json") as $it) {
|
||||||
|
$label = mb_strtolower((string)($it['Valore'] ?? ''));
|
||||||
|
if (mb_strpos($label, $term) !== false && isset($it['IdCustomFieldsValue'])) {
|
||||||
|
$ids[] = (int)$it['IdCustomFieldsValue'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array_values(array_unique($ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Costruzione WHERE ──────────────────────────────────────────────────────
|
||||||
|
$conds = ['d.templateid = ?', 'd.status = ?'];
|
||||||
|
$params = [$templateId, $status];
|
||||||
|
$needUserJoin = false;
|
||||||
|
|
||||||
|
if (!$showAll) {
|
||||||
|
$conds[] = 'd.user_id = ?';
|
||||||
|
$params[] = $userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scope su un singolo import (come imported.php ?importref=)
|
||||||
|
if ($importref !== '') {
|
||||||
|
$conds[] = 'd.importreferencecode = ?';
|
||||||
|
$params[] = $importref;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Aggiunge una condizione "colonna id IN (lista risolta)". */
|
||||||
|
function addIdInCondition(array &$conds, array &$params, string $col, array $ids): void
|
||||||
|
{
|
||||||
|
if (empty($ids)) {
|
||||||
|
$conds[] = '1 = 0'; // termine dato ma nessun match → zero risultati
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$ph = implode(',', array_fill(0, count($ids), '?'));
|
||||||
|
$conds[] = "d.`{$col}` IN ($ph)";
|
||||||
|
foreach ($ids as $id) $params[] = (int)$id;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($filters as $f) {
|
||||||
|
$term = trim((string)($f['term'] ?? ''));
|
||||||
|
if ($term === '') continue;
|
||||||
|
$type = (string)($f['type'] ?? '');
|
||||||
|
$key = (string)($f['key'] ?? '');
|
||||||
|
$like = '%' . $term . '%';
|
||||||
|
|
||||||
|
switch ($type) {
|
||||||
|
case 'detail':
|
||||||
|
case 'main_field':
|
||||||
|
$mappingId = (int)$key;
|
||||||
|
if ($mappingId <= 0) break;
|
||||||
|
$mm = mappingMeta($pdo, $mappingId);
|
||||||
|
if (($mm['data_type'] ?? '') === 'SceltaMultipla') {
|
||||||
|
// Dropdown: field_value = ID valore → risolvi label→id, match IN (= non LIKE)
|
||||||
|
$vids = resolveCustomFieldValueIds($cacheDir, $mm['field_id'], $term);
|
||||||
|
if (empty($vids)) {
|
||||||
|
$conds[] = '1 = 0';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$inph = implode(',', array_fill(0, count($vids), '?'));
|
||||||
|
$conds[] = "EXISTS (SELECT 1 FROM import_data_details x
|
||||||
|
WHERE x.id = d.iddatadb AND x.mapping_id = ? AND x.field_value IN ($inph))";
|
||||||
|
$params[] = $mappingId;
|
||||||
|
foreach ($vids as $vid) $params[] = $vid;
|
||||||
|
} else {
|
||||||
|
// Testo / Data: match testuale
|
||||||
|
$conds[] = "EXISTS (SELECT 1 FROM import_data_details x
|
||||||
|
WHERE x.id = d.iddatadb AND x.mapping_id = ? AND x.field_value LIKE ?)";
|
||||||
|
$params[] = $mappingId;
|
||||||
|
$params[] = $like;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'idclient':
|
||||||
|
addIdInCondition($conds, $params, 'idclient', resolveClientIdsByTerm($cacheDir, $term));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'cliente_fornitore_id':
|
||||||
|
addIdInCondition($conds, $params, 'cliente_fornitore_id', resolveClientIdsByTerm($cacheDir, $term));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'tested_component':
|
||||||
|
$conds[] = 'd.tested_component LIKE ?';
|
||||||
|
$params[] = $like;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'status':
|
||||||
|
// Il dropdown invia direttamente il codice ('i'/'P'/'l').
|
||||||
|
if (in_array($term, ['i', 'P', 'l'], true)) {
|
||||||
|
$conds[] = 'd.status = ?';
|
||||||
|
$params[] = $term;
|
||||||
|
} else {
|
||||||
|
$conds[] = '1 = 0';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'fixed':
|
||||||
|
$col = $fixedAliasMap[$key] ?? null;
|
||||||
|
if ($col === null) break;
|
||||||
|
if ($key === 'ConsegnaRichiesta') {
|
||||||
|
// Data: match testuale sul valore data (es. "2026-03").
|
||||||
|
$conds[] = "CAST(d.`{$col}` AS CHAR) LIKE ?";
|
||||||
|
$params[] = $like;
|
||||||
|
} elseif ($key === 'ClienteResponsabile') {
|
||||||
|
// Match sulla coppia (idclient, id responsabile).
|
||||||
|
$pairs = resolveResponsabilePairs($cacheDir, $term);
|
||||||
|
if (empty($pairs)) {
|
||||||
|
$conds[] = '1 = 0';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$rowvals = implode(',', array_fill(0, count($pairs), '(?,?)'));
|
||||||
|
$conds[] = "(d.idclient, d.`{$col}`) IN ($rowvals)";
|
||||||
|
foreach ($pairs as $pr) {
|
||||||
|
$params[] = $pr[0];
|
||||||
|
$params[] = $pr[1];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$ids = resolveFixedIds($cacheDir, $key, $term);
|
||||||
|
if ($ids === null) break; // non risolvibile → non filtrare (niente LIKE su id)
|
||||||
|
addIdInCondition($conds, $params, $col, $ids);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'static':
|
||||||
|
if ($key === 'user_name') {
|
||||||
|
$needUserJoin = true;
|
||||||
|
$conds[] = "CONCAT(COALESCE(u.first_name,''),' ',COALESCE(u.last_name,'')) LIKE ?";
|
||||||
|
$params[] = $like;
|
||||||
|
} elseif (in_array($key, $staticCols, true)) {
|
||||||
|
$conds[] = "d.`{$key}` LIKE ?";
|
||||||
|
$params[] = $like;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break; // tracking/awb ecc. → non filtrabili
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$whereSql = implode(' AND ', $conds);
|
||||||
|
$joinSql = $needUserJoin ? 'LEFT JOIN auth_users u ON d.user_id = u.id' : '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Totale + pagine
|
||||||
|
$countStmt = $pdo->prepare("SELECT COUNT(*) FROM datadb d {$joinSql} WHERE {$whereSql}");
|
||||||
|
$countStmt->execute($params);
|
||||||
|
$total = (int)$countStmt->fetchColumn();
|
||||||
|
$totalPages = max(1, (int)ceil($total / $perPage));
|
||||||
|
if ($page > $totalPages) $page = $totalPages;
|
||||||
|
$offset = ($page - 1) * $perPage;
|
||||||
|
|
||||||
|
// Pagina corrente
|
||||||
|
$pageStmt = $pdo->prepare("
|
||||||
|
SELECT d.iddatadb
|
||||||
|
FROM datadb d {$joinSql}
|
||||||
|
WHERE {$whereSql}
|
||||||
|
ORDER BY d.excelrow ASC, d.iddatadb ASC
|
||||||
|
LIMIT {$perPage} OFFSET {$offset}
|
||||||
|
");
|
||||||
|
$pageStmt->execute($params);
|
||||||
|
$pageIds = array_map('intval', $pageStmt->fetchAll(PDO::FETCH_COLUMN));
|
||||||
|
|
||||||
|
$config = buildGridConfig($pdo, $templateId);
|
||||||
|
$rows = buildGridRows($pdo, $pageIds, $config);
|
||||||
|
|
||||||
|
$out = [
|
||||||
|
'success' => true,
|
||||||
|
'total' => $total,
|
||||||
|
'page' => $page,
|
||||||
|
'per_page' => $perPage,
|
||||||
|
'total_pages' => $totalPages,
|
||||||
|
'rows' => $rows,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Tutti gli id del match (per "seleziona tutti"), solo su richiesta
|
||||||
|
if ($wantAllIds) {
|
||||||
|
$allStmt = $pdo->prepare("
|
||||||
|
SELECT d.iddatadb FROM datadb d {$joinSql}
|
||||||
|
WHERE {$whereSql}
|
||||||
|
ORDER BY d.excelrow ASC, d.iddatadb ASC
|
||||||
|
LIMIT " . FILTER_ALLIDS_CAP . "
|
||||||
|
");
|
||||||
|
$allStmt->execute($params);
|
||||||
|
$out['all_ids'] = array_map('intval', $allStmt->fetchAll(PDO::FETCH_COLUMN));
|
||||||
|
$out['all_ids_capped'] = $total > FILTER_ALLIDS_CAP;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($out);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log('filter_records error: ' . $e->getMessage());
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -21,8 +21,255 @@
|
|||||||
return R().getMeta();
|
return R().getMeta();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Filtro: decide se una riga passa i filtri colonna ──
|
// ── Filtro SERVER-SIDE (paginato, come la lista normale ma filtrata) ──────
|
||||||
|
// Il filtro gira su TUTTO il template; il server restituisce UNA PAGINA di righe
|
||||||
|
// filtrate + il totale, e navighiamo le pagine del set filtrato via AJAX.
|
||||||
|
const TEMPLATE_ID =
|
||||||
|
new URLSearchParams(location.search).get("id") || (meta().templateId ?? "");
|
||||||
|
const SHOW_ALL =
|
||||||
|
new URLSearchParams(location.search).get("all_users") === "1";
|
||||||
|
const IMPORTREF =
|
||||||
|
new URLSearchParams(location.search).get("importref") || "";
|
||||||
|
const FILTER_PER_PAGE =
|
||||||
|
parseInt(new URLSearchParams(location.search).get("limit"), 10) || 20;
|
||||||
|
|
||||||
|
let serverFiltered = false;
|
||||||
|
let filterPage = 1;
|
||||||
|
let filterTotalPages = 1;
|
||||||
|
let filterTotal = 0;
|
||||||
|
let originalData = null; // snapshot della pagina originale (per ripristino)
|
||||||
|
let filterReqSeq = 0;
|
||||||
|
|
||||||
|
function collectActiveFilters() {
|
||||||
|
const cols = meta().columns || [];
|
||||||
|
const out = [];
|
||||||
|
for (const [key, term] of Object.entries(colFilters)) {
|
||||||
|
if (!term || !String(term).trim()) continue;
|
||||||
|
const col = cols.find((c) => String(c.key) === String(key));
|
||||||
|
if (!col) continue;
|
||||||
|
out.push({ key: String(key), type: col.type, term: String(term).trim() });
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sostituisce il dataset del renderer in-place e ridisegna.
|
||||||
|
function swapData(rows) {
|
||||||
|
const arr = data();
|
||||||
|
arr.length = 0;
|
||||||
|
(rows || []).forEach((r) => arr.push(r));
|
||||||
|
R().renderVisibleRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hidePagination(hide) {
|
||||||
|
// Durante il filtro la paginazione server (per pagina, via URL) è fuorviante.
|
||||||
|
document.querySelectorAll(".pager-bar").forEach((el) => {
|
||||||
|
el.style.display = hide ? "none" : "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pager del set filtrato (Prec / pag X di Y / Succ) nella toolbar.
|
||||||
|
function updateFilterPager() {
|
||||||
|
const el = document.getElementById("filterPager");
|
||||||
|
if (!el) return;
|
||||||
|
if (!serverFiltered) {
|
||||||
|
el.style.display = "none";
|
||||||
|
el.innerHTML = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
el.style.display = "inline-flex";
|
||||||
|
el.innerHTML =
|
||||||
|
`<button type="button" class="btn btn-outline-secondary btn-sm" id="filterPrevBtn" ${filterPage <= 1 ? "disabled" : ""}>‹</button>` +
|
||||||
|
`<span style="font-size:12px;color:#333;">${filterTotal} trovati · pag ${filterPage}/${filterTotalPages}</span>` +
|
||||||
|
`<button type="button" class="btn btn-outline-secondary btn-sm" id="filterNextBtn" ${filterPage >= filterTotalPages ? "disabled" : ""}>›</button>`;
|
||||||
|
const prev = document.getElementById("filterPrevBtn");
|
||||||
|
const next = document.getElementById("filterNextBtn");
|
||||||
|
if (prev) prev.addEventListener("click", () => applyServerFilter(filterPage - 1));
|
||||||
|
if (next) next.addEventListener("click", () => applyServerFilter(filterPage + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreOriginal() {
|
||||||
|
serverFiltered = false;
|
||||||
|
filterPage = 1;
|
||||||
|
filterTotalPages = 1;
|
||||||
|
filterTotal = 0;
|
||||||
|
if (originalData) swapData(originalData);
|
||||||
|
updateFilterPager();
|
||||||
|
hidePagination(false);
|
||||||
|
updateToolbar();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyServerFilter(page = 1) {
|
||||||
|
const active = collectActiveFilters();
|
||||||
|
if (active.length === 0) {
|
||||||
|
if (serverFiltered) restoreOriginal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!originalData) originalData = [...data()]; // snapshot pagina 1 originale
|
||||||
|
|
||||||
|
const seq = ++filterReqSeq;
|
||||||
|
try {
|
||||||
|
const resp = await fetch("filter_records.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
template_id: parseInt(TEMPLATE_ID, 10),
|
||||||
|
all_users: SHOW_ALL,
|
||||||
|
importref: IMPORTREF,
|
||||||
|
status: "i",
|
||||||
|
filters: active,
|
||||||
|
page: Math.max(1, page),
|
||||||
|
per_page: FILTER_PER_PAGE,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const json = await resp.json();
|
||||||
|
if (seq !== filterReqSeq) return; // risposta obsoleta: ignora
|
||||||
|
if (!json.success) {
|
||||||
|
console.error("[gridFilter] filtro:", json.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
serverFiltered = true;
|
||||||
|
filterPage = json.page;
|
||||||
|
filterTotalPages = json.total_pages;
|
||||||
|
filterTotal = json.total;
|
||||||
|
swapData(json.rows || []);
|
||||||
|
updateFilterPager();
|
||||||
|
hidePagination(true);
|
||||||
|
updateToolbar();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[gridFilter] fetch filtro fallita", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Seleziona tutti i filtrati": chiede al server TUTTI gli id del match e li seleziona.
|
||||||
|
async function selectAllMatching(on) {
|
||||||
|
if (!serverFiltered) return;
|
||||||
|
const active = collectActiveFilters();
|
||||||
|
try {
|
||||||
|
const resp = await fetch("filter_records.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
template_id: parseInt(TEMPLATE_ID, 10),
|
||||||
|
all_users: SHOW_ALL,
|
||||||
|
importref: IMPORTREF,
|
||||||
|
status: "i",
|
||||||
|
filters: active,
|
||||||
|
page: filterPage,
|
||||||
|
per_page: FILTER_PER_PAGE,
|
||||||
|
want_all_ids: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const json = await resp.json();
|
||||||
|
if (!json.success) return;
|
||||||
|
(json.all_ids || []).forEach((id) => (on ? selected.add(id) : selected.delete(id)));
|
||||||
|
document
|
||||||
|
.querySelectorAll("#gridRowContainer .grid-row")
|
||||||
|
.forEach((rowEl) => {
|
||||||
|
rowEl.classList.toggle("row-selected", on);
|
||||||
|
const cb = rowEl.querySelector(".filter-row-checkbox");
|
||||||
|
if (cb) cb.checked = on;
|
||||||
|
});
|
||||||
|
updateToolbar();
|
||||||
|
if (json.all_ids_capped) {
|
||||||
|
alert("Selezione limitata a " + (json.all_ids || []).length + " record.");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[gridFilter] select-all fallita", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncFilterRow() {
|
||||||
|
const fr = document.getElementById("gridFilterRow");
|
||||||
|
const header = document.getElementById("gridHeaderContainer");
|
||||||
|
if (!fr || !header) return;
|
||||||
|
|
||||||
|
const actHeader = header.querySelector(".button-header");
|
||||||
|
const actCell = fr.querySelector(".filter-actions-cell");
|
||||||
|
if (actHeader && actCell) {
|
||||||
|
const aw = actHeader.offsetWidth;
|
||||||
|
actCell.style.flex = `0 0 ${aw}px`;
|
||||||
|
actCell.style.minWidth = `${aw}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerCells = header.querySelectorAll(
|
||||||
|
".grid-header:not(.button-header)",
|
||||||
|
);
|
||||||
|
const filterCells = fr.querySelectorAll(
|
||||||
|
".grid-cell:not(.filter-actions-cell)",
|
||||||
|
);
|
||||||
|
headerCells.forEach((hc, i) => {
|
||||||
|
const fc = filterCells[i];
|
||||||
|
if (fc) fc.style.flex = `0 0 ${hc.offsetWidth}px`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncStickyLeft() {
|
||||||
|
const btnHeader = document.querySelector(
|
||||||
|
"#gridHeaderContainer .button-header",
|
||||||
|
);
|
||||||
|
if (!btnHeader) return;
|
||||||
|
const w1 = btnHeader.offsetWidth; // larghezza reale colonna Actions
|
||||||
|
|
||||||
|
const fixRow = (rowEl) => {
|
||||||
|
if (!rowEl) return;
|
||||||
|
const cells = rowEl.querySelectorAll(
|
||||||
|
":scope > .grid-cell, :scope > .grid-header",
|
||||||
|
);
|
||||||
|
const c2 = cells[1];
|
||||||
|
const c3 = cells[2];
|
||||||
|
if (c2 && getComputedStyle(c2).position === "sticky") {
|
||||||
|
c2.style.left = `${w1}px`;
|
||||||
|
}
|
||||||
|
if (c3 && getComputedStyle(c3).position === "sticky") {
|
||||||
|
c3.style.left = `${w1 + (c2 ? c2.offsetWidth : 0)}px`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fixRow(document.getElementById("gridHeaderContainer"));
|
||||||
|
document.querySelectorAll(".grid-top").forEach(fixRow);
|
||||||
|
document
|
||||||
|
.querySelectorAll("#gridRowContainer .grid-row")
|
||||||
|
.forEach(fixRow);
|
||||||
|
fixRow(document.getElementById("gridFilterRow"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let _headerObs = null;
|
||||||
|
let _rowObs = null;
|
||||||
|
let _syncRaf = null;
|
||||||
|
|
||||||
|
function reconnectObservers() {
|
||||||
|
const header = document.getElementById("gridHeaderContainer");
|
||||||
|
const rows = document.getElementById("gridRowContainer");
|
||||||
|
if (_headerObs && header)
|
||||||
|
_headerObs.observe(header, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ["style"],
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
if (_rowObs && rows) _rowObs.observe(rows, { childList: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleSync() {
|
||||||
|
if (_syncRaf) return;
|
||||||
|
_syncRaf = requestAnimationFrame(() => {
|
||||||
|
_syncRaf = null;
|
||||||
|
if (_headerObs) _headerObs.disconnect();
|
||||||
|
if (_rowObs) _rowObs.disconnect();
|
||||||
|
syncStickyLeft();
|
||||||
|
syncFilterRow();
|
||||||
|
reconnectObservers();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hookGridSync() {
|
||||||
|
_headerObs = new MutationObserver(scheduleSync);
|
||||||
|
_rowObs = new MutationObserver(scheduleSync);
|
||||||
|
reconnectObservers();
|
||||||
|
scheduleSync(); // primo allineamento
|
||||||
|
}
|
||||||
|
|
||||||
function rowMatchesFilters(row) {
|
function rowMatchesFilters(row) {
|
||||||
|
if (serverFiltered) return true;
|
||||||
for (const [key, term] of Object.entries(colFilters)) {
|
for (const [key, term] of Object.entries(colFilters)) {
|
||||||
if (!term) continue;
|
if (!term) continue;
|
||||||
const t = term.toLowerCase();
|
const t = term.toLowerCase();
|
||||||
@@ -74,6 +321,11 @@
|
|||||||
|
|
||||||
// ── Lista iddatadb attualmente visibili (per propagazione/export ecc.) ──
|
// ── Lista iddatadb attualmente visibili (per propagazione/export ecc.) ──
|
||||||
window.getVisibleGridIds = function () {
|
window.getVisibleGridIds = function () {
|
||||||
|
if (serverFiltered) {
|
||||||
|
return data()
|
||||||
|
.map((r) => parseInt(r.iddatadb, 10))
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
const ids = [];
|
const ids = [];
|
||||||
document
|
document
|
||||||
.querySelectorAll("#gridRowContainer .grid-row")
|
.querySelectorAll("#gridRowContainer .grid-row")
|
||||||
@@ -178,6 +430,18 @@
|
|||||||
cell.style.flex = `0 0 ${w}px`;
|
cell.style.flex = `0 0 ${w}px`;
|
||||||
if (col.type === "tracking" || col.type === "awb") {
|
if (col.type === "tracking" || col.type === "awb") {
|
||||||
// niente filtro
|
// niente filtro
|
||||||
|
} else if (col.type === "status") {
|
||||||
|
// Status: dropdown (3 valori) invece del testo libero.
|
||||||
|
const cur = colFilters[col.key] || "";
|
||||||
|
const opt = (v, lbl) =>
|
||||||
|
`<option value="${v}" ${cur === v ? "selected" : ""}>${lbl}</option>`;
|
||||||
|
cell.innerHTML =
|
||||||
|
`<select class="filter-col-input" data-col-key="${col.key}" style="width:100%;padding:3px 6px;font-size:12px;border:1px solid #ced4da;border-radius:4px;">` +
|
||||||
|
opt("", "— tutti —") +
|
||||||
|
opt("i", "Imported") +
|
||||||
|
opt("P", "In Progress") +
|
||||||
|
opt("l", "To LIMS") +
|
||||||
|
`</select>`;
|
||||||
} else {
|
} 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;">`;
|
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;">`;
|
||||||
}
|
}
|
||||||
@@ -185,6 +449,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
top.parentNode.insertBefore(fr, top);
|
top.parentNode.insertBefore(fr, top);
|
||||||
|
scheduleSync(); // allinea larghezze filtri + offset sticky
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeFilterRow() {
|
function removeFilterRow() {
|
||||||
@@ -223,6 +488,9 @@
|
|||||||
btn.classList.remove("active");
|
btn.classList.remove("active");
|
||||||
removeCheckboxes();
|
removeCheckboxes();
|
||||||
removeFilterRow();
|
removeFilterRow();
|
||||||
|
// reset filtri colonna + ripristina la pagina originale
|
||||||
|
Object.keys(colFilters).forEach((k) => delete colFilters[k]);
|
||||||
|
if (serverFiltered) restoreOriginal();
|
||||||
}
|
}
|
||||||
updateToolbar();
|
updateToolbar();
|
||||||
}
|
}
|
||||||
@@ -313,6 +581,8 @@
|
|||||||
bar.style.cssText =
|
bar.style.cssText =
|
||||||
"display:none;align-items:center;gap:8px;flex-shrink:0;";
|
"display:none;align-items:center;gap:8px;flex-shrink:0;";
|
||||||
bar.innerHTML = `
|
bar.innerHTML = `
|
||||||
|
<span id="filterPager" style="display:none;align-items:center;gap:6px;"></span>
|
||||||
|
<button type="button" id="filterClearBtn" class="btn btn-outline-secondary btn-sm"><i class="fas fa-times"></i> Pulisci filtri</button>
|
||||||
<span style="font-size:12px;color:#555;">Selezionate: <strong id="filterSelCount">0</strong></span>
|
<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="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>
|
<button type="button" id="filterDeleteBtn" class="btn btn-outline-danger btn-sm"><i class="fas fa-trash"></i> Elimina selezionati</button>
|
||||||
@@ -320,6 +590,9 @@
|
|||||||
fBtn.parentNode.insertBefore(bar, fBtn.nextSibling);
|
fBtn.parentNode.insertBefore(bar, fBtn.nextSibling);
|
||||||
|
|
||||||
fBtn.addEventListener("click", toggleFilters);
|
fBtn.addEventListener("click", toggleFilters);
|
||||||
|
document
|
||||||
|
.getElementById("filterClearBtn")
|
||||||
|
.addEventListener("click", clearFilters);
|
||||||
document
|
document
|
||||||
.getElementById("filterDeleteBtn")
|
.getElementById("filterDeleteBtn")
|
||||||
.addEventListener("click", batchDelete);
|
.addEventListener("click", batchDelete);
|
||||||
@@ -328,6 +601,17 @@
|
|||||||
.addEventListener("click", toggleRestrict);
|
.addEventListener("click", toggleRestrict);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Svuota tutti i filtri di colonna (input + select) e torna alla pagina originale.
|
||||||
|
function clearFilters() {
|
||||||
|
Object.keys(colFilters).forEach((k) => delete colFilters[k]);
|
||||||
|
document
|
||||||
|
.querySelectorAll("#gridFilterRow .filter-col-input")
|
||||||
|
.forEach((el) => {
|
||||||
|
el.value = "";
|
||||||
|
});
|
||||||
|
if (serverFiltered) restoreOriginal();
|
||||||
|
}
|
||||||
|
|
||||||
// ── Event delegation ──
|
// ── Event delegation ──
|
||||||
function attachEvents() {
|
function attachEvents() {
|
||||||
// checkbox click
|
// checkbox click
|
||||||
@@ -338,10 +622,17 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Seleziona / deseleziona tutti i FILTRATI visibili
|
// Seleziona / deseleziona tutti i FILTRATI
|
||||||
document.addEventListener("change", function (e) {
|
document.addEventListener("change", function (e) {
|
||||||
if (e.target.id !== "filterSelectAll") return;
|
if (e.target.id !== "filterSelectAll") return;
|
||||||
const on = e.target.checked;
|
const on = e.target.checked;
|
||||||
|
|
||||||
|
// Con filtro server-side: seleziona TUTTI i match (anche altre pagine).
|
||||||
|
if (serverFiltered) {
|
||||||
|
selectAllMatching(on);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
document
|
document
|
||||||
.querySelectorAll("#gridRowContainer .grid-row")
|
.querySelectorAll("#gridRowContainer .grid-row")
|
||||||
.forEach((rowEl) => {
|
.forEach((rowEl) => {
|
||||||
@@ -379,14 +670,23 @@
|
|||||||
toggleRow(rowEl.dataset.id, cb.checked);
|
toggleRow(rowEl.dataset.id, cb.checked);
|
||||||
});
|
});
|
||||||
|
|
||||||
// filtri colonna live (debounce)
|
// filtri colonna testo (debounce) → filtro server-side su tutto il template
|
||||||
let t = null;
|
let t = null;
|
||||||
document.addEventListener("input", function (e) {
|
document.addEventListener("input", function (e) {
|
||||||
if (!e.target.classList.contains("filter-col-input")) return;
|
if (!e.target.classList.contains("filter-col-input")) return;
|
||||||
|
if (e.target.tagName === "SELECT") return; // i select li gestisce 'change'
|
||||||
const key = e.target.dataset.colKey;
|
const key = e.target.dataset.colKey;
|
||||||
colFilters[key] = e.target.value;
|
colFilters[key] = e.target.value;
|
||||||
clearTimeout(t);
|
clearTimeout(t);
|
||||||
t = setTimeout(applyVisibility, 200);
|
t = setTimeout(applyServerFilter, 250);
|
||||||
|
});
|
||||||
|
|
||||||
|
// dropdown (es. Status): applica subito, senza debounce
|
||||||
|
document.addEventListener("change", function (e) {
|
||||||
|
if (!e.target.classList.contains("filter-col-input")) return;
|
||||||
|
if (e.target.tagName !== "SELECT") return;
|
||||||
|
colFilters[e.target.dataset.colKey] = e.target.value;
|
||||||
|
applyServerFilter();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,6 +731,7 @@
|
|||||||
buildUI();
|
buildUI();
|
||||||
attachEvents();
|
attachEvents();
|
||||||
hookRerender();
|
hookRerender();
|
||||||
|
hookGridSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
// parte dopo che gridRenderer ha finito init
|
// parte dopo che gridRenderer ha finito init
|
||||||
|
|||||||
@@ -787,6 +787,7 @@ $gridMeta = [
|
|||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
|
min-width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-top .grid-cell {
|
.grid-top .grid-cell {
|
||||||
|
|||||||
@@ -0,0 +1,213 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
if (!function_exists('gdbFixedDefaultValue')) {
|
||||||
|
/** Default di un fixed field (DATE 'today' → data odierna). */
|
||||||
|
function gdbFixedDefaultValue(array $f): string
|
||||||
|
{
|
||||||
|
$v = $f['default_value'] ?? '';
|
||||||
|
if (($f['data_type'] ?? '') === 'DATE' && $v === 'today') {
|
||||||
|
return date('Y-m-d');
|
||||||
|
}
|
||||||
|
return (string)$v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('buildGridConfig')) {
|
||||||
|
/**
|
||||||
|
* Config del template necessaria per costruire le righe: fixed fields (ordinati
|
||||||
|
* come in imported.php), main field mappings, alias map, default idclient.
|
||||||
|
*
|
||||||
|
* @return array{fixedFields:array, fixedAliasMap:array, mainFieldMappings:array, default_idclient:mixed}
|
||||||
|
*/
|
||||||
|
function buildGridConfig(PDO $pdo, int $templateId): array
|
||||||
|
{
|
||||||
|
// Mappa logica fixed_field_key → colonna reale su datadb (come imported.php)
|
||||||
|
$fixedAliasMap = [
|
||||||
|
'ClienteResponsabile' => 'cliente_responsabile_id',
|
||||||
|
'ClienteFornitore' => 'cliente_fornitore_id',
|
||||||
|
'ClienteAnalisi' => 'clienteAnalisi',
|
||||||
|
'ClienteFatturazione' => 'ClienteFatturazione',
|
||||||
|
'MoltiplicatorePrezzo' => 'moltiplicatore_prezzo_id',
|
||||||
|
'AnagraficaCertestObject' => 'anagrafica_certest_object_id',
|
||||||
|
'AnagraficaCertestService' => 'anagrafica_certest_service_id',
|
||||||
|
'ConsegnaRichiesta' => 'consegna_richiesta',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Fixed fields visibili, ordinati come imported.php
|
||||||
|
$fixedStmt = $pdo->prepare("
|
||||||
|
SELECT id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import
|
||||||
|
FROM template_fixed_mapping
|
||||||
|
WHERE template_id = ? AND is_visible_import = 1
|
||||||
|
ORDER BY id
|
||||||
|
");
|
||||||
|
$fixedStmt->execute([$templateId]);
|
||||||
|
$fixedFieldsRaw = $fixedStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$desiredOrder = [
|
||||||
|
'ClienteResponsabile', 'ClienteFornitore', 'ClienteAnalisi', 'ClienteFatturazione',
|
||||||
|
'AnagraficaCertestObject', 'AnagraficaCertestService', 'MoltiplicatorePrezzo', 'ConsegnaRichiesta',
|
||||||
|
];
|
||||||
|
$excludeFromFixed = ['ClienteFornitore']; // reso come colonna a sé
|
||||||
|
|
||||||
|
$fixedFields = [];
|
||||||
|
$tempMap = [];
|
||||||
|
foreach ($fixedFieldsRaw as $f) {
|
||||||
|
if (in_array($f['fixed_field_key'], $excludeFromFixed, true)) continue;
|
||||||
|
$tempMap[$f['fixed_field_key']] = $f;
|
||||||
|
}
|
||||||
|
foreach ($desiredOrder as $key) {
|
||||||
|
if (isset($tempMap[$key])) {
|
||||||
|
$fixedFields[] = $tempMap[$key];
|
||||||
|
unset($tempMap[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($tempMap as $f) {
|
||||||
|
$fixedFields[] = $f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main field mappings (max 2), come imported.php
|
||||||
|
$mapStmt = $pdo->prepare("
|
||||||
|
SELECT id, field_label, data_type, is_required, field_id, field_order,
|
||||||
|
main_field, is_visible_import, manual_default
|
||||||
|
FROM template_mapping
|
||||||
|
WHERE template_id = ?
|
||||||
|
ORDER BY field_order ASC, id ASC
|
||||||
|
");
|
||||||
|
$mapStmt->execute([$templateId]);
|
||||||
|
$allMappings = $mapStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$mainFieldMappings = [];
|
||||||
|
foreach ($allMappings as $mapping) {
|
||||||
|
if ((string)$mapping['main_field'] === '1' && (int)$mapping['is_visible_import'] === 1) {
|
||||||
|
$mainFieldMappings[] = $mapping;
|
||||||
|
}
|
||||||
|
if (count($mainFieldMappings) >= 2) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default idclient dal template
|
||||||
|
$tplStmt = $pdo->prepare("SELECT idclient FROM excel_templates WHERE id = ?");
|
||||||
|
$tplStmt->execute([$templateId]);
|
||||||
|
$default_idclient = $tplStmt->fetchColumn();
|
||||||
|
$default_idclient = $default_idclient !== false ? $default_idclient : null;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'fixedFields' => $fixedFields,
|
||||||
|
'fixedAliasMap' => $fixedAliasMap,
|
||||||
|
'mainFieldMappings' => $mainFieldMappings,
|
||||||
|
'default_idclient' => $default_idclient,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('buildGridRows')) {
|
||||||
|
/**
|
||||||
|
* Costruisce le righe gridData per la lista di iddatadb data, nell'ordine passato.
|
||||||
|
*
|
||||||
|
* @param int[] $iddatadbList
|
||||||
|
* @param array $config output di buildGridConfig()
|
||||||
|
* @return array righe nella stessa forma di imported.php $gridDataArray
|
||||||
|
*/
|
||||||
|
function buildGridRows(PDO $pdo, array $iddatadbList, array $config): array
|
||||||
|
{
|
||||||
|
$iddatadbList = array_values(array_filter(array_map('intval', $iddatadbList), fn($v) => $v > 0));
|
||||||
|
if (empty($iddatadbList)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$fixedFields = $config['fixedFields'];
|
||||||
|
$fixedAliasMap = $config['fixedAliasMap'];
|
||||||
|
$mainFieldMappings = $config['mainFieldMappings'];
|
||||||
|
$default_idclient = $config['default_idclient'];
|
||||||
|
|
||||||
|
$ph = implode(',', array_fill(0, count($iddatadbList), '?'));
|
||||||
|
|
||||||
|
// Righe datadb + user_name
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT d.*, CONCAT(u.first_name, ' ', u.last_name) AS user_name
|
||||||
|
FROM datadb d
|
||||||
|
LEFT JOIN auth_users u ON d.user_id = u.id
|
||||||
|
WHERE d.iddatadb IN ($ph)
|
||||||
|
");
|
||||||
|
$stmt->execute($iddatadbList);
|
||||||
|
$byId = [];
|
||||||
|
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
||||||
|
$byId[(int)$r['iddatadb']] = $r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dettagli (import_data_details)
|
||||||
|
$detStmt = $pdo->prepare("
|
||||||
|
SELECT d.id AS datadb_id, d.mapping_id, d.field_value,
|
||||||
|
m.field_id, m.field_label, m.data_type, m.is_required, m.manual_default
|
||||||
|
FROM import_data_details d
|
||||||
|
JOIN template_mapping m ON d.mapping_id = m.id
|
||||||
|
WHERE d.id IN ($ph)
|
||||||
|
");
|
||||||
|
$detStmt->execute($iddatadbList);
|
||||||
|
$detailsByRow = [];
|
||||||
|
foreach ($detStmt->fetchAll(PDO::FETCH_ASSOC) as $d) {
|
||||||
|
$detailsByRow[(int)$d['datadb_id']][] = $d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Costruzione righe nell'ordine della lista passata
|
||||||
|
$rows = [];
|
||||||
|
foreach ($iddatadbList as $id) {
|
||||||
|
$row = $byId[$id] ?? null;
|
||||||
|
if ($row === null) continue;
|
||||||
|
|
||||||
|
$rowObj = [
|
||||||
|
'iddatadb' => (int)$row['iddatadb'],
|
||||||
|
'status' => $row['status'] ?? 'i',
|
||||||
|
'idclient' => $row['idclient'] ?? $default_idclient,
|
||||||
|
'cliente_fornitore_id' => $row['cliente_fornitore_id'] ?? null,
|
||||||
|
'tested_component' => $row['tested_component'] ?? '',
|
||||||
|
'commessaweb' => $row['commessaweb'] ?? null,
|
||||||
|
'user_name' => $row['user_name'] ?? '',
|
||||||
|
'importreferencecode' => $row['importreferencecode'] ?? '',
|
||||||
|
'filename_import' => $row['filename_import'] ?? '',
|
||||||
|
'importdate' => $row['importdate'] ?? '',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Fixed fields
|
||||||
|
$rowObj['fixedFields'] = [];
|
||||||
|
foreach ($fixedFields as $f) {
|
||||||
|
$key = $f['fixed_field_key'];
|
||||||
|
$dbCol = $fixedAliasMap[$key] ?? $key;
|
||||||
|
$val = $row[$dbCol] ?? '';
|
||||||
|
if ($val === '' || $val === null) {
|
||||||
|
$val = gdbFixedDefaultValue($f);
|
||||||
|
}
|
||||||
|
$rowObj['fixedFields'][$key] = (string)$val;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Details
|
||||||
|
$rowObj['details'] = [];
|
||||||
|
$rowDetails = $detailsByRow[$id] ?? [];
|
||||||
|
foreach ($rowDetails as $d) {
|
||||||
|
$rowObj['details'][(string)$d['mapping_id']] = $d['field_value'] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main field values
|
||||||
|
foreach ($mainFieldMappings as $mainMapping) {
|
||||||
|
$found = null;
|
||||||
|
foreach ($rowDetails as $d) {
|
||||||
|
if ($d['mapping_id'] == $mainMapping['id']) {
|
||||||
|
$found = $d;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$rowObj['details'][(string)$mainMapping['id']] =
|
||||||
|
($found['field_value'] ?? null) ?? ($mainMapping['manual_default'] ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($mainFieldMappings)) {
|
||||||
|
$firstMain = $mainFieldMappings[0];
|
||||||
|
$rowObj['mainFieldValue'] = $rowObj['details'][(string)$firstMain['id']] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$rowObj['_dirty'] = false;
|
||||||
|
$rows[] = $rowObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rows;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user