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";