318 lines
15 KiB
HTML
318 lines
15 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>Swap Station – Analytics</title>
|
||
|
||
<!-- Inter + Tailwind -->
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap" rel="stylesheet">
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<script>
|
||
tailwind.config = {
|
||
theme: {
|
||
extend: {
|
||
fontFamily: { sans: ["Inter","ui-sans-serif","system-ui"] },
|
||
keyframes: { pulseDot: { "0%,100%": { transform:"scale(1)", opacity:1 }, "50%": { transform:"scale(1.2)", opacity:.7 } } },
|
||
animation: { pulseDot: "pulseDot 1.2s ease-in-out infinite" }
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
:root { color-scheme: dark; }
|
||
html, body { height: 100%; }
|
||
body { background:#0a0a0a; }
|
||
|
||
/* soft background glow */
|
||
.bg-glow::before,
|
||
.bg-glow::after {
|
||
content:""; position:fixed; pointer-events:none; z-index:-1; border-radius:9999px; filter: blur(64px);
|
||
}
|
||
.bg-glow::before { width:34rem; height:34rem; left:-8rem; top:-8rem; background: rgba(16,185,129,.12); }
|
||
.bg-glow::after { width:36rem; height:36rem; right:-10rem; bottom:-10rem; background: rgba(56,189,248,.10); }
|
||
|
||
.badge { font-size:12px; font-weight:800; padding:.35rem .6rem; border-radius:.6rem; border:1px solid; display:inline-flex; align-items:center; gap:.4rem; }
|
||
.chip{display:inline-flex;align-items:center;gap:.4rem;padding:.4rem .5rem;border-radius:.4rem;
|
||
font-size:10px;font-weight:800;letter-spacing:.02em;text-transform:uppercase;border:1px solid}
|
||
.chip-emerald{background:rgba(16,185,129,.15);color:#a7f3d0;border-color:rgba(16,185,129,.35)}
|
||
.chip-rose{background:rgba(244,63,94,.16);color:#fecaca;border-color:rgba(244,63,94,.35)}
|
||
.chip-amber{background:rgba(245,158,11,.18);color:#fde68a;border-color:rgba(245,158,11,.40)}
|
||
.chip-sky{background:rgba(56,189,248,.15);color:#bae6fd;border-color:rgba(56,189,248,.35)}
|
||
.chip-slate{background:rgba(148,163,184,.12);color:#cbd5e1;border-color:rgba(148,163,184,.30)}
|
||
|
||
.glass { background: rgba(30,41,59,.45); border: 1px solid rgba(255,255,255,.10); border-radius: .9rem; backdrop-filter: saturate(150%) blur(12px); }
|
||
.tile { background: rgba(2,6,23,.45); border:1px solid rgba(255,255,255,.12); border-radius:.9rem; padding:1rem; }
|
||
.btn { font-weight:800; font-size:12px; padding:.5rem .7rem; border-radius:.6rem; border:1px solid rgba(255,255,255,.12); background:rgba(255,255,255,.06); transition:.18s; }
|
||
.btn:hover { border-color: rgba(16,185,129,.45); background: rgba(16,185,129,.10); }
|
||
.btn-ghost { border-color: rgba(255,255,255,.10); background: rgba(255,255,255,.05); }
|
||
.btn-danger { border-color: rgba(244,63,94,.35); background: rgba(244,63,94,.14); color:#fecaca; }
|
||
.btn-danger:hover { background: rgba(244,63,94,.22); }
|
||
.badge { font-size:12px; font-weight:800; padding:.35rem .6rem; border-radius:.6rem; border:1px solid; display:inline-flex; align-items:center; gap:.4rem; }
|
||
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
|
||
|
||
/* mini bar chart bars */
|
||
.bar { width: 10px; border-radius: 6px 6px 0 0; background: linear-gradient(180deg,#22c55e,#0ea5e9); }
|
||
</style>
|
||
</head>
|
||
<body class="min-h-screen text-gray-100 bg-glow">
|
||
|
||
<!-- STATUS BAR + TABS -->
|
||
<header class="relative z-10 border-b border-white/10 bg-black/20 backdrop-blur">
|
||
<div class="mx-auto max-w-7.5xl px-3 sm:px-4 py-2 grid grid-cols-[auto_1fr_auto] items-center gap-2">
|
||
<!-- Left -->
|
||
<div class="flex items-center gap-2 sm:gap-3">
|
||
<a href="./station_selection.html"
|
||
class="h-9 w-9 flex items-center justify-center rounded-full bg-white/5 hover:bg-white/10 transition"
|
||
title="Back">
|
||
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"/>
|
||
</svg>
|
||
</a>
|
||
<div class="flex flex-col leading-tight">
|
||
<div id="station-name" class="text-base sm:text-lg font-extrabold tracking-tight">
|
||
Loading...
|
||
</div>
|
||
<div id="station-location" class="text-xs sm:text-sm text-slate-100">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Center -->
|
||
<div class="flex items-center justify-center">
|
||
<img src="./assets/vec_logo.png" alt="VECMOCON"
|
||
class="h-7 w-auto opacity-90" onerror="this.style.display='none'"/>
|
||
</div>
|
||
|
||
<!-- Right badges/actions -->
|
||
<div class="flex items-center flex-wrap justify-end gap-1.5 sm:gap-2">
|
||
|
||
<span class="badge border-white/10 bg-white/5 text-slate-200">
|
||
<span>Device ID:</span>
|
||
<span id="device-id">—</span>
|
||
</span>
|
||
|
||
<span class="badge border-white/10 bg-white/5 text-slate-200">
|
||
<svg class="w-2.5 h-2.5 opacity-90" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||
<circle cx="12" cy="12" r="9"></circle><path d="M12 7v5l3 3"></path>
|
||
</svg>
|
||
<span id="last-update-status">Last Recv —</span>
|
||
</span>
|
||
|
||
<span class="chip chip-emerald">
|
||
<span class="h-2 w-2 rounded-full bg-emerald-400 animate-pulseDot"></span> Online
|
||
</span>
|
||
|
||
<span class="chip chip-amber" title="Running on backup supply">On Backup</span>
|
||
|
||
<div class="hidden sm:block w-px h-5 bg-white/10"></div>
|
||
|
||
<button id="refreshBtn" class="btn btn-ghost !p-2">
|
||
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||
<path d="M21 12a9 9 0 10-3.5 7.1M21 12h-4m4 0l-2.5-2.5"></path>
|
||
</svg>
|
||
</button>
|
||
|
||
<button id="downloadBtn" class="btn btn-ghost !p-2">
|
||
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||
<path d="M12 3v12m0 0l4-4m-4 4l-4-4"></path><path d="M5 21h14"></path>
|
||
</svg>
|
||
</button>
|
||
|
||
<button id="logout-btn" class="btn btn-danger !p-2">
|
||
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
|
||
<path d="M16 17l5-5-5-5"></path><path d="M21 12H9"></path>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tabs -->
|
||
<div class="border-t border-white/10 bg-black/10">
|
||
<nav class="mx-auto max-w-7.5xl px-3 sm:px-4 flex">
|
||
<a href="./dashboard.html" class="px-4 py-2 text-sm text-gray-400 hover:text-gray-200 hover:border-b-2 hover:border-white/30 font-semibold">Main</a>
|
||
<a href="./logs.html" class="px-4 py-2 text-sm text-gray-400 hover:text-gray-200 hover:border-b-2 hover:border-white/30 font-semibold">Logs</a>
|
||
<a href="./analytics.html" class="px-4 py-2 text-sm font-semibold border-b-2 border-emerald-400/70 text-white">Analytics</a>
|
||
</nav>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- CONTENT -->
|
||
<main class="relative z-10 mx-auto max-w-7.5xl px-3 sm:px-4 py-4 flex flex-col gap-4">
|
||
<!-- Device + Date Range -->
|
||
<section class="flex flex-wrap items-center gap-2">
|
||
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg border border-white/10 bg-white/5 text-slate-200">
|
||
<span>Device ID:</span>
|
||
<span id="device-id" class="font-bold mono">—</span>
|
||
</span>
|
||
|
||
<div class="ml-auto flex items-center gap-2">
|
||
<input id="from" type="date" class="rounded-md bg-white/5 border border-white/10 px-2 py-1.5 text-sm outline-none focus:ring-2 focus:ring-emerald-500/60">
|
||
<span class="text-gray-500">to</span>
|
||
<input id="to" type="date" class="rounded-md bg-white/5 border border-white/10 px-2 py-1.5 text-sm outline-none focus:ring-2 focus:ring-emerald-500/60">
|
||
<button id="applyRange" class="btn">Apply</button>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Stat Tiles -->
|
||
<section class="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||
<div class="tile">
|
||
<p class="text-xs text-gray-400">Total Swaps (Today)</p>
|
||
<p class="text-3xl font-extrabold">142</p>
|
||
</div>
|
||
<div class="tile">
|
||
<p class="text-xs text-gray-400">Avg. Swap Time</p>
|
||
<p class="text-3xl font-extrabold">2.1 <span class="text-lg font-bold text-gray-300">min</span></p>
|
||
</div>
|
||
<div class="tile">
|
||
<p class="text-xs text-gray-400">Station Uptime</p>
|
||
<p class="text-3xl font-extrabold text-emerald-400">99.8%</p>
|
||
</div>
|
||
<div class="tile">
|
||
<p class="text-xs text-gray-400">Peak Hours</p>
|
||
<p class="text-3xl font-extrabold">5–7 PM</p>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Charts -->
|
||
<section class="grid grid-cols-1 lg:grid-cols-2 gap-4 min-h-[20rem]">
|
||
<!-- Weekly Swaps - CSS bars -->
|
||
<div class="glass p-4">
|
||
<div class="flex items-center justify-between">
|
||
<h3 class="font-extrabold">Swaps This Week</h3>
|
||
<span class="text-xs text-gray-400">Mon → Sun</span>
|
||
</div>
|
||
|
||
<div class="mt-4 h-64 rounded-lg border border-white/10 bg-white/5 p-4 flex items-end gap-4 min-h-[20.8rem]">
|
||
<!-- Each group: bar + label -->
|
||
<div class="flex flex-col items-center gap-2">
|
||
<div class="bar" style="height:50%"></div><span class="text-xs text-gray-400">Mon</span>
|
||
</div>
|
||
<div class="flex flex-col items-center gap-2">
|
||
<div class="bar" style="height:74%"></div><span class="text-xs text-gray-400">Tue</span>
|
||
</div>
|
||
<div class="flex flex-col items-center gap-2">
|
||
<div class="bar" style="height:60%"></div><span class="text-xs text-gray-400">Wed</span>
|
||
</div>
|
||
<div class="flex flex-col items-center gap-2">
|
||
<div class="bar" style="height:85%"></div><span class="text-xs text-gray-400">Thu</span>
|
||
</div>
|
||
<div class="flex flex-col items-center gap-2">
|
||
<div class="bar" style="height:92%"></div><span class="text-xs text-gray-400">Fri</span>
|
||
</div>
|
||
<div class="flex flex-col items-center gap-2">
|
||
<div class="bar" style="height:42%"></div><span class="text-xs text-gray-400">Sat</span>
|
||
</div>
|
||
<div class="flex flex-col items-center gap-2">
|
||
<div class="bar" style="height:30%"></div><span class="text-xs text-gray-400">Sun</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Battery Health - donut style -->
|
||
<div class="glass p-4">
|
||
<h3 class="font-extrabold">Battery Health</h3>
|
||
<div class="h-64 flex items-center justify-center">
|
||
<div class="relative w-52 h-52">
|
||
<svg class="w-full h-full" viewBox="0 0 36 36">
|
||
<path d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"
|
||
fill="none" stroke="#1f2937" stroke-width="3"/>
|
||
<path d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831"
|
||
fill="none" stroke="#ef4444" stroke-width="3" stroke-dasharray="20, 100"/>
|
||
<path d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831"
|
||
fill="none" stroke="#f59e0b" stroke-width="3" stroke-dasharray="30, 100"/>
|
||
<path d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831"
|
||
fill="none" stroke="#22c55e" stroke-width="3" stroke-dasharray="50, 100"/>
|
||
</svg>
|
||
<div class="absolute inset-0 flex flex-col items-center justify-center">
|
||
<span class="text-2xl font-extrabold">250</span>
|
||
<span class="text-xs text-gray-400">Total Batteries</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-3 grid grid-cols-3 gap-2 text-xs">
|
||
<div class="flex items-center gap-2"><span class="h-2.5 w-2.5 rounded-full bg-emerald-400"></span><span>Good</span></div>
|
||
<div class="flex items-center gap-2"><span class="h-2.5 w-2.5 rounded-full bg-amber-400"></span><span>Warning</span></div>
|
||
<div class="flex items-center gap-2"><span class="h-2.5 w-2.5 rounded-full bg-rose-400"></span><span>Poor</span></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<script>
|
||
// Fill device id from selected station (if stored by station_selection)
|
||
(function setDevice() {
|
||
const el = document.querySelector('#device-id');
|
||
try {
|
||
const sel = JSON.parse(localStorage.getItem('selected_station') || '{}');
|
||
el.textContent = sel?.id || sel?.station_id || '—';
|
||
} catch { el.textContent = '—'; }
|
||
})();
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
// Replace 'VEC-STN-0128' with the actual ID of the station you want to load
|
||
const stationId = 'VEC-STN-0128';
|
||
loadStationInfo(stationId);
|
||
});
|
||
|
||
// This function fetches data from your backend and updates the page
|
||
async function loadStationInfo(stationId) {
|
||
// Find the HTML elements by their IDs
|
||
const nameElement = document.getElementById('station-name');
|
||
const locationElement = document.getElementById('station-location');
|
||
|
||
try {
|
||
// 1. Fetch data from your backend API endpoint
|
||
// You must replace this URL with your actual API endpoint
|
||
const response = await fetch(`/api/stations/${stationId}`);
|
||
|
||
if (!response.ok) {
|
||
throw new Error('Network response was not ok');
|
||
}
|
||
|
||
// 2. Convert the response into JSON format
|
||
// Example JSON: { "name": "VEC-STN-0128", "location": "Sector 62, Noida" }
|
||
const stationData = await response.json();
|
||
|
||
// 3. Update the HTML content with the data from the database
|
||
nameElement.textContent = stationData.name;
|
||
locationElement.textContent = stationData.location;
|
||
|
||
} catch (error) {
|
||
// If something goes wrong, show an error message
|
||
nameElement.textContent = 'Error Loading Station';
|
||
locationElement.textContent = 'Could not fetch data.';
|
||
console.error('Error fetching station data:', error);
|
||
}
|
||
}
|
||
|
||
// Demo "last recv" timestamp
|
||
document.querySelector('#last-update-status').textContent =
|
||
'Last Recv ' + new Date().toLocaleString();
|
||
|
||
// Actions
|
||
document.querySelector('#logout-btn')?.addEventListener('click', () => {
|
||
window.location.href = './index.html';
|
||
});
|
||
document.querySelector('#refreshBtn')?.addEventListener('click', () => location.reload());
|
||
document.querySelector('#downloadBtn')?.addEventListener('click', () => {
|
||
alert('Hook this to your /api/logs/export (or analytics export) endpoint.');
|
||
});
|
||
|
||
// Date range apply (wire to backend later)
|
||
document.querySelector('#applyRange')?.addEventListener('click', () => {
|
||
const f = document.querySelector('#from').value;
|
||
const t = document.querySelector('#to').value;
|
||
if (!f || !t) return alert('Choose a date range first.');
|
||
alert(`Apply analytics range:\n${f} → ${t}`);
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|