feat(dashboard): 3-col layout with Overview stats, speed chart fixes

- base.html: fix notification badge positioning (inline style, no overflow
  clip); remove py-4 from main; update footer text
- index.html: split bottom row into 3 cols (GPS / Overview / Speed);
  Overview card with 6 live stats from /api/stats; speed chart auto-expands
  on desktop; shown.bs.collapse triggers speedChart.resize()

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Mathieu Moran
2026-08-10 00:13:23 -04:00
co-authored by Copilot
parent ea03ef7f6c
commit 5d9b2f6556
2 changed files with 72 additions and 12 deletions
+69 -9
View File
@@ -91,9 +91,9 @@
</div>
</div>
{# ── GPS map (with inline coords footer) ─────────────────────────────── #}
{# ── GPS map + Stats + Speed history ──────────────────────────────────── #}
<div class="row g-3 mb-3">
<div class="col-lg-5 col-xl-4">
<div class="col-lg-4 col-xl-3">
<div class="card shadow-sm h-100">
<div class="card-header py-2">
<h6 class="text-primary fw-bold m-0">Live GPS</h6>
@@ -113,16 +113,50 @@
</div>
</div>
</div>
<div class="col-lg-7 col-xl-8">
<div class="col-lg-3 col-xl-2">
<div class="card shadow-sm h-100">
<div class="card-header py-2 d-flex align-items-center gap-3">
<div class="card-header py-2">
<h6 class="text-primary fw-bold m-0">Overview</h6>
</div>
<div class="card-body py-3 px-3" id="stats-body">
<div class="row g-3">
<div class="col-6 col-lg-12">
<div class="text-xs text-muted fw-bold text-uppercase mb-1">Trips</div>
<div class="fw-bold fs-5 text-dark" id="stat-trips">—</div>
</div>
<div class="col-6 col-lg-12">
<div class="text-xs text-muted fw-bold text-uppercase mb-1">Total distance</div>
<div class="fw-bold fs-5 text-dark" id="stat-km">—</div>
</div>
<div class="col-6 col-lg-12">
<div class="text-xs text-muted fw-bold text-uppercase mb-1">Drive time</div>
<div class="fw-bold fs-5 text-dark" id="stat-time">—</div>
</div>
<div class="col-6 col-lg-12">
<div class="text-xs text-muted fw-bold text-uppercase mb-1">Top speed ever</div>
<div class="fw-bold fs-5 text-dark" id="stat-topspeed">—</div>
</div>
<div class="col-6 col-lg-12">
<div class="text-xs text-muted fw-bold text-uppercase mb-1">Videos on Pi</div>
<div class="fw-bold fs-5 text-dark" id="stat-videos">—</div>
</div>
<div class="col-6 col-lg-12">
<div class="text-xs text-muted fw-bold text-uppercase mb-1">Active DTC</div>
<div class="fw-bold fs-5" id="stat-dtc">—</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-5 col-xl-7">
<div class="card shadow-sm h-100">
<div class="card-header py-2 d-flex align-items-center gap-2">
<h6 class="text-primary fw-bold m-0 me-auto">Speed history</h6>
{# Live speed badge — visible even when graph is collapsed #}
<span class="text-xs text-muted fw-bold text-uppercase">Now</span>
<span id="speed-now-badge" class="fw-bold text-dark" style="min-width:3.5rem;text-align:right;">—</span>
<span class="text-xs text-muted fw-bold text-uppercase ms-2">Max</span>
<span id="speed-max-badge" class="fw-bold text-danger" style="min-width:3.5rem;text-align:right;">—</span>
<button class="btn btn-link btn-sm p-0 text-muted ms-2" id="speed-graph-toggle" type="button"
<span id="speed-now-badge" class="fw-bold text-dark" style="min-width:3rem;text-align:right;">—</span>
<span class="text-xs text-muted fw-bold text-uppercase ms-2 speed-max-group">Max</span>
<span id="speed-max-badge" class="fw-bold text-danger speed-max-group" style="min-width:3rem;text-align:right;">—</span>
<button class="btn btn-link btn-sm p-0 text-muted ms-1" id="speed-graph-toggle" type="button"
aria-expanded="false" aria-controls="speed-graph-body">
<i class="fas fa-chevron-down"></i>
</button>
@@ -158,11 +192,37 @@
btn.setAttribute('aria-expanded', 'true');
btn.querySelector('i').className = 'fas fa-chevron-up';
});
body.addEventListener('shown.bs.collapse', function () {
if (window.speedChart) window.speedChart.resize();
});
body.addEventListener('hide.bs.collapse', function () {
btn.setAttribute('aria-expanded', 'false');
btn.querySelector('i').className = 'fas fa-chevron-down';
});
btn.addEventListener('click', function () { bsCollapse.toggle(); });
// Auto-expand on desktop; stay collapsed on mobile
if (window.innerWidth >= 768) {
bsCollapse.show();
}
})();
// Overview stats card
(function () {
function fmtDuration(s) {
const h = Math.floor(s / 3600);
const m = Math.floor((s % 3600) / 60);
return h > 0 ? h + 'h ' + m + 'm' : m + 'm';
}
fetch('/api/stats').then(r => r.json()).then(d => {
document.getElementById('stat-trips').textContent = d.trip_count;
document.getElementById('stat-km').textContent = d.total_km.toFixed(1) + ' km';
document.getElementById('stat-time').textContent = d.total_duration_s > 0 ? fmtDuration(d.total_duration_s) : '—';
document.getElementById('stat-topspeed').textContent = d.all_time_max_speed > 0 ? d.all_time_max_speed + ' km/h' : '—';
document.getElementById('stat-videos').textContent = d.video_count;
const dtcEl = document.getElementById('stat-dtc');
dtcEl.textContent = d.active_dtc;
dtcEl.className = 'fw-bold fs-5 ' + (d.active_dtc > 0 ? 'text-danger' : 'text-success');
}).catch(() => {});
})();
</script>
{% endblock %}