426 lines
16 KiB
PHP
426 lines
16 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">Export mese (status L)</div>
|
|
<div class="stat-value text-primary" id="kpiExport">0</div>
|
|
</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">Export anno <span id="yearLabel">—</span> (status L)</div>
|
|
<div class="stat-value text-dark" id="kpiExportYear">0</div>
|
|
<small class="text-muted">Dati a partire 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">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;
|
|
|
|
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: '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: '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
|
|
$('#kpiExport').text(res.totals.export);
|
|
$('#kpiClienti').text(res.byClient.length);
|
|
$('#kpiLimsSamples').text(res.limsSamples ?? 0);
|
|
|
|
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('—');
|
|
}
|
|
|
|
// Box annuale
|
|
$('#kpiExportYear').text(res.totalsYear.export);
|
|
$('#yearLabel').text(res.year);
|
|
|
|
// 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() {
|
|
$('#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>
|