Files
trf_certest/public/userarea/stats_export_lims_monthly.php
T
2026-07-17 10:53:31 +02:00

440 lines
17 KiB
PHP

<?php include('include/headscript.php'); ?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title>TRF-Project - Statistiche Export LIMS Mensili</title>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css">
<style>
.stat-card .card-body {
padding: 1rem 1.25rem;
}
.stat-value {
font-size: 1.75rem;
font-weight: 700;
line-height: 1.1;
}
.stat-label {
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: .03em;
color: #6c757d;
}
.chart-wrap {
position: relative;
height: 420px;
}
#tblClienti,
#tblUtenti {
font-size: 13px;
}
#tblClienti th,
#tblClienti td,
#tblUtenti th,
#tblUtenti td {
vertical-align: middle;
}
.filter-bar {
gap: .75rem;
}
.month-label {
font-size: 1.05rem;
font-weight: 600;
}
.stat-card-extra {
background: linear-gradient(135deg, #021bff 0%, #010983 100%);
border: none;
}
.stat-card-extra .stat-label {
color: #c9cfff;
}
.stat-card-extra .stat-value {
color: #ffffff !important;
}
.stat-card-extra small.text-muted {
color: #b3bbff !important;
}
</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 MESE ===== -->
<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 — Mensili</h6>
<div class="d-flex align-items-center">
<label for="filterMonth" class="me-2 mb-0 fw-semibold">Mese:</label>
<input type="month" id="filterMonth" 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="btnCurrentMonth">Mese corrente</button>
<button class="btn btn-outline-secondary" id="btnPrevMonth">Mese precedente</button>
</div>
<span class="month-label ms-2 text-primary" id="monthLabel">—</span>
<a href="stats_export_lims.php" class="btn btn-outline-primary btn-sm ms-auto">
<i class="bx bx-calendar"></i> Vista giornaliera
</a>
<button class="btn btn-primary btn-sm ms-2" id="btnRefresh">
<i class="bx bx-refresh"></i> Aggiorna
</button>
</div>
</div>
</div>
<!-- ===== KPI (una sola riga) ===== -->
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-5 g-3">
<div class="col">
<div class="card radius-10 stat-card h-100">
<div class="card-body">
<div class="stat-label">Campioni mese</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 class="col">
<div class="card radius-10 stat-card stat-card-extra h-100">
<div class="card-body">
<div class="stat-label">Campioni anno <span id="yearLabel">—</span></div>
<div class="stat-value" id="kpiPartsYear">0</div>
<small class="text-muted">Commesse: <span id="kpiExportYear">0</span> · dal 16/07/2026</small>
</div>
</div>
</div>
<div class="col">
<div class="card radius-10 stat-card h-100">
<div class="card-body">
<div class="stat-label">Campioni inseriti LIMS (mese)</div>
<div class="stat-value text-info" id="kpiLimsSamples">0</div>
</div>
</div>
</div>
<div class="col">
<div class="card radius-10 stat-card h-100">
<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">
<div class="card radius-10 stat-card h-100">
<div class="card-body">
<div class="stat-label">Clienti distinti (mese)</div>
<div class="stat-value text-warning" id="kpiClienti">0</div>
</div>
</div>
</div>
</div>
<!-- ===== GRAFICI ===== -->
<div class="row mt-1">
<div class="col-12 col-lg-6">
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Campioni 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">Campioni 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>Campioni</th>
<th>Commesse</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>Campioni</th>
<th>Commesse</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;
const MESI_IT = ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno',
'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'
];
function currentMonthStr() {
const d = new Date();
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
return `${y}-${m}`;
}
function monthLabelFromStr(monthStr) {
const parts = (monthStr || '').split('-');
if (parts.length !== 2) return '—';
const y = parseInt(parts[0], 10);
const m = parseInt(parts[1], 10);
if (isNaN(y) || isNaN(m) || m < 1 || m > 12) return '—';
return `${MESI_IT[m - 1]} ${y}`;
}
function initTables() {
dtClienti = $('#tblClienti').DataTable({
paging: false,
searching: false,
info: false,
order: [
[2, 'desc']
],
columns: [{
data: 'idclient'
},
{
data: 'clientname',
defaultContent: '—'
},
{
data: 'parts',
className: 'text-end'
},
{
data: 'tot',
className: 'text-end'
}
],
language: {
emptyTable: 'Nessun export in questo mese'
}
});
dtUtenti = $('#tblUtenti').DataTable({
paging: false,
searching: false,
info: false,
order: [
[2, 'desc']
],
columns: [{
data: 'user_id'
},
{
data: 'username',
defaultContent: '—'
},
{
data: 'parts',
className: 'text-end'
},
{
data: 'tot',
className: 'text-end'
}
],
language: {
emptyTable: 'Nessun export in questo mese'
}
});
}
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
}
}
}
}
});
}
const CHART_TOP_N = 15;
function topN(arr) {
return arr.slice(0, CHART_TOP_N);
}
function loadStats() {
const month = $('#filterMonth').val() || currentMonthStr();
$('#monthLabel').text(monthLabelFromStr(month));
$.getJSON('stats_export_lims_monthly_data.php', {
month: month
}, function(res) {
if (!res || !res.success) {
Swal.fire('Errore', (res && res.message) || 'Errore nel caricamento', 'error');
return;
}
// KPI mese — dato principale = campioni/parti, sotto le commesse
$('#kpiParts').text(res.totals.parts ?? 0);
$('#kpiExport').text(res.totals.export ?? 0);
$('#kpiClienti').text(res.byClient.length);
$('#kpiLimsSamples').text(res.limsSamples ?? 0);
const limsTot = parseInt(res.limsSamples ?? 0, 10);
const partsTot = parseInt(res.totals.parts ?? 0, 10);
if (limsTot > 0) {
const perc = (partsTot / limsTot) * 100;
$('#kpiPerc').text(perc.toFixed(1) + '%');
} else {
$('#kpiPerc').text('—');
}
// Box annuale — campioni + commesse
$('#kpiPartsYear').text(res.totalsYear ? (res.totalsYear.parts ?? 0) : 0);
$('#kpiExportYear').text(res.totalsYear ? (res.totalsYear.export ?? 0) : 0);
$('#yearLabel').text(res.year);
// Grafici — valori = campioni/parti
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.parts), 'Campioni');
chartUtenti = renderBarChart(chartUtenti, 'chartUtenti',
userTop.map(r => r.username ? (r.username + ' (' + r.user_id + ')') : ('ID ' + r.user_id)),
userTop.map(r => r.parts), 'Campioni');
// 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() {
$('#filterMonth').val(currentMonthStr());
initTables();
loadStats();
$('#btnRefresh').on('click', loadStats);
$('#filterMonth').on('change', loadStats);
$('#btnCurrentMonth').on('click', function() {
$('#filterMonth').val(currentMonthStr());
loadStats();
});
$('#btnPrevMonth').on('click', function() {
const cur = $('#filterMonth').val() || currentMonthStr();
const parts = cur.split('-');
let y = parseInt(parts[0], 10);
let m = parseInt(parts[1], 10);
m -= 1;
if (m < 1) {
m = 12;
y -= 1;
}
$('#filterMonth').val(`${y}-${String(m).padStart(2, '0')}`);
loadStats();
});
});
</script>
</body>
</html>