new stack
This commit is contained in:
parent
6922e9d63b
commit
ac9737b125
10 changed files with 1476 additions and 1221 deletions
117
static/stats.js
Normal file
117
static/stats.js
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
// stats.js — Chart.js logic for Essence QC stats page.
|
||||
// Reads window.__statsData injected by the server into the stats-content fragment.
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
let statsChart = null;
|
||||
|
||||
function darkMode() {
|
||||
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
|
||||
function cssVar(name) {
|
||||
return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
||||
}
|
||||
|
||||
function initChart() {
|
||||
const snaps = window.__statsData;
|
||||
const canvas = document.getElementById('stats-chart-canvas');
|
||||
if (!snaps || !snaps.length || !canvas) return;
|
||||
|
||||
// Destroy previous instance if the fragment was re-swapped.
|
||||
if (statsChart) {
|
||||
statsChart.destroy();
|
||||
statsChart = null;
|
||||
}
|
||||
|
||||
const textColor = cssVar('--foreground') || (darkMode() ? '#e4e4e7' : '#18181b');
|
||||
const gridColor = cssVar('--border') || (darkMode() ? '#3f3f46' : '#e4e4e7');
|
||||
const tooltipBg = cssVar('--card') || (darkMode() ? '#18181b' : '#ffffff');
|
||||
const tooltipText = cssVar('--card-foreground') || textColor;
|
||||
|
||||
const labels = snaps.map(s => {
|
||||
const d = new Date(s.generatedAt);
|
||||
return d.toLocaleString('fr-CA', {
|
||||
month: 'short', day: 'numeric',
|
||||
hour: '2-digit', minute: '2-digit',
|
||||
});
|
||||
});
|
||||
|
||||
const pt = snaps.length > 60 ? 0 : 3;
|
||||
const datasets = [
|
||||
{
|
||||
label: 'Régulier',
|
||||
data: snaps.map(s => s.regularAvg > 0 ? +s.regularAvg.toFixed(2) : null),
|
||||
borderColor: '#3b82f6', backgroundColor: 'rgba(59,130,246,0.1)',
|
||||
tension: 0.3, fill: false, pointRadius: pt,
|
||||
},
|
||||
{
|
||||
label: 'Super',
|
||||
data: snaps.map(s => s.superAvg > 0 ? +s.superAvg.toFixed(2) : null),
|
||||
borderColor: '#f59e0b', backgroundColor: 'rgba(245,158,11,0.1)',
|
||||
tension: 0.3, fill: false, pointRadius: pt,
|
||||
},
|
||||
{
|
||||
label: 'Diesel',
|
||||
data: snaps.map(s => s.dieselAvg > 0 ? +s.dieselAvg.toFixed(2) : null),
|
||||
borderColor: '#22c55e', backgroundColor: 'rgba(34,197,94,0.1)',
|
||||
tension: 0.3, fill: false, pointRadius: pt,
|
||||
},
|
||||
];
|
||||
|
||||
const chartOpts = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
interaction: { mode: 'index', intersect: false },
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
labels: { color: textColor },
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: tooltipBg,
|
||||
titleColor: tooltipText,
|
||||
bodyColor: tooltipText,
|
||||
borderColor: gridColor,
|
||||
borderWidth: 1,
|
||||
callbacks: {
|
||||
label: ctx => ctx.dataset.label + ': ' +
|
||||
(ctx.parsed.y != null ? ctx.parsed.y.toFixed(1) + '¢/L' : '—'),
|
||||
},
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: { maxTicksLimit: 10, maxRotation: 30, color: textColor },
|
||||
grid: { color: gridColor },
|
||||
},
|
||||
y: {
|
||||
title: { display: true, text: '¢/L', color: textColor },
|
||||
ticks: { callback: v => v + '¢', color: textColor },
|
||||
grid: { color: gridColor },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
statsChart = new Chart(canvas, { type: 'line', data: { labels, datasets }, options: chartOpts });
|
||||
}
|
||||
|
||||
// Called inline from stats-content.html after __statsData is set.
|
||||
window.__initStatsChart = initChart;
|
||||
|
||||
// Re-render on dark-mode change.
|
||||
if (window.matchMedia) {
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
||||
if (statsChart) initChart();
|
||||
});
|
||||
}
|
||||
|
||||
// Re-init after any htmx swap of #stats-content.
|
||||
document.body.addEventListener('htmx:afterSwap', function (evt) {
|
||||
if (evt.detail && evt.detail.target && evt.detail.target.id === 'stats-content') {
|
||||
// __statsData was updated inline by the swapped fragment; just init the chart.
|
||||
if (window.__statsData) initChart();
|
||||
}
|
||||
});
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue