Ripristina search_clienti.php sovrascritto per errore

This commit is contained in:
2026-07-15 11:12:59 +02:00
parent 1c2ea459ca
commit c6168dd5d4
+129 -409
View File
@@ -1,416 +1,136 @@
<?php include('include/headscript.php'); ?> <?php
<!doctype html> require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
<html lang="en"> require_once __DIR__ . '/class/db-functions.php';
include dirname(__DIR__) . '/../extra/auth.php';
<head> if (!Auth::check()) {
<meta charset="utf-8"> http_response_code(401);
<meta name="viewport" content="width=device-width, initial-scale=1"> echo json_encode(['error' => 'Unauthorized']);
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" /> exit;
<?php include('cssinclude.php'); ?> }
<title>TRF-Project - Statistiche Export LIMS</title>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css"> require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
<style>
.stat-card .card-body { header('Content-Type: application/json');
padding: 1rem 1.25rem; ini_set('display_errors', '0');
error_reporting(E_ALL);
$q = mb_strtolower(trim($_GET['q'] ?? ''));
$limit = max(1, min(50, intval($_GET['limit'] ?? 20)));
$id = isset($_GET['id']) ? intval($_GET['id']) : null;
$tipo = $_GET['tipo'] ?? ''; // 'attivo' | 'inattivo' | '' (nessun filtro)
function isInattivo(array $c): bool
{
$v = $c['Inattivo'] ?? false;
return $v === true || $v === 1 || $v === '1' || $v === 'true';
}
function formatClientLabel(array $client): string
{
$name = trim($client['Nominativo'] ?? '');
$id = trim((string)($client['IdCliente'] ?? ''));
$code = trim((string)($client['CodiceCliente'] ?? ''));
$parts = explode('_', $code);
$suffix = trim($parts[1] ?? '');
if ($suffix === '' && $code !== '') {
$suffix = substr($code, 0, 1);
}
if ($suffix === '') {
$suffix = '--';
}
return $name . ' - ' . $suffix . ' - ' . $id;
}
try {
// Load from cache or API
$cacheFile = __DIR__ . '/cache/clienti.json';
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
$data = json_decode(file_get_contents($cacheFile), true);
} else {
$api = VisualLimsApiClient::getInstance();
$params = [
'$select' => 'IdCliente,Nominativo,CodiceCliente,Inattivo',
'$orderby' => 'Nominativo asc'
];
$data = $api->get("Cliente?" . http_build_query($params));
if (!is_dir(__DIR__ . '/cache')) {
mkdir(__DIR__ . '/cache', 0777, true);
} }
.stat-value { file_put_contents($cacheFile, json_encode($data));
font-size: 1.75rem; }
font-weight: 700;
line-height: 1.1; $clients = $data['value'] ?? [];
// Filtro tipo, applicato SOLO alla ricerca, non al lookup per id
$searchClients = $clients;
if ($tipo === 'attivo') {
$searchClients = array_values(array_filter($clients, fn($c) => !isInattivo($c)));
} elseif ($tipo === 'inattivo') {
$searchClients = array_values(array_filter($clients, fn($c) => isInattivo($c)));
}
// If requesting by specific ID, used for loading selected value
if ($id !== null) {
foreach ($clients as $c) {
if ((int)$c['IdCliente'] === $id) {
echo json_encode([
'results' => [[
'id' => $c['IdCliente'],
'text' => formatClientLabel($c),
'IdCliente' => $c['IdCliente'],
'Nominativo' => trim($c['Nominativo'] ?? ''),
'CodiceCliente' => trim($c['CodiceCliente'] ?? '')
]]
]);
exit;
}
} }
.stat-label { echo json_encode(['results' => []]);
font-size: 0.8rem; exit;
text-transform: uppercase; }
letter-spacing: .03em;
color: #6c757d; // Search by query
$results = [];
foreach ($searchClients as $c) {
$name = trim($c['Nominativo'] ?? '');
$code = trim($c['CodiceCliente'] ?? '');
// Cerca solo su ciò che viene visualizzato: Nome - Lettera - ID
$label = formatClientLabel($c);
if (
$q === '' ||
mb_strpos(mb_strtolower($label), $q) !== false
) {
$results[] = [
'id' => $c['IdCliente'],
'text' => $label,
'IdCliente' => $c['IdCliente'],
'Nominativo' => $name,
'CodiceCliente' => $code
];
if (count($results) >= $limit) {
break;
}
} }
}
.chart-wrap { echo json_encode(['results' => $results]);
position: relative; } catch (Exception $e) {
height: 420px; http_response_code(500);
} echo json_encode(['error' => $e->getMessage()]);
}
#tblClienti,
#tblUtenti {
font-size: 13px;
}
#tblClienti th,
#tblClienti td,
#tblUtenti th,
#tblUtenti td {
vertical-align: middle;
}
.filter-bar {
gap: .75rem;
}
</style>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<!-- ===== FILTRO GIORNATA ===== -->
<div class="card radius-10">
<div class="card-body">
<div class="d-flex align-items-center flex-wrap filter-bar">
<h6 class="mb-0 me-3"><i class="bx bx-bar-chart-alt-2"></i> Statistiche Export LIMS</h6>
<div class="d-flex align-items-center">
<label for="filterDate" class="me-2 mb-0 fw-semibold">Giornata:</label>
<input type="date" id="filterDate" class="form-control form-control-sm" style="width:auto;">
</div>
<div class="btn-group btn-group-sm ms-2" role="group">
<button class="btn btn-outline-secondary" id="btnToday">Oggi</button>
<button class="btn btn-outline-secondary" id="btnYesterday">Ieri</button>
</div>
<button class="btn btn-primary btn-sm ms-auto" id="btnRefresh">
<i class="bx bx-refresh"></i> Aggiorna
</button>
</div>
</div>
</div>
<!-- ===== KPI ===== -->
<div class="row">
<div class="col-12 col-lg-3 col-md-6">
<div class="card radius-10 stat-card">
<div class="card-body">
<div class="stat-label">Export (status L)</div>
<div class="stat-value text-primary" id="kpiExport">0</div>
</div>
</div>
</div>
<div class="col-12 col-lg-3 col-md-6">
<div class="card radius-10 stat-card">
<div class="card-body">
<div class="stat-label">Campioni inseriti LIMS</div>
<div class="stat-value text-info" id="kpiLimsSamples">0</div>
</div>
</div>
</div>
<div class="col-12 col-lg-3 col-md-6">
<div class="card radius-10 stat-card">
<div class="card-body">
<div class="stat-label">% Esportati / inseriti</div>
<div class="stat-value text-success" id="kpiPerc"></div>
</div>
</div>
</div>
<div class="col-12 col-lg-3 col-md-6">
<div class="card radius-10 stat-card">
<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>
<!-- ===== DATO GIORNALIERO CAMPIONI LIMS ===== -->
<div class="card radius-10">
<div class="card-body">
<div class="d-flex align-items-center flex-wrap" style="gap:.75rem;">
<span class="fw-semibold">Campioni totali inseriti nel LIMS per questa giornata:</span>
<input type="number" min="0" id="limsSamplesInput" class="form-control form-control-sm" style="width:140px;" placeholder="0">
<button class="btn btn-success btn-sm" id="btnSaveLimsSamples">
<i class="bx bx-save"></i> Salva dato
</button>
<small class="text-muted">Un valore al giorno. Usato per calcolare la % di export.</small>
</div>
</div>
</div>
<!-- ===== GRAFICI ===== -->
<div class="row">
<div class="col-12 col-lg-6">
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Export per cliente (idclient) <small class="text-muted"> top 15</small></h6>
</div>
<div class="card-body">
<div class="chart-wrap"><canvas id="chartClienti"></canvas></div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Export per utente (user_id) <small class="text-muted"> top 15</small></h6>
</div>
<div class="card-body">
<div class="chart-wrap"><canvas id="chartUtenti"></canvas></div>
</div>
</div>
</div>
</div>
<!-- ===== LISTE ===== -->
<div class="row">
<div class="col-12 col-lg-6">
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Dettaglio per cliente</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="tblClienti" class="table table-striped table-bordered table-sm w-100">
<thead>
<tr>
<th>ID Cliente</th>
<th>Cliente</th>
<th>Export</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Dettaglio per utente</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="tblUtenti" class="table table-striped table-bordered table-sm w-100">
<thead>
<tr>
<th>ID Utente</th>
<th>Utente</th>
<th>Export</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<script>
let chartClienti = null;
let chartUtenti = null;
let dtClienti = null;
let dtUtenti = null;
function todayStr() {
const d = new Date();
const off = d.getTimezoneOffset();
return new Date(d.getTime() - off * 60000).toISOString().slice(0, 10);
}
function initTables() {
dtClienti = $('#tblClienti').DataTable({
paging: false,
searching: false,
info: false,
order: [
[2, 'desc']
],
columns: [{
data: 'idclient'
},
{
data: 'clientname',
defaultContent: '—'
},
{
data: 'tot',
className: 'text-end'
}
],
language: {
emptyTable: 'Nessun export in questa giornata'
}
});
dtUtenti = $('#tblUtenti').DataTable({
paging: false,
searching: false,
info: false,
order: [
[2, 'desc']
],
columns: [{
data: 'user_id'
},
{
data: 'username',
defaultContent: '—'
},
{
data: 'tot',
className: 'text-end'
}
],
language: {
emptyTable: 'Nessun export in questa giornata'
}
});
}
function renderBarChart(existing, canvasId, labels, values, label) {
if (existing) existing.destroy();
const ctx = document.getElementById(canvasId).getContext('2d');
return new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: label,
data: values,
borderWidth: 1
}]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
x: {
beginAtZero: true,
ticks: {
precision: 0
}
}
}
}
});
}
// Prende i primi N record (già ordinati desc dal backend) per il grafico
const CHART_TOP_N = 15;
function topN(arr) {
return arr.slice(0, CHART_TOP_N);
}
function loadStats() {
const date = $('#filterDate').val() || todayStr();
$.getJSON('stats_export_lims_data.php', {
date: date
}, function(res) {
if (!res || !res.success) {
Swal.fire('Errore', (res && res.message) || 'Errore nel caricamento', 'error');
return;
}
// KPI
$('#kpiExport').text(res.totals.export);
$('#kpiClienti').text(res.byClient.length);
$('#kpiLimsSamples').text(res.limsSamples ?? 0);
$('#limsSamplesInput').val(res.limsSamples ?? '');
const limsTot = parseInt(res.limsSamples ?? 0, 10);
if (limsTot > 0) {
const perc = (res.totals.export / limsTot) * 100;
$('#kpiPerc').text(perc.toFixed(1) + '%');
} else {
$('#kpiPerc').text('—');
}
// Grafici
const clientTop = topN(res.byClient);
const userTop = topN(res.byUser);
chartClienti = renderBarChart(chartClienti, 'chartClienti',
clientTop.map(r => r.clientname ? (r.clientname + ' (' + r.idclient + ')') : ('ID ' + r.idclient)),
clientTop.map(r => r.tot), 'Export');
chartUtenti = renderBarChart(chartUtenti, 'chartUtenti',
userTop.map(r => r.username ? (r.username + ' (' + r.user_id + ')') : ('ID ' + r.user_id)),
userTop.map(r => r.tot), 'Export');
// Tabelle
dtClienti.clear().rows.add(res.byClient).draw();
dtUtenti.clear().rows.add(res.byUser).draw();
}).fail(function(xhr) {
Swal.fire('Errore', 'Richiesta fallita: ' + xhr.status, 'error');
});
}
$(document).ready(function() {
$('#filterDate').val(todayStr());
initTables();
loadStats();
$('#btnRefresh').on('click', loadStats);
$('#filterDate').on('change', loadStats);
$('#btnToday').on('click', function() {
$('#filterDate').val(todayStr());
loadStats();
});
$('#btnYesterday').on('click', function() {
const d = new Date();
d.setDate(d.getDate() - 1);
const off = d.getTimezoneOffset();
$('#filterDate').val(new Date(d.getTime() - off * 60000).toISOString().slice(0, 10));
loadStats();
});
$('#btnSaveLimsSamples').on('click', function() {
const date = $('#filterDate').val() || todayStr();
const total = parseInt($('#limsSamplesInput').val(), 10);
if (isNaN(total) || total < 0) {
Swal.fire('Attenzione', 'Inserisci un numero valido', 'warning');
return;
}
$.ajax({
url: 'stats_save_lims_samples.php',
type: 'POST',
dataType: 'json',
data: {
date: date,
total_samples: total
},
success: function(res) {
if (res && res.success) {
Swal.fire({
title: 'Salvato',
icon: 'success',
timer: 1200,
showConfirmButton: false
});
loadStats();
} else {
Swal.fire('Errore', (res && res.message) || 'Salvataggio fallito', 'error');
}
},
error: function(xhr) {
Swal.fire('Errore', 'Richiesta fallita: ' + xhr.status, 'error');
}
});
});
});
</script>
</body>
</html>