// Shell: topbar + sidebar + main
const { useMemo } = React;
function Topbar({ nodes, search, setSearch, live, toggleLive, currentUser, onOpenProfile }) {
const counts = useMemo(() => {
const c = { ok: 0, warn: 0, crit: 0, off: 0, maint: 0, unknown: 0 };
for (const n of nodes) {
if (c[n.status] === undefined) c[n.status] = 0;
c[n.status]++;
}
return c;
}, [nodes]);
const avgLat = useMemo(() => {
const live = nodes.filter(n => n.status !== 'off');
return live.reduce((s, n) => s + n.latency, 0) / (live.length || 1);
}, [nodes]);
// Hamburger toggles a body class — CSS handles the rest.
// Kept out of React state because it's purely a transient UI flag
// and we don't need re-renders when it flips.
function toggleSidebar() {
document.body.classList.toggle('sb-open');
}
function closeSidebar() {
document.body.classList.remove('sb-open');
}
// Clicking the scrim (pseudo-element covering the rest of the page
// on mobile when the sidebar is open) should close the sidebar.
React.useEffect(() => {
function onClick(e) {
if (!document.body.classList.contains('sb-open')) return;
// If the click falls outside the sidebar AND outside the hamburger,
// treat it as a scrim tap.
if (e.target.closest('.sidebar') || e.target.closest('.hamburger')) return;
closeSidebar();
}
document.addEventListener('click', onClick);
return () => document.removeEventListener('click', onClick);
}, []);
return (