Spaces:
Sleeping
Sleeping
File size: 3,538 Bytes
f09c2db 0fd3a61 f09c2db f184c04 f09c2db 5fdeef5 0fd3a61 5fdeef5 f09c2db 5fdeef5 f09c2db 5fdeef5 f09c2db f184c04 f09c2db 5fdeef5 f184c04 5fdeef5 f09c2db 5fdeef5 f184c04 5fdeef5 f09c2db 5fdeef5 f09c2db 5fdeef5 f09c2db 5fdeef5 f09c2db | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | import { useEffect, useState } from "react";
import { NavLink, Outlet, useLocation, useNavigate } from "react-router-dom";
import {
BarChart3,
FileWarning,
LayoutGrid,
ListChecks,
Moon,
ScanLine,
Sun,
} from "lucide-react";
// The NETRA "eye" mark from the deck.
function EyeMark() {
return (
<svg viewBox="0 0 32 32" width="22" height="22" fill="none" aria-hidden>
<circle cx="16" cy="16" r="11" stroke="currentColor" strokeWidth="2.4" />
<circle cx="16" cy="16" r="4.4" fill="currentColor" />
<path
d="M5 16H2M30 16h-3M16 5V2M16 30v-3"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
);
}
import { getHealth } from "../api.js";
import { toggleTheme, useTheme } from "../hooks/useTheme.js";
const NAV = [
{ to: "/", label: "Overview", icon: LayoutGrid, end: true },
{ to: "/analyze", label: "Analyze", icon: ScanLine },
{ to: "/violations", label: "Records", icon: ListChecks },
{ to: "/challans", label: "Challans", icon: FileWarning },
{ to: "/analytics", label: "Analytics", icon: BarChart3 },
];
const TODAY = new Date().toLocaleDateString("en-IN", {
weekday: "long",
day: "numeric",
month: "long",
});
export default function Layout() {
const [online, setOnline] = useState(null);
const navigate = useNavigate();
const { pathname } = useLocation();
const theme = useTheme();
useEffect(() => {
const ping = () =>
getHealth().then(() => setOnline(true)).catch(() => setOnline(false));
ping();
const id = setInterval(ping, 15000);
return () => clearInterval(id);
}, []);
return (
<div className="shell">
<aside className="sidebar">
<div className="brand">
<span className="brand-mark">
<EyeMark />
</span>
<div>
<strong>NETRA</strong>
<span className="brand-sub">Command Center</span>
</div>
</div>
<nav>
<p className="nav-heading">Monitor</p>
{NAV.map(({ to, label, icon: Icon, end }) => (
<NavLink key={to} to={to} end={end} className="nav-link">
<Icon size={18} strokeWidth={1.75} />
{label}
</NavLink>
))}
</nav>
<div className="sidebar-foot">
<span className={`status-dot ${statusClass(online)}`} />
{online === null ? "Connecting…" : online ? "Backend online" : "Backend offline"}
</div>
</aside>
<main className="content">
<header className="topbar">
<span className="topbar-date">{TODAY}</span>
<div className="topbar-actions">
<button
className="theme-toggle"
onClick={toggleTheme}
title={theme === "dark" ? "Switch to light" : "Switch to dark"}
aria-label="Toggle theme"
>
{theme === "dark" ? <Sun size={17} strokeWidth={1.9} /> : <Moon size={17} strokeWidth={1.9} />}
</button>
{pathname !== "/analyze" && (
<button className="btn-primary" onClick={() => navigate("/analyze")}>
<ScanLine size={16} strokeWidth={2} />
New analysis
</button>
)}
</div>
</header>
<div className="page rise" key={pathname}>
<Outlet />
</div>
</main>
</div>
);
}
const statusClass = (online) =>
online === null ? "is-idle" : online ? "is-on" : "is-off";
|