import { useCallback, useEffect, useRef, useState } from 'react'; import { useSearchParams } from 'react-router'; import { useClient } from 'hooks/useClient'; import { SECTIONS, DEFAULT_SECTION, isSectionKey, type SectionKey } from './sections'; import { useNpmChanged } from './channel'; /** * The left column: which part of Nginx Proxy Manager the detail panel is showing. * * ── Selection is in the URL ── * * `?section=…`, per `docs/navigation-audit.md`: what is on screen is addressable, so it survives a reload, * can be linked, and the detail panel reads it directly rather than being handed it through a channel. The * two panels never talk to each other. * * ── The counts ── * * Each section fetches its own, which is one request per section on mount. That is affordable here — they * are small JSON lists from a local NPM — and it buys the thing a bare nav cannot give you: whether a * section is worth opening. "Streams 0" answers a question without a click. * * A count that fails is simply absent. A nav is not the place to report that NPM is unreachable; the * detail panel says so properly, once, with the reason. */ const rowClass = (active: boolean) => `flex w-full items-start gap-2.5 border-b px-3 py-2.5 text-left transition-colors ${ active ? 'bg-muted' : 'hover:bg-muted/50' }`; const useSectionCounts = () => { const client = useClient(); const clientRef = useRef(client); clientRef.current = client; const [counts, setCounts] = useState>>({}); const [connectionSignal] = useNpmChanged(); const load = useCallback(async () => { const results = await Promise.all( SECTIONS.filter((s) => s.path).map(async (section) => { try { const rows = await clientRef.current.get(section.path!); return [section.key, Array.isArray(rows) ? rows.length : undefined] as const; } catch { // Absent, not zero. Zero is a fact about NPM; a failure is a fact about the request. return [section.key, undefined] as const; } }), ); setCounts(Object.fromEntries(results.filter(([, n]) => n !== undefined))); }, []); useEffect(() => { void load(); }, [load, connectionSignal]); return counts; }; export const NavPanel = () => { const [params, setParams] = useSearchParams(); const raw = params.get('section'); const active: SectionKey = isSectionKey(raw) ? raw : DEFAULT_SECTION; const counts = useSectionCounts(); return ( ); };