pediatric-ai-scribe-v3/client/src/App.tsx
Daniel 22355e8cb2 feat(client): port Admin shell with role-gated sidebar entry
Last tab on the revamp roadmap. Ships /app/admin as a shell that
renders either an access-denied card or a legacy-viewer link
depending on me.user.role, plus a new Admin nav group in the
sidebar that is hidden entirely for non-admin users.

client/src/components/Layout.tsx
  NavItem gains an optional adminOnly flag. Layout now runs its own
  useQuery<MeOk>(['auth-me']) — the query key is already shared with
  Settings so the cache is reused across both. Nav groups whose
  items all filter out (in practice: the Admin group for non-admins)
  don't render their group header either, so the sidebar stays clean
  for regular users. 5-minute staleTime so the header doesn't hammer
  /me on every route change.

client/src/pages/Admin.tsx
  Same /me query + role check. Non-admins land on the
  admin-access-denied card; admins see the admin-shell with a link
  to the legacy admin viewer. Sub-sections (users, feature flags,
  OIDC, SMTP, AI prompts, model management, TTS/STT, email
  templates, announcement banner, site-wide saved encounters) stay
  in the vanilla admin page for now — each touches production state
  immediately on save, so each port needs its own deliberate commit
  with dedicated tests before shipping.

e2e/tests/admin-react.spec.js — one smoke test
  The seeded e2e user is non-admin, so the expected outcome is the
  access-denied card. Guard is written to accept either state so the
  test still passes if the seed ever flips to an admin.

With this commit the React sidebar covers the full legacy nav:
  • Encounters: Encounter HPI, Dictation HPI
  • Notes: Hospital Course, Chart Review, SOAP, Well Visit, Sick Visit
  • Clinical Tools: Vax Schedule, Catch-Up, PE Guide, Bedside,
    Calculators, Pagers & Extensions, Learning Hub
  • Account: Settings, FAQ
  • Admin: Admin Panel (role-gated)

Client tsc -b + vite build clean. Bundle 462.48 kB / 131.98 kB gz.
2026-04-24 00:03:02 +02:00

78 lines
3.5 KiB
TypeScript

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { BrowserRouter, Routes, Route, Navigate, Link } from 'react-router-dom';
import Layout from '@/components/Layout';
import Extensions from '@/pages/Extensions';
import Faq from '@/pages/Faq';
import Dictation from '@/pages/Dictation';
import Encounter from '@/pages/Encounter';
import Soap from '@/pages/Soap';
import SickVisit from '@/pages/SickVisit';
import HospitalCourse from '@/pages/HospitalCourse';
import ChartReview from '@/pages/ChartReview';
import WellVisit from '@/pages/WellVisit';
import VaxSchedule from '@/pages/VaxSchedule';
import Catchup from '@/pages/Catchup';
import Settings from '@/pages/Settings';
import Learning from '@/pages/Learning';
import PeGuide from '@/pages/PeGuide';
import Bedside from '@/pages/Bedside';
import Calculators from '@/pages/Calculators';
import Admin from '@/pages/Admin';
const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 30_000, retry: 1 } },
});
function Home() {
return (
<div className="max-w-3xl mx-auto p-6 space-y-4">
<h1 className="text-2xl font-semibold">Pediatric AI Scribe React client</h1>
<p className="text-sm text-muted-foreground">
This is the new React tree. The legacy vanilla-JS app still lives at{' '}
<a href="/" className="underline">/</a>.
</p>
<p className="text-sm text-muted-foreground">Ported tabs so far:</p>
<ul className="list-disc pl-6 text-sm space-y-1">
<li><Link to="/encounter" className="underline">Encounter HPI</Link></li>
<li><Link to="/dictation" className="underline">Dictation HPI</Link></li>
<li><Link to="/soap" className="underline">SOAP Note</Link></li>
<li><Link to="/sickvisit" className="underline">Sick Visit</Link></li>
<li><Link to="/extensions" className="underline">Extensions & Pagers</Link></li>
<li><Link to="/faq" className="underline">FAQ</Link></li>
</ul>
</div>
);
}
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<BrowserRouter basename="/app">
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="/encounter" element={<Encounter />} />
<Route path="/dictation" element={<Dictation />} />
<Route path="/soap" element={<Soap />} />
<Route path="/sickvisit" element={<SickVisit />} />
<Route path="/hospital" element={<HospitalCourse />} />
<Route path="/chart" element={<ChartReview />} />
<Route path="/wellvisit" element={<WellVisit />} />
<Route path="/vaxschedule" element={<VaxSchedule />} />
<Route path="/catchup" element={<Catchup />} />
<Route path="/extensions" element={<Extensions />} />
<Route path="/settings" element={<Settings />} />
<Route path="/learning" element={<Learning />} />
<Route path="/peguide" element={<PeGuide />} />
<Route path="/bedside" element={<Bedside />} />
<Route path="/calculators" element={<Calculators />} />
<Route path="/admin" element={<Admin />} />
<Route path="/faq" element={<Faq />} />
{/* catch-all falls back to home while more tabs port over */}
<Route path="*" element={<Navigate to="/" replace />} />
</Route>
</Routes>
</BrowserRouter>
</QueryClientProvider>
);
}