From 18550e263f925860fa85413bb1b18ffff6b7d261 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 24 Apr 2026 01:11:57 +0200 Subject: [PATCH] =?UTF-8?q?feat(client):=20port=20age=E2=86=92weight=20+?= =?UTF-8?q?=20BSA=20+=20dose=20+=20GCS;=20shared/clinical=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First batch of real calculator ports, routed through a new shared/clinical/calculators.ts module that both the server tree and the React client can import. Kept strictly to the simplest, closed-form formulas — tables (Rosner BP, Fenton LMS, AAP 2022 bili, Bhutani, CDC BMI) stay in the vanilla viewer until per-table vector files land. shared/clinical/calculators.ts Verbatim ports from public/js/calc-math.js: • parseAgeMonths / formatAgeMonths — legacy age-string parser • estimateWeightFromAgeMonths — APLS (Luscombe 2007) + Best Guess (Tinning 2007) weight-for-age. Cross-checked line-by-line against calc-math.js:14-39; identical branches, formulas, and roundTo behavior. • calculateMostellerBsa — sqrt(h·w/3600), Mosteller 1987. • calculateWeightBasedDose — generic mg/kg with optional max cap and mg/mL → mL conversion. • calculateGcs — 1-15 sum with 8/12 severity thresholds. shared/clinical/calculators.test.ts Vitest unit coverage for each helper. Numeric assertions match the legacy function outputs (3y APLS → 14 kg; 20 kg, 110 cm → 0.782 m²; 15 kg × 100 mg/kg capped at 500 mg, etc.). For these closed-form formulas the hand-verified expected values are equivalent to a vanilla-captured vector file — table-driven calculators still need a JSON fixture before they port. client/src/pages/Bedside.tsx Top-level age-to-weight estimator now runs in React (BedsideWeightEstimator). Formula dropdown switches between APLS and Best Guess live; weight field accepts a manual override. The 15 clinical dosing sub-modules still fall through to the legacy viewer via LegacyPanel. client/src/pages/Calculators.tsx BSA, Weight-Based Dosing, and GCS panels render real React forms backed by the shared helpers. PILLS gain a `ported` flag so the four covered panels (bsa, dose, gcs, + the already-shipped pills) swap out of the legacy fallback while the others remain linked out. Result blocks carry data-testid hooks for parity tests. Config + dep hygiene picked up along the way • client/tsconfig.app.json: @shared/* path alias, exclude test files from the React tsc pass. • tsconfig.json: exclude **/*.test.ts from the backend tsc pass. • package.json: declare google-auth-library and jszip explicitly — both were already required() in src/utils/ttsGoogle.ts and src/routes/learningAI.ts but missing from dependencies, which would break a clean `npm install`. Also adds engines: node >=20 and convenience verify / verify:full scripts. • knip.json: quiet now-expected ignoreDependencies / ignoreBinaries entries for marp-cli, tiptap, cap, etc. • .gitignore: ignore the .codex CLI marker. e2e/tests/bedside-react.spec.js Adds the age→weight parity test: 3y APLS → 14 kg, 3y Best Guess → 16 kg, weight field mirrors the estimator output. e2e/tests/calculators-react.spec.js Adds BSA (20 kg, 110 cm → 0.782 m²), dose cap (15 kg × 100 mg/kg, max 500 mg → 500 mg capped), and GCS (15 → 10 when motor drops to 1) parity tests. Client tsc -b + backend tsc --noEmit + vite build all clean. Bundle 476.48 kB / 135.44 kB gzipped. Co-Authored-By: Codex + vendor model Opus 4.7 (1M context) --- .gitignore | 3 + client/package.json | 3 + client/src/pages/Bedside.tsx | 106 +++++- client/src/pages/Calculators.tsx | 347 +++++++++++++++++- client/tsconfig.app.json | 6 +- e2e/tests/bedside-react.spec.js | 10 + e2e/tests/calculators-react.spec.js | 28 ++ knip.json | 13 + package-lock.json | 2 + package.json | 9 +- public/app/assets/index-0qzAvrmR.css | 2 - public/app/assets/index-9V57IPOu.css | 2 + .../{index-x592EjRR.js => index-DmvH7O2M.js} | 2 +- public/app/index.html | 4 +- shared/clinical/calculators.test.ts | 143 ++++++++ shared/clinical/calculators.ts | 206 +++++++++++ tsconfig.json | 3 +- 17 files changed, 866 insertions(+), 23 deletions(-) delete mode 100644 public/app/assets/index-0qzAvrmR.css create mode 100644 public/app/assets/index-9V57IPOu.css rename public/app/assets/{index-x592EjRR.js => index-DmvH7O2M.js} (94%) create mode 100644 shared/clinical/calculators.test.ts create mode 100644 shared/clinical/calculators.ts diff --git a/.gitignore b/.gitignore index 931f661..65171d8 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,6 @@ public/models/ e2e/node_modules/ e2e/test-results/ e2e/playwright-report/ + +# Codex CLI marker +.codex diff --git a/client/package.json b/client/package.json index e69f4e9..243764c 100644 --- a/client/package.json +++ b/client/package.json @@ -3,6 +3,9 @@ "private": true, "version": "0.0.0", "type": "module", + "engines": { + "node": ">=20" + }, "scripts": { "dev": "vite", "build": "tsc -b && vite build", diff --git a/client/src/pages/Bedside.tsx b/client/src/pages/Bedside.tsx index f8b93dd..4d5e5a5 100644 --- a/client/src/pages/Bedside.tsx +++ b/client/src/pages/Bedside.tsx @@ -1,7 +1,7 @@ // ============================================================ -// BEDSIDE — emergency + rapid-reference pediatric tools. This first -// port ships the sub-nav shell at /app/bedside so the sidebar links -// light up and users land on a page that mirrors the legacy layout. +// BEDSIDE — emergency + rapid-reference pediatric tools. +// Top-level age-to-weight estimation is React + pure shared TS. +// Individual dosing modules port one at a time after parity tests. // // The 15 clinical sub-modules (neonatal, airway, cardiac, respiratory, // ventilation, seizures, sepsis, anaphylaxis, sedation, agitation, @@ -13,15 +13,20 @@ // BP splines, Fenton LMS, AAP 2022 bilirubin, APLS weights) where // test vectors can verify byte-for-byte parity. // -// The top-level age → weight estimator (APLS / Best Guess formulas) -// also depends on public/js/calculators.js which has not been ported -// yet. It will appear at the top of this page when calculators port. // ============================================================ import { useState } from 'react'; +import { + estimateWeightFromAgeMonths, + formatAgeMonths, + parseAgeMonths, +} from '@shared/clinical/calculators'; const card = 'rounded-lg border border-border bg-card p-5 space-y-3'; const btnPrimary = 'rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium'; +const btnGhost = 'rounded-md border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted'; +const label = 'block text-xs font-medium text-muted-foreground'; +const input = 'w-full rounded-md border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring'; interface Pill { id: string; @@ -49,6 +54,94 @@ const PILLS: Pill[] = [ { id: 'trauma', label: 'Trauma', icon: '🩹', summary: 'PECARN, c-spine, blood-product dosing, TXA.' }, ]; +function BedsideWeightEstimator() { + const [age, setAge] = useState(''); + const [formula, setFormula] = useState<'apls' | 'bestguess'>('apls'); + const [manualWeight, setManualWeight] = useState(''); + + const months = parseAgeMonths(age); + const estimate = months == null ? null : estimateWeightFromAgeMonths(months); + const pickedWeight = estimate + ? formula === 'bestguess' + ? estimate.all.bestGuess + : estimate.all.apls + : null; + const displayedWeight = manualWeight.trim() || (pickedWeight == null ? '' : String(pickedWeight)); + + function clear() { + setAge(''); + setFormula('apls'); + setManualWeight(''); + } + + return ( +
+
+

Age → Weight Estimator

+

+ Shared starting point for Bedside dosing. Uses the same APLS and Best Guess formulas as the legacy app. +

+
+
+
+ + setAge(event.target.value)} + placeholder='e.g. "18m", "3y", "2y5m"' + className={input} + data-testid="bedside-age-input" + /> +
+
+ + +
+
+ + setManualWeight(event.target.value)} + className={input} + data-testid="bedside-weight-input" + /> +
+ +
+ {age.trim() && months == null ? ( +
+ Could not parse age. Try "3y", "18 months", or "15 days". +
+ ) : null} + {estimate && pickedWeight != null ? ( +
+
{pickedWeight} kg estimated from {formatAgeMonths(months ?? 0)}
+
+ APLS: {estimate.all.apls} kg · Best Guess: {estimate.all.bestGuess} kg. You can override the weight field. +
+
+ ) : null} +
+ ); +} + function LegacyPanel({ pill }: { pill: Pill }) { return (
@@ -103,6 +196,7 @@ export default function Bedside() { ))} + ); diff --git a/client/src/pages/Calculators.tsx b/client/src/pages/Calculators.tsx index e83eab4..24cb6af 100644 --- a/client/src/pages/Calculators.tsx +++ b/client/src/pages/Calculators.tsx @@ -1,7 +1,7 @@ // ============================================================ -// CALCULATORS — shell + sub-nav. This port intentionally delivers -// ONLY the page shell and sub-navigation. The actual math runs in -// the vanilla viewer until test vectors land. +// CALCULATORS — incremental React port. +// Low-risk pure formulas run here; high-risk table-driven calculators +// stay in the vanilla viewer until legacy vectors land. // // WHY this is gated on test vectors (from the migration checkpoint): // • AAP 2017 BP percentile uses Rosner quantile splines with long @@ -22,15 +22,27 @@ // ============================================================ import { useState } from 'react'; +import { + calculateGcs, + calculateMostellerBsa, + calculateWeightBasedDose, +} from '@shared/clinical/calculators'; const card = 'rounded-lg border border-border bg-card p-5 space-y-3'; const btnPrimary = 'rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium'; +const btnGhost = 'rounded-md border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted'; +const field = 'space-y-1'; +const label = 'block text-xs font-medium text-muted-foreground'; +const input = 'w-full rounded-md border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring'; +const resultBox = 'rounded-lg border border-border bg-muted/40 p-4'; +const errorBox = 'rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-700 dark:bg-red-950/30 dark:text-red-200'; interface Pill { id: string; label: string; summary: string; source: string; // where the formulas live + ported?: boolean; } const PILLS: Pill[] = [ @@ -39,13 +51,322 @@ const PILLS: Pill[] = [ { id: 'growth', label: 'Growth Charts', summary: 'WHO 0–2 y / CDC 2–20 y; Fenton 2013 preterm (weight, length, head).', source: 'WHO 2006 + CDC 2000 + Fenton 2013 LMS' }, { id: 'bili', label: 'Bilirubin', summary: 'AAP 2022 phototherapy + exchange thresholds and Bhutani nomogram risk zones.', source: 'AAP 2022 (Kemper) + Bhutani 1999' }, { id: 'vitals', label: 'Vital Signs', summary: 'Normal HR / RR / BP ranges by age.', source: 'PALS + AHA reference' }, - { id: 'bsa', label: 'Body Surface Area', summary: 'Mosteller + DuBois formulas.', source: 'Mosteller 1987 / DuBois 1916' }, - { id: 'dose', label: 'Weight-Based Dosing', summary: 'Common pediatric med doses per kg + max dose.', source: 'Lexicomp / Harriet Lane' }, + { id: 'bsa', label: 'Body Surface Area', summary: 'Mosteller body surface area formula.', source: 'Mosteller 1987', ported: true }, + { id: 'dose', label: 'Weight-Based Dosing', summary: 'Generic mg/kg dosing with optional max-dose cap and concentration conversion.', source: 'Legacy calculator formula', ported: true }, { id: 'resus', label: 'Resus Meds', summary: 'Code-cart dosing (epinephrine, amiodarone, atropine, etc.).', source: 'PALS' }, - { id: 'gcs', label: 'GCS', summary: 'Child/adult and infant Glasgow Coma Scale variants.', source: 'Teasdale + pediatric modification' }, + { id: 'gcs', label: 'GCS', summary: 'Child/adult and infant Glasgow Coma Scale variants.', source: 'Teasdale + pediatric modification', ported: true }, { id: 'equipment', label: 'Equipment', summary: 'ETT size, blade, NG, Foley, suction by age/weight.', source: 'PALS + Broselow cross-reference' }, ]; +function parseOptionalNumber(value: string): number | null { + if (!value.trim()) return null; + const parsed = Number(value); + return Number.isFinite(parsed) ? parsed : null; +} + +function FormField({ + id, + labelText, + value, + onChange, + min, + max, + step = '0.1', + placeholder, +}: { + id: string; + labelText: string; + value: string; + onChange: (value: string) => void; + min?: string; + max?: string; + step?: string; + placeholder?: string; +}) { + return ( +
+ + onChange(event.target.value)} + placeholder={placeholder} + className={input} + /> +
+ ); +} + +function BsaPanel() { + const [weight, setWeight] = useState(''); + const [height, setHeight] = useState(''); + const [result, setResult] = useState(null); + const [error, setError] = useState(''); + + function calculate() { + const next = calculateMostellerBsa(Number(weight), Number(height)); + if (next == null) { + setError('Enter a valid weight and height.'); + setResult(null); + return; + } + setError(''); + setResult(next); + } + + function clear() { + setWeight(''); + setHeight(''); + setResult(null); + setError(''); + } + + return ( +
+

Body Surface Area

+

+ Mosteller formula: BSA (m2) = sqrt(height(cm) x weight(kg) / 3600). +

+
+ + +
+
+ + +
+ {error ?
{error}
: null} + {result == null ? null : ( +
+
Mosteller BSA
+
{result.toFixed(3)} m²
+
{weight} kg, {height} cm
+
+ )} +
+ ); +} + +function DosePanel() { + const [weight, setWeight] = useState(''); + const [dosePerKg, setDosePerKg] = useState(''); + const [frequency, setFrequency] = useState('1'); + const [maxDose, setMaxDose] = useState(''); + const [concentration, setConcentration] = useState(''); + const [result, setResult] = useState>(null); + const [error, setError] = useState(''); + + function calculate() { + const next = calculateWeightBasedDose({ + weightKg: Number(weight), + dosePerKg: Number(dosePerKg), + frequencyPerDay: Number(frequency), + maxSingleDoseMg: parseOptionalNumber(maxDose), + concentrationMgPerMl: parseOptionalNumber(concentration), + }); + if (next == null) { + setError('Enter a valid weight, mg/kg dose, and frequency.'); + setResult(null); + return; + } + setError(''); + setResult(next); + } + + function clear() { + setWeight(''); + setDosePerKg(''); + setFrequency('1'); + setMaxDose(''); + setConcentration(''); + setResult(null); + setError(''); + } + + return ( +
+

Weight-Based Dosing

+

+ Generic mg/kg calculator. Always verify medication-specific dosing against formulary and local policy. +

+
+ + +
+ + +
+ + +
+
+ + +
+ {error ?
{error}
: null} + {result == null ? null : ( +
+
+
+
Single Dose
+
{result.singleDoseMg.toFixed(1)} mg
+ {result.capped ?
Capped at max dose
: null} +
+
+
Daily Total
+
{result.dailyDoseMg.toFixed(1)} mg/day
+
x {result.frequencyPerDay}/day
+
+
+
Volume
+
{result.volumeMl == null ? 'n/a' : `${result.volumeMl.toFixed(1)} mL`}
+
per dose
+
+
+
+ )} +
+ ); +} + +const GCS_OPTIONS = { + child: { + eye: [ + ['4', '4 - Spontaneous'], + ['3', '3 - To speech'], + ['2', '2 - To pain'], + ['1', '1 - None'], + ], + verbal: [ + ['5', '5 - Oriented'], + ['4', '4 - Confused'], + ['3', '3 - Inappropriate words'], + ['2', '2 - Incomprehensible sounds'], + ['1', '1 - None'], + ], + motor: [ + ['6', '6 - Obeys commands'], + ['5', '5 - Localizes pain'], + ['4', '4 - Withdraws to pain'], + ['3', '3 - Abnormal flexion'], + ['2', '2 - Abnormal extension'], + ['1', '1 - None'], + ], + }, + infant: { + eye: [ + ['4', '4 - Spontaneous'], + ['3', '3 - To speech/sound'], + ['2', '2 - To painful stimuli'], + ['1', '1 - None'], + ], + verbal: [ + ['5', '5 - Coos/babbles'], + ['4', '4 - Irritable cry'], + ['3', '3 - Cries to pain'], + ['2', '2 - Moans to pain'], + ['1', '1 - None'], + ], + motor: [ + ['6', '6 - Normal spontaneous movement'], + ['5', '5 - Withdraws to touch'], + ['4', '4 - Withdraws to pain'], + ['3', '3 - Abnormal flexion'], + ['2', '2 - Abnormal extension'], + ['1', '1 - None'], + ], + }, +} as const; + +function GcsSelect({ + id, + labelText, + value, + options, + onChange, +}: { + id: string; + labelText: string; + value: string; + options: readonly (readonly [string, string])[]; + onChange: (value: string) => void; +}) { + return ( +
+ + +
+ ); +} + +function GcsPanel() { + const [scale, setScale] = useState<'child' | 'infant'>('child'); + const [eye, setEye] = useState('4'); + const [verbal, setVerbal] = useState('5'); + const [motor, setMotor] = useState('6'); + const result = calculateGcs(Number(eye), Number(verbal), Number(motor)); + const options = GCS_OPTIONS[scale]; + + function switchScale(next: 'child' | 'infant') { + setScale(next); + setEye('4'); + setVerbal('5'); + setMotor('6'); + } + + return ( +
+

Glasgow Coma Scale

+

+ Select responses to calculate child/adult or infant-modified GCS. Total score 3-15. +

+
+ + +
+
+ + + +
+ {result == null ? null : ( +
+
{scale === 'infant' ? 'Infant-modified GCS' : 'Child / adult GCS'}
+
GCS: {result.total}/15
+
{result.severity}
+
Interpretation: 13-15 Mild, 9-12 Moderate, 3-8 Severe/Coma.
+
+ )} +
+ ); +} + function LegacyPanel({ pill }: { pill: Pill }) { return (
@@ -69,6 +390,13 @@ function LegacyPanel({ pill }: { pill: Pill }) { ); } +function ActivePanel({ pill }: { pill: Pill }) { + if (pill.id === 'bsa') return ; + if (pill.id === 'dose') return ; + if (pill.id === 'gcs') return ; + return ; +} + export default function Calculators() { const [active, setActive] = useState(PILLS[0].id); const pill = PILLS.find((p) => p.id === active) ?? PILLS[0]; @@ -79,7 +407,8 @@ export default function Calculators() {

Calculators

Pediatric calculators — BP percentiles, bilirubin thresholds, growth, dosing, equipment sizing. - Formula ports arrive one at a time, each verified against captured test vectors. + Simple pure-formula calculators run in React now; high-risk table-driven calculators remain + legacy-gated until vectors are captured.

@@ -97,12 +426,12 @@ export default function Calculators() { } data-testid={'calc-pill-' + p.id} > - {p.label} + {p.label}{p.ported ? React : null} ))} - + ); } diff --git a/client/tsconfig.app.json b/client/tsconfig.app.json index 19439e6..ca2688c 100644 --- a/client/tsconfig.app.json +++ b/client/tsconfig.app.json @@ -22,12 +22,16 @@ "noFallthroughCasesInSwitch": true, "paths": { - "@/*": ["./src/*"] + "@/*": ["./src/*"], + "@shared/*": ["../shared/*"] } }, "include": [ "src/**/*.ts", "src/**/*.tsx", "../shared/**/*.ts" + ], + "exclude": [ + "../shared/**/*.test.ts" ] } diff --git a/e2e/tests/bedside-react.spec.js b/e2e/tests/bedside-react.spec.js index f912fc5..417ad27 100644 --- a/e2e/tests/bedside-react.spec.js +++ b/e2e/tests/bedside-react.spec.js @@ -40,4 +40,14 @@ test.describe('React Bedside — sub-nav shell', () => { await openReactBedside(page); await expect(page.getByText('Open in legacy viewer').first()).toBeVisible(); }); + + test('age-to-weight estimator runs in React', async ({ authedPage: _, page }) => { + await openReactBedside(page); + await page.fill('[data-testid="bedside-age-input"]', '3y'); + await expect(page.locator('[data-testid="bedside-estimate-result"]')).toContainText('14 kg'); + await expect(page.locator('[data-testid="bedside-weight-input"]')).toHaveValue('14'); + await page.selectOption('[data-testid="bedside-formula-select"]', 'bestguess'); + await expect(page.locator('[data-testid="bedside-estimate-result"]')).toContainText('16 kg'); + await expect(page.locator('[data-testid="bedside-weight-input"]')).toHaveValue('16'); + }); }); diff --git a/e2e/tests/calculators-react.spec.js b/e2e/tests/calculators-react.spec.js index 9d6aab5..835568f 100644 --- a/e2e/tests/calculators-react.spec.js +++ b/e2e/tests/calculators-react.spec.js @@ -37,4 +37,32 @@ test.describe('React Calculators — sub-nav shell', () => { await openReactCalc(page); await expect(page.getByText('Open in legacy viewer').first()).toBeVisible(); }); + + test('BSA calculator runs in React', async ({ authedPage: _, page }) => { + await openReactCalc(page); + await page.click('[data-testid="calc-pill-bsa"]'); + await page.fill('#react-bsa-weight', '20'); + await page.fill('#react-bsa-height', '110'); + await page.click('[data-testid="calc-bsa-calculate"]'); + await expect(page.locator('[data-testid="calc-bsa-result"]')).toContainText('0.782'); + }); + + test('weight-based dose calculator caps max dose', async ({ authedPage: _, page }) => { + await openReactCalc(page); + await page.click('[data-testid="calc-pill-dose"]'); + await page.fill('#react-dose-weight', '15'); + await page.fill('#react-dose-per-kg', '100'); + await page.fill('#react-dose-max', '500'); + await page.click('[data-testid="calc-dose-calculate"]'); + await expect(page.locator('[data-testid="calc-dose-result"]')).toContainText('500.0 mg'); + await expect(page.locator('[data-testid="calc-dose-result"]')).toContainText('Capped'); + }); + + test('GCS calculator updates score from selected components', async ({ authedPage: _, page }) => { + await openReactCalc(page); + await page.click('[data-testid="calc-pill-gcs"]'); + await expect(page.locator('[data-testid="calc-gcs-result"]')).toContainText('GCS: 15/15'); + await page.selectOption('#react-gcs-motor', '1'); + await expect(page.locator('[data-testid="calc-gcs-result"]')).toContainText('GCS: 10/15'); + }); }); diff --git a/knip.json b/knip.json index 478ff12..462de3f 100644 --- a/knip.json +++ b/knip.json @@ -17,7 +17,20 @@ "client/**", "e2e/**" ], + "exclude": [ + "exports", + "types", + "duplicates" + ], + "ignoreFiles": [ + "src/utils/config.ts" + ], + "ignoreBinaries": [ + "cap" + ], "ignoreDependencies": [ + "@marp-team/marp-cli", + "@tiptap/.*", "@tsconfig/node20", "@types/.*", "ts-node-dev", diff --git a/package-lock.json b/package-lock.json index dc7fe7a..cc7a47d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,9 @@ "dotenv": "^16.4.5", "express": "^4.21.0", "express-rate-limit": "^7.4.0", + "google-auth-library": "^9.15.1", "helmet": "^8.0.0", + "jszip": "^3.10.1", "jsonwebtoken": "^9.0.2", "mammoth": "^1.8.0", "multer": "^1.4.5-lts.1", diff --git a/package.json b/package.json index f7eda2e..7a10e22 100644 --- a/package.json +++ b/package.json @@ -3,18 +3,23 @@ "version": "6.35.0", "description": "AI-powered pediatric clinical documentation platform", "main": "dist/server.js", + "engines": { + "node": ">=20" + }, "scripts": { "start": "node dist/server.js", "prebuild": "rm -rf dist", "build": "tsc", "typecheck": "tsc --noEmit", + "verify": "npm run typecheck && npm test && npm run test:node && npm run lint:refs && npm run lint:dead && npm --prefix client run build", + "verify:full": "npm run verify && npm run e2e", "dev": "ts-node-dev --respawn --transpile-only server.ts", "test": "vitest run", "test:node": "node --test test/", "test:coverage": "vitest run --coverage", "e2e": "./scripts/e2e.sh", "lint:refs": "node scripts/lint-references.js", - "lint:dead": "knip", + "lint:dead": "knip --no-config-hints", "maint:check": "node scripts/maintenance.js check", "maint:reindex": "node scripts/maintenance.js reindex", "migrate": "node-pg-migrate", @@ -41,7 +46,9 @@ "dotenv": "^16.4.5", "express": "^4.21.0", "express-rate-limit": "^7.4.0", + "google-auth-library": "^9.15.1", "helmet": "^8.0.0", + "jszip": "^3.10.1", "jsonwebtoken": "^9.0.2", "mammoth": "^1.8.0", "multer": "^1.4.5-lts.1", diff --git a/public/app/assets/index-0qzAvrmR.css b/public/app/assets/index-0qzAvrmR.css deleted file mode 100644 index 011eb8a..0000000 --- a/public/app/assets/index-0qzAvrmR.css +++ /dev/null @@ -1,2 +0,0 @@ -/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-orange-50:oklch(98% .016 73.684);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-900:oklch(40.8% .123 38.172);--color-orange-950:oklch(26.6% .079 36.259);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-900:oklch(41.4% .112 45.904);--color-amber-950:oklch(27.9% .077 45.635);--color-green-50:oklch(98.2% .018 155.826);--color-green-300:oklch(87.1% .15 154.449);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-950:oklch(26.6% .065 152.934);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-relaxed:1.625;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--radius:.5rem;--color-background:#fff;--color-foreground:#0f172a;--color-muted:#f1f5f9;--color-muted-foreground:#64748b;--color-card:#fff;--color-primary:#0f172a;--color-primary-foreground:#f8fafc;--color-destructive:#ef4343;--color-border:#e2e8f0;--color-input:#e2e8f0}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.fixed{position:fixed}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.top-0{top:calc(var(--spacing) * 0)}.left-0{left:calc(var(--spacing) * 0)}.z-50{z-index:50}.col-span-2{grid-column:span 2/span 2}.col-span-3{grid-column:span 3/span 3}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mr-1{margin-right:calc(var(--spacing) * 1)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.ml-2{margin-left:calc(var(--spacing) * 2)}.block{display:block}.flex{display:flex}.grid{display:grid}.inline{display:inline}.inline-block{display:inline-block}.table{display:table}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.h-10{height:calc(var(--spacing) * 10)}.h-screen{height:100vh}.min-h-\[60px\]{min-height:60px}.min-h-\[100px\]{min-height:100px}.min-h-\[120px\]{min-height:120px}.min-h-\[160px\]{min-height:160px}.min-h-\[200px\]{min-height:200px}.min-h-\[220px\]{min-height:220px}.min-h-screen{min-height:100vh}.w-10{width:calc(var(--spacing) * 10)}.w-20{width:calc(var(--spacing) * 20)}.w-64{width:calc(var(--spacing) * 64)}.w-full{width:100%}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-\[140px\]{max-width:140px}.max-w-\[240px\]{max-width:240px}.max-w-full{max-width:100%}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[150px\]{min-width:150px}.min-w-\[180px\]{min-width:180px}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize-y{resize:vertical}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing) * 1)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:var(--radius)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-amber-300{border-color:var(--color-amber-300)}.border-border{border-color:var(--color-border)}.border-input{border-color:var(--color-input)}.border-primary{border-color:var(--color-primary)}.border-l-orange-500{border-left-color:var(--color-orange-500)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-background{background-color:var(--color-background)}.bg-black\/40{background-color:#0006}@supports (color:color-mix(in lab, red, red)){.bg-black\/40{background-color:color-mix(in oklab, var(--color-black) 40%, transparent)}}.bg-card{background-color:var(--color-card)}.bg-destructive{background-color:var(--color-destructive)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-600{background-color:var(--color-green-600)}.bg-muted{background-color:var(--color-muted)}.bg-muted\/20{background-color:#f1f5f933}@supports (color:color-mix(in lab, red, red)){.bg-muted\/20{background-color:color-mix(in oklab, var(--color-muted) 20%, transparent)}}.bg-muted\/30{background-color:#f1f5f94d}@supports (color:color-mix(in lab, red, red)){.bg-muted\/30{background-color:color-mix(in oklab, var(--color-muted) 30%, transparent)}}.bg-muted\/40{background-color:#f1f5f966}@supports (color:color-mix(in lab, red, red)){.bg-muted\/40{background-color:color-mix(in oklab, var(--color-muted) 40%, transparent)}}.bg-orange-50{background-color:var(--color-orange-50)}.bg-primary{background-color:var(--color-primary)}.bg-primary\/5{background-color:#0f172a0d}@supports (color:color-mix(in lab, red, red)){.bg-primary\/5{background-color:color-mix(in oklab, var(--color-primary) 5%, transparent)}}.bg-primary\/10{background-color:#0f172a1a}@supports (color:color-mix(in lab, red, red)){.bg-primary\/10{background-color:color-mix(in oklab, var(--color-primary) 10%, transparent)}}.bg-white{background-color:var(--color-white)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-8{padding-block:calc(var(--spacing) * 8)}.pt-1{padding-top:calc(var(--spacing) * 1)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pl-5{padding-left:calc(var(--spacing) * 5)}.pl-6{padding-left:calc(var(--spacing) * 6)}.pl-8{padding-left:calc(var(--spacing) * 8)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-600{color:var(--color-amber-600)}.text-amber-900{color:var(--color-amber-900)}.text-destructive{color:var(--color-destructive)}.text-foreground{color:var(--color-foreground)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-muted-foreground{color:var(--color-muted-foreground)}.text-orange-500{color:var(--color-orange-500)}.text-orange-900{color:var(--color-orange-900)}.text-primary{color:var(--color-primary)}.text-primary-foreground{color:var(--color-primary-foreground)}.text-white{color:var(--color-white)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.italic{font-style:italic}.underline{text-decoration-line:underline}.accent-orange-500{accent-color:var(--color-orange-500)}.accent-primary{accent-color:var(--color-primary)}.opacity-60{opacity:.6}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}.even\:bg-muted\/20:nth-child(2n){background-color:#f1f5f933}@supports (color:color-mix(in lab, red, red)){.even\:bg-muted\/20:nth-child(2n){background-color:color-mix(in oklab, var(--color-muted) 20%, transparent)}}@media (hover:hover){.hover\:bg-muted:hover{background-color:var(--color-muted)}.hover\:bg-muted\/40:hover{background-color:#f1f5f966}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/40:hover{background-color:color-mix(in oklab, var(--color-muted) 40%, transparent)}}.hover\:bg-muted\/50:hover{background-color:#f1f5f980}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/50:hover{background-color:color-mix(in oklab, var(--color-muted) 50%, transparent)}}.hover\:bg-muted\/60:hover{background-color:#f1f5f999}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/60:hover{background-color:color-mix(in oklab, var(--color-muted) 60%, transparent)}}.hover\:bg-muted\/80:hover{background-color:#f1f5f9cc}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/80:hover{background-color:color-mix(in oklab, var(--color-muted) 80%, transparent)}}}.disabled\:opacity-50:disabled{opacity:.5}@media (width>=40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (width>=48rem){.md\:col-span-1{grid-column:span 1/span 1}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (prefers-color-scheme:dark){.dark\:bg-amber-950\/30{background-color:#4619014d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-amber-950\/30{background-color:color-mix(in oklab, var(--color-amber-950) 30%, transparent)}}.dark\:bg-green-950\/30{background-color:#032e154d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-green-950\/30{background-color:color-mix(in oklab, var(--color-green-950) 30%, transparent)}}.dark\:bg-orange-950\/30{background-color:#4413064d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-orange-950\/30{background-color:color-mix(in oklab, var(--color-orange-950) 30%, transparent)}}.dark\:text-amber-100{color:var(--color-amber-100)}.dark\:text-green-300{color:var(--color-green-300)}.dark\:text-orange-100{color:var(--color-orange-100)}}}body{background:var(--color-background);color:var(--color-foreground);margin:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false} diff --git a/public/app/assets/index-9V57IPOu.css b/public/app/assets/index-9V57IPOu.css new file mode 100644 index 0000000..5e2af10 --- /dev/null +++ b/public/app/assets/index-9V57IPOu.css @@ -0,0 +1,2 @@ +/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-950:oklch(25.8% .092 26.042);--color-orange-50:oklch(98% .016 73.684);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-900:oklch(40.8% .123 38.172);--color-orange-950:oklch(26.6% .079 36.259);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-900:oklch(41.4% .112 45.904);--color-amber-950:oklch(27.9% .077 45.635);--color-green-50:oklch(98.2% .018 155.826);--color-green-300:oklch(87.1% .15 154.449);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-950:oklch(26.6% .065 152.934);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-relaxed:1.625;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--radius:.5rem;--color-background:#fff;--color-foreground:#0f172a;--color-muted:#f1f5f9;--color-muted-foreground:#64748b;--color-card:#fff;--color-primary:#0f172a;--color-primary-foreground:#f8fafc;--color-destructive:#ef4343;--color-border:#e2e8f0;--color-input:#e2e8f0;--color-ring:#94a3b8}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.fixed{position:fixed}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.top-0{top:calc(var(--spacing) * 0)}.left-0{left:calc(var(--spacing) * 0)}.z-50{z-index:50}.col-span-2{grid-column:span 2/span 2}.col-span-3{grid-column:span 3/span 3}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mr-1{margin-right:calc(var(--spacing) * 1)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-2{margin-left:calc(var(--spacing) * 2)}.block{display:block}.flex{display:flex}.grid{display:grid}.inline{display:inline}.inline-block{display:inline-block}.table{display:table}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.h-10{height:calc(var(--spacing) * 10)}.h-screen{height:100vh}.min-h-\[60px\]{min-height:60px}.min-h-\[100px\]{min-height:100px}.min-h-\[120px\]{min-height:120px}.min-h-\[160px\]{min-height:160px}.min-h-\[200px\]{min-height:200px}.min-h-\[220px\]{min-height:220px}.min-h-screen{min-height:100vh}.w-10{width:calc(var(--spacing) * 10)}.w-20{width:calc(var(--spacing) * 20)}.w-64{width:calc(var(--spacing) * 64)}.w-full{width:100%}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-\[140px\]{max-width:140px}.max-w-\[240px\]{max-width:240px}.max-w-full{max-width:100%}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[150px\]{min-width:150px}.min-w-\[180px\]{min-width:180px}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize-y{resize:vertical}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing) * 1)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:var(--radius)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-amber-300{border-color:var(--color-amber-300)}.border-border{border-color:var(--color-border)}.border-input{border-color:var(--color-input)}.border-primary{border-color:var(--color-primary)}.border-red-200{border-color:var(--color-red-200)}.border-l-orange-500{border-left-color:var(--color-orange-500)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-background{background-color:var(--color-background)}.bg-black\/40{background-color:#0006}@supports (color:color-mix(in lab, red, red)){.bg-black\/40{background-color:color-mix(in oklab, var(--color-black) 40%, transparent)}}.bg-card{background-color:var(--color-card)}.bg-destructive{background-color:var(--color-destructive)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-600{background-color:var(--color-green-600)}.bg-muted{background-color:var(--color-muted)}.bg-muted\/20{background-color:#f1f5f933}@supports (color:color-mix(in lab, red, red)){.bg-muted\/20{background-color:color-mix(in oklab, var(--color-muted) 20%, transparent)}}.bg-muted\/30{background-color:#f1f5f94d}@supports (color:color-mix(in lab, red, red)){.bg-muted\/30{background-color:color-mix(in oklab, var(--color-muted) 30%, transparent)}}.bg-muted\/40{background-color:#f1f5f966}@supports (color:color-mix(in lab, red, red)){.bg-muted\/40{background-color:color-mix(in oklab, var(--color-muted) 40%, transparent)}}.bg-orange-50{background-color:var(--color-orange-50)}.bg-primary{background-color:var(--color-primary)}.bg-primary\/5{background-color:#0f172a0d}@supports (color:color-mix(in lab, red, red)){.bg-primary\/5{background-color:color-mix(in oklab, var(--color-primary) 5%, transparent)}}.bg-primary\/10{background-color:#0f172a1a}@supports (color:color-mix(in lab, red, red)){.bg-primary\/10{background-color:color-mix(in oklab, var(--color-primary) 10%, transparent)}}.bg-red-50{background-color:var(--color-red-50)}.bg-white{background-color:var(--color-white)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-8{padding-block:calc(var(--spacing) * 8)}.pt-1{padding-top:calc(var(--spacing) * 1)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pl-5{padding-left:calc(var(--spacing) * 5)}.pl-6{padding-left:calc(var(--spacing) * 6)}.pl-8{padding-left:calc(var(--spacing) * 8)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-600{color:var(--color-amber-600)}.text-amber-900{color:var(--color-amber-900)}.text-destructive{color:var(--color-destructive)}.text-foreground{color:var(--color-foreground)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-muted-foreground{color:var(--color-muted-foreground)}.text-orange-500{color:var(--color-orange-500)}.text-orange-900{color:var(--color-orange-900)}.text-primary{color:var(--color-primary)}.text-primary-foreground{color:var(--color-primary-foreground)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-white{color:var(--color-white)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.italic{font-style:italic}.underline{text-decoration-line:underline}.accent-orange-500{accent-color:var(--color-orange-500)}.accent-primary{accent-color:var(--color-primary)}.opacity-60{opacity:.6}.opacity-80{opacity:.8}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}.even\:bg-muted\/20:nth-child(2n){background-color:#f1f5f933}@supports (color:color-mix(in lab, red, red)){.even\:bg-muted\/20:nth-child(2n){background-color:color-mix(in oklab, var(--color-muted) 20%, transparent)}}@media (hover:hover){.hover\:bg-muted:hover{background-color:var(--color-muted)}.hover\:bg-muted\/40:hover{background-color:#f1f5f966}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/40:hover{background-color:color-mix(in oklab, var(--color-muted) 40%, transparent)}}.hover\:bg-muted\/50:hover{background-color:#f1f5f980}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/50:hover{background-color:color-mix(in oklab, var(--color-muted) 50%, transparent)}}.hover\:bg-muted\/60:hover{background-color:#f1f5f999}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/60:hover{background-color:color-mix(in oklab, var(--color-muted) 60%, transparent)}}.hover\:bg-muted\/80:hover{background-color:#f1f5f9cc}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/80:hover{background-color:color-mix(in oklab, var(--color-muted) 80%, transparent)}}}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-ring:focus{--tw-ring-color:var(--color-ring)}.disabled\:opacity-50:disabled{opacity:.5}@media (width>=40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (width>=48rem){.md\:col-span-1{grid-column:span 1/span 1}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-\[1\.2fr_1fr_1fr_auto\]{grid-template-columns:1.2fr 1fr 1fr auto}.md\:items-end{align-items:flex-end}}@media (width>=64rem){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (prefers-color-scheme:dark){.dark\:bg-amber-950\/30{background-color:#4619014d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-amber-950\/30{background-color:color-mix(in oklab, var(--color-amber-950) 30%, transparent)}}.dark\:bg-green-950\/30{background-color:#032e154d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-green-950\/30{background-color:color-mix(in oklab, var(--color-green-950) 30%, transparent)}}.dark\:bg-orange-950\/30{background-color:#4413064d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-orange-950\/30{background-color:color-mix(in oklab, var(--color-orange-950) 30%, transparent)}}.dark\:bg-red-950\/30{background-color:#4608094d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-red-950\/30{background-color:color-mix(in oklab, var(--color-red-950) 30%, transparent)}}.dark\:text-amber-100{color:var(--color-amber-100)}.dark\:text-green-300{color:var(--color-green-300)}.dark\:text-orange-100{color:var(--color-orange-100)}.dark\:text-red-200{color:var(--color-red-200)}}}body{background:var(--color-background);color:var(--color-foreground);margin:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false} diff --git a/public/app/assets/index-x592EjRR.js b/public/app/assets/index-DmvH7O2M.js similarity index 94% rename from public/app/assets/index-x592EjRR.js rename to public/app/assets/index-DmvH7O2M.js index a1b9064..0218267 100644 --- a/public/app/assets/index-x592EjRR.js +++ b/public/app/assets/index-DmvH7O2M.js @@ -49,4 +49,4 @@ Please change the parent to 1&&e.reused===`ref`){a(n);continue}}}function bc(e,t){let n=e.seen.get(t);if(!n)throw Error(`Unprocessed schema. This is a bug in Zod.`);let r=t=>{let n=e.seen.get(t);if(n.ref===null)return;let i=n.def??n.schema,a={...i},o=n.ref;if(n.ref=null,o){r(o);let n=e.seen.get(o),s=n.schema;if(s.$ref&&(e.target===`draft-07`||e.target===`draft-04`||e.target===`openapi-3.0`)?(i.allOf=i.allOf??[],i.allOf.push(s)):Object.assign(i,s),Object.assign(i,a),t._zod.parent===o)for(let e in i)e===`$ref`||e===`allOf`||e in a||delete i[e];if(s.$ref&&n.def)for(let e in i)e===`$ref`||e===`allOf`||e in n.def&&JSON.stringify(i[e])===JSON.stringify(n.def[e])&&delete i[e]}let s=t._zod.parent;if(s&&s!==o){r(s);let t=e.seen.get(s);if(t?.schema.$ref&&(i.$ref=t.schema.$ref,t.def))for(let e in i)e===`$ref`||e===`allOf`||e in t.def&&JSON.stringify(i[e])===JSON.stringify(t.def[e])&&delete i[e]}e.override({zodSchema:t,jsonSchema:i,path:n.path??[]})};for(let t of[...e.seen.entries()].reverse())r(t[0]);let i={};if(e.target===`draft-2020-12`?i.$schema=`https://json-schema.org/draft/2020-12/schema`:e.target===`draft-07`?i.$schema=`http://json-schema.org/draft-07/schema#`:e.target===`draft-04`?i.$schema=`http://json-schema.org/draft-04/schema#`:e.target,e.external?.uri){let n=e.external.registry.get(t)?.id;if(!n)throw Error("Schema is missing an `id` property");i.$id=e.external.uri(n)}Object.assign(i,n.def??n.schema);let a=e.external?.defs??{};for(let t of e.seen.entries()){let e=t[1];e.def&&e.defId&&(a[e.defId]=e.def)}e.external||Object.keys(a).length>0&&(e.target===`draft-2020-12`?i.$defs=a:i.definitions=a);try{let n=JSON.parse(JSON.stringify(i));return Object.defineProperty(n,`~standard`,{value:{...t[`~standard`],jsonSchema:{input:Cc(t,`input`,e.processors),output:Cc(t,`output`,e.processors)}},enumerable:!1,writable:!1}),n}catch{throw Error(`Error converting schema to JSON.`)}}function xc(e,t){let n=t??{seen:new Set};if(n.seen.has(e))return!1;n.seen.add(e);let r=e._zod.def;if(r.type===`transform`)return!0;if(r.type===`array`)return xc(r.element,n);if(r.type===`set`)return xc(r.valueType,n);if(r.type===`lazy`)return xc(r.getter(),n);if(r.type===`promise`||r.type===`optional`||r.type===`nonoptional`||r.type===`nullable`||r.type===`readonly`||r.type===`default`||r.type===`prefault`)return xc(r.innerType,n);if(r.type===`intersection`)return xc(r.left,n)||xc(r.right,n);if(r.type===`record`||r.type===`map`)return xc(r.keyType,n)||xc(r.valueType,n);if(r.type===`pipe`)return xc(r.in,n)||xc(r.out,n);if(r.type===`object`){for(let e in r.shape)if(xc(r.shape[e],n))return!0;return!1}if(r.type===`union`){for(let e of r.options)if(xc(e,n))return!0;return!1}if(r.type===`tuple`){for(let e of r.items)if(xc(e,n))return!0;return!!(r.rest&&xc(r.rest,n))}return!1}var Sc=(e,t={})=>n=>{let r=_c({...n,processors:t});return vc(e,r),yc(r,e),bc(r,e)},Cc=(e,t,n={})=>r=>{let{libraryOptions:i,target:a}=r??{},o=_c({...i??{},target:a,io:t,processors:n});return vc(e,o),yc(o,e),bc(o,e)},wc={guid:`uuid`,url:`uri`,datetime:`date-time`,json_string:`json-string`,regex:``},Tc=(e,t,n,r)=>{let i=n;i.type=`string`;let{minimum:a,maximum:o,format:s,patterns:c,contentEncoding:l}=e._zod.bag;if(typeof a==`number`&&(i.minLength=a),typeof o==`number`&&(i.maxLength=o),s&&(i.format=wc[s]??s,i.format===``&&delete i.format,s===`time`&&delete i.format),l&&(i.contentEncoding=l),c&&c.size>0){let e=[...c];e.length===1?i.pattern=e[0].source:e.length>1&&(i.allOf=[...e.map(e=>({...t.target===`draft-07`||t.target===`draft-04`||t.target===`openapi-3.0`?{type:`string`}:{},pattern:e.source}))])}},Ec=(e,t,n,r)=>{n.not={}},Dc=(e,t,n,r)=>{let i=e._zod.def,a=ji(i.entries);a.every(e=>typeof e==`number`)&&(n.type=`number`),a.every(e=>typeof e==`string`)&&(n.type=`string`),n.enum=a},Oc=(e,t,n,r)=>{if(t.unrepresentable===`throw`)throw Error(`Custom types cannot be represented in JSON Schema`)},kc=(e,t,n,r)=>{if(t.unrepresentable===`throw`)throw Error(`Transforms cannot be represented in JSON Schema`)},Ac=(e,t,n,r)=>{let i=n,a=e._zod.def,{minimum:o,maximum:s}=e._zod.bag;typeof o==`number`&&(i.minItems=o),typeof s==`number`&&(i.maxItems=s),i.type=`array`,i.items=vc(a.element,t,{...r,path:[...r.path,`items`]})},jc=(e,t,n,r)=>{let i=n,a=e._zod.def;i.type=`object`,i.properties={};let o=a.shape;for(let e in o)i.properties[e]=vc(o[e],t,{...r,path:[...r.path,`properties`,e]});let s=new Set(Object.keys(o)),c=new Set([...s].filter(e=>{let n=a.shape[e]._zod;return t.io===`input`?n.optin===void 0:n.optout===void 0}));c.size>0&&(i.required=Array.from(c)),a.catchall?._zod.def.type===`never`?i.additionalProperties=!1:a.catchall?a.catchall&&(i.additionalProperties=vc(a.catchall,t,{...r,path:[...r.path,`additionalProperties`]})):t.io===`output`&&(i.additionalProperties=!1)},Mc=(e,t,n,r)=>{let i=e._zod.def,a=i.inclusive===!1,o=i.options.map((e,n)=>vc(e,t,{...r,path:[...r.path,a?`oneOf`:`anyOf`,n]}));a?n.oneOf=o:n.anyOf=o},Nc=(e,t,n,r)=>{let i=e._zod.def,a=vc(i.left,t,{...r,path:[...r.path,`allOf`,0]}),o=vc(i.right,t,{...r,path:[...r.path,`allOf`,1]}),s=e=>`allOf`in e&&Object.keys(e).length===1;n.allOf=[...s(a)?a.allOf:[a],...s(o)?o.allOf:[o]]},Pc=(e,t,n,r)=>{let i=e._zod.def,a=vc(i.innerType,t,r),o=t.seen.get(e);t.target===`openapi-3.0`?(o.ref=i.innerType,n.nullable=!0):n.anyOf=[a,{type:`null`}]},Fc=(e,t,n,r)=>{let i=e._zod.def;vc(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType},Ic=(e,t,n,r)=>{let i=e._zod.def;vc(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType,n.default=JSON.parse(JSON.stringify(i.defaultValue))},Lc=(e,t,n,r)=>{let i=e._zod.def;vc(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType,t.io===`input`&&(n._prefault=JSON.parse(JSON.stringify(i.defaultValue)))},Rc=(e,t,n,r)=>{let i=e._zod.def;vc(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType;let o;try{o=i.catchValue(void 0)}catch{throw Error(`Dynamic catch values are not supported in JSON Schema`)}n.default=o},U=(e,t,n,r)=>{let i=e._zod.def,a=t.io===`input`?i.in._zod.def.type===`transform`?i.out:i.in:i.out;vc(a,t,r);let o=t.seen.get(e);o.ref=a},zc=(e,t,n,r)=>{let i=e._zod.def;vc(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType,n.readOnly=!0},Bc=(e,t,n,r)=>{let i=e._zod.def;vc(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType},Vc=I(`ZodISODateTime`,(e,t)=>{Eo.init(e,t),fl.init(e,t)});function Hc(e){return Ks(Vc,e)}var Uc=I(`ZodISODate`,(e,t)=>{Do.init(e,t),fl.init(e,t)});function Wc(e){return qs(Uc,e)}var Gc=I(`ZodISOTime`,(e,t)=>{Oo.init(e,t),fl.init(e,t)});function Kc(e){return Js(Gc,e)}var qc=I(`ZodISODuration`,(e,t)=>{ko.init(e,t),fl.init(e,t)});function Jc(e){return Ys(qc,e)}var Yc=(e,t)=>{la.init(e,t),e.name=`ZodError`,Object.defineProperties(e,{format:{value:t=>fa(e,t)},flatten:{value:t=>da(e,t)},addIssue:{value:t=>{e.issues.push(t),e.message=JSON.stringify(e.issues,Mi,2)}},addIssues:{value:t=>{e.issues.push(...t),e.message=JSON.stringify(e.issues,Mi,2)}},isEmpty:{get(){return e.issues.length===0}}})};I(`ZodError`,Yc);var Xc=I(`ZodError`,Yc,{Parent:Error}),Zc=pa(Xc),Qc=ma(Xc),$c=ha(Xc),el=_a(Xc),tl=ya(Xc),nl=ba(Xc),rl=xa(Xc),il=Sa(Xc),al=Ca(Xc),ol=wa(Xc),sl=Ta(Xc),cl=Ea(Xc),W=I(`ZodType`,(e,t)=>(V.init(e,t),Object.assign(e[`~standard`],{jsonSchema:{input:Cc(e,`input`),output:Cc(e,`output`)}}),e.toJSONSchema=Sc(e,{}),e.def=t,e.type=t.type,Object.defineProperty(e,`_def`,{value:t}),e.check=(...n)=>e.clone(Li(t,{checks:[...t.checks??[],...n.map(e=>typeof e==`function`?{_zod:{check:e,def:{check:`custom`},onattach:[]}}:e)]}),{parent:!0}),e.with=e.check,e.clone=(t,n)=>qi(e,t,n),e.brand=()=>e,e.register=((t,n)=>(t.add(e,n),e)),e.parse=(t,n)=>Zc(e,t,n,{callee:e.parse}),e.safeParse=(t,n)=>$c(e,t,n),e.parseAsync=async(t,n)=>Qc(e,t,n,{callee:e.parseAsync}),e.safeParseAsync=async(t,n)=>el(e,t,n),e.spa=e.safeParseAsync,e.encode=(t,n)=>tl(e,t,n),e.decode=(t,n)=>nl(e,t,n),e.encodeAsync=async(t,n)=>rl(e,t,n),e.decodeAsync=async(t,n)=>il(e,t,n),e.safeEncode=(t,n)=>al(e,t,n),e.safeDecode=(t,n)=>ol(e,t,n),e.safeEncodeAsync=async(t,n)=>sl(e,t,n),e.safeDecodeAsync=async(t,n)=>cl(e,t,n),e.refine=(t,n)=>e.check(cu(t,n)),e.superRefine=t=>e.check(lu(t)),e.overwrite=t=>e.check(sc(t)),e.optional=()=>Wl(e),e.exactOptional=()=>Kl(e),e.nullable=()=>Jl(e),e.nullish=()=>Wl(Jl(e)),e.nonoptional=t=>eu(e,t),e.array=()=>Ll(e),e.or=t=>K([e,t]),e.and=t=>J(e,t),e.transform=t=>iu(e,Hl(t)),e.default=t=>Xl(e,t),e.prefault=t=>Ql(e,t),e.catch=t=>nu(e,t),e.pipe=t=>iu(e,t),e.readonly=()=>ou(e),e.describe=t=>{let n=e.clone();return Ss.add(n,{description:t}),n},Object.defineProperty(e,`description`,{get(){return Ss.get(e)?.description},configurable:!0}),e.meta=(...t)=>{if(t.length===0)return Ss.get(e);let n=e.clone();return Ss.add(n,t[0]),n},e.isOptional=()=>e.safeParse(void 0).success,e.isNullable=()=>e.safeParse(null).success,e.apply=t=>t(e),e)),ll=I(`_ZodString`,(e,t)=>{mo.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Tc(e,t,n,r);let n=e._zod.bag;e.format=n.format??null,e.minLength=n.minimum??null,e.maxLength=n.maximum??null,e.regex=(...t)=>e.check(tc(...t)),e.includes=(...t)=>e.check(ic(...t)),e.startsWith=(...t)=>e.check(ac(...t)),e.endsWith=(...t)=>e.check(oc(...t)),e.min=(...t)=>e.check($s(...t)),e.max=(...t)=>e.check(Qs(...t)),e.length=(...t)=>e.check(ec(...t)),e.nonempty=(...t)=>e.check($s(1,...t)),e.lowercase=t=>e.check(nc(t)),e.uppercase=t=>e.check(rc(t)),e.trim=()=>e.check(lc()),e.normalize=(...t)=>e.check(cc(...t)),e.toLowerCase=()=>e.check(uc()),e.toUpperCase=()=>e.check(dc()),e.slugify=()=>e.check(fc())}),ul=I(`ZodString`,(e,t)=>{mo.init(e,t),ll.init(e,t),e.email=t=>e.check(ws(pl,t)),e.url=t=>e.check(As(gl,t)),e.jwt=t=>e.check(Gs(jl,t)),e.emoji=t=>e.check(js(_l,t)),e.guid=t=>e.check(Ts(ml,t)),e.uuid=t=>e.check(Es(hl,t)),e.uuidv4=t=>e.check(Ds(hl,t)),e.uuidv6=t=>e.check(Os(hl,t)),e.uuidv7=t=>e.check(ks(hl,t)),e.nanoid=t=>e.check(Ms(vl,t)),e.guid=t=>e.check(Ts(ml,t)),e.cuid=t=>e.check(Ns(yl,t)),e.cuid2=t=>e.check(Ps(bl,t)),e.ulid=t=>e.check(Fs(xl,t)),e.base64=t=>e.check(Hs(Ol,t)),e.base64url=t=>e.check(Us(kl,t)),e.xid=t=>e.check(Is(Sl,t)),e.ksuid=t=>e.check(Ls(Cl,t)),e.ipv4=t=>e.check(Rs(wl,t)),e.ipv6=t=>e.check(zs(Tl,t)),e.cidrv4=t=>e.check(Bs(El,t)),e.cidrv6=t=>e.check(Vs(Dl,t)),e.e164=t=>e.check(Ws(Al,t)),e.datetime=t=>e.check(Hc(t)),e.date=t=>e.check(Wc(t)),e.time=t=>e.check(Kc(t)),e.duration=t=>e.check(Jc(t))});function dl(e){return Cs(ul,e)}var fl=I(`ZodStringFormat`,(e,t)=>{H.init(e,t),ll.init(e,t)}),pl=I(`ZodEmail`,(e,t)=>{_o.init(e,t),fl.init(e,t)}),ml=I(`ZodGUID`,(e,t)=>{ho.init(e,t),fl.init(e,t)}),hl=I(`ZodUUID`,(e,t)=>{go.init(e,t),fl.init(e,t)}),gl=I(`ZodURL`,(e,t)=>{vo.init(e,t),fl.init(e,t)}),_l=I(`ZodEmoji`,(e,t)=>{yo.init(e,t),fl.init(e,t)}),vl=I(`ZodNanoID`,(e,t)=>{bo.init(e,t),fl.init(e,t)}),yl=I(`ZodCUID`,(e,t)=>{xo.init(e,t),fl.init(e,t)}),bl=I(`ZodCUID2`,(e,t)=>{So.init(e,t),fl.init(e,t)}),xl=I(`ZodULID`,(e,t)=>{Co.init(e,t),fl.init(e,t)}),Sl=I(`ZodXID`,(e,t)=>{wo.init(e,t),fl.init(e,t)}),Cl=I(`ZodKSUID`,(e,t)=>{To.init(e,t),fl.init(e,t)}),wl=I(`ZodIPv4`,(e,t)=>{Ao.init(e,t),fl.init(e,t)}),Tl=I(`ZodIPv6`,(e,t)=>{jo.init(e,t),fl.init(e,t)}),El=I(`ZodCIDRv4`,(e,t)=>{Mo.init(e,t),fl.init(e,t)}),Dl=I(`ZodCIDRv6`,(e,t)=>{No.init(e,t),fl.init(e,t)}),Ol=I(`ZodBase64`,(e,t)=>{Fo.init(e,t),fl.init(e,t)}),kl=I(`ZodBase64URL`,(e,t)=>{Lo.init(e,t),fl.init(e,t)}),Al=I(`ZodE164`,(e,t)=>{Ro.init(e,t),fl.init(e,t)}),jl=I(`ZodJWT`,(e,t)=>{Bo.init(e,t),fl.init(e,t)}),Ml=I(`ZodUnknown`,(e,t)=>{Vo.init(e,t),W.init(e,t),e._zod.processJSONSchema=(e,t,n)=>void 0});function Nl(){return Xs(Ml)}var Pl=I(`ZodNever`,(e,t)=>{Ho.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Ec(e,t,n,r)});function Fl(e){return Zs(Pl,e)}var Il=I(`ZodArray`,(e,t)=>{Wo.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Ac(e,t,n,r),e.element=t.element,e.min=(t,n)=>e.check($s(t,n)),e.nonempty=t=>e.check($s(1,t)),e.max=(t,n)=>e.check(Qs(t,n)),e.length=(t,n)=>e.check(ec(t,n)),e.unwrap=()=>e.element});function Ll(e,t){return pc(Il,e,t)}var Rl=I(`ZodObject`,(e,t)=>{Yo.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>jc(e,t,n,r),R(e,`shape`,()=>t.shape),e.keyof=()=>Bl(Object.keys(e._zod.def.shape)),e.catchall=t=>e.clone({...e._zod.def,catchall:t}),e.passthrough=()=>e.clone({...e._zod.def,catchall:Nl()}),e.loose=()=>e.clone({...e._zod.def,catchall:Nl()}),e.strict=()=>e.clone({...e._zod.def,catchall:Fl()}),e.strip=()=>e.clone({...e._zod.def,catchall:void 0}),e.extend=t=>Zi(e,t),e.safeExtend=t=>Qi(e,t),e.merge=t=>$i(e,t),e.pick=t=>Yi(e,t),e.omit=t=>Xi(e,t),e.partial=(...t)=>ea(Ul,e,t[0]),e.required=(...t)=>ta($l,e,t[0])});function zl(e,t){return new Rl({type:`object`,shape:e??{},...z(t)})}var G=I(`ZodUnion`,(e,t)=>{Zo.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Mc(e,t,n,r),e.options=t.options});function K(e,t){return new G({type:`union`,options:e,...z(t)})}var q=I(`ZodIntersection`,(e,t)=>{Qo.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Nc(e,t,n,r)});function J(e,t){return new q({type:`intersection`,left:e,right:t})}var Y=I(`ZodEnum`,(e,t)=>{ts.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Dc(e,t,n,r),e.enum=t.entries,e.options=Object.values(t.entries);let n=new Set(Object.keys(t.entries));e.extract=(e,r)=>{let i={};for(let r of e)if(n.has(r))i[r]=t.entries[r];else throw Error(`Key ${r} not found in enum`);return new Y({...t,checks:[],...z(r),entries:i})},e.exclude=(e,r)=>{let i={...t.entries};for(let t of e)if(n.has(t))delete i[t];else throw Error(`Key ${t} not found in enum`);return new Y({...t,checks:[],...z(r),entries:i})}});function Bl(e,t){return new Y({type:`enum`,entries:Array.isArray(e)?Object.fromEntries(e.map(e=>[e,e])):e,...z(t)})}var Vl=I(`ZodTransform`,(e,t)=>{ns.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>kc(e,t,n,r),e._zod.parse=(n,r)=>{if(r.direction===`backward`)throw new Oi(e.constructor.name);n.addIssue=r=>{if(typeof r==`string`)n.issues.push(sa(r,n.value,t));else{let t=r;t.fatal&&(t.continue=!1),t.code??=`custom`,t.input??=n.value,t.inst??=e,n.issues.push(sa(t))}};let i=t.transform(n.value,n);return i instanceof Promise?i.then(e=>(n.value=e,n)):(n.value=i,n)}});function Hl(e){return new Vl({type:`transform`,transform:e})}var Ul=I(`ZodOptional`,(e,t)=>{is.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Bc(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function Wl(e){return new Ul({type:`optional`,innerType:e})}var Gl=I(`ZodExactOptional`,(e,t)=>{as.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Bc(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function Kl(e){return new Gl({type:`optional`,innerType:e})}var ql=I(`ZodNullable`,(e,t)=>{os.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Pc(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function Jl(e){return new ql({type:`nullable`,innerType:e})}var Yl=I(`ZodDefault`,(e,t)=>{ss.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Ic(e,t,n,r),e.unwrap=()=>e._zod.def.innerType,e.removeDefault=e.unwrap});function Xl(e,t){return new Yl({type:`default`,innerType:e,get defaultValue(){return typeof t==`function`?t():Wi(t)}})}var Zl=I(`ZodPrefault`,(e,t)=>{ls.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Lc(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function Ql(e,t){return new Zl({type:`prefault`,innerType:e,get defaultValue(){return typeof t==`function`?t():Wi(t)}})}var $l=I(`ZodNonOptional`,(e,t)=>{us.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Fc(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function eu(e,t){return new $l({type:`nonoptional`,innerType:e,...z(t)})}var tu=I(`ZodCatch`,(e,t)=>{fs.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Rc(e,t,n,r),e.unwrap=()=>e._zod.def.innerType,e.removeCatch=e.unwrap});function nu(e,t){return new tu({type:`catch`,innerType:e,catchValue:typeof t==`function`?t:()=>t})}var ru=I(`ZodPipe`,(e,t)=>{ps.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>U(e,t,n,r),e.in=t.in,e.out=t.out});function iu(e,t){return new ru({type:`pipe`,in:e,out:t})}var au=I(`ZodReadonly`,(e,t)=>{hs.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>zc(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function ou(e){return new au({type:`readonly`,innerType:e})}var su=I(`ZodCustom`,(e,t)=>{_s.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Oc(e,t,n,r)});function cu(e,t={}){return mc(su,e,t)}function lu(e){return hc(e)}var uu=dl().min(1),X=dl().optional(),du=dl().optional();zl({email:dl().email(),password:uu,turnstileToken:X,totpCode:X}),zl({email:dl().email(),password:dl().min(8,`Password must be at least 8 characters`),name:uu,turnstileToken:X}),zl({email:dl().email(),turnstileToken:X});var fu=zl({transcript:uu,patientAge:X,patientGender:X,model:du,setting:Bl([`outpatient`,`inpatient`]).optional(),physicianMemories:X}),pu=zl({transcript:uu,patientAge:X,patientGender:X,model:du,type:Bl([`full`,`subjective`]).optional(),additionalInstructions:X,physicianMemories:X}),mu=zl({patientAge:X,patientGender:X,chiefComplaint:uu,transcript:X,dictation:X,ros:X,physicalExam:X,diagnoses:X,physicianMemories:X,model:du});zl({currentDocument:uu,instructions:uu,sourceContext:X,model:du}),zl({steps:Ll(zl({component:X,label:uu,method:X,normal:X,status:Bl([`normal`,`abnormal`]).nullable().optional(),note:X})).min(1),ageGroup:X,system:X,patientAge:X,patientGender:X,model:du,format:Bl([`narrative`,`list`]).optional()});var hu=zl({location:dl().min(1).max(120),name:dl().min(1).max(120),number:dl().min(1).max(40),type:Bl([`extension`,`pager`]).optional(),notes:dl().max(500).optional()});function gu({ext:e}){return(0,A.jsxs)(`div`,{className:`flex items-center gap-3 px-4 py-2 border-b border-border`,children:[(0,A.jsxs)(`div`,{className:`flex-1`,children:[(0,A.jsx)(`div`,{className:`font-medium`,children:e.name}),(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground`,children:e.location})]}),(0,A.jsx)(`div`,{className:`font-mono text-sm`,children:e.number}),(0,A.jsx)(`div`,{className:`text-xs uppercase text-muted-foreground w-20 text-right`,children:e.type})]})}function _u({onDone:e}){let t=et(),[n,r]=(0,_.useState)({location:``,name:``,number:``,type:`extension`,notes:``}),[i,a]=(0,_.useState)(null),o=M({mutationFn:e=>F.post(`/api/extensions`,e),onSuccess:()=>{t.invalidateQueries({queryKey:[`extensions`]}),e()},onError:e=>a(e.message)});function s(e){e.preventDefault(),a(null);let t=hu.safeParse(n);if(!t.success){a(t.error.issues.map(e=>e.message).join(`, `));return}o.mutate(t.data)}let c=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`;return(0,A.jsxs)(`form`,{onSubmit:s,className:`space-y-3 p-4 bg-muted/40 rounded-lg border border-border`,children:[(0,A.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,A.jsx)(`input`,{className:c,placeholder:`Location (e.g. Main Hospital)`,value:n.location,onChange:e=>r({...n,location:e.target.value})}),(0,A.jsx)(`input`,{className:c,placeholder:`Name / department`,value:n.name,onChange:e=>r({...n,name:e.target.value})}),(0,A.jsx)(`input`,{className:c,placeholder:`Number`,value:n.number,onChange:e=>r({...n,number:e.target.value})}),(0,A.jsxs)(`select`,{className:c,value:n.type,onChange:e=>r({...n,type:e.target.value}),children:[(0,A.jsx)(`option`,{value:`extension`,children:`Extension`}),(0,A.jsx)(`option`,{value:`pager`,children:`Pager`})]})]}),i&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:i}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsx)(`button`,{type:`submit`,disabled:o.isPending,className:`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium disabled:opacity-50`,children:o.isPending?`Saving…`:`Save`}),(0,A.jsx)(`button`,{type:`button`,onClick:e,className:`rounded-md border border-border px-4 py-2 text-sm`,children:`Cancel`})]})]})}function vu(){let[e,t]=(0,_.useState)(!1),{data:n,isLoading:r,error:i}=j({queryKey:[`extensions`],queryFn:()=>F.get(`/api/extensions`)});return(0,A.jsxs)(`div`,{className:`max-w-3xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{className:`flex items-center justify-between`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Pagers & Extensions`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Per-user directory. This React port is the migration proof-of-life.`})]}),!e&&(0,A.jsx)(`button`,{onClick:()=>t(!0),className:`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium`,children:`+ Add`})]}),e&&(0,A.jsx)(_u,{onDone:()=>t(!1)}),r&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`}),i&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:i.message}),n&&n.items.length===0&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground italic py-8 text-center`,children:`No extensions yet. Click Add to create the first one.`}),n&&n.items.length>0&&(0,A.jsx)(`div`,{className:`rounded-lg border border-border overflow-hidden`,children:n.items.map(e=>(0,A.jsx)(gu,{ext:e},e.id))})]})}var yu=[{section:`Getting Started`,items:[{q:`What is Pediatric AI Scribe?`,a:`Pediatric AI Scribe is an AI-powered clinical documentation tool designed specifically for pediatric medicine. It helps physicians generate structured clinical notes from voice recordings or typed text, saving time on documentation so you can focus on patient care. It supports HPIs, SOAP notes, hospital courses, chart reviews, well visits, sick visits, developmental milestone assessments, and more.`},{q:`How do I create my first note?`,a:`The easiest way to start is with Live Encounter: Go to the Encounter tab Enter the patient's age and gender Click Start Recording and speak naturally during your patient encounter Click Stop when done — the audio is transcribed automatically Click Generate HPI to create a structured note Edit the note as needed, then Copy to paste into your EHR`},{q:`Can I type or paste notes instead of recording?`,a:`Yes. Every transcript box is editable. You can type directly, paste from another source, or combine typed text with a recording. The AI works with whatever text is in the transcript area when you click Generate.`},{q:`What types of notes can I generate?`,a:`HPI — from live encounters or dictation, with OLDCARTS structure SOAP Notes — full SOAP or subjective-only from dictation Hospital Course — from progress notes, in prose, day-by-day, or organ-system format Chart Review — summarize outpatient, subspecialty, or ED visits for precharting Well Visit — complete preventive care notes with SSHADESS, milestones, and vaccines Sick Visit — quick documentation with auto-suggested ROS and PE Milestone Assessment — developmental narrative from selected milestones`}]},{section:`AI & Models`,items:[{q:`What AI model should I use?`,a:`Each tab has a model selector dropdown. All available models have been tested and configured by your administrator for clinical documentation quality. They are routed through HIPAA-compliant providers with signed Business Associate Agreements (BAAs). All models are capable of generating accurate clinical notes. If you are unsure which to pick, start with the default. You can experiment with different models and see which output style you prefer — some may be faster, some more detailed, some more concise. You can choose a different model per tab depending on the task.`},{q:`Does the AI learn from my edits?`,a:`Yes. The app uses a correction tracking system inspired by Dragon Medical's adaptive learning. Here is how it works: When the AI generates a note, the original output is stored in memory You edit the note to match your preferred style — fix phrasing, add details, restructure sections When you click Save, the app detects what you changed and stores the correction On future notes, your past corrections are included as style hints so the AI adapts to your documentation preferences The more you use the app and save your edits, the better the AI gets at matching your style. You can view and manage your stored corrections in Settings > AI Corrections. Note: Corrections are applied as gentle suggestions, not strict rules. The AI prioritizes clinical accuracy over style matching.`},{q:`Can I customize the AI's prompts?`,a:`Administrators can edit all AI prompts from the Admin Panel > Settings > Prompts section. This lets you adjust the instructions the AI follows for each note type without changing any code. Changes take effect immediately.`},{q:`What does the "Refine" button do?`,a:`After generating a note, you can give the AI plain-language instructions to modify it. For example: "Make it shorter" "Add that the patient has a history of asthma" "Summarize the labs" "Change the assessment to include bronchiolitis" The AI references both its current output and your original source material (transcript, pasted notes, labs) when refining, so it can look up details from the original input.`}]},{section:`Voice & Transcription`,items:[{q:`How does voice transcription work?`,a:`When you stop recording, the audio is sent to a speech-to-text service that converts it to text. The app supports multiple transcription providers including Whisper, Deepgram, and Google Gemini. Your administrator configures which provider is used. You will see a blue status bar at the top while transcription is in progress. You can continue working on the page while it processes.`},{q:`What is Browser Whisper?`,a:`Browser Whisper runs the Whisper AI model entirely in your browser using WebAssembly. Your audio never leaves your device, making it the most private transcription option. You can enable it in Settings > Browser Whisper. It works offline and is HIPAA-safe since no data is transmitted. The tradeoff is that it is slower than cloud-based transcription and requires downloading the model (~40–240 MB) on first use.`},{q:`Can I use the app on my phone?`,a:`Yes. The app is a Progressive Web App (PWA) that works in any modern browser. On mobile: Open the app in Chrome or Safari Tap "Add to Home Screen" to install it as a standalone app Recording works in the foreground, but audio stops if you lock the screen or switch apps on iOS and most Android devices — this is a browser limitation, not specific to this app On desktop, recording continues normally when the browser is minimized or in the background.`},{q:`What happens if transcription fails?`,a:`If transcription fails, your audio is automatically backed up to the server for 24 hours. You can retry transcription from Settings > Audio Backups. If browser speech recognition was active during recording, the live transcript is preserved as a fallback.`},{q:`Can the AI read my notes aloud?`,a:`Yes. Click the Read button on any generated note to hear it spoken aloud. This uses text-to-speech (TTS) powered by Google, OpenAI, or ElevenLabs depending on your setup. You can choose your preferred voice in Settings > Voice Preferences.`}]},{section:`Saving & Export`,items:[{q:`Are my encounters saved?`,a:`You can save encounters using the Save button at the top of each tab. Saved encounters include the transcript, generated note, and patient label. You can reload them later using the Load button. Saved encounters are automatically deleted after 7 days (configurable by your administrator). This is intentional — the app is a documentation tool, not a medical record system. Copy your final notes to your EHR for permanent storage.`},{q:`How do I export notes?`,a:`Copy — one-click copy to clipboard, ready to paste into any EHR Nextcloud — export directly to your Nextcloud instance (configure in Settings) Documents — upload files to S3-compatible storage from Settings All generated text is plain text with no markdown formatting, designed to paste cleanly into any EHR system.`}]},{section:`Privacy & Security`,items:[{q:`Is my patient data safe?`,a:`The app is designed with clinical privacy in mind: All connections use HTTPS/TLS encryption Audio and encounter data are temporary — auto-deleted within hours or days No patient data is stored long-term on the server Every action is audit-logged (who accessed what, when) Two-factor authentication (2FA) and session management are available Browser Whisper keeps audio entirely on your device For HIPAA compliance, ensure your administrator has configured a BAA-covered AI provider (such as AWS Bedrock, Google Vertex AI, or Azure OpenAI).`},{q:`What is two-factor authentication (2FA)?`,a:`2FA adds an extra layer of security to your account. After entering your password, you also enter a 6-digit code from an authenticator app (like Google Authenticator or Authy). Enable it in Settings > Two-Factor Authentication.`},{q:`How do I manage my active sessions?`,a:`Go to Settings > Active Sessions to see all devices where you are logged in. You can revoke any session individually or click Revoke All Other Sessions to log out every other device. Your current session is highlighted and cannot be revoked from this screen — use the Logout button instead.`},{q:`What happens when I change my password?`,a:`When you change your password in Settings > Change Password, all other active sessions are automatically logged out for security. Only your current session remains active. The app also checks if your new password has appeared in known data breaches and warns you (but does not prevent you from using it).`}]},{section:`Well Visit & Sick Visit`,items:[{q:`How does the Well Visit tab work?`,a:`The Well Visit tab follows the AAP Bright Futures periodicity schedule. It includes: Visit by Age — recommended screenings, vaccines, and anticipatory guidance for each visit age Milestones — developmental milestone tracker from birth through 11 years across multiple domains SSHADESS — adolescent psychosocial assessment (Strengths, School, Home, Activities, Drugs, Emotions, Sexuality, Safety) for ages 12+ Visit Note — generates a complete well visit note combining ROS, PE, milestones, and SSHADESS data`},{q:`What do the WNL / Abnormal / Not Reviewed buttons do?`,a:`In the ROS and Physical Exam sections, each system has three options: WNL / Normal — within normal limits, no concerns Abnormal — a text box appears so you can describe the finding Not Reviewed / Not Examined — explicitly not assessed Use All WNL to quickly mark everything normal, then click individual systems to change specific ones. Use Clear to reset all selections.`}]},{section:`Learning Hub`,items:[{q:`What is the Learning Hub?`,a:`The Learning Hub is an educational platform integrated into the app. It contains articles, clinical pearls, quizzes, and slide presentations created by moderators and administrators. You can browse by category, search content, and take quizzes to test your knowledge.`},{q:`How do quizzes work?`,a:`Quizzes include multiple-choice, multi-select, and true/false questions. After submitting your answers, you see your score along with explanations for each question. Your past attempts and scores are tracked so you can monitor your progress over time.`},{q:`Can I create Learning Hub content?`,a:`Moderators and administrators can create content using the CMS tab. You can write articles manually, or use AI to generate content from a topic description, uploaded PDFs, or files from Nextcloud. The CMS also supports Marp-based slide presentations with PPTX export.`}]},{section:`Pediatric Calculators`,items:[{q:`What calculators are available?`,a:`The Calculators tab includes clinical tools commonly used in pediatric practice: Blood Pressure Percentile — AAP 2017 guidelines using the Rosner quantile spline regression method. Requires age, sex, height, and BP. Provides exact systolic and diastolic percentiles adjusted for height, with AAP classification (Normal, Elevated, Stage 1, Stage 2). Includes definitions of hypertension and hypotension. BMI Percentile — CDC 2000 growth reference with extended obesity classification (Class 1, 2, 3 using % of 95th percentile). Shows BMI chart with percentile curves. Growth Charts — Visual percentile curves (3rd through 97th) with your patient plotted. Includes Weight-for-Age, Length/Height-for-Age (with mid-parental height), Head Circumference, Weight-for-Length, and Fenton preterm charts. Bilirubin — AAP 2022 phototherapy threshold calculator and Bhutani hour-specific nomogram with risk zone classification. Includes Nelson Table 137.1 risk factors. Vital Signs by Age — Harriet Lane reference table for HR, RR, BP, and weight by age from preterm through 18 years. Includes quick formulas for estimated weight, minimum SBP, ETT size, and maintenance fluids. Body Surface Area — Mosteller formula for BSA calculation. Weight-Based Dosing — Dose per kg with frequency, max dose cap, and volume calculation from concentration.`},{q:`How accurate is the BP calculator?`,a:`The BP calculator uses the same Rosner quantile spline regression method as the Baylor College of Medicine reference calculator. It computes exact percentiles (1st-99th) based on your patient's age, sex, and height using published regression coefficients. This is the same methodology underlying the AAP 2017 normative tables. Results are height-adjusted and clinically accurate.`},{q:`What are the growth chart curves?`,a:`The growth charts display WHO/CDC percentile curves (3rd, 5th, 10th, 25th, 50th, 75th, 90th, 95th, 97th percentiles) with your patient's measurement plotted as a blue dot. The 50th percentile is shown as a bold green line. Shaded bands show the normal range between symmetric percentiles. For Length/Height-for-Age, you can optionally enter both parents' heights to see the mid-parental target height range plotted on the chart.`}]}];function bu({q:e,a:t}){let[n,r]=(0,_.useState)(!1);return(0,A.jsxs)(`div`,{className:`border-b border-border last:border-0`,children:[(0,A.jsxs)(`button`,{onClick:()=>r(!n),className:`w-full text-left py-3 px-4 flex items-center justify-between hover:bg-muted/40 transition-colors`,"aria-expanded":n,children:[(0,A.jsx)(`span`,{className:`font-medium text-sm`,children:e}),(0,A.jsx)(`span`,{className:`text-muted-foreground text-sm`,children:n?`−`:`+`})]}),n&&(0,A.jsx)(`div`,{className:`px-4 pb-4 text-sm text-muted-foreground leading-relaxed whitespace-pre-line`,children:t})]})}function xu(){return(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-6`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Frequently Asked Questions`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Learn how Pediatric AI Scribe works and get the most out of it.`})]}),yu.map(e=>(0,A.jsxs)(`section`,{className:`rounded-lg border border-border overflow-hidden`,children:[(0,A.jsx)(`h2`,{className:`bg-muted/40 px-4 py-2 text-sm font-semibold`,children:e.section}),(0,A.jsx)(`div`,{className:`bg-card`,children:e.items.map(e=>(0,A.jsx)(bu,{q:e.q,a:e.a},e.q))})]},e.section))]})}function Su(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(`outpatient`),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(null),[u,d]=(0,_.useState)(null),f=M({mutationFn:e=>F.post(`/api/generate-hpi-dictation`,e),onSuccess:e=>l(e.hpi),onError:()=>l(null)});function p(t){t.preventDefault(),d(null);let r={transcript:o,patientAge:e,patientGender:n,setting:i},a=fu.safeParse(r);if(!a.success){d(a.error.issues.map(e=>e.message).join(`, `));return}l(null),f.mutate(a.data)}function m(){s(``),l(null),d(null)}let h=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`;return(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Voice Dictation → HPI`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Dictate your narrative → AI restructures into polished HPI. Audio-capture UI is a follow-up; this minimal form supports typed/pasted transcripts.`})]}),(0,A.jsxs)(`form`,{onSubmit:p,className:`space-y-4`,children:[(0,A.jsxs)(`div`,{className:`grid grid-cols-3 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Age`}),(0,A.jsx)(`input`,{className:h,placeholder:`e.g. 8 months`,value:e,onChange:e=>t(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Gender`}),(0,A.jsxs)(`select`,{className:h,value:n,onChange:e=>r(e.target.value),children:[(0,A.jsx)(`option`,{value:``,children:`Select`}),(0,A.jsx)(`option`,{children:`Male`}),(0,A.jsx)(`option`,{children:`Female`})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Setting`}),(0,A.jsxs)(`select`,{className:h,value:i,onChange:e=>a(e.target.value),children:[(0,A.jsx)(`option`,{value:`outpatient`,children:`Outpatient`}),(0,A.jsx)(`option`,{value:`inpatient`,children:`Inpatient / Floors`})]})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Transcript / dictation`}),(0,A.jsx)(`button`,{type:`button`,onClick:m,className:`text-xs text-muted-foreground underline`,children:`Clear`})]}),(0,A.jsx)(`textarea`,{className:h+` min-h-[200px] font-mono text-sm`,placeholder:`Type or paste your dictation here, then click Generate.`,value:o,onChange:e=>s(e.target.value)})]}),u&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:u}),f.error&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:f.error.message}),(0,A.jsx)(`div`,{className:`flex gap-2`,children:(0,A.jsx)(`button`,{type:`submit`,disabled:f.isPending||!o.trim(),className:`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium disabled:opacity-50`,children:f.isPending?`Generating…`:`Generate HPI`})})]}),c&&(0,A.jsxs)(`section`,{className:`rounded-lg border border-border bg-card`,children:[(0,A.jsxs)(`header`,{className:`px-4 py-2 border-b border-border flex items-center justify-between bg-muted/40`,children:[(0,A.jsx)(`h2`,{className:`text-sm font-semibold`,children:`Generated HPI`}),(0,A.jsx)(`button`,{onClick:()=>navigator.clipboard.writeText(c),className:`text-xs text-muted-foreground underline`,children:`Copy`})]}),(0,A.jsx)(`div`,{className:`p-4 whitespace-pre-wrap text-sm`,children:c})]})]})}function Cu(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(`outpatient`),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(null),[u,d]=(0,_.useState)(null),f=M({mutationFn:e=>F.post(`/api/generate-hpi-encounter`,e),onSuccess:e=>l(e.hpi)});function p(t){t.preventDefault(),d(null);let r={transcript:o,patientAge:e,patientGender:n,setting:i},a=fu.safeParse(r);if(!a.success){d(a.error.issues.map(e=>e.message).join(`, `));return}l(null),f.mutate(a.data)}let m=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`;return(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Live Encounter → HPI`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Record or paste an encounter transcript; generate a structured HPI.`})]}),(0,A.jsxs)(`form`,{onSubmit:p,className:`space-y-4`,children:[(0,A.jsxs)(`div`,{className:`grid grid-cols-3 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Age`}),(0,A.jsx)(`input`,{className:m,placeholder:`e.g. 5 years`,value:e,onChange:e=>t(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Gender`}),(0,A.jsxs)(`select`,{className:m,value:n,onChange:e=>r(e.target.value),children:[(0,A.jsx)(`option`,{value:``,children:`Select`}),(0,A.jsx)(`option`,{children:`Male`}),(0,A.jsx)(`option`,{children:`Female`})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Setting`}),(0,A.jsxs)(`select`,{className:m,value:i,onChange:e=>a(e.target.value),children:[(0,A.jsx)(`option`,{value:`outpatient`,children:`Outpatient`}),(0,A.jsx)(`option`,{value:`inpatient`,children:`Inpatient / Floors`})]})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Transcript`}),(0,A.jsx)(`textarea`,{className:m+` min-h-[220px] font-mono text-sm`,placeholder:`Type or paste an encounter transcript, then click Generate.`,value:o,onChange:e=>s(e.target.value)})]}),u&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:u}),f.error&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:f.error.message}),(0,A.jsx)(`button`,{type:`submit`,disabled:f.isPending||!o.trim(),className:`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium disabled:opacity-50`,children:f.isPending?`Generating…`:`Generate HPI`})]}),c&&(0,A.jsxs)(`section`,{className:`rounded-lg border border-border bg-card`,children:[(0,A.jsxs)(`header`,{className:`px-4 py-2 border-b border-border flex items-center justify-between bg-muted/40`,children:[(0,A.jsx)(`h2`,{className:`text-sm font-semibold`,children:`Generated HPI`}),(0,A.jsx)(`button`,{onClick:()=>navigator.clipboard.writeText(c),className:`text-xs text-muted-foreground underline`,children:`Copy`})]}),(0,A.jsx)(`div`,{className:`p-4 whitespace-pre-wrap text-sm`,children:c})]})]})}function wu(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(`full`),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(``),[u,d]=(0,_.useState)(null),[f,p]=(0,_.useState)(null),m=M({mutationFn:e=>F.post(`/api/generate-soap`,e),onSuccess:e=>d(e.soap)});function h(t){t.preventDefault(),p(null);let r={transcript:o,patientAge:e,patientGender:n,type:i,additionalInstructions:c},a=pu.safeParse(r);if(!a.success){p(a.error.issues.map(e=>e.message).join(`, `));return}d(null),m.mutate(a.data)}let g=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`;return(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`SOAP Note`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Encounter transcript → full SOAP or subjective-only narrative.`})]}),(0,A.jsxs)(`form`,{onSubmit:h,className:`space-y-4`,children:[(0,A.jsxs)(`div`,{className:`grid grid-cols-3 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Age`}),(0,A.jsx)(`input`,{className:g,placeholder:`e.g. 3 years`,value:e,onChange:e=>t(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Gender`}),(0,A.jsxs)(`select`,{className:g,value:n,onChange:e=>r(e.target.value),children:[(0,A.jsx)(`option`,{value:``,children:`Select`}),(0,A.jsx)(`option`,{children:`Male`}),(0,A.jsx)(`option`,{children:`Female`})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Output type`}),(0,A.jsxs)(`select`,{className:g,value:i,onChange:e=>a(e.target.value),children:[(0,A.jsx)(`option`,{value:`full`,children:`Full SOAP`}),(0,A.jsx)(`option`,{value:`subjective`,children:`Subjective only`})]})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Transcript`}),(0,A.jsx)(`textarea`,{className:g+` min-h-[200px] font-mono text-sm`,placeholder:`Type or paste the encounter transcript.`,value:o,onChange:e=>s(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsxs)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:[`Additional instructions `,(0,A.jsx)(`span`,{className:`text-muted-foreground normal-case font-normal`,children:`(optional)`})]}),(0,A.jsx)(`textarea`,{className:g+` min-h-[60px] text-sm`,placeholder:`e.g., 'Include return precautions', 'Add differential for otitis media'`,value:c,onChange:e=>l(e.target.value)})]}),f&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:f}),m.error&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:m.error.message}),(0,A.jsx)(`button`,{type:`submit`,disabled:m.isPending||!o.trim(),className:`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium disabled:opacity-50`,children:m.isPending?`Generating…`:`Generate SOAP`})]}),u&&(0,A.jsxs)(`section`,{className:`rounded-lg border border-border bg-card`,children:[(0,A.jsxs)(`header`,{className:`px-4 py-2 border-b border-border flex items-center justify-between bg-muted/40`,children:[(0,A.jsx)(`h2`,{className:`text-sm font-semibold`,children:`Generated SOAP`}),(0,A.jsx)(`button`,{onClick:()=>navigator.clipboard.writeText(u),className:`text-xs text-muted-foreground underline`,children:`Copy`})]}),(0,A.jsx)(`div`,{className:`p-4 whitespace-pre-wrap text-sm`,children:u})]})]})}function Tu(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(``),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(null),[u,d]=(0,_.useState)(null),f=M({mutationFn:e=>F.post(`/api/sick-visit/note`,e),onSuccess:e=>l(e.note)});function p(t){t.preventDefault(),d(null);let r={patientAge:e,patientGender:n,chiefComplaint:i,transcript:o},a=mu.safeParse(r);if(!a.success){d(a.error.issues.map(e=>e.message).join(`, `));return}l(null),f.mutate(a.data)}let m=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`;return(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Sick Visit`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Chief complaint + transcript → structured sick-visit note.`})]}),(0,A.jsxs)(`form`,{onSubmit:p,className:`space-y-4`,children:[(0,A.jsxs)(`div`,{className:`grid grid-cols-3 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Age`}),(0,A.jsx)(`input`,{className:m,placeholder:`e.g. 4 years`,value:e,onChange:e=>t(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Gender`}),(0,A.jsxs)(`select`,{className:m,value:n,onChange:e=>r(e.target.value),children:[(0,A.jsx)(`option`,{value:``,children:`Select`}),(0,A.jsx)(`option`,{children:`Male`}),(0,A.jsx)(`option`,{children:`Female`})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1 col-span-3 md:col-span-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Chief complaint`}),(0,A.jsx)(`input`,{className:m,placeholder:`e.g. Fever x 2 days`,value:i,onChange:e=>a(e.target.value)})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Transcript / dictation`}),(0,A.jsx)(`textarea`,{className:m+` min-h-[200px] font-mono text-sm`,placeholder:`Encounter narrative — transcribed or dictated.`,value:o,onChange:e=>s(e.target.value)})]}),u&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:u}),f.error&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:f.error.message}),(0,A.jsx)(`button`,{type:`submit`,disabled:f.isPending||!i.trim(),className:`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium disabled:opacity-50`,children:f.isPending?`Generating…`:`Generate Note`})]}),c&&(0,A.jsxs)(`section`,{className:`rounded-lg border border-border bg-card`,children:[(0,A.jsxs)(`header`,{className:`px-4 py-2 border-b border-border flex items-center justify-between bg-muted/40`,children:[(0,A.jsx)(`h2`,{className:`text-sm font-semibold`,children:`Sick Visit Note`}),(0,A.jsx)(`button`,{onClick:()=>navigator.clipboard.writeText(c),className:`text-xs text-muted-foreground underline`,children:`Copy`})]}),(0,A.jsx)(`div`,{className:`p-4 whitespace-pre-wrap text-sm`,children:c})]})]})}function Eu(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(``),[o,s]=(0,_.useState)(`floor`),[c,l]=(0,_.useState)(``),[u,d]=(0,_.useState)(`auto`),[f,p]=(0,_.useState)(``),[m,h]=(0,_.useState)(``),[g,v]=(0,_.useState)(``),[y,b]=(0,_.useState)(null),x=M({mutationFn:e=>F.post(`/api/generate-hospital-course`,e),onSuccess:e=>b({hospitalCourse:e.hospitalCourse,format:e.format||`auto`})});function S(t){t.preventDefault(),b(null);let r=m.split(/\n\s*\n/).map(e=>e.trim()).filter(Boolean).map((e,t)=>({date:`Day ${t+1}`,type:`Progress Note`,content:e}));x.mutate({notes:r,hAndP:f?{date:`Admission`,content:f}:void 0,patientAge:e,patientGender:n,pmh:i,setting:o,los:c?parseInt(c):void 0,formatPreference:u,additionalInstructions:g||void 0})}let C=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`;return(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Hospital Course`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Progress notes + H&P → hospital course summary (prose, day-by-day, or organ-system format).`})]}),(0,A.jsxs)(`form`,{onSubmit:S,className:`space-y-4`,children:[(0,A.jsxs)(`div`,{className:`grid grid-cols-3 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Age`}),(0,A.jsx)(`input`,{className:C,value:e,onChange:e=>t(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Gender`}),(0,A.jsxs)(`select`,{className:C,value:n,onChange:e=>r(e.target.value),children:[(0,A.jsx)(`option`,{value:``,children:`Select`}),(0,A.jsx)(`option`,{children:`Male`}),(0,A.jsx)(`option`,{children:`Female`})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Setting`}),(0,A.jsxs)(`select`,{className:C,value:o,onChange:e=>s(e.target.value),children:[(0,A.jsx)(`option`,{value:`floor`,children:`Floor`}),(0,A.jsx)(`option`,{value:`picu`,children:`PICU`}),(0,A.jsx)(`option`,{value:`nicu`,children:`NICU`}),(0,A.jsx)(`option`,{value:`psych`,children:`Psych`})]})]})]}),(0,A.jsxs)(`div`,{className:`grid grid-cols-3 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1 col-span-2`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`PMH`}),(0,A.jsx)(`input`,{className:C,placeholder:`e.g. Asthma, hypothyroidism`,value:i,onChange:e=>a(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`LOS (days)`}),(0,A.jsx)(`input`,{className:C,type:`number`,value:c,onChange:e=>l(e.target.value)})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Format`}),(0,A.jsxs)(`select`,{className:C,value:u,onChange:e=>d(e.target.value),children:[(0,A.jsx)(`option`,{value:`auto`,children:`Auto (infer from setting + LOS)`}),(0,A.jsx)(`option`,{value:`prose`,children:`Prose summary`}),(0,A.jsx)(`option`,{value:`dayByDay`,children:`Day-by-day`}),(0,A.jsx)(`option`,{value:`organSystem`,children:`Organ-system (ICU)`})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`H&P`}),(0,A.jsx)(`textarea`,{className:C+` min-h-[120px] font-mono text-sm`,value:f,onChange:e=>p(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsxs)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:[`Progress notes `,(0,A.jsx)(`span`,{className:`normal-case font-normal text-muted-foreground`,children:`(separate each note with a blank line)`})]}),(0,A.jsx)(`textarea`,{className:C+` min-h-[200px] font-mono text-sm`,value:m,onChange:e=>h(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Additional instructions`}),(0,A.jsx)(`textarea`,{className:C+` min-h-[60px] text-sm`,value:g,onChange:e=>v(e.target.value)})]}),x.error&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:x.error.message}),(0,A.jsx)(`button`,{type:`submit`,disabled:x.isPending||!m.trim(),className:`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium disabled:opacity-50`,children:x.isPending?`Generating…`:`Generate Hospital Course`})]}),y&&(0,A.jsxs)(`section`,{className:`rounded-lg border border-border bg-card`,children:[(0,A.jsxs)(`header`,{className:`px-4 py-2 border-b border-border flex items-center justify-between bg-muted/40`,children:[(0,A.jsxs)(`h2`,{className:`text-sm font-semibold`,children:[`Hospital Course `,(0,A.jsxs)(`span`,{className:`text-xs font-normal text-muted-foreground`,children:[`(`,y.format,`)`]})]}),(0,A.jsx)(`button`,{onClick:()=>navigator.clipboard.writeText(y.hospitalCourse),className:`text-xs text-muted-foreground underline`,children:`Copy`})]}),(0,A.jsx)(`div`,{className:`p-4 whitespace-pre-wrap text-sm`,children:y.hospitalCourse})]})]})}function Du(){return{date:``,content:``,labs:``}}function Ou(){let[e,t]=(0,_.useState)(`outpatient`),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(``),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)([Du()]),[u,d]=(0,_.useState)(``),[f,p]=(0,_.useState)(null),m=M({mutationFn:e=>F.post(`/api/generate-chart-review`,e),onSuccess:e=>p(e.review)});function h(e,t){l(n=>n.map((n,r)=>r===e?{...n,...t}:n))}function g(t){t.preventDefault(),p(null);let r=c.filter(e=>e.content.trim());m.mutate({type:e,patientAge:n,patientGender:i,pmh:o,visits:e===`outpatient`?r:void 0,subspecialty:e===`subspecialty`?r:void 0,edVisits:e===`ed`?r:void 0,additionalInstructions:u||void 0})}let v=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`;return(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Chart Review`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Past visits → summary for pre-charting.`})]}),(0,A.jsxs)(`form`,{onSubmit:g,className:`space-y-4`,children:[(0,A.jsxs)(`div`,{className:`grid grid-cols-4 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Review type`}),(0,A.jsxs)(`select`,{className:v,value:e,onChange:e=>t(e.target.value),children:[(0,A.jsx)(`option`,{value:`outpatient`,children:`Outpatient`}),(0,A.jsx)(`option`,{value:`subspecialty`,children:`Subspecialty`}),(0,A.jsx)(`option`,{value:`ed`,children:`ED`})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Age`}),(0,A.jsx)(`input`,{className:v,value:n,onChange:e=>r(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Gender`}),(0,A.jsxs)(`select`,{className:v,value:i,onChange:e=>a(e.target.value),children:[(0,A.jsx)(`option`,{value:``,children:`Select`}),(0,A.jsx)(`option`,{children:`Male`}),(0,A.jsx)(`option`,{children:`Female`})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`PMH`}),(0,A.jsx)(`input`,{className:v,value:o,onChange:e=>s(e.target.value)})]})]}),(0,A.jsxs)(`div`,{className:`space-y-3`,children:[(0,A.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,A.jsx)(`span`,{className:`text-sm font-semibold`,children:`Visits`}),(0,A.jsx)(`button`,{type:`button`,onClick:()=>l(e=>[...e,Du()]),className:`text-xs rounded-md border border-border px-2 py-1`,children:`+ Add visit`})]}),c.map((e,t)=>(0,A.jsxs)(`div`,{className:`rounded-lg border border-border p-3 space-y-2 bg-card`,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,A.jsx)(`input`,{type:`date`,className:v+` max-w-xs`,value:e.date,onChange:e=>h(t,{date:e.target.value})}),c.length>1&&(0,A.jsx)(`button`,{type:`button`,onClick:()=>l(e=>e.filter((e,n)=>n!==t)),className:`text-xs text-destructive`,children:`Remove`})]}),(0,A.jsx)(`textarea`,{className:v+` min-h-[100px] font-mono text-sm`,placeholder:`Visit note content — paste here.`,value:e.content,onChange:e=>h(t,{content:e.target.value})}),(0,A.jsx)(`textarea`,{className:v+` min-h-[60px] font-mono text-xs`,placeholder:`Labs from this visit (optional)`,value:e.labs,onChange:e=>h(t,{labs:e.target.value})})]},t))]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Additional instructions`}),(0,A.jsx)(`textarea`,{className:v+` min-h-[60px] text-sm`,placeholder:`e.g. 'Focus on thyroid management', 'Highlight medication changes'`,value:u,onChange:e=>d(e.target.value)})]}),m.error&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:m.error.message}),(0,A.jsx)(`button`,{type:`submit`,disabled:m.isPending||!c.some(e=>e.content.trim()),className:`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium disabled:opacity-50`,children:m.isPending?`Generating…`:`Generate Chart Review`})]}),f&&(0,A.jsxs)(`section`,{className:`rounded-lg border border-border bg-card`,children:[(0,A.jsxs)(`header`,{className:`px-4 py-2 border-b border-border flex items-center justify-between bg-muted/40`,children:[(0,A.jsx)(`h2`,{className:`text-sm font-semibold`,children:`Chart Review`}),(0,A.jsx)(`button`,{onClick:()=>navigator.clipboard.writeText(f),className:`text-xs text-muted-foreground underline`,children:`Copy`})]}),(0,A.jsx)(`div`,{className:`p-4 whitespace-pre-wrap text-sm`,children:f})]})]})}function ku(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(``),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(``),[u,d]=(0,_.useState)(``),[f,p]=(0,_.useState)(``),[m,h]=(0,_.useState)(``),[g,v]=(0,_.useState)(``),[y,b]=(0,_.useState)(`full`),[x,S]=(0,_.useState)(null),C=M({mutationFn:e=>F.post(`/api/well-visit/note`,e),onSuccess:e=>S(e.note)});function w(t){t.preventDefault(),S(null),C.mutate({patientAge:e,patientGender:n,visitAge:i,vitals:o,measurements:c,parentConcerns:u,transcript:f,screenings:m,vaccines:g,noteStyle:y})}let T=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`;return(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Well Visit`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Preventive-care note generation. Milestones and SSHADESS sub-tabs land in a follow-up; this first port covers the Visit Note pane.`})]}),(0,A.jsxs)(`form`,{onSubmit:w,className:`space-y-4`,children:[(0,A.jsxs)(`div`,{className:`grid grid-cols-3 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Age`}),(0,A.jsx)(`input`,{className:T,value:e,onChange:e=>t(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Gender`}),(0,A.jsxs)(`select`,{className:T,value:n,onChange:e=>r(e.target.value),children:[(0,A.jsx)(`option`,{value:``,children:`Select`}),(0,A.jsx)(`option`,{children:`Male`}),(0,A.jsx)(`option`,{children:`Female`})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Visit age`}),(0,A.jsx)(`input`,{className:T,placeholder:`e.g. 6 months`,value:i,onChange:e=>a(e.target.value)})]})]}),(0,A.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Vital signs`}),(0,A.jsx)(`textarea`,{className:T+` min-h-[60px] font-mono text-xs`,value:o,onChange:e=>s(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Measurements / growth`}),(0,A.jsx)(`textarea`,{className:T+` min-h-[60px] font-mono text-xs`,value:c,onChange:e=>l(e.target.value)})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Parent / patient concerns`}),(0,A.jsx)(`textarea`,{className:T+` min-h-[60px] text-sm`,value:u,onChange:e=>d(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Transcript / dictation`}),(0,A.jsx)(`textarea`,{className:T+` min-h-[160px] font-mono text-sm`,value:f,onChange:e=>p(e.target.value)})]}),(0,A.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Screenings completed`}),(0,A.jsx)(`textarea`,{className:T+` min-h-[60px] text-xs`,value:m,onChange:e=>h(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Immunizations today`}),(0,A.jsx)(`textarea`,{className:T+` min-h-[60px] text-xs`,value:g,onChange:e=>v(e.target.value)})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Note style`}),(0,A.jsxs)(`select`,{className:T,value:y,onChange:e=>b(e.target.value),children:[(0,A.jsx)(`option`,{value:`full`,children:`Full encounter note`}),(0,A.jsx)(`option`,{value:`short`,children:`Brief SOAP`})]})]}),C.error&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:C.error.message}),(0,A.jsx)(`button`,{type:`submit`,disabled:C.isPending||!e.trim()&&!i.trim(),className:`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium disabled:opacity-50`,children:C.isPending?`Generating…`:`Generate Well Visit Note`})]}),x&&(0,A.jsxs)(`section`,{className:`rounded-lg border border-border bg-card`,children:[(0,A.jsxs)(`header`,{className:`px-4 py-2 border-b border-border flex items-center justify-between bg-muted/40`,children:[(0,A.jsx)(`h2`,{className:`text-sm font-semibold`,children:`Well Visit Note`}),(0,A.jsx)(`button`,{onClick:()=>navigator.clipboard.writeText(x),className:`text-xs text-muted-foreground underline`,children:`Copy`})]}),(0,A.jsx)(`div`,{className:`p-4 whitespace-pre-wrap text-sm`,children:x})]})]})}function Au(){let{data:e,isLoading:t,error:n}=j({queryKey:[`schedule-data`],queryFn:()=>F.get(`/api/schedule-data`)});if(t)return(0,A.jsx)(`div`,{className:`p-6 text-sm text-muted-foreground`,children:`Loading schedule…`});if(n)return(0,A.jsx)(`div`,{className:`p-6 text-sm text-destructive`,children:n.message});if(!e)return null;let r=e.visitAges.filter(t=>e.periodicity[t.id]?.vaccines?.length),i=[],a=new Set;return r.forEach(t=>{e.periodicity[t.id].vaccines.forEach(e=>{a.has(e.vaccine)||(a.add(e.vaccine),i.push(e.vaccine))})}),(0,A.jsxs)(`div`,{className:`max-w-full mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Vaccine Schedule`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`AAP/ACIP 2025 complete immunization schedule (0–18 years).`})]}),(0,A.jsx)(`div`,{className:`rounded-lg border border-border overflow-auto bg-card`,children:(0,A.jsxs)(`table`,{className:`text-xs`,children:[(0,A.jsx)(`thead`,{className:`sticky top-0 bg-muted`,children:(0,A.jsxs)(`tr`,{children:[(0,A.jsx)(`th`,{className:`text-left font-semibold px-3 py-2 border-b border-border min-w-[180px] sticky left-0 bg-muted`,children:`Vaccine`}),r.map(e=>(0,A.jsx)(`th`,{className:`px-2 py-2 border-b border-border text-center whitespace-nowrap`,children:e.label},e.id))]})}),(0,A.jsx)(`tbody`,{children:i.map(t=>(0,A.jsxs)(`tr`,{className:`even:bg-muted/20`,children:[(0,A.jsx)(`td`,{className:`px-3 py-2 border-b border-border font-medium sticky left-0 bg-card`,children:e.vaccineFullNames[t]||t}),r.map(n=>{let r=(e.periodicity[n.id].vaccines||[]).find(e=>e.vaccine===t);if(!r)return(0,A.jsx)(`td`,{className:`border-b border-border`},n.id);let i=typeof r.dose==`number`?`#`+r.dose:r.dose||`•`;return(0,A.jsx)(`td`,{className:`border-b border-border text-center bg-primary/10 font-mono text-[11px]`,title:r.notes||`${t} dose ${r.dose}`,children:i},n.id)})]},t))})]})}),(0,A.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Hover any filled cell for notes. Sources: AAP/Bright Futures (Feb 2025), CDC Child & Adolescent Immunization Schedule (2025).`})]})}function ju(){let{data:e,isLoading:t,error:n}=j({queryKey:[`schedule-data`],queryFn:()=>F.get(`/api/schedule-data`)});return t?(0,A.jsx)(`div`,{className:`p-6 text-sm text-muted-foreground`,children:`Loading…`}):n?(0,A.jsx)(`div`,{className:`p-6 text-sm text-destructive`,children:n.message}):e?(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Catch-Up Schedule`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`CDC 2025 catch-up immunization schedule — minimum ages and intervals per vaccine.`})]}),Object.entries(e.catchUpSchedule).map(([t,n])=>{let r=e.vaccineFullNames[t]||t,i=n.catchUpNotes?Array.isArray(n.catchUpNotes)?n.catchUpNotes:[n.catchUpNotes]:[];return(0,A.jsxs)(`section`,{className:`rounded-lg border border-border bg-card overflow-hidden`,children:[(0,A.jsxs)(`header`,{className:`px-4 py-2 border-b border-border bg-muted/40 flex items-center justify-between`,children:[(0,A.jsx)(`h2`,{className:`text-sm font-semibold`,children:r}),n.minimumAgeForDose1&&(0,A.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[`Min age dose 1: `,(0,A.jsx)(`strong`,{children:n.minimumAgeForDose1})]})]}),n.series&&n.series.length>0&&(0,A.jsxs)(`table`,{className:`w-full text-xs`,children:[(0,A.jsx)(`thead`,{className:`bg-muted/20`,children:(0,A.jsxs)(`tr`,{children:[(0,A.jsx)(`th`,{className:`text-left px-3 py-2`,children:`Dose`}),(0,A.jsx)(`th`,{className:`text-left px-3 py-2`,children:`Min age`}),(0,A.jsx)(`th`,{className:`text-left px-3 py-2`,children:`Min interval from prev`}),(0,A.jsx)(`th`,{className:`text-left px-3 py-2`,children:`Notes`})]})}),(0,A.jsx)(`tbody`,{children:n.series.map(e=>(0,A.jsxs)(`tr`,{className:`border-t border-border`,children:[(0,A.jsxs)(`td`,{className:`px-3 py-2 font-semibold`,children:[`Dose `,e.dose]}),(0,A.jsx)(`td`,{className:`px-3 py-2`,children:e.minimumAge||`—`}),(0,A.jsx)(`td`,{className:`px-3 py-2`,children:e.minimumIntervalToPrev||`—`}),(0,A.jsx)(`td`,{className:`px-3 py-2 text-muted-foreground`,children:e.notes||``})]},String(e.dose)))})]}),i.length>0&&(0,A.jsx)(`ul`,{className:`list-disc pl-8 py-2 text-xs text-muted-foreground space-y-1`,children:i.map((e,t)=>(0,A.jsx)(`li`,{children:e},t))})]},t)})]}):null}function Mu({open:e,title:t,body:n,confirmText:r=`Confirm`,cancelText:i=`Cancel`,danger:a=!1,requirePassword:o=!1,passwordPlaceholder:s=`Password`,onConfirm:c,onCancel:l,busy:u=!1}){let[d,f]=(0,_.useState)(``);if((0,_.useEffect)(()=>{e||f(``)},[e]),(0,_.useEffect)(()=>{function t(t){e&&t.key===`Escape`&&l()}return window.addEventListener(`keydown`,t),()=>window.removeEventListener(`keydown`,t)},[e,l]),!e)return null;let p=u||o&&!d;return(0,A.jsx)(`div`,{role:`dialog`,"aria-modal":`true`,"aria-labelledby":`confirm-modal-title`,className:`fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4`,onClick:l,children:(0,A.jsxs)(`div`,{className:`w-full max-w-sm rounded-lg border border-border bg-background p-5 shadow-lg space-y-3`,onClick:e=>e.stopPropagation(),children:[(0,A.jsx)(`h3`,{id:`confirm-modal-title`,className:`text-base font-semibold`,children:t}),n&&(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:n}),o&&(0,A.jsx)(`input`,{type:`password`,autoFocus:!0,value:d,onChange:e=>f(e.target.value),placeholder:s,className:`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`,onKeyDown:e=>{e.key===`Enter`&&!p&&c(d)}}),(0,A.jsxs)(`div`,{className:`flex justify-end gap-2 pt-1`,children:[(0,A.jsx)(`button`,{type:`button`,onClick:l,className:`rounded-md border border-border px-3 py-2 text-sm`,"data-testid":`confirm-modal-cancel`,children:i}),(0,A.jsx)(`button`,{type:`button`,disabled:p,onClick:()=>c(o?d:void 0),className:`rounded-md px-3 py-2 text-sm font-medium text-white disabled:opacity-50 `+(a?`bg-destructive`:`bg-primary`),"data-testid":`confirm-modal-ok`,children:u?`Working…`:r})]})]})})}var Nu=`rounded-lg border border-border bg-card p-5 space-y-3`,Pu=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`,Fu=`rounded-md bg-primary text-primary-foreground px-3 py-2 text-sm font-medium disabled:opacity-50`,Iu=`rounded-md border border-border px-3 py-2 text-sm disabled:opacity-50`,Lu=`rounded-md bg-destructive text-white px-3 py-2 text-sm font-medium disabled:opacity-50`;function Ru({msg:e}){return e?(0,A.jsx)(`div`,{className:`text-sm `+(e.kind===`ok`?`text-green-600`:e.kind===`err`?`text-destructive`:`text-muted-foreground`),children:e.text}):null}function zu(e){let t=Math.floor((Date.now()-new Date(e).getTime())/1e3);return t<60?`just now`:t<3600?Math.floor(t/60)+`m ago`:t<86400?Math.floor(t/3600)+`h ago`:Math.floor(t/86400)+`d ago`}function Bu(){let e=et(),[t,n]=(0,_.useState)(``),[r,i]=(0,_.useState)(``),[a,o]=(0,_.useState)(``),[s,c]=(0,_.useState)(null),l=M({mutationFn:e=>F.post(`/api/auth/change-password`,e),onSuccess:t=>{c({text:t.message||`Password changed`,kind:`ok`}),n(``),i(``),o(``),e.invalidateQueries({queryKey:[`sessions`]}),t.passwordWarning&&setTimeout(()=>c({text:t.passwordWarning,kind:`info`}),2e3)},onError:e=>c({text:e.message,kind:`err`})});function u(e){if(e.preventDefault(),c(null),!t||!r)return c({text:`Fill in all fields`,kind:`err`});if(r.length<8)return c({text:`New password must be 8+ characters`,kind:`err`});if(r!==a)return c({text:`Passwords do not match`,kind:`err`});l.mutate({currentPassword:t,newPassword:r})}return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`change-password-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Change Password`}),(0,A.jsxs)(`form`,{onSubmit:u,className:`space-y-2 max-w-sm`,children:[(0,A.jsx)(`input`,{type:`password`,className:Pu,placeholder:`Current password`,value:t,onChange:e=>n(e.target.value),"data-testid":`pw-current`}),(0,A.jsx)(`input`,{type:`password`,className:Pu,placeholder:`New password (8+ characters)`,minLength:8,value:r,onChange:e=>i(e.target.value),"data-testid":`pw-new`}),(0,A.jsx)(`input`,{type:`password`,className:Pu,placeholder:`Confirm new password`,minLength:8,value:a,onChange:e=>o(e.target.value),"data-testid":`pw-confirm`}),(0,A.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,A.jsx)(`button`,{type:`submit`,disabled:l.isPending,className:Fu,"data-testid":`btn-change-password`,children:l.isPending?`Changing…`:`Change Password`}),(0,A.jsx)(Ru,{msg:s})]})]})]})}function Vu({codes:e,onClose:t}){return(0,A.jsx)(`div`,{role:`dialog`,"aria-modal":`true`,className:`fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4`,children:(0,A.jsxs)(`div`,{className:`w-full max-w-md rounded-lg border border-border bg-background p-5 shadow-lg space-y-3`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Save your backup codes`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Each code can be used once if you lose access to your authenticator. They will not be shown again.`}),(0,A.jsx)(`pre`,{className:`bg-muted p-3 rounded text-sm font-mono whitespace-pre-wrap break-all`,children:e.join(` `)}),(0,A.jsxs)(`div`,{className:`flex justify-end gap-2`,children:[(0,A.jsx)(`button`,{type:`button`,onClick:()=>navigator.clipboard?.writeText(e.join(` `)),className:Iu,children:`Copy`}),(0,A.jsx)(`button`,{type:`button`,onClick:t,className:Fu,children:`I've saved them`})]})]})})}function Hu({user:e}){let t=et(),n=e.totp_enabled===!0,[r,i]=(0,_.useState)(`idle`),[a,o]=(0,_.useState)(null),[s,c]=(0,_.useState)(``),[l,u]=(0,_.useState)(``),[d,f]=(0,_.useState)(null),[p,m]=(0,_.useState)(!1),[h,g]=(0,_.useState)(null),{data:v}=j({queryKey:[`2fa-backup-count`],queryFn:()=>F.get(`/api/auth/2fa/backup-codes/count`),enabled:n}),y=M({mutationFn:()=>F.post(`/api/auth/setup-2fa`,{}),onSuccess:e=>{o(e),i(`setup`),g(null)},onError:e=>g({text:e.message,kind:`err`})}),b=M({mutationFn:e=>F.post(`/api/auth/verify-2fa`,{code:e}),onSuccess:e=>{i(`idle`),c(``),o(null),e.backupCodes&&e.backupCodes.length&&f(e.backupCodes),t.invalidateQueries({queryKey:[`auth-me`]}),t.invalidateQueries({queryKey:[`2fa-backup-count`]}),g({text:`2FA enabled`,kind:`ok`})},onError:e=>g({text:e.message||`Invalid code`,kind:`err`})}),x=M({mutationFn:e=>F.post(`/api/auth/disable-2fa`,{password:e}),onSuccess:()=>{i(`idle`),u(``),t.invalidateQueries({queryKey:[`auth-me`]}),t.invalidateQueries({queryKey:[`2fa-backup-count`]}),g({text:`2FA disabled`,kind:`info`})},onError:e=>g({text:e.message||`Failed`,kind:`err`})}),S=M({mutationFn:e=>F.post(`/api/auth/2fa/backup-codes`,{password:e}),onSuccess:e=>{m(!1),e.codes&&e.codes.length&&f(e.codes),t.invalidateQueries({queryKey:[`2fa-backup-count`]})},onError:e=>{m(!1),g({text:e.message||`Failed to regenerate codes`,kind:`err`})}});return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`2fa-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Two-Factor Authentication`}),(0,A.jsxs)(`p`,{className:`text-sm`,"data-testid":`2fa-status`,children:[`Status:`,` `,(0,A.jsx)(`span`,{className:n?`text-green-600 font-medium`:`text-destructive font-medium`,children:n?`✅ Enabled`:`❌ Not enabled`})]}),!n&&r===`idle`&&(0,A.jsx)(`button`,{type:`button`,className:Fu,onClick:()=>y.mutate(),disabled:y.isPending,"data-testid":`btn-setup-2fa`,children:y.isPending?`Setting up…`:`Enable 2FA`}),n&&r===`idle`&&(0,A.jsxs)(`div`,{className:`flex items-center gap-3 flex-wrap`,children:[(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive`,onClick:()=>i(`disabling`),"data-testid":`btn-disable-2fa`,children:`Disable 2FA`}),v&&(0,A.jsxs)(`span`,{className:`text-sm `+(v.remaining<=2?`text-orange-500`:`text-muted-foreground`),children:[v.remaining,` backup codes remaining.`]}),v&&(0,A.jsx)(`button`,{type:`button`,className:Iu,onClick:()=>m(!0),"data-testid":`btn-regen-backup-codes`,children:`Regenerate`})]}),r===`setup`&&a&&(0,A.jsxs)(`div`,{className:`space-y-3 p-3 bg-muted/40 rounded-md`,children:[(0,A.jsx)(`p`,{className:`text-sm`,children:`Scan this QR code with your authenticator app:`}),(0,A.jsx)(`img`,{src:a.qrCode,alt:`2FA QR code`,className:`bg-white p-2 rounded max-w-[240px]`}),(0,A.jsxs)(`p`,{className:`text-sm`,children:[`Or enter manually: `,(0,A.jsx)(`code`,{className:`bg-muted px-2 py-0.5 rounded text-xs`,children:a.secret})]}),(0,A.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,A.jsx)(`input`,{type:`text`,maxLength:6,className:Pu+` max-w-[140px] font-mono tracking-widest`,placeholder:`123456`,value:s,onChange:e=>c(e.target.value.replace(/\D/g,``)),"data-testid":`2fa-verify-code`}),(0,A.jsx)(`button`,{type:`button`,className:Fu,disabled:s.length!==6||b.isPending,onClick:()=>b.mutate(s),"data-testid":`btn-verify-2fa`,children:b.isPending?`Verifying…`:`Verify & Enable`}),(0,A.jsx)(`button`,{type:`button`,className:Iu,onClick:()=>{i(`idle`),o(null),c(``)},children:`Cancel`})]})]}),r===`disabling`&&(0,A.jsxs)(`div`,{className:`space-y-2 p-3 bg-muted/40 rounded-md max-w-sm`,children:[(0,A.jsx)(`label`,{className:`block text-sm font-medium`,children:`Enter your password to confirm:`}),(0,A.jsx)(`input`,{type:`password`,className:Pu,value:l,onChange:e=>u(e.target.value),placeholder:`Password`,"data-testid":`2fa-disable-password`}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsx)(`button`,{type:`button`,className:Lu,disabled:!l||x.isPending,onClick:()=>x.mutate(l),"data-testid":`btn-disable-2fa-confirm`,children:x.isPending?`Disabling…`:`Confirm Disable`}),(0,A.jsx)(`button`,{type:`button`,className:Iu,onClick:()=>{i(`idle`),u(``)},"data-testid":`btn-disable-2fa-cancel`,children:`Cancel`})]})]}),(0,A.jsx)(Ru,{msg:h}),(0,A.jsx)(Mu,{open:p,title:`Regenerate backup codes?`,body:`This invalidates your existing codes. Enter your current password to confirm.`,confirmText:`Regenerate`,danger:!0,requirePassword:!0,passwordPlaceholder:`Current password`,busy:S.isPending,onConfirm:e=>e&&S.mutate(e),onCancel:()=>m(!1)}),d&&(0,A.jsx)(Vu,{codes:d,onClose:()=>f(null)})]})}function Uu({s:e,isCurrent:t,onRevoke:n}){return(0,A.jsxs)(`div`,{className:`flex items-center justify-between gap-3 rounded-md border-2 px-3 py-2 `+(t?`border-primary bg-primary/5`:`border-border bg-muted/40`),"data-testid":`session-row-`+e.id,children:[(0,A.jsxs)(`div`,{className:`min-w-0`,children:[(0,A.jsxs)(`div`,{className:`text-sm font-medium truncate`,children:[e.device_label||`Unknown device`,t&&(0,A.jsx)(`span`,{className:`ml-2 text-xs text-primary font-medium`,children:`(this device)`})]}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground`,children:[e.ip_address||`—`,` · Created `,new Date(e.created_at).toLocaleDateString(),` · Active `,zu(e.last_activity)]})]}),!t&&(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive text-xs`,onClick:n,"data-testid":`btn-revoke-session-`+e.id,children:`Revoke`})]})}function Wu(){let e=et(),[t,n]=(0,_.useState)(null),[r,i]=(0,_.useState)(null),{data:a,isLoading:o,error:s}=j({queryKey:[`sessions`],queryFn:()=>F.get(`/api/sessions`)}),c=M({mutationFn:e=>F.delete(`/api/sessions/`+e),onSuccess:()=>{i({text:`Session revoked`,kind:`info`}),e.invalidateQueries({queryKey:[`sessions`]})},onError:e=>i({text:e.message,kind:`err`})}),l=M({mutationFn:()=>F.delete(`/api/sessions`),onSuccess:t=>{i({text:`All other sessions revoked (`+(t.revoked||0)+` removed)`,kind:`info`}),e.invalidateQueries({queryKey:[`sessions`]})},onError:e=>i({text:e.message,kind:`err`})}),u=c.isPending||l.isPending;return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`sessions-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Active Sessions`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Devices where you are currently logged in. Revoke any session to immediately log that device out.`}),(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive text-xs`,onClick:()=>n({kind:`all`}),"data-testid":`btn-revoke-all-sessions`,children:`Revoke All Other Sessions`}),o&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading sessions…`}),s&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:`Could not load sessions.`}),(0,A.jsxs)(`div`,{className:`space-y-2`,children:[a?.sessions.map(e=>(0,A.jsx)(Uu,{s:e,isCurrent:e.id===a.currentSessionId,onRevoke:()=>n({kind:`one`,id:e.id})},e.id)),a&&a.sessions.length===0&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`No active sessions found.`})]}),(0,A.jsx)(Ru,{msg:r}),(0,A.jsx)(Mu,{open:t?.kind===`one`,title:`Revoke this session?`,body:`That device will be logged out.`,confirmText:`Revoke`,danger:!0,busy:u,onConfirm:()=>{t?.kind===`one`&&c.mutate(t.id),n(null)},onCancel:()=>n(null)}),(0,A.jsx)(Mu,{open:t?.kind===`all`,title:`Revoke all other sessions?`,body:`All other devices will be logged out.`,confirmText:`Revoke All`,danger:!0,busy:u,onConfirm:()=>{l.mutate(),n(null)},onCancel:()=>n(null)})]})}function Z({user:e}){let t=et(),n=!!e.nextcloud_url,[r,i]=(0,_.useState)(e.nextcloud_url||``),[a,o]=(0,_.useState)(e.nextcloud_user||``),[s,c]=(0,_.useState)(``),[l,u]=(0,_.useState)(e.webdav_learning_path||``),[d,f]=(0,_.useState)(!1),[p,m]=(0,_.useState)(null),h=M({mutationFn:e=>F.post(`/api/nextcloud/connect`,e),onSuccess:e=>{m({text:e.message||`Connected`,kind:`ok`}),c(``),t.invalidateQueries({queryKey:[`auth-me`]})},onError:e=>m({text:e.message||`Connection failed`,kind:`err`})}),g=M({mutationFn:()=>F.post(`/api/nextcloud/disconnect`,{}),onSuccess:()=>{m({text:`Disconnected`,kind:`info`}),c(``),t.invalidateQueries({queryKey:[`auth-me`]})},onError:e=>m({text:e.message,kind:`err`})}),v=M({mutationFn:e=>F.post(`/api/user/webdav-path`,{path:e}),onSuccess:()=>{m({text:`Path saved`,kind:`ok`}),t.invalidateQueries({queryKey:[`auth-me`]})},onError:e=>m({text:e.message||`Failed to save`,kind:`err`})});function y(e){e.preventDefault(),m(null);let t=r.trim().replace(/\/+$/,``),n=a.trim(),i=s.trim();if(!t||!n||!i)return m({text:`Fill all Nextcloud fields`,kind:`err`});h.mutate({nextcloudUrl:t,username:n,appPassword:i})}return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`nextcloud-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Nextcloud Integration`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Export generated documents to your Nextcloud.`}),(0,A.jsx)(`div`,{className:`text-sm`,"data-testid":`nc-status`,children:n?(0,A.jsxs)(A.Fragment,{children:[`✅ Connected to `,(0,A.jsx)(`strong`,{children:e.nextcloud_url}),` as `,e.nextcloud_user]}):(0,A.jsx)(A.Fragment,{children:`Not connected`})}),(0,A.jsxs)(`form`,{onSubmit:y,className:`space-y-2 max-w-md`,children:[(0,A.jsxs)(`label`,{className:`block text-sm`,children:[(0,A.jsx)(`span`,{className:`block text-xs font-semibold text-muted-foreground mb-1`,children:`Nextcloud URL`}),(0,A.jsx)(`input`,{type:`url`,className:Pu,value:r,onChange:e=>i(e.target.value),placeholder:`https://cloud.example.com`,"data-testid":`nc-url`})]}),(0,A.jsxs)(`label`,{className:`block text-sm`,children:[(0,A.jsx)(`span`,{className:`block text-xs font-semibold text-muted-foreground mb-1`,children:`Username`}),(0,A.jsx)(`input`,{type:`text`,className:Pu,value:a,onChange:e=>o(e.target.value),placeholder:`your-username`,"data-testid":`nc-user`})]}),(0,A.jsxs)(`label`,{className:`block text-sm`,children:[(0,A.jsx)(`span`,{className:`block text-xs font-semibold text-muted-foreground mb-1`,children:`App Password`}),(0,A.jsx)(`input`,{type:`password`,className:Pu,value:s,onChange:e=>c(e.target.value),placeholder:`Generate in Nextcloud → Settings → Security`,"data-testid":`nc-pass`}),(0,A.jsx)(`span`,{className:`block text-xs text-muted-foreground mt-1`,children:`Go to Nextcloud → Settings → Security → Create new app password`})]}),(0,A.jsxs)(`div`,{className:`flex gap-2 flex-wrap`,children:[(0,A.jsx)(`button`,{type:`submit`,className:Fu,disabled:h.isPending,"data-testid":`btn-nc-connect`,children:h.isPending?`Connecting…`:n?`Reconnect`:`Connect`}),n&&(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive`,onClick:()=>f(!0),"data-testid":`btn-nc-disconnect`,children:`Disconnect`})]})]}),n&&(0,A.jsx)(`div`,{className:`border-t border-border pt-3 space-y-2 max-w-md`,children:(0,A.jsxs)(`label`,{className:`block text-sm`,children:[(0,A.jsx)(`span`,{className:`block text-xs font-semibold text-muted-foreground mb-1`,children:`Learning Hub — Default Browse Path`}),(0,A.jsxs)(`span`,{className:`block text-xs text-muted-foreground mb-2`,children:[`Folder opened first when picking files for AI content generation (e.g. `,(0,A.jsx)(`code`,{children:`/Medical-Resources`}),`)`]}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsx)(`input`,{type:`text`,className:Pu,value:l,onChange:e=>u(e.target.value),placeholder:`/Medical-Resources`,"data-testid":`nc-webdav-path`}),(0,A.jsx)(`button`,{type:`button`,className:Fu,disabled:v.isPending,onClick:()=>v.mutate(l.trim()),"data-testid":`btn-nc-save-path`,children:v.isPending?`Saving…`:`Save Path`})]})]})}),(0,A.jsx)(Ru,{msg:p}),(0,A.jsx)(Mu,{open:d,title:`Disconnect Nextcloud?`,body:`Future exports will fail until you reconnect. Your stored credentials will be cleared.`,confirmText:`Disconnect`,danger:!0,busy:g.isPending,onConfirm:()=>{g.mutate(),f(!1)},onCancel:()=>f(!1)})]})}function Gu(e){return e<1024?e+` B`:e<1048576?Math.round(e/1024)+` KB`:(e/1048576).toFixed(1)+` MB`}function Ku({doc:e,onDownload:t,onDelete:n,downloading:r}){return(0,A.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-2 rounded-md bg-muted/40 border border-border`,"data-testid":`doc-row-`+e.id,children:[(0,A.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,A.jsx)(`div`,{className:`text-sm font-medium truncate`,children:e.filename}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground`,children:[Gu(e.size_bytes),` · `,new Date(e.created_at).toLocaleDateString(),e.description?` · `+e.description:``]})]}),(0,A.jsx)(`button`,{type:`button`,className:Fu+` text-xs`,disabled:r,onClick:t,"data-testid":`btn-doc-download-`+e.id,children:r?`…`:`Download`}),(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive text-xs`,onClick:n,"data-testid":`btn-doc-delete-`+e.id,children:`Delete`})]})}function qu(){let e=et(),[t,n]=(0,_.useState)(null),[r,i]=(0,_.useState)(``),[a,o]=(0,_.useState)(null),[s,c]=(0,_.useState)(null),{data:l,isLoading:u,error:d}=j({queryKey:[`documents`],queryFn:()=>F.get(`/api/documents`)}),f=M({mutationFn:async e=>{let t=new FormData;t.append(`file`,e.file),t.append(`description`,e.description);let n=await fetch(`/api/documents/upload`,{method:`POST`,credentials:`include`,body:t}),r=(n.headers.get(`content-type`)||``).includes(`application/json`)?await n.json():null;if(!n.ok||r&&r.success===!1)throw Error(r&&r.error||n.statusText);return r},onSuccess:t=>{o({text:`Document uploaded: `+t.filename,kind:`ok`}),n(null),i(``),e.invalidateQueries({queryKey:[`documents`]})},onError:e=>o({text:`Upload failed: `+e.message,kind:`err`})}),p=M({mutationFn:e=>F.delete(`/api/documents/`+e),onSuccess:()=>{o({text:`Document deleted`,kind:`info`}),e.invalidateQueries({queryKey:[`documents`]})},onError:e=>o({text:e.message||`Delete failed`,kind:`err`})}),m=M({mutationFn:e=>F.get(`/api/documents/`+e+`/download`),onSuccess:e=>{e.url?window.open(e.url,`_blank`,`noopener,noreferrer`):o({text:`Download failed`,kind:`err`})},onError:e=>o({text:e.message||`Download failed`,kind:`err`})});function h(e){if(e.preventDefault(),o(null),!t)return o({text:`Select a file first`,kind:`err`});f.mutate({file:t,description:r})}return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`documents-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Documents`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Upload and manage documents via S3 storage (PDF, images, Word docs, text files). Max 10 MB per file.`}),u&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`}),d&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:`Failed to load documents.`}),l&&!l.s3_configured&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground italic`,children:`S3 storage not configured. Set S3_BUCKET in server environment.`}),l&&l.s3_configured&&(0,A.jsxs)(A.Fragment,{children:[(0,A.jsxs)(`form`,{onSubmit:h,className:`flex flex-wrap gap-2 items-center`,"data-testid":`doc-upload-area`,children:[(0,A.jsx)(`input`,{type:`file`,className:`text-sm`,accept:`.pdf,.jpg,.jpeg,.png,.gif,.doc,.docx,.txt,.csv`,onChange:e=>n(e.target.files?.[0]??null),"data-testid":`doc-file-input`}),(0,A.jsx)(`input`,{type:`text`,className:Pu+` max-w-xs`,placeholder:`Description (optional)`,value:r,onChange:e=>i(e.target.value),"data-testid":`doc-description`}),(0,A.jsx)(`button`,{type:`submit`,className:Fu,disabled:!t||f.isPending,"data-testid":`btn-doc-upload`,children:f.isPending?`Uploading…`:`Upload`})]}),(0,A.jsx)(`div`,{className:`space-y-2`,children:l.documents.length===0?(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`No documents uploaded yet.`}):l.documents.map(e=>(0,A.jsx)(Ku,{doc:e,onDownload:()=>m.mutate(e.id),onDelete:()=>c(e),downloading:m.isPending&&m.variables===e.id},e.id))})]}),(0,A.jsx)(Ru,{msg:a}),(0,A.jsx)(Mu,{open:!!s,title:`Delete this document permanently?`,body:s?.filename,confirmText:`Delete`,danger:!0,busy:p.isPending,onConfirm:()=>{s&&p.mutate(s.id),c(null)},onCancel:()=>c(null)})]})}function Ju(){let e=et(),[t,n]=(0,_.useState)(null),[r,i]=(0,_.useState)(``),[a,o]=(0,_.useState)(``),[s,c]=(0,_.useState)(!1),[l,u]=(0,_.useState)(!1),{data:d}=j({queryKey:[`voice-options`],queryFn:()=>F.get(`/api/user/preferences/options`)}),{data:f}=j({queryKey:[`voice-prefs`],queryFn:()=>F.get(`/api/user/preferences`)});!l&&f&&(i(f.stt_model||``),o(f.tts_voice||``),u(!0));let p=M({mutationFn:e=>F.post(`/api/user/preferences`,e),onSuccess:()=>{n({text:`Voice preferences saved`,kind:`ok`}),e.invalidateQueries({queryKey:[`voice-prefs`]})},onError:e=>n({text:e.message||`Save failed`,kind:`err`})});async function m(){n(null),c(!0);try{await F.post(`/api/user/preferences`,{tts_voice:a||null}),e.invalidateQueries({queryKey:[`voice-prefs`]});let t=`Hello, this is a preview of the `+(a||`server default`)+` voice. This is how your read-aloud feature will sound.`,n=await fetch(`/api/text-to-speech`,{method:`POST`,credentials:`include`,headers:{"Content-Type":`application/json`},body:JSON.stringify({text:t})});if(!n.ok)throw Error(`Preview failed`);let r=await n.blob(),i=URL.createObjectURL(r),o=new Audio(i);o.onended=()=>URL.revokeObjectURL(i),await o.play()}catch(e){n({text:`Preview failed: `+e.message,kind:`err`})}finally{c(!1)}}return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`voice-preferences-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Voice Preferences`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Customize your speech-to-text model and text-to-speech voice. These settings apply to all recording and read-aloud features.`}),(0,A.jsxs)(`div`,{className:`space-y-2 max-w-md`,children:[(0,A.jsxs)(`label`,{className:`block text-sm`,children:[(0,A.jsx)(`span`,{className:`block text-xs font-semibold text-muted-foreground mb-1`,children:`Speech-to-Text Model (Transcription)`}),(0,A.jsxs)(`select`,{className:Pu,value:r,onChange:e=>i(e.target.value),"data-testid":`stt-model-select`,children:[(0,A.jsxs)(`option`,{value:``,children:[`Server default`,d?` (`+d.sttProvider+`)`:``]}),d?.sttModels.map(e=>(0,A.jsx)(`option`,{value:e.value,children:e.label},e.value))]})]}),(0,A.jsxs)(`label`,{className:`block text-sm`,children:[(0,A.jsx)(`span`,{className:`block text-xs font-semibold text-muted-foreground mb-1`,children:`Text-to-Speech Voice (Read Aloud)`}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsxs)(`select`,{className:Pu,value:a,onChange:e=>o(e.target.value),"data-testid":`tts-voice-select`,children:[(0,A.jsxs)(`option`,{value:``,children:[`Server default`,d?` (`+d.ttsProvider+`)`:``]}),d?.ttsVoices.map(e=>(0,A.jsx)(`option`,{value:e.value,children:e.label},e.value))]}),(0,A.jsx)(`button`,{type:`button`,className:Iu,disabled:s,onClick:m,"data-testid":`btn-preview-voice`,children:s?`Loading…`:`Preview`})]})]}),(0,A.jsx)(`button`,{type:`button`,className:Fu,disabled:p.isPending,onClick:()=>p.mutate({stt_model:r||null,tts_voice:a||null}),"data-testid":`btn-save-voice-prefs`,children:p.isPending?`Saving…`:`Save Voice Preferences`})]}),(0,A.jsx)(Ru,{msg:t})]})}var Yu=`ped_browser_whisper_enabled`,Xu=`ped_browser_whisper_model`,Zu=[{value:`Xenova/whisper-tiny.en`,label:`Tiny (~39MB) — fastest, ~2-3s`},{value:`Xenova/whisper-base.en`,label:`Base (~74MB) — balanced, ~3-5s`},{value:`Xenova/whisper-small.en`,label:`Small (~244MB) — best quality, ~6-10s`}];function Qu(e,t=``){try{return window.localStorage.getItem(e)??t}catch{return t}}function $u(e,t){try{window.localStorage.setItem(e,t)}catch{}}function ed(){let[e,t]=(0,_.useState)(Qu(Yu)===`true`),[n,r]=(0,_.useState)(Qu(Xu)||Zu[0].value);function i(e){t(e),$u(Yu,String(e))}function a(e){r(e),$u(Xu,e)}return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`browser-whisper-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Browser Transcription (Local Whisper)`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Transcribes audio entirely in your browser — no audio sent to any server. Powered by OpenAI Whisper running in WebAssembly. Model is downloaded once and cached locally.`}),(0,A.jsxs)(`div`,{className:`flex items-center gap-3 flex-wrap`,children:[(0,A.jsx)(`label`,{className:`text-sm font-medium`,children:`Enable browser transcription:`}),(0,A.jsxs)(`label`,{className:`flex items-center gap-2 cursor-pointer`,children:[(0,A.jsx)(`input`,{type:`checkbox`,className:`accent-primary size-4`,checked:e,onChange:e=>i(e.target.checked),"data-testid":`browser-whisper-enabled`}),(0,A.jsx)(`span`,{className:`text-sm`,"data-testid":`browser-whisper-status`,children:e?`On — audio stays on device`:`Off`})]})]}),(0,A.jsxs)(`div`,{className:`flex items-center gap-3 flex-wrap`,children:[(0,A.jsx)(`label`,{className:`text-sm font-medium`,children:`Model:`}),(0,A.jsx)(`select`,{className:Pu+` max-w-md`,value:n,onChange:e=>a(e.target.value),"data-testid":`browser-whisper-model`,children:Zu.map(e=>(0,A.jsx)(`option`,{value:e.value,children:e.label},e.value))})]}),(0,A.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`When enabled, overrides server transcription. Falls back to server if browser transcription fails. The actual WASM download runs from the recording components — pre-download will light up when those port to React.`})]})}var td=`ped_web_speech_enabled`;function nd(){let[e,t]=(0,_.useState)(Qu(td)===`true`),[n,r]=(0,_.useState)(!1),i=typeof window<`u`&&(`webkitSpeechRecognition`in window||`SpeechRecognition`in window);function a(){$u(Yu,`false`),t(!0),$u(td,`true`)}function o(){t(!1),$u(td,`false`)}return(0,A.jsxs)(`section`,{className:Nu+` border-l-4 border-l-orange-500`,"data-testid":`web-speech-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Real-Time Streaming Transcription`}),(0,A.jsxs)(`div`,{className:`bg-orange-50 dark:bg-orange-950/30 p-3 rounded-md text-sm text-orange-900 dark:text-orange-100`,children:[(0,A.jsx)(`strong`,{children:`Privacy Warning:`}),` Uses your browser's built-in speech recognition, which `,(0,A.jsx)(`strong`,{children:`may send audio to cloud servers`}),` (Chrome/Edge send to Google). Only enable if you accept this trade-off for real-time transcription.`]}),(0,A.jsxs)(`p`,{className:`text-sm text-muted-foreground`,children:[`See words appear as you speak (streaming). Overrides browser and server transcription when enabled. `,(0,A.jsx)(`strong`,{children:`Not HIPAA-compliant`}),` in most browsers.`]}),(0,A.jsxs)(`div`,{className:`flex items-center gap-3 flex-wrap`,children:[(0,A.jsx)(`label`,{className:`text-sm font-medium`,children:`Enable real-time streaming:`}),(0,A.jsxs)(`label`,{className:`flex items-center gap-2 cursor-pointer`,children:[(0,A.jsx)(`input`,{type:`checkbox`,className:`accent-orange-500 size-4`,checked:e,disabled:!i,onChange:e=>{e.target.checked?r(!0):o()},"data-testid":`web-speech-enabled`}),(0,A.jsx)(`span`,{className:`text-sm`,"data-testid":`web-speech-status`,children:i?e?`On — real-time streaming`:`Off`:`Not supported in this browser`})]})]}),i&&(0,A.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Streaming integration lights up when the recording components port to React.`}),(0,A.jsx)(Mu,{open:n,title:`Enable real-time streaming transcription?`,body:`Your browser's speech recognition may send audio to cloud servers (e.g. Google). This is NOT HIPAA-compliant. Only enable if you understand and accept this privacy trade-off.`,confirmText:`Enable`,danger:!0,onConfirm:()=>{a(),r(!1)},onCancel:()=>r(!1)})]})}var rd=[{value:`physical_exam`,label:`Physical Exam Template`},{value:`ros`,label:`Review of Systems Template`},{value:`encounter_format`,label:`Encounter Note Format`},{value:`family_history`,label:`Family History Format`},{value:`assessment_plan`,label:`Assessment & Plan Format`},{value:`template_soap`,label:`SOAP Note Template`},{value:`template_hpi`,label:`HPI Template`},{value:`template_wellvisit`,label:`Well Visit Template`},{value:`template_sickvisit`,label:`Sick Visit Template`},{value:`custom`,label:`Custom`}],id=Object.fromEntries(rd.map(e=>[e.value,e.label.replace(/ Template$| Format$/,``)]));function ad(){let e=et(),[t,n]=(0,_.useState)(null),[r,i]=(0,_.useState)(null),[a,o]=(0,_.useState)(rd[0].value),[s,c]=(0,_.useState)(``),[l,u]=(0,_.useState)(``),[d,f]=(0,_.useState)(null),{data:p}=j({queryKey:[`memories`],queryFn:()=>F.get(`/api/memories`)}),m=(p?.memories||[]).filter(e=>!e.category.startsWith(`correction_`)),h=M({mutationFn:e=>{let{id:t,...n}=e;return t?F.put(`/api/memories/`+t,n):F.post(`/api/memories`,n)},onSuccess:()=>{n({text:r?`Template updated`:`Template saved`,kind:`ok`}),i(null),c(``),u(``),e.invalidateQueries({queryKey:[`memories`]})},onError:e=>n({text:e.message||`Save failed`,kind:`err`})}),g=M({mutationFn:e=>F.delete(`/api/memories/`+e),onSuccess:()=>{n({text:`Template deleted`,kind:`info`}),e.invalidateQueries({queryKey:[`memories`]})},onError:e=>n({text:e.message||`Delete failed`,kind:`err`})});function v(e){i(e.id),o(e.category),c(e.name),u(e.content)}function y(){i(null),c(``),u(``)}function b(e){if(e.preventDefault(),n(null),!s.trim())return n({text:`Enter a template name`,kind:`err`});if(!l.trim())return n({text:`Enter template content`,kind:`err`});h.mutate({id:r??void 0,category:a,name:s.trim(),content:l.trim()})}return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`templates-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`My Templates`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Save reusable templates for physical exam, ROS, encounter format, etc. The AI will use these when generating notes.`}),(0,A.jsxs)(`form`,{onSubmit:b,className:`space-y-2`,children:[(0,A.jsxs)(`div`,{className:`flex gap-2 flex-wrap items-center`,children:[(0,A.jsx)(`select`,{className:Pu+` max-w-xs`,value:a,onChange:e=>o(e.target.value),"data-testid":`mem-category`,children:rd.map(e=>(0,A.jsx)(`option`,{value:e.value,children:e.label},e.value))}),(0,A.jsx)(`input`,{type:`text`,className:Pu+` flex-1 min-w-[150px]`,placeholder:`Template name (e.g. Normal PE)`,value:s,onChange:e=>c(e.target.value),"data-testid":`mem-name`})]}),(0,A.jsx)(`textarea`,{rows:5,className:Pu+` resize-y`,placeholder:`Paste your template here. Example: HEENT: Normocephalic, atraumatic. Eyes: PERRL…`,value:l,onChange:e=>u(e.target.value),"data-testid":`mem-content`}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsx)(`button`,{type:`submit`,className:Fu,disabled:h.isPending,"data-testid":`btn-mem-save`,children:h.isPending?`Saving…`:r?`Update Template`:`Add Template`}),r!==null&&(0,A.jsx)(`button`,{type:`button`,className:Iu,onClick:y,children:`Cancel`})]})]}),(0,A.jsx)(`div`,{className:`space-y-2`,children:m.length===0?(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`No templates saved yet. Add one above.`}):m.map(e=>(0,A.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-2 rounded-md bg-muted/40 border border-border`,"data-testid":`mem-row-`+e.id,children:[(0,A.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-2 text-sm`,children:[(0,A.jsx)(`span`,{className:`font-medium`,children:e.name}),(0,A.jsx)(`span`,{className:`text-xs text-muted-foreground uppercase tracking-wide`,children:id[e.category]||e.category})]}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground truncate`,children:[(e.content||``).slice(0,100).replace(/\n/g,` `),e.content&&e.content.length>100?`…`:``]})]}),(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-xs`,onClick:()=>v(e),"data-testid":`btn-mem-edit-`+e.id,children:`Edit`}),(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive text-xs`,onClick:()=>f(e),"data-testid":`btn-mem-delete-`+e.id,children:`Delete`})]},e.id))}),(0,A.jsx)(Ru,{msg:t}),(0,A.jsx)(Mu,{open:!!d,title:`Delete template "`+(d?.name||``)+`"?`,body:`This cannot be undone.`,confirmText:`Delete`,danger:!0,busy:g.isPending,onConfirm:()=>{d&&g.mutate(d.id),f(null)},onCancel:()=>f(null)})]})}var od={correction_soap:`SOAP Correction`,correction_hpi:`HPI Correction`,correction_encounter:`Encounter Correction`,correction_wellvisit:`Well Visit Correction`,correction_sickvisit:`Sick Visit Correction`};function sd(e){let t=e.indexOf(` -CORRECTED TO: `);return t===-1?{original:e.trim(),corrected:``}:{original:e.substring(0,t).replace(/^ORIGINAL:\s*/i,``).trim(),corrected:e.substring(t+15).trim()}}function cd(){let e=et(),[t,n]=(0,_.useState)({}),[r,i]=(0,_.useState)(null),[a,o]=(0,_.useState)(null),{data:s}=j({queryKey:[`memories`],queryFn:()=>F.get(`/api/memories`)}),c=(s?.memories||[]).filter(e=>e.category.startsWith(`correction_`)),l=M({mutationFn:e=>F.delete(`/api/memories/`+e),onSuccess:()=>{i({text:`Correction deleted`,kind:`info`}),e.invalidateQueries({queryKey:[`memories`]})},onError:e=>i({text:e.message||`Delete failed`,kind:`err`})});return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`corrections-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`AI Learning (Corrections)`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`The AI automatically learns from your edits. When you modify AI-generated text and save, corrections are stored here and applied to future notes. Latest 20 per section.`}),(0,A.jsx)(`div`,{className:`space-y-2`,children:c.length===0?(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`No corrections yet. Edit AI-generated notes and save to start learning.`}):c.map(e=>{let r=t[e.id],i=sd(e.content||``),a=e.created_at?new Date(e.created_at).toLocaleDateString():``;return(0,A.jsxs)(`div`,{className:`rounded-md bg-muted/40 border border-border overflow-hidden`,"data-testid":`corr-row-`+e.id,children:[(0,A.jsxs)(`button`,{type:`button`,className:`w-full flex items-center gap-2 px-3 py-2 text-left`,onClick:()=>n({...t,[e.id]:!r}),children:[(0,A.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:r?`▾`:`▸`}),(0,A.jsx)(`span`,{className:`text-xs text-muted-foreground uppercase tracking-wide`,children:od[e.category]||e.category}),(0,A.jsx)(`span`,{className:`flex-1 text-sm truncate`,children:e.name}),(0,A.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:a}),(0,A.jsx)(`span`,{role:`button`,className:`text-destructive text-xs px-2`,onClick:t=>{t.stopPropagation(),o(e)},"data-testid":`btn-corr-delete-`+e.id,children:`Delete`})]}),r&&(0,A.jsxs)(`div`,{className:`px-3 py-2 text-sm space-y-2 border-t border-border`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`div`,{className:`text-xs font-semibold uppercase text-destructive mb-1`,children:`Original (AI generated):`}),(0,A.jsx)(`div`,{className:`whitespace-pre-wrap text-muted-foreground`,children:i.original})]}),i.corrected&&(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`div`,{className:`text-xs font-semibold uppercase text-green-600 mb-1`,children:`Corrected to:`}),(0,A.jsx)(`div`,{className:`whitespace-pre-wrap`,children:i.corrected})]})]})]},e.id)})}),(0,A.jsx)(Ru,{msg:r}),(0,A.jsx)(Mu,{open:!!a,title:`Delete this correction?`,body:`The AI will no longer apply this edit in future notes.`,confirmText:`Delete`,danger:!0,busy:l.isPending,onConfirm:()=>{a&&l.mutate(a.id),o(null)},onCancel:()=>o(null)})]})}function ld(){let e=et(),[t,n]=(0,_.useState)(null),[r,i]=(0,_.useState)(null),{data:a,isLoading:o}=j({queryKey:[`audio-backups`],queryFn:()=>F.get(`/api/audio-backups`)}),s=M({mutationFn:e=>F.delete(`/api/audio-backups/`+e),onSuccess:()=>{n({text:`Backup deleted`,kind:`info`}),e.invalidateQueries({queryKey:[`audio-backups`]})},onError:e=>n({text:e.message,kind:`err`})});function c(e){window.open(`/api/audio-backups/`+e+`/audio`,`_blank`,`noopener,noreferrer`)}return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`audio-backups-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Audio Backups`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Recordings are automatically backed up on the server and kept for 24 hours. Retry flow ports with the recording components.`}),o&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`}),(0,A.jsxs)(`div`,{className:`space-y-2`,children:[a&&a.backups.length===0&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`No audio backups.`}),a?.backups.map(e=>{let t=Math.round(e.size_bytes/1024),n=e.compressed_bytes?` (`+Math.round(e.compressed_bytes/1024)+` KB compressed)`:``;return(0,A.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-2 rounded-md bg-muted/40 border border-border`,"data-testid":`audio-backup-row-`+e.id,children:[(0,A.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,A.jsxs)(`div`,{className:`text-sm font-medium`,children:[e.module,` recording`]}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground`,children:[new Date(e.created_at).toLocaleString(),` · `,t,` KB`,n,` · `,zu(e.created_at)]})]}),(0,A.jsx)(`button`,{type:`button`,className:Fu+` text-xs`,onClick:()=>c(e.id),children:`Play`}),(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive text-xs`,onClick:()=>i(e),"data-testid":`btn-audio-delete-`+e.id,children:`Delete`})]},e.id)})]}),(0,A.jsx)(Ru,{msg:t}),(0,A.jsx)(Mu,{open:!!r,title:`Delete this audio backup?`,body:`This cannot be undone.`,confirmText:`Delete`,danger:!0,busy:s.isPending,onConfirm:()=>{r&&s.mutate(r.id),i(null)},onCancel:()=>i(null)})]})}function ud(){let e=et(),[t,n]=(0,_.useState)(null),[r,i]=(0,_.useState)(null),{data:a,isLoading:o}=j({queryKey:[`saved-encounters`],queryFn:()=>F.get(`/api/encounters/saved`)}),s=M({mutationFn:e=>F.delete(`/api/encounters/saved/`+e),onSuccess:()=>{n({text:`Deleted`,kind:`info`}),e.invalidateQueries({queryKey:[`saved-encounters`]})},onError:e=>n({text:e.message,kind:`err`})});return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`saved-encounters-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Saved Encounters`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Encounters are automatically deleted after 7 days per site policy. Resume action ports with the encounter components.`}),o&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`}),(0,A.jsxs)(`div`,{className:`space-y-2`,children:[a&&a.encounters.length===0&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`No saved encounters.`}),a?.encounters.map(e=>{let t=new Date(e.updated_at).toLocaleDateString(),n=new Date(e.expires_at).toLocaleDateString(),r=(e.transcript_preview||``).slice(0,80);return(0,A.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-2 rounded-md bg-muted/40 border border-border`,"data-testid":`enc-row-`+e.id,children:[(0,A.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-2 text-sm`,children:[(0,A.jsx)(`span`,{className:`font-medium truncate`,children:e.label||`Untitled`}),(0,A.jsx)(`span`,{className:`text-xs text-muted-foreground uppercase tracking-wide`,children:e.enc_type})]}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground truncate`,children:[t,` · expires `,n,r?` · `+r+`…`:``]})]}),(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive text-xs`,onClick:()=>i(e),"data-testid":`btn-enc-delete-`+e.id,children:`Delete`})]},e.id)})]}),(0,A.jsx)(Ru,{msg:t}),(0,A.jsx)(Mu,{open:!!r,title:`Delete this saved encounter?`,body:r?.label||void 0,confirmText:`Delete`,danger:!0,busy:s.isPending,onConfirm:()=>{r&&s.mutate(r.id),i(null)},onCancel:()=>i(null)})]})}function dd(){return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`compliance-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Compliance & Usage`}),(0,A.jsxs)(`div`,{className:`text-sm space-y-2`,children:[(0,A.jsxs)(`p`,{children:[(0,A.jsx)(`strong`,{children:`AWS Bedrock`}),` is available with a Business Associate Agreement (BAA) for HIPAA-eligible workloads.`]}),(0,A.jsxs)(`ul`,{className:`list-disc pl-5 space-y-1 text-muted-foreground`,children:[(0,A.jsx)(`li`,{children:`All connections use HTTPS/TLS encryption`}),(0,A.jsx)(`li`,{children:`Authentication with optional 2FA`}),(0,A.jsx)(`li`,{children:`No patient data stored on server beyond session`}),(0,A.jsx)(`li`,{children:`AWS Bedrock supports BAA for HIPAA compliance`}),(0,A.jsx)(`li`,{children:`Azure OpenAI supports BAA for HIPAA compliance`})]}),(0,A.jsxs)(`p`,{children:[(0,A.jsx)(`strong`,{children:`Important:`}),` Check with your institution's guidelines and policies before use. This tool is not intended for production clinical use without proper organizational authorization and provider BAAs in place. Use with caution.`]})]})]})}function fd(){let{data:e,isLoading:t,error:n}=j({queryKey:[`auth-me`],queryFn:()=>F.get(`/api/auth/me`)}),r=e?.user.canLocalAuth!==!1;return(0,A.jsxs)(`div`,{className:`max-w-3xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Settings`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Account security, integrations, and personal templates. More sub-sections port over in follow-up commits.`})]}),t&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`}),n&&(0,A.jsxs)(`div`,{className:`text-sm text-destructive`,children:[`Could not load your account: `,n.message]}),e&&r&&(0,A.jsxs)(A.Fragment,{children:[(0,A.jsx)(Bu,{}),(0,A.jsx)(Hu,{user:e.user}),(0,A.jsx)(Wu,{})]}),e&&!r&&(0,A.jsxs)(`section`,{className:Nu,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Account managed by single sign-on`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Your password and two-factor authentication are managed by your identity provider.`})]}),e&&(0,A.jsx)(Z,{user:e.user}),e&&(0,A.jsx)(qu,{}),e&&(0,A.jsx)(Ju,{}),e&&(0,A.jsx)(ed,{}),e&&(0,A.jsx)(nd,{}),e&&(0,A.jsx)(ad,{}),e&&(0,A.jsx)(cd,{}),e&&(0,A.jsx)(ld,{}),e&&(0,A.jsx)(ud,{}),(0,A.jsx)(dd,{})]})}var pd=`rounded-lg border border-border bg-card p-5 space-y-3`,md=`px-3 py-1 rounded-full text-xs font-medium border transition-colors cursor-pointer`,hd=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`,gd=`rounded-md bg-primary text-primary-foreground px-3 py-2 text-sm font-medium disabled:opacity-50`,_d=`rounded-md border border-border px-3 py-2 text-sm disabled:opacity-50`;function vd(e){switch(e){case`quiz`:return`Quiz`;case`pearl`:return`Pearl`;case`presentation`:return`Slides`;default:return`Article`}}function yd({row:e,onOpen:t}){return(0,A.jsxs)(`button`,{type:`button`,onClick:t,className:`w-full text-left rounded-lg border border-border bg-card hover:bg-muted/60 p-4 transition-colors`,"data-testid":`lh-feed-item-`+e.slug,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-2 text-xs text-muted-foreground uppercase tracking-wide mb-1`,children:[(0,A.jsx)(`span`,{className:`font-semibold`,children:vd(e.content_type)}),e.category_name&&(0,A.jsxs)(`span`,{children:[`· `,e.category_name]}),e.question_count?(0,A.jsxs)(`span`,{children:[`· `,e.question_count,` Q`]}):null]}),(0,A.jsx)(`div`,{className:`text-sm font-semibold`,children:e.title}),e.subject&&(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground mt-0.5 truncate`,children:e.subject})]})}function Q({filter:e,query:t,onOpen:n}){let{data:r,isLoading:i,error:a}=j({queryKey:t?[`learning-search`,t]:e?[`learning-category`,e]:[`learning-feed`],queryFn:()=>t?F.get(`/api/learning/search?q=`+encodeURIComponent(t)):e?F.get(`/api/learning/category/`+encodeURIComponent(e)):F.get(`/api/learning/feed?limit=30`)});if(i)return(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`});if(a)return(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:a.message});let o=r?.content||[];return o.length===0?(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground italic py-4`,children:`No content found.`}):(0,A.jsx)(`div`,{className:`grid grid-cols-1 sm:grid-cols-2 gap-3`,"data-testid":`lh-feed`,children:o.map(e=>(0,A.jsx)(yd,{row:e,onOpen:()=>n(e.slug)},e.id))})}function bd(e){let t={};for(let n of e)t[n.id]={optionIds:new Set};return t}function xd({content:e,onReset:t}){let n=et(),[r,i]=(0,_.useState)(()=>bd(e.questions)),[a,o]=(0,_.useState)(null),[s,c]=(0,_.useState)(null),l=M({mutationFn:e=>F.post(`/api/learning/submit-quiz`,e),onSuccess:t=>{o(t),n.invalidateQueries({queryKey:[`learning-content`,e.slug]})},onError:e=>c(e.message||`Submit failed`)});function u(t){t.preventDefault(),c(null);let n=e.questions.map(e=>{let t=r[e.id];return e.question_type===`multi`?{questionId:e.id,optionIds:Array.from(t?.optionIds||[])}:{questionId:e.id,optionId:t?.optionId??null}});l.mutate({contentId:e.id,answers:n})}function d(e,t){i(n=>({...n,[e.id]:{optionId:t,optionIds:new Set}}))}function f(e,t){i(n=>{let r=new Set(n[e.id]?.optionIds||[]);return r.has(t)?r.delete(t):r.add(t),{...n,[e.id]:{optionIds:r}}})}if(a){let n=a.percentage>=80?`bg-green-600`:a.percentage>=50?`bg-amber-500`:`bg-destructive`;return(0,A.jsxs)(`section`,{className:pd,"data-testid":`lh-quiz-results`,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Results`}),(0,A.jsxs)(`span`,{className:`px-2 py-0.5 rounded text-xs font-semibold text-white `+n,"data-testid":`lh-quiz-score`,children:[a.score,`/`,a.total,` (`,a.percentage,`%)`]})]}),(0,A.jsx)(`div`,{className:`space-y-3`,children:a.results.map((e,t)=>(0,A.jsxs)(`div`,{className:`rounded-md border border-border p-3 bg-muted/30`,children:[(0,A.jsxs)(`div`,{className:`text-sm font-medium`,children:[(0,A.jsx)(`span`,{className:e.isCorrect?`text-green-600`:`text-destructive`,children:e.isCorrect?`✓`:`✗`}),` `,`Q`,t+1,`: `,e.questionText]}),!e.isCorrect&&e.correctOptionText&&(0,A.jsxs)(`div`,{className:`text-xs text-green-700 mt-1`,children:[(0,A.jsx)(`strong`,{children:`Correct:`}),` `,e.correctOptionText]}),!e.isCorrect&&e.selectedExplanation&&(0,A.jsxs)(`div`,{className:`text-xs text-destructive mt-1`,children:[(0,A.jsx)(`strong`,{children:`Why incorrect:`}),` `,e.selectedExplanation]}),e.generalExplanation&&(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground mt-1`,children:e.generalExplanation})]},e.questionId))}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsx)(`button`,{type:`button`,className:_d,onClick:()=>{o(null),i(bd(e.questions))},children:`Retake`}),(0,A.jsx)(`button`,{type:`button`,className:gd,onClick:t,children:`Back to Feed`})]})]})}return(0,A.jsxs)(`section`,{className:pd,"data-testid":`lh-quiz`,children:[(0,A.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Quiz`}),(0,A.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[e.questions.length,` question`,e.questions.length===1?``:`s`]})]}),(0,A.jsxs)(`form`,{onSubmit:u,className:`space-y-4`,children:[e.questions.map((e,t)=>{let n=e.question_type===`multi`,i=e.question_type===`true_false`?`True / False`:n?`Multiple Select`:`Single Choice`;return(0,A.jsxs)(`div`,{className:`rounded-md border border-border p-3 space-y-2 bg-muted/30`,children:[(0,A.jsxs)(`div`,{className:`flex items-center justify-between text-xs text-muted-foreground`,children:[(0,A.jsxs)(`span`,{className:`font-semibold`,children:[`Q`,t+1]}),(0,A.jsx)(`span`,{children:i})]}),(0,A.jsx)(`div`,{className:`text-sm font-medium`,children:e.question_text}),n&&(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground italic`,children:`Select all that apply`}),(0,A.jsx)(`div`,{className:`space-y-1`,children:e.options.map(t=>{let i=r[e.id],a=n?i?.optionIds.has(t.id)===!0:i?.optionId===t.id;return(0,A.jsxs)(`label`,{className:`flex items-start gap-2 text-sm cursor-pointer hover:bg-muted/50 rounded px-2 py-1`,children:[(0,A.jsx)(`input`,{type:n?`checkbox`:`radio`,name:`q-`+e.id,checked:a,onChange:()=>n?f(e,t.id):d(e,t.id),className:`mt-0.5`}),(0,A.jsx)(`span`,{children:t.option_text})]},t.id)})})]},e.id)}),s&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:s}),(0,A.jsx)(`button`,{type:`submit`,className:gd,disabled:l.isPending,"data-testid":`btn-lh-submit-quiz`,children:l.isPending?`Submitting…`:`Submit Answers`})]})]})}function Sd({slug:e,onBack:t}){let{data:n,isLoading:r,error:i}=j({queryKey:[`learning-content`,e],queryFn:()=>F.get(`/api/learning/content/`+encodeURIComponent(e))});if(r)return(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`});if(i)return(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:i.message});if(!n)return null;let a=n.content;return(0,A.jsxs)(`div`,{className:`space-y-4`,children:[(0,A.jsx)(`button`,{type:`button`,className:_d,onClick:t,"data-testid":`btn-lh-back`,children:`← Back to Feed`}),(0,A.jsxs)(`section`,{className:pd,"data-testid":`lh-viewer`,children:[(0,A.jsxs)(`div`,{className:`flex items-center justify-between gap-4`,children:[(0,A.jsx)(`h2`,{className:`text-xl font-semibold`,"data-testid":`lh-viewer-title`,children:a.title}),(0,A.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[vd(a.content_type),a.category_name?` · `+a.category_name:``,a.author_name?` · `+a.author_name:``]})]}),a.content_type===`presentation`?(0,A.jsxs)(`div`,{className:`text-center py-8 space-y-3 bg-muted/30 rounded-md`,children:[(0,A.jsx)(`div`,{className:`text-4xl`,children:`📊`}),(0,A.jsx)(`div`,{className:`text-sm font-medium`,children:a.title}),(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground`,children:`Slide rendering lives in the legacy viewer.`}),(0,A.jsx)(`a`,{href:`/#learning/`+encodeURIComponent(a.slug),className:gd+` inline-block`,rel:`noreferrer`,children:`Open in legacy viewer`})]}):(0,A.jsx)(`div`,{className:`whitespace-pre-wrap text-sm leading-relaxed`,"data-testid":`lh-viewer-body`,children:a.body||``})]}),a.progress&&a.progress.length>0&&(0,A.jsxs)(`section`,{className:pd,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Your past attempts`}),(0,A.jsx)(`div`,{className:`space-y-1 text-sm`,children:a.progress.map((e,t)=>{let n=e.total>0?Math.round(e.score/e.total*100):0,r=n>=70?`text-green-600`:`text-amber-600`;return(0,A.jsxs)(`div`,{className:`flex justify-between border-b border-border py-1`,children:[(0,A.jsx)(`span`,{children:new Date(e.completed_at).toLocaleDateString()}),(0,A.jsxs)(`span`,{className:`font-semibold `+r,children:[e.score,`/`,e.total,` (`,n,`%)`]})]},t)})})]}),a.questions&&a.questions.length>0&&(0,A.jsx)(xd,{content:a,onReset:t})]})}function Cd(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(null),{data:o}=j({queryKey:[`learning-categories`],queryFn:()=>F.get(`/api/learning/categories`)});return i?(0,A.jsx)(`div`,{className:`max-w-4xl mx-auto p-6`,children:(0,A.jsx)(Sd,{slug:i,onBack:()=>a(null)})}):(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Learning Hub`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Pediatric education, clinical pearls, and self-assessment quizzes.`})]}),(0,A.jsx)(`div`,{className:pd,children:(0,A.jsx)(`input`,{type:`search`,className:hd,placeholder:`Search topics, subjects…`,value:e,onChange:e=>t(e.target.value),"data-testid":`lh-search`})}),(0,A.jsxs)(`div`,{className:`flex flex-wrap gap-2`,"data-testid":`lh-categories`,children:[(0,A.jsx)(`button`,{type:`button`,onClick:()=>r(``),className:md+(n===``?` bg-primary text-primary-foreground border-primary`:` bg-muted hover:bg-muted/80`),children:`All`}),o?.categories.map(e=>(0,A.jsx)(`button`,{type:`button`,onClick:()=>r(e.slug),className:md+(n===e.slug?` bg-primary text-primary-foreground border-primary`:` bg-muted hover:bg-muted/80`),"data-testid":`lh-cat-`+e.slug,children:e.name},e.id))]}),(0,A.jsx)(Q,{filter:n,query:e.trim(),onOpen:e=>a(e)})]})}var wd={mrc:{title:`MRC strength grade (0–5)`,icon:`fa-hand-fist`,rows:[[`5`,`Normal power — holds against full resistance`],[`4`,`Reduced — moves against gravity + some resistance`],[`3`,`Moves against gravity only (no added resistance)`],[`2`,`Full range with gravity eliminated (horizontal plane)`],[`1`,`Flicker / trace contraction, no joint movement`],[`0`,`No contraction`]]},dtr:{title:`Deep-tendon reflex grade (0–4+)`,icon:`fa-circle-dot`,rows:[[`0`,`Absent`],[`1+`,`Hypoactive — trace, only with reinforcement`],[`2+`,`Normal`],[`3+`,`Brisk — may still be normal in anxious patients`],[`4+`,`Hyperactive with sustained clonus — always abnormal`]]},plantar:{title:`Plantar response (Babinski)`,icon:`fa-shoe-prints`,rows:[[`Down-going`,`Normal in anyone ≥ 2 years`],[`Up-going`,`Normal < 2 years; abnormal after — UMN lesion`],[`Asymmetric`,`Always abnormal at any age`]]},beighton:{title:`Beighton hypermobility score (0–9)`,icon:`fa-hands`,rows:[[`≤ 3`,`Normal flexibility`],[`4`,`Borderline — consider in context`],[`≥ 5`,`Hypermobility spectrum; screen for hEDS if other features present`]]},atr:{title:`Scoliometer — angle of trunk rotation`,icon:`fa-ruler`,rows:[[`< 5°`,`Normal, no follow-up`],[`5–6°`,`Borderline — re-check at each visit`],[`≥ 7°`,`Refer for PA/lateral spine x-ray + orthopedic evaluation`]]},rr:{title:`Respiratory rate — upper limit by age (awake)`,icon:`fa-lungs`,rows:[[`Newborn`,`≤ 60 /min`],[`< 2 months`,`≤ 60 /min (WHO tachypnea cutoff)`],[`2–12 months`,`≤ 50 /min (WHO tachypnea cutoff)`],[`1–5 years`,`≤ 40 /min (WHO tachypnea cutoff)`],[`6–11 years`,`≤ 30 /min`],[`≥ 12 years`,`≤ 20 /min (adult pattern)`]]},spo2:{title:`Pulse oximetry (SpO₂) — at room air`,icon:`fa-heart-pulse`,rows:[[`≥ 95%`,`Normal`],[`92–94%`,`Mild hypoxemia — investigate cause`],[`< 92%`,`Moderate hypoxemia — supplemental O₂`],[`< 88%`,`Severe — urgent intervention; target ≥ 90% acutely`]]},silverman:{title:`Silverman–Andersen retraction score (neonatal, 0–10)`,icon:`fa-baby`,rows:[[`0`,`No respiratory distress`],[`1–3`,`Mild — close observation`],[`4–6`,`Moderate distress — consider CPAP / support`],[`7–10`,`Severe — imminent respiratory failure, intubate`]]},westley:{title:`Westley croup severity score`,icon:`fa-stethoscope`,rows:[[`≤ 2`,`Mild — home management, cool mist, oral dexamethasone`],[`3–5`,`Moderate — nebulised epinephrine + dexamethasone`],[`6–11`,`Severe — admit, continuous monitoring`],[`≥ 12`,`Impending respiratory failure — ICU / airway management`]]},murmurGrade:{title:`Heart-murmur grading (Levine 1–6)`,icon:`fa-wave-square`,rows:[[`1/6`,`Very faint — heard only with concentration`],[`2/6`,`Soft but readily heard`],[`3/6`,`Moderately loud, no thrill`],[`4/6`,`Loud WITH a palpable thrill`],[`5/6`,`Very loud; audible with stethoscope just off the chest`],[`6/6`,`Audible without the stethoscope touching the chest`]]},pulseAmp:{title:`Pulse amplitude grade (0–4)`,icon:`fa-heart-pulse`,rows:[[`0`,`Absent`],[`1+`,`Diminished, thready`],[`2+`,`Normal`],[`3+`,`Bounding`],[`4+`,`Bounding with visible pulsation (e.g., aortic regurgitation)`]]},capRefill:{title:`Capillary refill time`,icon:`fa-hand`,rows:[[`< 2 sec`,`Normal`],[`2–3 sec`,`Borderline — consider hydration / perfusion`],[`≥ 3 sec`,`Delayed — dehydration, shock, low cardiac output`]]}},Td=[{letter:`A`,color:`#dc2626`,title:`Aortic area`,location:`2nd ICS, right sternal border`,listen:`S2 (aortic component), aortic stenosis, aortic regurgitation`},{letter:`P`,color:`#2563eb`,title:`Pulmonic area`,location:`2nd ICS, left sternal border`,listen:`S2 (pulmonic component), pulmonic stenosis, PDA, physiologic split of S2`,innocent:`Pulmonary flow murmur (children, adolescents) — upper left sternal border`},{letter:`E`,color:`#059669`,title:`Erb's point`,location:`3rd ICS, left sternal border`,listen:`Aortic regurgitation (best here), transitional zone murmurs`,innocent:`Still's murmur classically radiates to Erb's / LLSB`},{letter:`T`,color:`#d97706`,title:`Tricuspid area`,location:`4th–5th ICS, lower left sternal border`,listen:`Tricuspid regurgitation, VSD, S3/S4, holosystolic murmurs`,innocent:`Still's murmur — vibratory, musical, age 3–7 y (loudest between LLSB and apex)`},{letter:`M`,color:`#7c3aed`,title:`Mitral area (apex)`,location:`5th ICS, mid-clavicular line`,listen:`S1, mitral regurgitation, mitral stenosis (with bell, left-lateral decubitus)`}],Ed=[{name:`Still's (vibratory) murmur`,age:`3–7 y (most common in children)`,location:`LLSB, radiating to apex`,character:`Low-frequency vibratory / musical systolic, grade 2–3/6, mid-systolic, "twanging-string" quality`,confirm:`Louder supine, softer or disappears on standing or Valsalva. No radiation to neck/back. Normal S2.`},{name:`Pulmonary flow murmur`,age:`School-age and adolescents, thin chest`,location:`Upper left sternal border (2nd–3rd ICS)`,character:`Soft blowing early systolic ejection, grade 1–2/6, higher-pitched`,confirm:`No ejection click. Physiologic split of S2. Louder supine, softer on standing. No radiation.`},{name:`Venous hum`,age:`Ages 3–8, disappears by adolescence`,location:`Supraclavicular or infraclavicular area, usually right`,character:`Soft continuous hum, louder in diastole. Only innocent continuous murmur.`,confirm:`Disappears when supine OR when jugular vein is gently compressed (key maneuver). Turning head to opposite side also alters it.`},{name:`Carotid bruit / supraclavicular bruit`,age:`Children and adolescents`,location:`Supraclavicular fossa, right > left; may radiate to carotid`,character:`Brief early systolic, grade 2–3/6, higher-pitched than Still's`,confirm:`Softer or disappears with hyperextension of the shoulders. Normal cardiac exam otherwise. No radiation below the clavicles.`},{name:`Peripheral pulmonary stenosis (PPS, neonatal)`,age:`Newborns and infants < 6–12 months`,location:`Upper LSB, radiates to BOTH axillae and the back`,character:`Soft systolic ejection murmur, grade 1–2/6`,confirm:`Typical age + radiation to back/axillae. Resolves by age 1 as branch pulmonary arteries grow. Persistence or louder grade warrants echo.`}],Dd=[{key:`normal`,src:`/audio/respiratory/normal-vesicular.ogg`,title:`Normal vesicular breath sounds`,where:`Peripheral lung fields`,features:`Soft, rustling. Inspiration louder and longer than expiration.`,clinical:`Baseline — deviation elsewhere is what you listen for.`},{key:`wheeze`,src:`/audio/respiratory/wheeze.ogg`,title:`Wheeze`,where:`Diffuse in asthma; localised in foreign body`,features:`Continuous, high-pitched, musical. Usually expiratory; biphasic if severe.`,clinical:`Lower-airway narrowing — asthma, bronchiolitis, foreign body, bronchomalacia. Silent chest in severe asthma is an ominous sign.`},{key:`stridor`,src:`/audio/respiratory/stridor.ogg`,title:`Stridor`,where:`Louder over neck than chest — upper airway`,features:`Continuous, high-pitched, harsh. Classically inspiratory (extrathoracic obstruction); biphasic if fixed.`,clinical:`Croup, epiglottitis, foreign body, laryngomalacia (infant). Distinguish from wheeze by auscultating the neck — stridor is loudest there.`},{key:`finecrackles`,src:`/audio/respiratory/crackles-fine.ogg`,title:`Fine (end-inspiratory) crackles`,where:`Bibasilar in pulmonary edema/fibrosis; focal in pneumonia`,features:`Discontinuous, brief, high-pitched. "Velcro" quality. Late inspiratory, do NOT clear with cough.`,clinical:`Alveolar opening — pulmonary fibrosis, pulmonary edema, early pneumonia, atelectasis.`},{key:`coarsecrackles`,src:`/audio/respiratory/crackles-coarse.ogg`,title:`Coarse crackles`,where:`Lower lobes; either side`,features:`Discontinuous, longer and louder than fine crackles. Lower-pitched. Can be early or late inspiratory; often clear partly with cough.`,clinical:`Secretions in larger airways — bronchitis, later pneumonia, bronchiectasis, aspiration.`},{key:`rhonchi`,src:`/audio/respiratory/rhonchi.ogg`,title:`Rhonchi`,where:`Central or anywhere with airway secretions`,features:`Continuous, low-pitched, snore-like. Typically expiratory. Clear or change with cough.`,clinical:`Large-airway secretions — bronchitis, pneumonia with large-airway involvement, cystic fibrosis, bronchiectasis.`},{key:`pleuralrub`,src:`/audio/respiratory/pleural-rub.ogg`,title:`Pleural friction rub`,where:`Focal, often lateral or posterior lower chest`,features:`Grating, creaky — "leather on leather". Biphasic (heard in inspiration and expiration). Does NOT clear with cough.`,clinical:`Pleural inflammation — pleuritis, pulmonary embolism, pneumonia with pleural involvement, viral pleurisy.`}],Od=[{key:`normal`,src:`/audio/cardiac/normal.ogg`,title:`Normal heart sounds (S1, S2)`,where:`All four classic auscultation points`,rate:`~61 bpm reference`,features:`"lub-dub": S1 (closure of mitral + tricuspid) louder at apex; S2 (closure of aortic + pulmonic) louder at base. Physiologic S2 split on inspiration.`,clinical:`Reference for rhythm, rate, and the normal S1–S2 interval. Listen for what's changed — not just what's added.`},{key:`infant-normal`,src:`/audio/cardiac/infant-normal.ogg`,title:`Infant normal heart sounds`,where:`Infant chest — rate will be higher than adult`,rate:`Pediatric reference (120–160 bpm range)`,features:`Same S1–S2 pattern, faster rate. Short diastole makes murmurs easier to miss — careful auscultation needed.`,clinical:`Reference for neonatal/infant rhythm. Any murmur in the first 72 h should prompt pre/postductal sat screening.`},{key:`vsd`,src:`/audio/cardiac/vsd.wav`,title:`Ventricular septal defect (VSD)`,where:`Lower left sternal border (4th ICS)`,features:`Harsh, blowing, holosystolic (pansystolic) murmur — plateau shape through all of systole. Often accompanied by a thrill if large.`,clinical:`Most common congenital heart defect. Small VSD: loud murmur, usually asymptomatic, may close spontaneously. Large VSD: softer murmur (less pressure gradient) but signs of heart failure, pulmonary hypertension.`},{key:`mvp`,src:`/audio/cardiac/mitral-prolapse.wav`,title:`Mitral valve prolapse (MVP) — click + late systolic murmur`,where:`Apex (5th ICS, mid-clavicular line)`,features:`Mid-systolic click followed by a late-systolic crescendo murmur. Timing of click changes with maneuvers: earlier with standing or Valsalva, later with squatting.`,clinical:`Often benign, especially in thin young women. Features suggesting need for echo: thickened/redundant leaflets, associated MR, symptoms (palpitations, chest pain), arrhythmias.`},{key:`stills`,src:`/audio/cardiac/stills-murmur.ogg`,title:`Still's murmur (innocent)`,where:`LLSB, radiating to apex`,rate:`Classic age 3–7 y (this recording is a toddler)`,features:`Low-frequency vibratory / musical systolic, grade 2–3/6, mid-systolic, "twanging-string" quality.`,clinical:`The most common innocent murmur of childhood. Louder supine, softer or disappears on standing or Valsalva. Normal S2. No radiation to neck or back. No workup needed when classic.`},{key:`functional`,src:`/audio/cardiac/functional-murmur.wav`,title:`Functional (innocent) murmur — adult female`,where:`Left sternal border, soft systolic`,features:`Soft systolic murmur in a structurally normal heart — often from increased cardiac output, thin chest wall, anemia, hyperthyroidism, or pregnancy.`,clinical:`Benign if it meets the 7 S criteria. Investigate if loud (≥3/6), holosystolic, diastolic, radiating, or with thrill / symptoms.`}],kd=`rounded-lg border border-border bg-card p-5 space-y-3`,Ad=`rounded-md bg-primary text-primary-foreground px-3 py-2 text-sm font-medium disabled:opacity-50`;function jd({id:e,scale:t}){return(0,A.jsxs)(`section`,{className:kd,"data-testid":`scale-`+e,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:t.title}),(0,A.jsx)(`table`,{className:`w-full text-sm`,children:(0,A.jsx)(`tbody`,{children:t.rows.map(([e,t],n)=>(0,A.jsxs)(`tr`,{className:`border-b border-border last:border-0`,children:[(0,A.jsx)(`td`,{className:`py-1.5 pr-3 font-mono font-semibold whitespace-nowrap`,children:e}),(0,A.jsx)(`td`,{className:`py-1.5 text-muted-foreground`,children:t})]},n))})})]})}function Md({entry:e}){let t=(0,_.useRef)(null);return(0,A.jsxs)(`section`,{className:kd,"data-testid":`sound-`+e.key,children:[(0,A.jsx)(`h4`,{className:`text-sm font-semibold`,children:e.title}),(0,A.jsxs)(`audio`,{ref:t,controls:!0,preload:`none`,className:`w-full`,children:[(0,A.jsx)(`source`,{src:e.src}),`Your browser does not support HTML5 audio.`]}),(0,A.jsxs)(`div`,{className:`text-xs space-y-1`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide`,children:`Where:`}),` `,e.where]}),e.rate&&(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide`,children:`Rate:`}),` `,e.rate]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide`,children:`Features:`}),` `,e.features]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide`,children:`Clinical:`}),` `,e.clinical]})]})]})}function $(){return(0,A.jsxs)(`div`,{className:`max-w-5xl mx-auto p-6 space-y-6`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Physical Exam Guide`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Reference scales, the five cardiac auscultation points, innocent childhood murmurs, and sound libraries.`})]}),(0,A.jsxs)(`section`,{className:`rounded-md border border-amber-300 bg-amber-50 dark:bg-amber-950/30 p-4 text-sm space-y-2`,children:[(0,A.jsx)(`div`,{className:`font-semibold text-amber-900 dark:text-amber-100`,children:`Exam-step checklist lives in the legacy viewer`}),(0,A.jsx)(`p`,{className:`text-amber-900 dark:text-amber-100`,children:`The full age-group × system checklist and the Generate Exam Report flow still run in the legacy app. They port in a dedicated session so the clinical content can be verified entry-for-entry against the vanilla source.`}),(0,A.jsx)(`a`,{href:`/#peGuide`,className:Ad+` inline-block`,children:`Open checklist in legacy viewer`})]}),(0,A.jsxs)(`section`,{className:`space-y-3`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Grading scales & reference ranges`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-4`,children:Object.entries(wd).map(([e,t])=>(0,A.jsx)(jd,{id:e,scale:t},e))})]}),(0,A.jsxs)(`section`,{className:`space-y-3`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Cardiac auscultation — APTM (the five points)`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-4`,children:Td.map(e=>(0,A.jsxs)(`div`,{className:kd,"data-testid":`aptm-`+e.letter.toLowerCase(),children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,A.jsx)(`div`,{className:`w-10 h-10 rounded-full flex items-center justify-center font-bold text-white`,style:{background:e.color},children:e.letter}),(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:e.title})]}),(0,A.jsxs)(`div`,{className:`text-sm text-muted-foreground`,children:[(0,A.jsx)(`strong`,{children:`Location:`}),` `,e.location]}),(0,A.jsxs)(`div`,{className:`text-sm text-muted-foreground`,children:[(0,A.jsx)(`strong`,{children:`Listen for:`}),` `,e.listen]}),e.innocent&&(0,A.jsxs)(`div`,{className:`text-xs text-green-700 dark:text-green-300 bg-green-50 dark:bg-green-950/30 rounded px-2 py-1`,children:[(0,A.jsx)(`strong`,{children:`Innocent:`}),` `,e.innocent]})]},e.letter))})]}),(0,A.jsxs)(`section`,{className:`space-y-3`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Innocent murmurs of childhood`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-4`,children:Ed.map(e=>(0,A.jsxs)(`div`,{className:kd,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:e.name}),(0,A.jsxs)(`div`,{className:`text-sm space-y-1`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide text-xs`,children:`Age:`}),` `,e.age]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide text-xs`,children:`Location:`}),` `,e.location]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide text-xs`,children:`Character:`}),` `,e.character]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide text-xs`,children:`Confirm benign:`}),` `,e.confirm]})]})]},e.name))})]}),(0,A.jsxs)(`section`,{className:`space-y-3`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Respiratory sounds library`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-4`,children:Dd.map(e=>(0,A.jsx)(Md,{entry:e},e.key))})]}),(0,A.jsxs)(`section`,{className:`space-y-3`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Cardiac sounds library`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-4`,children:Od.map(e=>(0,A.jsx)(Md,{entry:e},e.key))})]})]})}var Nd=`rounded-lg border border-border bg-card p-5 space-y-3`,Pd=`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium`,Fd=[{id:`neonatal`,label:`Neonatal`,icon:`👶`,summary:`GA classification, AGA/SGA/LGA, prematurity category (Fenton 2013 / WHO).`},{id:`airway`,label:`Airway / RSI`,icon:`💨`,summary:`ETT size + depth, RSI induction + paralytic dosing by weight.`},{id:`cardiac`,label:`Cardiac Arrest`,icon:`❤️`,summary:`PALS dosing (epinephrine, amiodarone, lidocaine), defibrillation J/kg.`},{id:`respiratory`,label:`Respiratory`,icon:`🫁`,summary:`Asthma, bronchiolitis, croup severity + dosing.`},{id:`ventilation`,label:`O₂ & Ventilation`,icon:`🌀`,summary:`NC / HFNC / CPAP / BiPAP flow + FiO₂ targets by age.`},{id:`seizure`,label:`Seizures`,icon:`🧠`,summary:`Benzodiazepine + second/third-line weight-based dosing.`},{id:`sepsis`,label:`Sepsis & Fever`,icon:`🦠`,summary:`Empirical antibiotics + fluid bolus dosing by weight.`},{id:`anaphylaxis`,label:`Anaphylaxis`,icon:`💉`,summary:`Epinephrine IM, IV infusion, steroid + antihistamine dosing.`},{id:`sedation`,label:`Sedation`,icon:`🛌`,summary:`Procedural sedation regimens — ketamine, propofol, midazolam.`},{id:`agitation`,label:`Agitation`,icon:`😤`,summary:`Weight-based haloperidol, olanzapine, lorazepam.`},{id:`antiemetics`,label:`Antiemetics`,icon:`💊`,summary:`Ondansetron, metoclopramide, promethazine dosing.`},{id:`antimicrobials`,label:`Antimicrobials`,icon:`🧫`,summary:`Common empirical regimens keyed to syndrome + weight.`},{id:`burns`,label:`Burns`,icon:`🔥`,summary:`TBSA % (Lund-Browder, Rule of Nines-children), Parkland fluids.`},{id:`toxicology`,label:`Toxicology`,icon:`☠️`,summary:`Common toxidromes + antidotes + decontamination windows.`},{id:`trauma`,label:`Trauma`,icon:`🩹`,summary:`PECARN, c-spine, blood-product dosing, TXA.`}];function Id({pill:e}){return(0,A.jsxs)(`section`,{className:Nd,"data-testid":`bedside-panel-`+e.id,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,A.jsx)(`span`,{className:`text-2xl`,"aria-hidden":!0,children:e.icon}),(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:e.label})]}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:e.summary}),(0,A.jsx)(`div`,{className:`rounded-md border border-amber-300 bg-amber-50 dark:bg-amber-950/30 p-3 text-sm space-y-2`,children:(0,A.jsx)(`p`,{className:`text-amber-900 dark:text-amber-100`,children:`Weight-based calculators for this module run in the legacy viewer while the clinical data is verified for a direct React port. Open the legacy Bedside tab to use the full dosing flow.`})}),(0,A.jsx)(`a`,{href:`/#bedside`,className:Pd+` inline-block`,children:`Open in legacy viewer`})]})}function Ld(){let[e,t]=(0,_.useState)(Fd[0].id),n=Fd.find(t=>t.id===e)??Fd[0];return(0,A.jsxs)(`div`,{className:`max-w-5xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Bedside`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Emergency and rapid-reference pediatric tools. Weight-based dosing throughout — always verify against institutional protocols.`})]}),(0,A.jsx)(`div`,{className:`flex flex-wrap gap-2`,"data-testid":`bedside-subnav`,children:Fd.map(n=>(0,A.jsxs)(`button`,{type:`button`,onClick:()=>t(n.id),className:`px-3 py-1.5 rounded-full text-xs font-medium border transition-colors `+(e===n.id?`bg-primary text-primary-foreground border-primary`:`bg-muted hover:bg-muted/80 border-border`),"data-testid":`bedside-pill-`+n.id,children:[(0,A.jsx)(`span`,{className:`mr-1`,"aria-hidden":!0,children:n.icon}),n.label]},n.id))}),(0,A.jsx)(Id,{pill:n})]})}var Rd=`rounded-lg border border-border bg-card p-5 space-y-3`,zd=`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium`,Bd=[{id:`bp`,label:`BP Percentile`,summary:`AAP 2017 age/height/sex-adjusted BP percentiles (Rosner quantile splines).`,source:`AAP 2017 (Flynn) — Rosner splines`},{id:`bmi`,label:`BMI Percentile`,summary:`BMI-for-age (CDC 2000 z-score tables).`,source:`CDC 2000 LMS`},{id:`growth`,label:`Growth Charts`,summary:`WHO 0–2 y / CDC 2–20 y; Fenton 2013 preterm (weight, length, head).`,source:`WHO 2006 + CDC 2000 + Fenton 2013 LMS`},{id:`bili`,label:`Bilirubin`,summary:`AAP 2022 phototherapy + exchange thresholds and Bhutani nomogram risk zones.`,source:`AAP 2022 (Kemper) + Bhutani 1999`},{id:`vitals`,label:`Vital Signs`,summary:`Normal HR / RR / BP ranges by age.`,source:`PALS + AHA reference`},{id:`bsa`,label:`Body Surface Area`,summary:`Mosteller + DuBois formulas.`,source:`Mosteller 1987 / DuBois 1916`},{id:`dose`,label:`Weight-Based Dosing`,summary:`Common pediatric med doses per kg + max dose.`,source:`Lexicomp / Harriet Lane`},{id:`resus`,label:`Resus Meds`,summary:`Code-cart dosing (epinephrine, amiodarone, atropine, etc.).`,source:`PALS`},{id:`gcs`,label:`GCS`,summary:`Child/adult and infant Glasgow Coma Scale variants.`,source:`Teasdale + pediatric modification`},{id:`equipment`,label:`Equipment`,summary:`ETT size, blade, NG, Foley, suction by age/weight.`,source:`PALS + Broselow cross-reference`}];function Vd({pill:e}){return(0,A.jsxs)(`section`,{className:Rd,"data-testid":`calc-panel-`+e.id,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:e.label}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:e.summary}),(0,A.jsxs)(`div`,{className:`rounded-md border border-amber-300 bg-amber-50 dark:bg-amber-950/30 p-3 text-sm space-y-2`,children:[(0,A.jsxs)(`p`,{className:`text-amber-900 dark:text-amber-100`,children:[(0,A.jsx)(`strong`,{children:`Source of truth:`}),` `,e.source,`.`]}),(0,A.jsx)(`p`,{className:`text-amber-900 dark:text-amber-100`,children:`This calculator runs in the legacy viewer. A React port is gated on capturing test vectors from the vanilla implementation so the numerical output can be verified byte-for-byte — the migration checkpoint specifically flags this class of data as the one an LLM is most likely to silently simplify.`})]}),(0,A.jsx)(`a`,{href:`/#calculators`,className:zd+` inline-block`,children:`Open in legacy viewer`})]})}function Hd(){let[e,t]=(0,_.useState)(Bd[0].id),n=Bd.find(t=>t.id===e)??Bd[0];return(0,A.jsxs)(`div`,{className:`max-w-5xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Calculators`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Pediatric calculators — BP percentiles, bilirubin thresholds, growth, dosing, equipment sizing. Formula ports arrive one at a time, each verified against captured test vectors.`})]}),(0,A.jsx)(`div`,{className:`flex flex-wrap gap-2`,"data-testid":`calc-subnav`,children:Bd.map(n=>(0,A.jsx)(`button`,{type:`button`,onClick:()=>t(n.id),className:`px-3 py-1.5 rounded-full text-xs font-medium border transition-colors `+(e===n.id?`bg-primary text-primary-foreground border-primary`:`bg-muted hover:bg-muted/80 border-border`),"data-testid":`calc-pill-`+n.id,children:n.label},n.id))}),(0,A.jsx)(Vd,{pill:n})]})}var Ud=`rounded-lg border border-border bg-card p-5 space-y-3`,Wd=`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium`;function Gd(){let{data:e,isLoading:t}=j({queryKey:[`auth-me`],queryFn:()=>F.get(`/api/auth/me`),staleTime:5*6e4});return t?(0,A.jsx)(`div`,{className:`max-w-3xl mx-auto p-6`,children:(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Checking permissions…`})}):e?.user.role===`admin`?(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Admin Panel`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Registration, users, feature flags, OIDC, email templates, AI prompts, SMTP, saved encounters (site-wide), AI model management, TTS/STT provider selection.`})]}),(0,A.jsxs)(`section`,{className:Ud,"data-testid":`admin-shell`,children:[(0,A.jsx)(`div`,{className:`rounded-md border border-amber-300 bg-amber-50 dark:bg-amber-950/30 p-3 text-sm space-y-2`,children:(0,A.jsx)(`p`,{className:`text-amber-900 dark:text-amber-100`,children:`Admin sub-sections still live in the legacy viewer. Each one (user management, feature flags, announcements, OIDC config, SMTP, AI prompts, model management, TTS/STT) touches production state immediately when saved — those controls port in discrete follow-up commits with their own tests before they go live in the React tree.`})}),(0,A.jsx)(`a`,{href:`/#admin`,className:Wd+` inline-block`,children:`Open admin in legacy viewer`})]})]}):(0,A.jsx)(`div`,{className:`max-w-3xl mx-auto p-6`,children:(0,A.jsxs)(`section`,{className:Ud,"data-testid":`admin-access-denied`,children:[(0,A.jsx)(`h1`,{className:`text-xl font-semibold`,children:`Admin only`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`This page is restricted to users with the admin role. If you believe this is a mistake, contact your site administrator.`})]})})}var Kd=new Ze({defaultOptions:{queries:{staleTime:3e4,retry:1}}});function qd(){return(0,A.jsxs)(`div`,{className:`max-w-3xl mx-auto p-6 space-y-4`,children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Pediatric AI Scribe — React client`}),(0,A.jsxs)(`p`,{className:`text-sm text-muted-foreground`,children:[`This is the new React tree. The legacy vanilla-JS app still lives at`,` `,(0,A.jsx)(`a`,{href:`/`,className:`underline`,children:`/`}),`.`]}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Ported tabs so far:`}),(0,A.jsxs)(`ul`,{className:`list-disc pl-6 text-sm space-y-1`,children:[(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/encounter`,className:`underline`,children:`Encounter HPI`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/dictation`,className:`underline`,children:`Dictation HPI`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/soap`,className:`underline`,children:`SOAP Note`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/sickvisit`,className:`underline`,children:`Sick Visit`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/extensions`,className:`underline`,children:`Extensions & Pagers`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/faq`,className:`underline`,children:`FAQ`})})]})]})}function Jd(){return(0,A.jsx)(tt,{client:Kd,children:(0,A.jsx)(ti,{basename:`/app`,children:(0,A.jsx)(mr,{children:(0,A.jsxs)(fr,{element:(0,A.jsx)(Ei,{}),children:[(0,A.jsx)(fr,{path:`/`,element:(0,A.jsx)(qd,{})}),(0,A.jsx)(fr,{path:`/encounter`,element:(0,A.jsx)(Cu,{})}),(0,A.jsx)(fr,{path:`/dictation`,element:(0,A.jsx)(Su,{})}),(0,A.jsx)(fr,{path:`/soap`,element:(0,A.jsx)(wu,{})}),(0,A.jsx)(fr,{path:`/sickvisit`,element:(0,A.jsx)(Tu,{})}),(0,A.jsx)(fr,{path:`/hospital`,element:(0,A.jsx)(Eu,{})}),(0,A.jsx)(fr,{path:`/chart`,element:(0,A.jsx)(Ou,{})}),(0,A.jsx)(fr,{path:`/wellvisit`,element:(0,A.jsx)(ku,{})}),(0,A.jsx)(fr,{path:`/vaxschedule`,element:(0,A.jsx)(Au,{})}),(0,A.jsx)(fr,{path:`/catchup`,element:(0,A.jsx)(ju,{})}),(0,A.jsx)(fr,{path:`/extensions`,element:(0,A.jsx)(vu,{})}),(0,A.jsx)(fr,{path:`/settings`,element:(0,A.jsx)(fd,{})}),(0,A.jsx)(fr,{path:`/learning`,element:(0,A.jsx)(Cd,{})}),(0,A.jsx)(fr,{path:`/peguide`,element:(0,A.jsx)($,{})}),(0,A.jsx)(fr,{path:`/bedside`,element:(0,A.jsx)(Ld,{})}),(0,A.jsx)(fr,{path:`/calculators`,element:(0,A.jsx)(Hd,{})}),(0,A.jsx)(fr,{path:`/admin`,element:(0,A.jsx)(Gd,{})}),(0,A.jsx)(fr,{path:`/faq`,element:(0,A.jsx)(xu,{})}),(0,A.jsx)(fr,{path:`*`,element:(0,A.jsx)(ur,{to:`/`,replace:!0})})]})})})})}(0,v.createRoot)(document.getElementById(`root`)).render((0,A.jsx)(_.StrictMode,{children:(0,A.jsx)(Jd,{})})); \ No newline at end of file +CORRECTED TO: `);return t===-1?{original:e.trim(),corrected:``}:{original:e.substring(0,t).replace(/^ORIGINAL:\s*/i,``).trim(),corrected:e.substring(t+15).trim()}}function cd(){let e=et(),[t,n]=(0,_.useState)({}),[r,i]=(0,_.useState)(null),[a,o]=(0,_.useState)(null),{data:s}=j({queryKey:[`memories`],queryFn:()=>F.get(`/api/memories`)}),c=(s?.memories||[]).filter(e=>e.category.startsWith(`correction_`)),l=M({mutationFn:e=>F.delete(`/api/memories/`+e),onSuccess:()=>{i({text:`Correction deleted`,kind:`info`}),e.invalidateQueries({queryKey:[`memories`]})},onError:e=>i({text:e.message||`Delete failed`,kind:`err`})});return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`corrections-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`AI Learning (Corrections)`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`The AI automatically learns from your edits. When you modify AI-generated text and save, corrections are stored here and applied to future notes. Latest 20 per section.`}),(0,A.jsx)(`div`,{className:`space-y-2`,children:c.length===0?(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`No corrections yet. Edit AI-generated notes and save to start learning.`}):c.map(e=>{let r=t[e.id],i=sd(e.content||``),a=e.created_at?new Date(e.created_at).toLocaleDateString():``;return(0,A.jsxs)(`div`,{className:`rounded-md bg-muted/40 border border-border overflow-hidden`,"data-testid":`corr-row-`+e.id,children:[(0,A.jsxs)(`button`,{type:`button`,className:`w-full flex items-center gap-2 px-3 py-2 text-left`,onClick:()=>n({...t,[e.id]:!r}),children:[(0,A.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:r?`▾`:`▸`}),(0,A.jsx)(`span`,{className:`text-xs text-muted-foreground uppercase tracking-wide`,children:od[e.category]||e.category}),(0,A.jsx)(`span`,{className:`flex-1 text-sm truncate`,children:e.name}),(0,A.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:a}),(0,A.jsx)(`span`,{role:`button`,className:`text-destructive text-xs px-2`,onClick:t=>{t.stopPropagation(),o(e)},"data-testid":`btn-corr-delete-`+e.id,children:`Delete`})]}),r&&(0,A.jsxs)(`div`,{className:`px-3 py-2 text-sm space-y-2 border-t border-border`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`div`,{className:`text-xs font-semibold uppercase text-destructive mb-1`,children:`Original (AI generated):`}),(0,A.jsx)(`div`,{className:`whitespace-pre-wrap text-muted-foreground`,children:i.original})]}),i.corrected&&(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`div`,{className:`text-xs font-semibold uppercase text-green-600 mb-1`,children:`Corrected to:`}),(0,A.jsx)(`div`,{className:`whitespace-pre-wrap`,children:i.corrected})]})]})]},e.id)})}),(0,A.jsx)(Ru,{msg:r}),(0,A.jsx)(Mu,{open:!!a,title:`Delete this correction?`,body:`The AI will no longer apply this edit in future notes.`,confirmText:`Delete`,danger:!0,busy:l.isPending,onConfirm:()=>{a&&l.mutate(a.id),o(null)},onCancel:()=>o(null)})]})}function ld(){let e=et(),[t,n]=(0,_.useState)(null),[r,i]=(0,_.useState)(null),{data:a,isLoading:o}=j({queryKey:[`audio-backups`],queryFn:()=>F.get(`/api/audio-backups`)}),s=M({mutationFn:e=>F.delete(`/api/audio-backups/`+e),onSuccess:()=>{n({text:`Backup deleted`,kind:`info`}),e.invalidateQueries({queryKey:[`audio-backups`]})},onError:e=>n({text:e.message,kind:`err`})});function c(e){window.open(`/api/audio-backups/`+e+`/audio`,`_blank`,`noopener,noreferrer`)}return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`audio-backups-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Audio Backups`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Recordings are automatically backed up on the server and kept for 24 hours. Retry flow ports with the recording components.`}),o&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`}),(0,A.jsxs)(`div`,{className:`space-y-2`,children:[a&&a.backups.length===0&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`No audio backups.`}),a?.backups.map(e=>{let t=Math.round(e.size_bytes/1024),n=e.compressed_bytes?` (`+Math.round(e.compressed_bytes/1024)+` KB compressed)`:``;return(0,A.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-2 rounded-md bg-muted/40 border border-border`,"data-testid":`audio-backup-row-`+e.id,children:[(0,A.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,A.jsxs)(`div`,{className:`text-sm font-medium`,children:[e.module,` recording`]}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground`,children:[new Date(e.created_at).toLocaleString(),` · `,t,` KB`,n,` · `,zu(e.created_at)]})]}),(0,A.jsx)(`button`,{type:`button`,className:Fu+` text-xs`,onClick:()=>c(e.id),children:`Play`}),(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive text-xs`,onClick:()=>i(e),"data-testid":`btn-audio-delete-`+e.id,children:`Delete`})]},e.id)})]}),(0,A.jsx)(Ru,{msg:t}),(0,A.jsx)(Mu,{open:!!r,title:`Delete this audio backup?`,body:`This cannot be undone.`,confirmText:`Delete`,danger:!0,busy:s.isPending,onConfirm:()=>{r&&s.mutate(r.id),i(null)},onCancel:()=>i(null)})]})}function ud(){let e=et(),[t,n]=(0,_.useState)(null),[r,i]=(0,_.useState)(null),{data:a,isLoading:o}=j({queryKey:[`saved-encounters`],queryFn:()=>F.get(`/api/encounters/saved`)}),s=M({mutationFn:e=>F.delete(`/api/encounters/saved/`+e),onSuccess:()=>{n({text:`Deleted`,kind:`info`}),e.invalidateQueries({queryKey:[`saved-encounters`]})},onError:e=>n({text:e.message,kind:`err`})});return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`saved-encounters-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Saved Encounters`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Encounters are automatically deleted after 7 days per site policy. Resume action ports with the encounter components.`}),o&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`}),(0,A.jsxs)(`div`,{className:`space-y-2`,children:[a&&a.encounters.length===0&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`No saved encounters.`}),a?.encounters.map(e=>{let t=new Date(e.updated_at).toLocaleDateString(),n=new Date(e.expires_at).toLocaleDateString(),r=(e.transcript_preview||``).slice(0,80);return(0,A.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-2 rounded-md bg-muted/40 border border-border`,"data-testid":`enc-row-`+e.id,children:[(0,A.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-2 text-sm`,children:[(0,A.jsx)(`span`,{className:`font-medium truncate`,children:e.label||`Untitled`}),(0,A.jsx)(`span`,{className:`text-xs text-muted-foreground uppercase tracking-wide`,children:e.enc_type})]}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground truncate`,children:[t,` · expires `,n,r?` · `+r+`…`:``]})]}),(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive text-xs`,onClick:()=>i(e),"data-testid":`btn-enc-delete-`+e.id,children:`Delete`})]},e.id)})]}),(0,A.jsx)(Ru,{msg:t}),(0,A.jsx)(Mu,{open:!!r,title:`Delete this saved encounter?`,body:r?.label||void 0,confirmText:`Delete`,danger:!0,busy:s.isPending,onConfirm:()=>{r&&s.mutate(r.id),i(null)},onCancel:()=>i(null)})]})}function dd(){return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`compliance-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Compliance & Usage`}),(0,A.jsxs)(`div`,{className:`text-sm space-y-2`,children:[(0,A.jsxs)(`p`,{children:[(0,A.jsx)(`strong`,{children:`AWS Bedrock`}),` is available with a Business Associate Agreement (BAA) for HIPAA-eligible workloads.`]}),(0,A.jsxs)(`ul`,{className:`list-disc pl-5 space-y-1 text-muted-foreground`,children:[(0,A.jsx)(`li`,{children:`All connections use HTTPS/TLS encryption`}),(0,A.jsx)(`li`,{children:`Authentication with optional 2FA`}),(0,A.jsx)(`li`,{children:`No patient data stored on server beyond session`}),(0,A.jsx)(`li`,{children:`AWS Bedrock supports BAA for HIPAA compliance`}),(0,A.jsx)(`li`,{children:`Azure OpenAI supports BAA for HIPAA compliance`})]}),(0,A.jsxs)(`p`,{children:[(0,A.jsx)(`strong`,{children:`Important:`}),` Check with your institution's guidelines and policies before use. This tool is not intended for production clinical use without proper organizational authorization and provider BAAs in place. Use with caution.`]})]})]})}function fd(){let{data:e,isLoading:t,error:n}=j({queryKey:[`auth-me`],queryFn:()=>F.get(`/api/auth/me`)}),r=e?.user.canLocalAuth!==!1;return(0,A.jsxs)(`div`,{className:`max-w-3xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Settings`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Account security, integrations, and personal templates. More sub-sections port over in follow-up commits.`})]}),t&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`}),n&&(0,A.jsxs)(`div`,{className:`text-sm text-destructive`,children:[`Could not load your account: `,n.message]}),e&&r&&(0,A.jsxs)(A.Fragment,{children:[(0,A.jsx)(Bu,{}),(0,A.jsx)(Hu,{user:e.user}),(0,A.jsx)(Wu,{})]}),e&&!r&&(0,A.jsxs)(`section`,{className:Nu,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Account managed by single sign-on`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Your password and two-factor authentication are managed by your identity provider.`})]}),e&&(0,A.jsx)(Z,{user:e.user}),e&&(0,A.jsx)(qu,{}),e&&(0,A.jsx)(Ju,{}),e&&(0,A.jsx)(ed,{}),e&&(0,A.jsx)(nd,{}),e&&(0,A.jsx)(ad,{}),e&&(0,A.jsx)(cd,{}),e&&(0,A.jsx)(ld,{}),e&&(0,A.jsx)(ud,{}),(0,A.jsx)(dd,{})]})}var pd=`rounded-lg border border-border bg-card p-5 space-y-3`,md=`px-3 py-1 rounded-full text-xs font-medium border transition-colors cursor-pointer`,hd=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`,gd=`rounded-md bg-primary text-primary-foreground px-3 py-2 text-sm font-medium disabled:opacity-50`,_d=`rounded-md border border-border px-3 py-2 text-sm disabled:opacity-50`;function vd(e){switch(e){case`quiz`:return`Quiz`;case`pearl`:return`Pearl`;case`presentation`:return`Slides`;default:return`Article`}}function yd({row:e,onOpen:t}){return(0,A.jsxs)(`button`,{type:`button`,onClick:t,className:`w-full text-left rounded-lg border border-border bg-card hover:bg-muted/60 p-4 transition-colors`,"data-testid":`lh-feed-item-`+e.slug,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-2 text-xs text-muted-foreground uppercase tracking-wide mb-1`,children:[(0,A.jsx)(`span`,{className:`font-semibold`,children:vd(e.content_type)}),e.category_name&&(0,A.jsxs)(`span`,{children:[`· `,e.category_name]}),e.question_count?(0,A.jsxs)(`span`,{children:[`· `,e.question_count,` Q`]}):null]}),(0,A.jsx)(`div`,{className:`text-sm font-semibold`,children:e.title}),e.subject&&(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground mt-0.5 truncate`,children:e.subject})]})}function Q({filter:e,query:t,onOpen:n}){let{data:r,isLoading:i,error:a}=j({queryKey:t?[`learning-search`,t]:e?[`learning-category`,e]:[`learning-feed`],queryFn:()=>t?F.get(`/api/learning/search?q=`+encodeURIComponent(t)):e?F.get(`/api/learning/category/`+encodeURIComponent(e)):F.get(`/api/learning/feed?limit=30`)});if(i)return(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`});if(a)return(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:a.message});let o=r?.content||[];return o.length===0?(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground italic py-4`,children:`No content found.`}):(0,A.jsx)(`div`,{className:`grid grid-cols-1 sm:grid-cols-2 gap-3`,"data-testid":`lh-feed`,children:o.map(e=>(0,A.jsx)(yd,{row:e,onOpen:()=>n(e.slug)},e.id))})}function bd(e){let t={};for(let n of e)t[n.id]={optionIds:new Set};return t}function xd({content:e,onReset:t}){let n=et(),[r,i]=(0,_.useState)(()=>bd(e.questions)),[a,o]=(0,_.useState)(null),[s,c]=(0,_.useState)(null),l=M({mutationFn:e=>F.post(`/api/learning/submit-quiz`,e),onSuccess:t=>{o(t),n.invalidateQueries({queryKey:[`learning-content`,e.slug]})},onError:e=>c(e.message||`Submit failed`)});function u(t){t.preventDefault(),c(null);let n=e.questions.map(e=>{let t=r[e.id];return e.question_type===`multi`?{questionId:e.id,optionIds:Array.from(t?.optionIds||[])}:{questionId:e.id,optionId:t?.optionId??null}});l.mutate({contentId:e.id,answers:n})}function d(e,t){i(n=>({...n,[e.id]:{optionId:t,optionIds:new Set}}))}function f(e,t){i(n=>{let r=new Set(n[e.id]?.optionIds||[]);return r.has(t)?r.delete(t):r.add(t),{...n,[e.id]:{optionIds:r}}})}if(a){let n=a.percentage>=80?`bg-green-600`:a.percentage>=50?`bg-amber-500`:`bg-destructive`;return(0,A.jsxs)(`section`,{className:pd,"data-testid":`lh-quiz-results`,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Results`}),(0,A.jsxs)(`span`,{className:`px-2 py-0.5 rounded text-xs font-semibold text-white `+n,"data-testid":`lh-quiz-score`,children:[a.score,`/`,a.total,` (`,a.percentage,`%)`]})]}),(0,A.jsx)(`div`,{className:`space-y-3`,children:a.results.map((e,t)=>(0,A.jsxs)(`div`,{className:`rounded-md border border-border p-3 bg-muted/30`,children:[(0,A.jsxs)(`div`,{className:`text-sm font-medium`,children:[(0,A.jsx)(`span`,{className:e.isCorrect?`text-green-600`:`text-destructive`,children:e.isCorrect?`✓`:`✗`}),` `,`Q`,t+1,`: `,e.questionText]}),!e.isCorrect&&e.correctOptionText&&(0,A.jsxs)(`div`,{className:`text-xs text-green-700 mt-1`,children:[(0,A.jsx)(`strong`,{children:`Correct:`}),` `,e.correctOptionText]}),!e.isCorrect&&e.selectedExplanation&&(0,A.jsxs)(`div`,{className:`text-xs text-destructive mt-1`,children:[(0,A.jsx)(`strong`,{children:`Why incorrect:`}),` `,e.selectedExplanation]}),e.generalExplanation&&(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground mt-1`,children:e.generalExplanation})]},e.questionId))}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsx)(`button`,{type:`button`,className:_d,onClick:()=>{o(null),i(bd(e.questions))},children:`Retake`}),(0,A.jsx)(`button`,{type:`button`,className:gd,onClick:t,children:`Back to Feed`})]})]})}return(0,A.jsxs)(`section`,{className:pd,"data-testid":`lh-quiz`,children:[(0,A.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Quiz`}),(0,A.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[e.questions.length,` question`,e.questions.length===1?``:`s`]})]}),(0,A.jsxs)(`form`,{onSubmit:u,className:`space-y-4`,children:[e.questions.map((e,t)=>{let n=e.question_type===`multi`,i=e.question_type===`true_false`?`True / False`:n?`Multiple Select`:`Single Choice`;return(0,A.jsxs)(`div`,{className:`rounded-md border border-border p-3 space-y-2 bg-muted/30`,children:[(0,A.jsxs)(`div`,{className:`flex items-center justify-between text-xs text-muted-foreground`,children:[(0,A.jsxs)(`span`,{className:`font-semibold`,children:[`Q`,t+1]}),(0,A.jsx)(`span`,{children:i})]}),(0,A.jsx)(`div`,{className:`text-sm font-medium`,children:e.question_text}),n&&(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground italic`,children:`Select all that apply`}),(0,A.jsx)(`div`,{className:`space-y-1`,children:e.options.map(t=>{let i=r[e.id],a=n?i?.optionIds.has(t.id)===!0:i?.optionId===t.id;return(0,A.jsxs)(`label`,{className:`flex items-start gap-2 text-sm cursor-pointer hover:bg-muted/50 rounded px-2 py-1`,children:[(0,A.jsx)(`input`,{type:n?`checkbox`:`radio`,name:`q-`+e.id,checked:a,onChange:()=>n?f(e,t.id):d(e,t.id),className:`mt-0.5`}),(0,A.jsx)(`span`,{children:t.option_text})]},t.id)})})]},e.id)}),s&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:s}),(0,A.jsx)(`button`,{type:`submit`,className:gd,disabled:l.isPending,"data-testid":`btn-lh-submit-quiz`,children:l.isPending?`Submitting…`:`Submit Answers`})]})]})}function Sd({slug:e,onBack:t}){let{data:n,isLoading:r,error:i}=j({queryKey:[`learning-content`,e],queryFn:()=>F.get(`/api/learning/content/`+encodeURIComponent(e))});if(r)return(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`});if(i)return(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:i.message});if(!n)return null;let a=n.content;return(0,A.jsxs)(`div`,{className:`space-y-4`,children:[(0,A.jsx)(`button`,{type:`button`,className:_d,onClick:t,"data-testid":`btn-lh-back`,children:`← Back to Feed`}),(0,A.jsxs)(`section`,{className:pd,"data-testid":`lh-viewer`,children:[(0,A.jsxs)(`div`,{className:`flex items-center justify-between gap-4`,children:[(0,A.jsx)(`h2`,{className:`text-xl font-semibold`,"data-testid":`lh-viewer-title`,children:a.title}),(0,A.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[vd(a.content_type),a.category_name?` · `+a.category_name:``,a.author_name?` · `+a.author_name:``]})]}),a.content_type===`presentation`?(0,A.jsxs)(`div`,{className:`text-center py-8 space-y-3 bg-muted/30 rounded-md`,children:[(0,A.jsx)(`div`,{className:`text-4xl`,children:`📊`}),(0,A.jsx)(`div`,{className:`text-sm font-medium`,children:a.title}),(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground`,children:`Slide rendering lives in the legacy viewer.`}),(0,A.jsx)(`a`,{href:`/#learning/`+encodeURIComponent(a.slug),className:gd+` inline-block`,rel:`noreferrer`,children:`Open in legacy viewer`})]}):(0,A.jsx)(`div`,{className:`whitespace-pre-wrap text-sm leading-relaxed`,"data-testid":`lh-viewer-body`,children:a.body||``})]}),a.progress&&a.progress.length>0&&(0,A.jsxs)(`section`,{className:pd,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Your past attempts`}),(0,A.jsx)(`div`,{className:`space-y-1 text-sm`,children:a.progress.map((e,t)=>{let n=e.total>0?Math.round(e.score/e.total*100):0,r=n>=70?`text-green-600`:`text-amber-600`;return(0,A.jsxs)(`div`,{className:`flex justify-between border-b border-border py-1`,children:[(0,A.jsx)(`span`,{children:new Date(e.completed_at).toLocaleDateString()}),(0,A.jsxs)(`span`,{className:`font-semibold `+r,children:[e.score,`/`,e.total,` (`,n,`%)`]})]},t)})})]}),a.questions&&a.questions.length>0&&(0,A.jsx)(xd,{content:a,onReset:t})]})}function Cd(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(null),{data:o}=j({queryKey:[`learning-categories`],queryFn:()=>F.get(`/api/learning/categories`)});return i?(0,A.jsx)(`div`,{className:`max-w-4xl mx-auto p-6`,children:(0,A.jsx)(Sd,{slug:i,onBack:()=>a(null)})}):(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Learning Hub`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Pediatric education, clinical pearls, and self-assessment quizzes.`})]}),(0,A.jsx)(`div`,{className:pd,children:(0,A.jsx)(`input`,{type:`search`,className:hd,placeholder:`Search topics, subjects…`,value:e,onChange:e=>t(e.target.value),"data-testid":`lh-search`})}),(0,A.jsxs)(`div`,{className:`flex flex-wrap gap-2`,"data-testid":`lh-categories`,children:[(0,A.jsx)(`button`,{type:`button`,onClick:()=>r(``),className:md+(n===``?` bg-primary text-primary-foreground border-primary`:` bg-muted hover:bg-muted/80`),children:`All`}),o?.categories.map(e=>(0,A.jsx)(`button`,{type:`button`,onClick:()=>r(e.slug),className:md+(n===e.slug?` bg-primary text-primary-foreground border-primary`:` bg-muted hover:bg-muted/80`),"data-testid":`lh-cat-`+e.slug,children:e.name},e.id))]}),(0,A.jsx)(Q,{filter:n,query:e.trim(),onOpen:e=>a(e)})]})}var wd={mrc:{title:`MRC strength grade (0–5)`,icon:`fa-hand-fist`,rows:[[`5`,`Normal power — holds against full resistance`],[`4`,`Reduced — moves against gravity + some resistance`],[`3`,`Moves against gravity only (no added resistance)`],[`2`,`Full range with gravity eliminated (horizontal plane)`],[`1`,`Flicker / trace contraction, no joint movement`],[`0`,`No contraction`]]},dtr:{title:`Deep-tendon reflex grade (0–4+)`,icon:`fa-circle-dot`,rows:[[`0`,`Absent`],[`1+`,`Hypoactive — trace, only with reinforcement`],[`2+`,`Normal`],[`3+`,`Brisk — may still be normal in anxious patients`],[`4+`,`Hyperactive with sustained clonus — always abnormal`]]},plantar:{title:`Plantar response (Babinski)`,icon:`fa-shoe-prints`,rows:[[`Down-going`,`Normal in anyone ≥ 2 years`],[`Up-going`,`Normal < 2 years; abnormal after — UMN lesion`],[`Asymmetric`,`Always abnormal at any age`]]},beighton:{title:`Beighton hypermobility score (0–9)`,icon:`fa-hands`,rows:[[`≤ 3`,`Normal flexibility`],[`4`,`Borderline — consider in context`],[`≥ 5`,`Hypermobility spectrum; screen for hEDS if other features present`]]},atr:{title:`Scoliometer — angle of trunk rotation`,icon:`fa-ruler`,rows:[[`< 5°`,`Normal, no follow-up`],[`5–6°`,`Borderline — re-check at each visit`],[`≥ 7°`,`Refer for PA/lateral spine x-ray + orthopedic evaluation`]]},rr:{title:`Respiratory rate — upper limit by age (awake)`,icon:`fa-lungs`,rows:[[`Newborn`,`≤ 60 /min`],[`< 2 months`,`≤ 60 /min (WHO tachypnea cutoff)`],[`2–12 months`,`≤ 50 /min (WHO tachypnea cutoff)`],[`1–5 years`,`≤ 40 /min (WHO tachypnea cutoff)`],[`6–11 years`,`≤ 30 /min`],[`≥ 12 years`,`≤ 20 /min (adult pattern)`]]},spo2:{title:`Pulse oximetry (SpO₂) — at room air`,icon:`fa-heart-pulse`,rows:[[`≥ 95%`,`Normal`],[`92–94%`,`Mild hypoxemia — investigate cause`],[`< 92%`,`Moderate hypoxemia — supplemental O₂`],[`< 88%`,`Severe — urgent intervention; target ≥ 90% acutely`]]},silverman:{title:`Silverman–Andersen retraction score (neonatal, 0–10)`,icon:`fa-baby`,rows:[[`0`,`No respiratory distress`],[`1–3`,`Mild — close observation`],[`4–6`,`Moderate distress — consider CPAP / support`],[`7–10`,`Severe — imminent respiratory failure, intubate`]]},westley:{title:`Westley croup severity score`,icon:`fa-stethoscope`,rows:[[`≤ 2`,`Mild — home management, cool mist, oral dexamethasone`],[`3–5`,`Moderate — nebulised epinephrine + dexamethasone`],[`6–11`,`Severe — admit, continuous monitoring`],[`≥ 12`,`Impending respiratory failure — ICU / airway management`]]},murmurGrade:{title:`Heart-murmur grading (Levine 1–6)`,icon:`fa-wave-square`,rows:[[`1/6`,`Very faint — heard only with concentration`],[`2/6`,`Soft but readily heard`],[`3/6`,`Moderately loud, no thrill`],[`4/6`,`Loud WITH a palpable thrill`],[`5/6`,`Very loud; audible with stethoscope just off the chest`],[`6/6`,`Audible without the stethoscope touching the chest`]]},pulseAmp:{title:`Pulse amplitude grade (0–4)`,icon:`fa-heart-pulse`,rows:[[`0`,`Absent`],[`1+`,`Diminished, thready`],[`2+`,`Normal`],[`3+`,`Bounding`],[`4+`,`Bounding with visible pulsation (e.g., aortic regurgitation)`]]},capRefill:{title:`Capillary refill time`,icon:`fa-hand`,rows:[[`< 2 sec`,`Normal`],[`2–3 sec`,`Borderline — consider hydration / perfusion`],[`≥ 3 sec`,`Delayed — dehydration, shock, low cardiac output`]]}},Td=[{letter:`A`,color:`#dc2626`,title:`Aortic area`,location:`2nd ICS, right sternal border`,listen:`S2 (aortic component), aortic stenosis, aortic regurgitation`},{letter:`P`,color:`#2563eb`,title:`Pulmonic area`,location:`2nd ICS, left sternal border`,listen:`S2 (pulmonic component), pulmonic stenosis, PDA, physiologic split of S2`,innocent:`Pulmonary flow murmur (children, adolescents) — upper left sternal border`},{letter:`E`,color:`#059669`,title:`Erb's point`,location:`3rd ICS, left sternal border`,listen:`Aortic regurgitation (best here), transitional zone murmurs`,innocent:`Still's murmur classically radiates to Erb's / LLSB`},{letter:`T`,color:`#d97706`,title:`Tricuspid area`,location:`4th–5th ICS, lower left sternal border`,listen:`Tricuspid regurgitation, VSD, S3/S4, holosystolic murmurs`,innocent:`Still's murmur — vibratory, musical, age 3–7 y (loudest between LLSB and apex)`},{letter:`M`,color:`#7c3aed`,title:`Mitral area (apex)`,location:`5th ICS, mid-clavicular line`,listen:`S1, mitral regurgitation, mitral stenosis (with bell, left-lateral decubitus)`}],Ed=[{name:`Still's (vibratory) murmur`,age:`3–7 y (most common in children)`,location:`LLSB, radiating to apex`,character:`Low-frequency vibratory / musical systolic, grade 2–3/6, mid-systolic, "twanging-string" quality`,confirm:`Louder supine, softer or disappears on standing or Valsalva. No radiation to neck/back. Normal S2.`},{name:`Pulmonary flow murmur`,age:`School-age and adolescents, thin chest`,location:`Upper left sternal border (2nd–3rd ICS)`,character:`Soft blowing early systolic ejection, grade 1–2/6, higher-pitched`,confirm:`No ejection click. Physiologic split of S2. Louder supine, softer on standing. No radiation.`},{name:`Venous hum`,age:`Ages 3–8, disappears by adolescence`,location:`Supraclavicular or infraclavicular area, usually right`,character:`Soft continuous hum, louder in diastole. Only innocent continuous murmur.`,confirm:`Disappears when supine OR when jugular vein is gently compressed (key maneuver). Turning head to opposite side also alters it.`},{name:`Carotid bruit / supraclavicular bruit`,age:`Children and adolescents`,location:`Supraclavicular fossa, right > left; may radiate to carotid`,character:`Brief early systolic, grade 2–3/6, higher-pitched than Still's`,confirm:`Softer or disappears with hyperextension of the shoulders. Normal cardiac exam otherwise. No radiation below the clavicles.`},{name:`Peripheral pulmonary stenosis (PPS, neonatal)`,age:`Newborns and infants < 6–12 months`,location:`Upper LSB, radiates to BOTH axillae and the back`,character:`Soft systolic ejection murmur, grade 1–2/6`,confirm:`Typical age + radiation to back/axillae. Resolves by age 1 as branch pulmonary arteries grow. Persistence or louder grade warrants echo.`}],Dd=[{key:`normal`,src:`/audio/respiratory/normal-vesicular.ogg`,title:`Normal vesicular breath sounds`,where:`Peripheral lung fields`,features:`Soft, rustling. Inspiration louder and longer than expiration.`,clinical:`Baseline — deviation elsewhere is what you listen for.`},{key:`wheeze`,src:`/audio/respiratory/wheeze.ogg`,title:`Wheeze`,where:`Diffuse in asthma; localised in foreign body`,features:`Continuous, high-pitched, musical. Usually expiratory; biphasic if severe.`,clinical:`Lower-airway narrowing — asthma, bronchiolitis, foreign body, bronchomalacia. Silent chest in severe asthma is an ominous sign.`},{key:`stridor`,src:`/audio/respiratory/stridor.ogg`,title:`Stridor`,where:`Louder over neck than chest — upper airway`,features:`Continuous, high-pitched, harsh. Classically inspiratory (extrathoracic obstruction); biphasic if fixed.`,clinical:`Croup, epiglottitis, foreign body, laryngomalacia (infant). Distinguish from wheeze by auscultating the neck — stridor is loudest there.`},{key:`finecrackles`,src:`/audio/respiratory/crackles-fine.ogg`,title:`Fine (end-inspiratory) crackles`,where:`Bibasilar in pulmonary edema/fibrosis; focal in pneumonia`,features:`Discontinuous, brief, high-pitched. "Velcro" quality. Late inspiratory, do NOT clear with cough.`,clinical:`Alveolar opening — pulmonary fibrosis, pulmonary edema, early pneumonia, atelectasis.`},{key:`coarsecrackles`,src:`/audio/respiratory/crackles-coarse.ogg`,title:`Coarse crackles`,where:`Lower lobes; either side`,features:`Discontinuous, longer and louder than fine crackles. Lower-pitched. Can be early or late inspiratory; often clear partly with cough.`,clinical:`Secretions in larger airways — bronchitis, later pneumonia, bronchiectasis, aspiration.`},{key:`rhonchi`,src:`/audio/respiratory/rhonchi.ogg`,title:`Rhonchi`,where:`Central or anywhere with airway secretions`,features:`Continuous, low-pitched, snore-like. Typically expiratory. Clear or change with cough.`,clinical:`Large-airway secretions — bronchitis, pneumonia with large-airway involvement, cystic fibrosis, bronchiectasis.`},{key:`pleuralrub`,src:`/audio/respiratory/pleural-rub.ogg`,title:`Pleural friction rub`,where:`Focal, often lateral or posterior lower chest`,features:`Grating, creaky — "leather on leather". Biphasic (heard in inspiration and expiration). Does NOT clear with cough.`,clinical:`Pleural inflammation — pleuritis, pulmonary embolism, pneumonia with pleural involvement, viral pleurisy.`}],Od=[{key:`normal`,src:`/audio/cardiac/normal.ogg`,title:`Normal heart sounds (S1, S2)`,where:`All four classic auscultation points`,rate:`~61 bpm reference`,features:`"lub-dub": S1 (closure of mitral + tricuspid) louder at apex; S2 (closure of aortic + pulmonic) louder at base. Physiologic S2 split on inspiration.`,clinical:`Reference for rhythm, rate, and the normal S1–S2 interval. Listen for what's changed — not just what's added.`},{key:`infant-normal`,src:`/audio/cardiac/infant-normal.ogg`,title:`Infant normal heart sounds`,where:`Infant chest — rate will be higher than adult`,rate:`Pediatric reference (120–160 bpm range)`,features:`Same S1–S2 pattern, faster rate. Short diastole makes murmurs easier to miss — careful auscultation needed.`,clinical:`Reference for neonatal/infant rhythm. Any murmur in the first 72 h should prompt pre/postductal sat screening.`},{key:`vsd`,src:`/audio/cardiac/vsd.wav`,title:`Ventricular septal defect (VSD)`,where:`Lower left sternal border (4th ICS)`,features:`Harsh, blowing, holosystolic (pansystolic) murmur — plateau shape through all of systole. Often accompanied by a thrill if large.`,clinical:`Most common congenital heart defect. Small VSD: loud murmur, usually asymptomatic, may close spontaneously. Large VSD: softer murmur (less pressure gradient) but signs of heart failure, pulmonary hypertension.`},{key:`mvp`,src:`/audio/cardiac/mitral-prolapse.wav`,title:`Mitral valve prolapse (MVP) — click + late systolic murmur`,where:`Apex (5th ICS, mid-clavicular line)`,features:`Mid-systolic click followed by a late-systolic crescendo murmur. Timing of click changes with maneuvers: earlier with standing or Valsalva, later with squatting.`,clinical:`Often benign, especially in thin young women. Features suggesting need for echo: thickened/redundant leaflets, associated MR, symptoms (palpitations, chest pain), arrhythmias.`},{key:`stills`,src:`/audio/cardiac/stills-murmur.ogg`,title:`Still's murmur (innocent)`,where:`LLSB, radiating to apex`,rate:`Classic age 3–7 y (this recording is a toddler)`,features:`Low-frequency vibratory / musical systolic, grade 2–3/6, mid-systolic, "twanging-string" quality.`,clinical:`The most common innocent murmur of childhood. Louder supine, softer or disappears on standing or Valsalva. Normal S2. No radiation to neck or back. No workup needed when classic.`},{key:`functional`,src:`/audio/cardiac/functional-murmur.wav`,title:`Functional (innocent) murmur — adult female`,where:`Left sternal border, soft systolic`,features:`Soft systolic murmur in a structurally normal heart — often from increased cardiac output, thin chest wall, anemia, hyperthyroidism, or pregnancy.`,clinical:`Benign if it meets the 7 S criteria. Investigate if loud (≥3/6), holosystolic, diastolic, radiating, or with thrill / symptoms.`}],kd=`rounded-lg border border-border bg-card p-5 space-y-3`,Ad=`rounded-md bg-primary text-primary-foreground px-3 py-2 text-sm font-medium disabled:opacity-50`;function jd({id:e,scale:t}){return(0,A.jsxs)(`section`,{className:kd,"data-testid":`scale-`+e,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:t.title}),(0,A.jsx)(`table`,{className:`w-full text-sm`,children:(0,A.jsx)(`tbody`,{children:t.rows.map(([e,t],n)=>(0,A.jsxs)(`tr`,{className:`border-b border-border last:border-0`,children:[(0,A.jsx)(`td`,{className:`py-1.5 pr-3 font-mono font-semibold whitespace-nowrap`,children:e}),(0,A.jsx)(`td`,{className:`py-1.5 text-muted-foreground`,children:t})]},n))})})]})}function Md({entry:e}){let t=(0,_.useRef)(null);return(0,A.jsxs)(`section`,{className:kd,"data-testid":`sound-`+e.key,children:[(0,A.jsx)(`h4`,{className:`text-sm font-semibold`,children:e.title}),(0,A.jsxs)(`audio`,{ref:t,controls:!0,preload:`none`,className:`w-full`,children:[(0,A.jsx)(`source`,{src:e.src}),`Your browser does not support HTML5 audio.`]}),(0,A.jsxs)(`div`,{className:`text-xs space-y-1`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide`,children:`Where:`}),` `,e.where]}),e.rate&&(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide`,children:`Rate:`}),` `,e.rate]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide`,children:`Features:`}),` `,e.features]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide`,children:`Clinical:`}),` `,e.clinical]})]})]})}function $(){return(0,A.jsxs)(`div`,{className:`max-w-5xl mx-auto p-6 space-y-6`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Physical Exam Guide`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Reference scales, the five cardiac auscultation points, innocent childhood murmurs, and sound libraries.`})]}),(0,A.jsxs)(`section`,{className:`rounded-md border border-amber-300 bg-amber-50 dark:bg-amber-950/30 p-4 text-sm space-y-2`,children:[(0,A.jsx)(`div`,{className:`font-semibold text-amber-900 dark:text-amber-100`,children:`Exam-step checklist lives in the legacy viewer`}),(0,A.jsx)(`p`,{className:`text-amber-900 dark:text-amber-100`,children:`The full age-group × system checklist and the Generate Exam Report flow still run in the legacy app. They port in a dedicated session so the clinical content can be verified entry-for-entry against the vanilla source.`}),(0,A.jsx)(`a`,{href:`/#peGuide`,className:Ad+` inline-block`,children:`Open checklist in legacy viewer`})]}),(0,A.jsxs)(`section`,{className:`space-y-3`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Grading scales & reference ranges`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-4`,children:Object.entries(wd).map(([e,t])=>(0,A.jsx)(jd,{id:e,scale:t},e))})]}),(0,A.jsxs)(`section`,{className:`space-y-3`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Cardiac auscultation — APTM (the five points)`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-4`,children:Td.map(e=>(0,A.jsxs)(`div`,{className:kd,"data-testid":`aptm-`+e.letter.toLowerCase(),children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,A.jsx)(`div`,{className:`w-10 h-10 rounded-full flex items-center justify-center font-bold text-white`,style:{background:e.color},children:e.letter}),(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:e.title})]}),(0,A.jsxs)(`div`,{className:`text-sm text-muted-foreground`,children:[(0,A.jsx)(`strong`,{children:`Location:`}),` `,e.location]}),(0,A.jsxs)(`div`,{className:`text-sm text-muted-foreground`,children:[(0,A.jsx)(`strong`,{children:`Listen for:`}),` `,e.listen]}),e.innocent&&(0,A.jsxs)(`div`,{className:`text-xs text-green-700 dark:text-green-300 bg-green-50 dark:bg-green-950/30 rounded px-2 py-1`,children:[(0,A.jsx)(`strong`,{children:`Innocent:`}),` `,e.innocent]})]},e.letter))})]}),(0,A.jsxs)(`section`,{className:`space-y-3`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Innocent murmurs of childhood`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-4`,children:Ed.map(e=>(0,A.jsxs)(`div`,{className:kd,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:e.name}),(0,A.jsxs)(`div`,{className:`text-sm space-y-1`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide text-xs`,children:`Age:`}),` `,e.age]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide text-xs`,children:`Location:`}),` `,e.location]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide text-xs`,children:`Character:`}),` `,e.character]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide text-xs`,children:`Confirm benign:`}),` `,e.confirm]})]})]},e.name))})]}),(0,A.jsxs)(`section`,{className:`space-y-3`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Respiratory sounds library`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-4`,children:Dd.map(e=>(0,A.jsx)(Md,{entry:e},e.key))})]}),(0,A.jsxs)(`section`,{className:`space-y-3`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Cardiac sounds library`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-4`,children:Od.map(e=>(0,A.jsx)(Md,{entry:e},e.key))})]})]})}function Nd(e){if(e==null)return null;let t=String(e).toLowerCase().trim();if(!t)return null;if(/^[\d.]+$/.test(t)){let e=Number.parseFloat(t);return Number.isNaN(e)?null:e}let n=0,r=!1,i=t.match(/([\d.]+)\s*(?:years|year|yrs|yr|y)(?![a-z])/);i&&(n+=Number.parseFloat(i[1])*12,r=!0);let a=t.match(/([\d.]+)\s*(?:months|month|mos|mo|m)(?![a-z])/);a&&(n+=Number.parseFloat(a[1]),r=!0);let o=t.match(/([\d.]+)\s*(?:weeks|week|wks|wk|w)(?![a-z])/);o&&(n+=Number.parseFloat(o[1])*7/30.4375,r=!0);let s=t.match(/([\d.]+)\s*(?:days|day|d)(?![a-z])/);return s&&(n+=Number.parseFloat(s[1])/30.4375,r=!0),r?n:null}function Pd(e){if(e<1){let t=Math.round(e*30.4375);return`${t} day${t===1?``:`s`} (${e.toFixed(2)} mo)`}if(e<24)return`${Fd(e,1)} months`;let t=Math.floor(e/12),n=Math.round(e-t*12);return n===12&&(t+=1,n=0),`${t} yr${n?` ${n} mo`:``} (${Math.round(e)} mo total)`}function Fd(e,t){let n=10**t;return Math.round(e*n)/n}function Id(e){if(e==null||Number.isNaN(e)||e<0)return null;let t=e/12,n;n=e<12?.5*e+4:t<=5?2*t+8:3*t+7;let r;r=e<12?(e+9)/2:t<=5?2*(t+5):4*t;let i;return i=t<13?e<12?`APLS 0-12 mo`:t<=5?`APLS 1-5 yr`:`APLS 6-12 yr`:`Best Guess 5-14 yr`,{weight:Math.max(.3,Fd(t<13?n:r,1)),formulaLabel:i,all:{apls:Fd(n,1),bestGuess:Fd(r,1)}}}function Ld(e,t){return!Number.isFinite(e)||!Number.isFinite(t)||e<=0||t<=0?null:Math.sqrt(t*e/3600)}function Rd(e){let t=e.frequencyPerDay??1,n=e.maxSingleDoseMg??0,r=e.concentrationMgPerMl??0;if(!Number.isFinite(e.weightKg)||!Number.isFinite(e.dosePerKg)||!Number.isFinite(t)||e.weightKg<=0||e.dosePerKg<=0||t<=0)return null;let i=e.weightKg*e.dosePerKg,a=!1;return n>0&&i>n&&(i=n,a=!0),{singleDoseMg:i,dailyDoseMg:i*t,frequencyPerDay:t,capped:a,volumeMl:r>0?i/r:null}}function zd(e,t,n){if(!Number.isFinite(e)||!Number.isFinite(t)||!Number.isFinite(n)||e<1||e>4||t<1||t>5||n<1||n>6)return null;let r=e+t+n,i;return i=r<=8?`Severe (Coma)`:r<=12?`Moderate`:`Mild`,{total:r,severity:i}}var Bd=`rounded-lg border border-border bg-card p-5 space-y-3`,Vd=`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium`,Hd=`rounded-md border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted`,Ud=`block text-xs font-medium text-muted-foreground`,Wd=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring`,Gd=[{id:`neonatal`,label:`Neonatal`,icon:`👶`,summary:`GA classification, AGA/SGA/LGA, prematurity category (Fenton 2013 / WHO).`},{id:`airway`,label:`Airway / RSI`,icon:`💨`,summary:`ETT size + depth, RSI induction + paralytic dosing by weight.`},{id:`cardiac`,label:`Cardiac Arrest`,icon:`❤️`,summary:`PALS dosing (epinephrine, amiodarone, lidocaine), defibrillation J/kg.`},{id:`respiratory`,label:`Respiratory`,icon:`🫁`,summary:`Asthma, bronchiolitis, croup severity + dosing.`},{id:`ventilation`,label:`O₂ & Ventilation`,icon:`🌀`,summary:`NC / HFNC / CPAP / BiPAP flow + FiO₂ targets by age.`},{id:`seizure`,label:`Seizures`,icon:`🧠`,summary:`Benzodiazepine + second/third-line weight-based dosing.`},{id:`sepsis`,label:`Sepsis & Fever`,icon:`🦠`,summary:`Empirical antibiotics + fluid bolus dosing by weight.`},{id:`anaphylaxis`,label:`Anaphylaxis`,icon:`💉`,summary:`Epinephrine IM, IV infusion, steroid + antihistamine dosing.`},{id:`sedation`,label:`Sedation`,icon:`🛌`,summary:`Procedural sedation regimens — ketamine, propofol, midazolam.`},{id:`agitation`,label:`Agitation`,icon:`😤`,summary:`Weight-based haloperidol, olanzapine, lorazepam.`},{id:`antiemetics`,label:`Antiemetics`,icon:`💊`,summary:`Ondansetron, metoclopramide, promethazine dosing.`},{id:`antimicrobials`,label:`Antimicrobials`,icon:`🧫`,summary:`Common empirical regimens keyed to syndrome + weight.`},{id:`burns`,label:`Burns`,icon:`🔥`,summary:`TBSA % (Lund-Browder, Rule of Nines-children), Parkland fluids.`},{id:`toxicology`,label:`Toxicology`,icon:`☠️`,summary:`Common toxidromes + antidotes + decontamination windows.`},{id:`trauma`,label:`Trauma`,icon:`🩹`,summary:`PECARN, c-spine, blood-product dosing, TXA.`}];function Kd(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(`apls`),[i,a]=(0,_.useState)(``),o=Nd(e),s=o==null?null:Id(o),c=s?n===`bestguess`?s.all.bestGuess:s.all.apls:null,l=i.trim()||(c==null?``:String(c));function u(){t(``),r(`apls`),a(``)}return(0,A.jsxs)(`section`,{className:Bd,"data-testid":`bedside-weight-estimator`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Age → Weight Estimator`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Shared starting point for Bedside dosing. Uses the same APLS and Best Guess formulas as the legacy app.`})]}),(0,A.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-[1.2fr_1fr_1fr_auto] md:items-end`,children:[(0,A.jsxs)(`div`,{className:`space-y-1`,children:[(0,A.jsx)(`label`,{htmlFor:`bedside-react-age`,className:Ud,children:`Age`}),(0,A.jsx)(`input`,{id:`bedside-react-age`,value:e,onChange:e=>t(e.target.value),placeholder:`e.g. "18m", "3y", "2y5m"`,className:Wd,"data-testid":`bedside-age-input`})]}),(0,A.jsxs)(`div`,{className:`space-y-1`,children:[(0,A.jsx)(`label`,{htmlFor:`bedside-react-formula`,className:Ud,children:`Formula`}),(0,A.jsxs)(`select`,{id:`bedside-react-formula`,value:n,onChange:e=>{r(e.target.value),a(``)},className:Wd,"data-testid":`bedside-formula-select`,children:[(0,A.jsx)(`option`,{value:`apls`,children:`APLS`}),(0,A.jsx)(`option`,{value:`bestguess`,children:`Best Guess`})]})]}),(0,A.jsxs)(`div`,{className:`space-y-1`,children:[(0,A.jsx)(`label`,{htmlFor:`bedside-react-weight`,className:Ud,children:`Weight (kg)`}),(0,A.jsx)(`input`,{id:`bedside-react-weight`,type:`number`,min:`0.3`,step:`0.1`,value:l,onChange:e=>a(e.target.value),className:Wd,"data-testid":`bedside-weight-input`})]}),(0,A.jsx)(`button`,{type:`button`,onClick:u,className:Hd,children:`Clear`})]}),e.trim()&&o==null?(0,A.jsx)(`div`,{className:`rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-700 dark:bg-red-950/30 dark:text-red-200`,children:`Could not parse age. Try "3y", "18 months", or "15 days".`}):null,s&&c!=null?(0,A.jsxs)(`div`,{className:`rounded-lg border border-border bg-muted/40 p-4 text-sm`,"data-testid":`bedside-estimate-result`,children:[(0,A.jsxs)(`div`,{className:`font-semibold`,children:[c,` kg estimated from `,Pd(o??0)]}),(0,A.jsxs)(`div`,{className:`text-muted-foreground`,children:[`APLS: `,s.all.apls,` kg · Best Guess: `,s.all.bestGuess,` kg. You can override the weight field.`]})]}):null]})}function qd({pill:e}){return(0,A.jsxs)(`section`,{className:Bd,"data-testid":`bedside-panel-`+e.id,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,A.jsx)(`span`,{className:`text-2xl`,"aria-hidden":!0,children:e.icon}),(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:e.label})]}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:e.summary}),(0,A.jsx)(`div`,{className:`rounded-md border border-amber-300 bg-amber-50 dark:bg-amber-950/30 p-3 text-sm space-y-2`,children:(0,A.jsx)(`p`,{className:`text-amber-900 dark:text-amber-100`,children:`Weight-based calculators for this module run in the legacy viewer while the clinical data is verified for a direct React port. Open the legacy Bedside tab to use the full dosing flow.`})}),(0,A.jsx)(`a`,{href:`/#bedside`,className:Vd+` inline-block`,children:`Open in legacy viewer`})]})}function Jd(){let[e,t]=(0,_.useState)(Gd[0].id),n=Gd.find(t=>t.id===e)??Gd[0];return(0,A.jsxs)(`div`,{className:`max-w-5xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Bedside`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Emergency and rapid-reference pediatric tools. Weight-based dosing throughout — always verify against institutional protocols.`})]}),(0,A.jsx)(`div`,{className:`flex flex-wrap gap-2`,"data-testid":`bedside-subnav`,children:Gd.map(n=>(0,A.jsxs)(`button`,{type:`button`,onClick:()=>t(n.id),className:`px-3 py-1.5 rounded-full text-xs font-medium border transition-colors `+(e===n.id?`bg-primary text-primary-foreground border-primary`:`bg-muted hover:bg-muted/80 border-border`),"data-testid":`bedside-pill-`+n.id,children:[(0,A.jsx)(`span`,{className:`mr-1`,"aria-hidden":!0,children:n.icon}),n.label]},n.id))}),(0,A.jsx)(Kd,{}),(0,A.jsx)(qd,{pill:n})]})}var Yd=`rounded-lg border border-border bg-card p-5 space-y-3`,Xd=`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium`,Zd=`rounded-md border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted`,Qd=`space-y-1`,$d=`block text-xs font-medium text-muted-foreground`,ef=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring`,tf=`rounded-lg border border-border bg-muted/40 p-4`,nf=`rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-700 dark:bg-red-950/30 dark:text-red-200`,rf=[{id:`bp`,label:`BP Percentile`,summary:`AAP 2017 age/height/sex-adjusted BP percentiles (Rosner quantile splines).`,source:`AAP 2017 (Flynn) — Rosner splines`},{id:`bmi`,label:`BMI Percentile`,summary:`BMI-for-age (CDC 2000 z-score tables).`,source:`CDC 2000 LMS`},{id:`growth`,label:`Growth Charts`,summary:`WHO 0–2 y / CDC 2–20 y; Fenton 2013 preterm (weight, length, head).`,source:`WHO 2006 + CDC 2000 + Fenton 2013 LMS`},{id:`bili`,label:`Bilirubin`,summary:`AAP 2022 phototherapy + exchange thresholds and Bhutani nomogram risk zones.`,source:`AAP 2022 (Kemper) + Bhutani 1999`},{id:`vitals`,label:`Vital Signs`,summary:`Normal HR / RR / BP ranges by age.`,source:`PALS + AHA reference`},{id:`bsa`,label:`Body Surface Area`,summary:`Mosteller body surface area formula.`,source:`Mosteller 1987`,ported:!0},{id:`dose`,label:`Weight-Based Dosing`,summary:`Generic mg/kg dosing with optional max-dose cap and concentration conversion.`,source:`Legacy calculator formula`,ported:!0},{id:`resus`,label:`Resus Meds`,summary:`Code-cart dosing (epinephrine, amiodarone, atropine, etc.).`,source:`PALS`},{id:`gcs`,label:`GCS`,summary:`Child/adult and infant Glasgow Coma Scale variants.`,source:`Teasdale + pediatric modification`,ported:!0},{id:`equipment`,label:`Equipment`,summary:`ETT size, blade, NG, Foley, suction by age/weight.`,source:`PALS + Broselow cross-reference`}];function af(e){if(!e.trim())return null;let t=Number(e);return Number.isFinite(t)?t:null}function of({id:e,labelText:t,value:n,onChange:r,min:i,max:a,step:o=`0.1`,placeholder:s}){return(0,A.jsxs)(`div`,{className:Qd,children:[(0,A.jsx)(`label`,{htmlFor:e,className:$d,children:t}),(0,A.jsx)(`input`,{id:e,type:`number`,min:i,max:a,step:o,value:n,onChange:e=>r(e.target.value),placeholder:s,className:ef})]})}function sf(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(null),[o,s]=(0,_.useState)(``);function c(){let t=Ld(Number(e),Number(n));if(t==null){s(`Enter a valid weight and height.`),a(null);return}s(``),a(t)}function l(){t(``),r(``),a(null),s(``)}return(0,A.jsxs)(`section`,{className:Yd,"data-testid":`calc-panel-bsa`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Body Surface Area`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Mosteller formula: BSA (m2) = sqrt(height(cm) x weight(kg) / 3600).`}),(0,A.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-2`,children:[(0,A.jsx)(of,{id:`react-bsa-weight`,labelText:`Weight (kg)`,value:e,onChange:t,min:`1`,max:`200`,placeholder:`20`}),(0,A.jsx)(of,{id:`react-bsa-height`,labelText:`Height (cm)`,value:n,onChange:r,min:`30`,max:`220`,placeholder:`110`})]}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsx)(`button`,{type:`button`,onClick:c,className:Xd,"data-testid":`calc-bsa-calculate`,children:`Calculate`}),(0,A.jsx)(`button`,{type:`button`,onClick:l,className:Zd,children:`Clear`})]}),o?(0,A.jsx)(`div`,{className:nf,children:o}):null,i==null?null:(0,A.jsxs)(`div`,{className:tf,"data-testid":`calc-bsa-result`,children:[(0,A.jsx)(`div`,{className:`text-xs uppercase tracking-wide text-muted-foreground`,children:`Mosteller BSA`}),(0,A.jsxs)(`div`,{className:`text-2xl font-semibold`,children:[i.toFixed(3),` m²`]}),(0,A.jsxs)(`div`,{className:`text-sm text-muted-foreground`,children:[e,` kg, `,n,` cm`]})]})]})}function cf(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(`1`),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(``),[u,d]=(0,_.useState)(null),[f,p]=(0,_.useState)(``);function m(){let t=Rd({weightKg:Number(e),dosePerKg:Number(n),frequencyPerDay:Number(i),maxSingleDoseMg:af(o),concentrationMgPerMl:af(c)});if(t==null){p(`Enter a valid weight, mg/kg dose, and frequency.`),d(null);return}p(``),d(t)}function h(){t(``),r(``),a(`1`),s(``),l(``),d(null),p(``)}return(0,A.jsxs)(`section`,{className:Yd,"data-testid":`calc-panel-dose`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Weight-Based Dosing`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Generic mg/kg calculator. Always verify medication-specific dosing against formulary and local policy.`}),(0,A.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-2 lg:grid-cols-3`,children:[(0,A.jsx)(of,{id:`react-dose-weight`,labelText:`Patient Weight (kg)`,value:e,onChange:t,min:`1`,max:`200`,placeholder:`15`}),(0,A.jsx)(of,{id:`react-dose-per-kg`,labelText:`Dose (mg/kg)`,value:n,onChange:r,min:`0.01`,step:`0.01`,placeholder:`10`}),(0,A.jsxs)(`div`,{className:Qd,children:[(0,A.jsx)(`label`,{htmlFor:`react-dose-frequency`,className:$d,children:`Frequency`}),(0,A.jsxs)(`select`,{id:`react-dose-frequency`,value:i,onChange:e=>a(e.target.value),className:ef,children:[(0,A.jsx)(`option`,{value:`1`,children:`Once daily`}),(0,A.jsx)(`option`,{value:`2`,children:`Twice daily (BID)`}),(0,A.jsx)(`option`,{value:`3`,children:`Three times daily (TID)`}),(0,A.jsx)(`option`,{value:`4`,children:`Four times daily (QID)`}),(0,A.jsx)(`option`,{value:`6`,children:`Every 4 hours (Q4H)`})]})]}),(0,A.jsx)(of,{id:`react-dose-max`,labelText:`Max single dose (mg, optional)`,value:o,onChange:s,min:`0`,step:`1`,placeholder:`500`}),(0,A.jsx)(of,{id:`react-dose-concentration`,labelText:`Concentration (mg/mL, optional)`,value:c,onChange:l,min:`0`,placeholder:`40`})]}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsx)(`button`,{type:`button`,onClick:m,className:Xd,"data-testid":`calc-dose-calculate`,children:`Calculate`}),(0,A.jsx)(`button`,{type:`button`,onClick:h,className:Zd,children:`Clear`})]}),f?(0,A.jsx)(`div`,{className:nf,children:f}):null,u==null?null:(0,A.jsx)(`div`,{className:tf,"data-testid":`calc-dose-result`,children:(0,A.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-3`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`div`,{className:`text-xs uppercase tracking-wide text-muted-foreground`,children:`Single Dose`}),(0,A.jsxs)(`div`,{className:`text-xl font-semibold`,children:[u.singleDoseMg.toFixed(1),` mg`]}),u.capped?(0,A.jsx)(`div`,{className:`text-xs text-red-600`,children:`Capped at max dose`}):null]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`div`,{className:`text-xs uppercase tracking-wide text-muted-foreground`,children:`Daily Total`}),(0,A.jsxs)(`div`,{className:`text-xl font-semibold`,children:[u.dailyDoseMg.toFixed(1),` mg/day`]}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground`,children:[`x `,u.frequencyPerDay,`/day`]})]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`div`,{className:`text-xs uppercase tracking-wide text-muted-foreground`,children:`Volume`}),(0,A.jsx)(`div`,{className:`text-xl font-semibold`,children:u.volumeMl==null?`n/a`:`${u.volumeMl.toFixed(1)} mL`}),(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground`,children:`per dose`})]})]})})]})}var lf={child:{eye:[[`4`,`4 - Spontaneous`],[`3`,`3 - To speech`],[`2`,`2 - To pain`],[`1`,`1 - None`]],verbal:[[`5`,`5 - Oriented`],[`4`,`4 - Confused`],[`3`,`3 - Inappropriate words`],[`2`,`2 - Incomprehensible sounds`],[`1`,`1 - None`]],motor:[[`6`,`6 - Obeys commands`],[`5`,`5 - Localizes pain`],[`4`,`4 - Withdraws to pain`],[`3`,`3 - Abnormal flexion`],[`2`,`2 - Abnormal extension`],[`1`,`1 - None`]]},infant:{eye:[[`4`,`4 - Spontaneous`],[`3`,`3 - To speech/sound`],[`2`,`2 - To painful stimuli`],[`1`,`1 - None`]],verbal:[[`5`,`5 - Coos/babbles`],[`4`,`4 - Irritable cry`],[`3`,`3 - Cries to pain`],[`2`,`2 - Moans to pain`],[`1`,`1 - None`]],motor:[[`6`,`6 - Normal spontaneous movement`],[`5`,`5 - Withdraws to touch`],[`4`,`4 - Withdraws to pain`],[`3`,`3 - Abnormal flexion`],[`2`,`2 - Abnormal extension`],[`1`,`1 - None`]]}};function uf({id:e,labelText:t,value:n,options:r,onChange:i}){return(0,A.jsxs)(`div`,{className:Qd,children:[(0,A.jsx)(`label`,{htmlFor:e,className:$d,children:t}),(0,A.jsx)(`select`,{id:e,value:n,onChange:e=>i(e.target.value),className:ef,children:r.map(([e,t])=>(0,A.jsx)(`option`,{value:e,children:t},e))})]})}function df(){let[e,t]=(0,_.useState)(`child`),[n,r]=(0,_.useState)(`4`),[i,a]=(0,_.useState)(`5`),[o,s]=(0,_.useState)(`6`),c=zd(Number(n),Number(i),Number(o)),l=lf[e];function u(e){t(e),r(`4`),a(`5`),s(`6`)}return(0,A.jsxs)(`section`,{className:Yd,"data-testid":`calc-panel-gcs`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Glasgow Coma Scale`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Select responses to calculate child/adult or infant-modified GCS. Total score 3-15.`}),(0,A.jsxs)(`div`,{className:`flex flex-wrap gap-2`,children:[(0,A.jsx)(`button`,{type:`button`,onClick:()=>u(`child`),className:`px-3 py-1.5 rounded-full text-xs font-medium border `+(e===`child`?`bg-primary text-primary-foreground border-primary`:`bg-muted border-border`),children:`Child / Adult`}),(0,A.jsx)(`button`,{type:`button`,onClick:()=>u(`infant`),className:`px-3 py-1.5 rounded-full text-xs font-medium border `+(e===`infant`?`bg-primary text-primary-foreground border-primary`:`bg-muted border-border`),children:`Infant`})]}),(0,A.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-3`,children:[(0,A.jsx)(uf,{id:`react-gcs-eye`,labelText:`Eye Opening`,value:n,options:l.eye,onChange:r}),(0,A.jsx)(uf,{id:`react-gcs-verbal`,labelText:`Verbal Response`,value:i,options:l.verbal,onChange:a}),(0,A.jsx)(uf,{id:`react-gcs-motor`,labelText:`Motor Response`,value:o,options:l.motor,onChange:s})]}),c==null?null:(0,A.jsxs)(`div`,{className:tf,"data-testid":`calc-gcs-result`,children:[(0,A.jsx)(`div`,{className:`text-xs uppercase tracking-wide text-muted-foreground`,children:e===`infant`?`Infant-modified GCS`:`Child / adult GCS`}),(0,A.jsxs)(`div`,{className:`text-3xl font-semibold`,children:[`GCS: `,c.total,`/15`]}),(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:c.severity}),(0,A.jsx)(`div`,{className:`mt-2 text-xs text-muted-foreground`,children:`Interpretation: 13-15 Mild, 9-12 Moderate, 3-8 Severe/Coma.`})]})]})}function ff({pill:e}){return(0,A.jsxs)(`section`,{className:Yd,"data-testid":`calc-panel-`+e.id,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:e.label}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:e.summary}),(0,A.jsxs)(`div`,{className:`rounded-md border border-amber-300 bg-amber-50 dark:bg-amber-950/30 p-3 text-sm space-y-2`,children:[(0,A.jsxs)(`p`,{className:`text-amber-900 dark:text-amber-100`,children:[(0,A.jsx)(`strong`,{children:`Source of truth:`}),` `,e.source,`.`]}),(0,A.jsx)(`p`,{className:`text-amber-900 dark:text-amber-100`,children:`This calculator runs in the legacy viewer. A React port is gated on capturing test vectors from the vanilla implementation so the numerical output can be verified byte-for-byte — the migration checkpoint specifically flags this class of data as the one an LLM is most likely to silently simplify.`})]}),(0,A.jsx)(`a`,{href:`/#calculators`,className:Xd+` inline-block`,children:`Open in legacy viewer`})]})}function pf({pill:e}){return e.id===`bsa`?(0,A.jsx)(sf,{}):e.id===`dose`?(0,A.jsx)(cf,{}):e.id===`gcs`?(0,A.jsx)(df,{}):(0,A.jsx)(ff,{pill:e})}function mf(){let[e,t]=(0,_.useState)(rf[0].id),n=rf.find(t=>t.id===e)??rf[0];return(0,A.jsxs)(`div`,{className:`max-w-5xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Calculators`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Pediatric calculators — BP percentiles, bilirubin thresholds, growth, dosing, equipment sizing. Simple pure-formula calculators run in React now; high-risk table-driven calculators remain legacy-gated until vectors are captured.`})]}),(0,A.jsx)(`div`,{className:`flex flex-wrap gap-2`,"data-testid":`calc-subnav`,children:rf.map(n=>(0,A.jsxs)(`button`,{type:`button`,onClick:()=>t(n.id),className:`px-3 py-1.5 rounded-full text-xs font-medium border transition-colors `+(e===n.id?`bg-primary text-primary-foreground border-primary`:`bg-muted hover:bg-muted/80 border-border`),"data-testid":`calc-pill-`+n.id,children:[n.label,n.ported?(0,A.jsx)(`span`,{className:`ml-1 text-[10px] opacity-80`,children:`React`}):null]},n.id))}),(0,A.jsx)(pf,{pill:n})]})}var hf=`rounded-lg border border-border bg-card p-5 space-y-3`,gf=`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium`;function _f(){let{data:e,isLoading:t}=j({queryKey:[`auth-me`],queryFn:()=>F.get(`/api/auth/me`),staleTime:5*6e4});return t?(0,A.jsx)(`div`,{className:`max-w-3xl mx-auto p-6`,children:(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Checking permissions…`})}):e?.user.role===`admin`?(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Admin Panel`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Registration, users, feature flags, OIDC, email templates, AI prompts, SMTP, saved encounters (site-wide), AI model management, TTS/STT provider selection.`})]}),(0,A.jsxs)(`section`,{className:hf,"data-testid":`admin-shell`,children:[(0,A.jsx)(`div`,{className:`rounded-md border border-amber-300 bg-amber-50 dark:bg-amber-950/30 p-3 text-sm space-y-2`,children:(0,A.jsx)(`p`,{className:`text-amber-900 dark:text-amber-100`,children:`Admin sub-sections still live in the legacy viewer. Each one (user management, feature flags, announcements, OIDC config, SMTP, AI prompts, model management, TTS/STT) touches production state immediately when saved — those controls port in discrete follow-up commits with their own tests before they go live in the React tree.`})}),(0,A.jsx)(`a`,{href:`/#admin`,className:gf+` inline-block`,children:`Open admin in legacy viewer`})]})]}):(0,A.jsx)(`div`,{className:`max-w-3xl mx-auto p-6`,children:(0,A.jsxs)(`section`,{className:hf,"data-testid":`admin-access-denied`,children:[(0,A.jsx)(`h1`,{className:`text-xl font-semibold`,children:`Admin only`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`This page is restricted to users with the admin role. If you believe this is a mistake, contact your site administrator.`})]})})}var vf=new Ze({defaultOptions:{queries:{staleTime:3e4,retry:1}}});function yf(){return(0,A.jsxs)(`div`,{className:`max-w-3xl mx-auto p-6 space-y-4`,children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Pediatric AI Scribe — React client`}),(0,A.jsxs)(`p`,{className:`text-sm text-muted-foreground`,children:[`This is the new React tree. The legacy vanilla-JS app still lives at`,` `,(0,A.jsx)(`a`,{href:`/`,className:`underline`,children:`/`}),`.`]}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Ported tabs so far:`}),(0,A.jsxs)(`ul`,{className:`list-disc pl-6 text-sm space-y-1`,children:[(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/encounter`,className:`underline`,children:`Encounter HPI`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/dictation`,className:`underline`,children:`Dictation HPI`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/soap`,className:`underline`,children:`SOAP Note`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/sickvisit`,className:`underline`,children:`Sick Visit`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/extensions`,className:`underline`,children:`Extensions & Pagers`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/faq`,className:`underline`,children:`FAQ`})})]})]})}function bf(){return(0,A.jsx)(tt,{client:vf,children:(0,A.jsx)(ti,{basename:`/app`,children:(0,A.jsx)(mr,{children:(0,A.jsxs)(fr,{element:(0,A.jsx)(Ei,{}),children:[(0,A.jsx)(fr,{path:`/`,element:(0,A.jsx)(yf,{})}),(0,A.jsx)(fr,{path:`/encounter`,element:(0,A.jsx)(Cu,{})}),(0,A.jsx)(fr,{path:`/dictation`,element:(0,A.jsx)(Su,{})}),(0,A.jsx)(fr,{path:`/soap`,element:(0,A.jsx)(wu,{})}),(0,A.jsx)(fr,{path:`/sickvisit`,element:(0,A.jsx)(Tu,{})}),(0,A.jsx)(fr,{path:`/hospital`,element:(0,A.jsx)(Eu,{})}),(0,A.jsx)(fr,{path:`/chart`,element:(0,A.jsx)(Ou,{})}),(0,A.jsx)(fr,{path:`/wellvisit`,element:(0,A.jsx)(ku,{})}),(0,A.jsx)(fr,{path:`/vaxschedule`,element:(0,A.jsx)(Au,{})}),(0,A.jsx)(fr,{path:`/catchup`,element:(0,A.jsx)(ju,{})}),(0,A.jsx)(fr,{path:`/extensions`,element:(0,A.jsx)(vu,{})}),(0,A.jsx)(fr,{path:`/settings`,element:(0,A.jsx)(fd,{})}),(0,A.jsx)(fr,{path:`/learning`,element:(0,A.jsx)(Cd,{})}),(0,A.jsx)(fr,{path:`/peguide`,element:(0,A.jsx)($,{})}),(0,A.jsx)(fr,{path:`/bedside`,element:(0,A.jsx)(Jd,{})}),(0,A.jsx)(fr,{path:`/calculators`,element:(0,A.jsx)(mf,{})}),(0,A.jsx)(fr,{path:`/admin`,element:(0,A.jsx)(_f,{})}),(0,A.jsx)(fr,{path:`/faq`,element:(0,A.jsx)(xu,{})}),(0,A.jsx)(fr,{path:`*`,element:(0,A.jsx)(ur,{to:`/`,replace:!0})})]})})})})}(0,v.createRoot)(document.getElementById(`root`)).render((0,A.jsx)(_.StrictMode,{children:(0,A.jsx)(bf,{})})); \ No newline at end of file diff --git a/public/app/index.html b/public/app/index.html index 1b8129f..09e7623 100644 --- a/public/app/index.html +++ b/public/app/index.html @@ -5,8 +5,8 @@ client - - + +
diff --git a/shared/clinical/calculators.test.ts b/shared/clinical/calculators.test.ts new file mode 100644 index 0000000..2a3a41c --- /dev/null +++ b/shared/clinical/calculators.test.ts @@ -0,0 +1,143 @@ +import { describe, expect, it } from 'vitest'; +import { + calculateGcs, + calculateMostellerBsa, + calculateWeightBasedDose, + estimateWeightFromAgeMonths, + formatAgeMonths, + parseAgeMonths, +} from './calculators'; + +describe('parseAgeMonths', () => { + it('parses flexible age strings from the legacy calculator parser', () => { + expect(parseAgeMonths('3y')).toBe(36); + expect(parseAgeMonths('2y5m')).toBe(29); + expect(parseAgeMonths('2 years 5 months')).toBe(29); + expect(parseAgeMonths('6 months')).toBe(6); + expect(parseAgeMonths('2 wk')).toBeCloseTo(14 / 30.4375); + expect(parseAgeMonths('15 days')).toBeCloseTo(15 / 30.4375); + }); + + it('keeps plain numbers as months for legacy parity', () => { + expect(parseAgeMonths('36')).toBe(36); + expect(parseAgeMonths(6)).toBe(6); + }); + + it('rejects blank and unparseable ages', () => { + expect(parseAgeMonths('')).toBeNull(); + expect(parseAgeMonths('older child')).toBeNull(); + expect(parseAgeMonths(null)).toBeNull(); + }); +}); + +describe('formatAgeMonths', () => { + it('formats days, months, and years', () => { + expect(formatAgeMonths(15 / 30.4375)).toBe('15 days (0.49 mo)'); + expect(formatAgeMonths(18)).toBe('18 months'); + expect(formatAgeMonths(29)).toBe('2 yr 5 mo (29 mo total)'); + }); +}); + +describe('estimateWeightFromAgeMonths', () => { + it('matches APLS infant band', () => { + const result = estimateWeightFromAgeMonths(6); + expect(result).toEqual({ + weight: 7, + formulaLabel: 'APLS 0-12 mo', + all: { apls: 7, bestGuess: 7.5 }, + }); + }); + + it('matches APLS 1-5 year band', () => { + const result = estimateWeightFromAgeMonths(36); + expect(result?.weight).toBe(14); + expect(result?.formulaLabel).toBe('APLS 1-5 yr'); + expect(result?.all).toEqual({ apls: 14, bestGuess: 16 }); + }); + + it('matches APLS 6-12 year band', () => { + const result = estimateWeightFromAgeMonths(96); + expect(result?.weight).toBe(31); + expect(result?.formulaLabel).toBe('APLS 6-12 yr'); + expect(result?.all).toEqual({ apls: 31, bestGuess: 32 }); + }); + + it('uses Best Guess from 13 years onward', () => { + const result = estimateWeightFromAgeMonths(168); + expect(result?.weight).toBe(56); + expect(result?.formulaLabel).toBe('Best Guess 5-14 yr'); + }); + + it('rejects invalid ages', () => { + expect(estimateWeightFromAgeMonths(null)).toBeNull(); + expect(estimateWeightFromAgeMonths(Number.NaN)).toBeNull(); + expect(estimateWeightFromAgeMonths(-1)).toBeNull(); + }); +}); + +describe('calculateMostellerBsa', () => { + it('matches the legacy 20 kg, 110 cm smoke-test value', () => { + expect(calculateMostellerBsa(20, 110)?.toFixed(3)).toBe('0.782'); + }); + + it('rejects invalid measurements', () => { + expect(calculateMostellerBsa(0, 110)).toBeNull(); + expect(calculateMostellerBsa(20, 0)).toBeNull(); + }); +}); + +describe('calculateWeightBasedDose', () => { + it('calculates a single dose and daily total', () => { + const result = calculateWeightBasedDose({ weightKg: 15, dosePerKg: 10, frequencyPerDay: 3 }); + expect(result).toEqual({ + singleDoseMg: 150, + dailyDoseMg: 450, + frequencyPerDay: 3, + capped: false, + volumeMl: null, + }); + }); + + it('respects a max single-dose cap', () => { + const result = calculateWeightBasedDose({ weightKg: 15, dosePerKg: 100, maxSingleDoseMg: 500 }); + expect(result?.singleDoseMg).toBe(500); + expect(result?.capped).toBe(true); + }); + + it('calculates dose volume when concentration is supplied', () => { + const result = calculateWeightBasedDose({ + weightKg: 20, + dosePerKg: 12.5, + concentrationMgPerMl: 50, + }); + expect(result?.singleDoseMg).toBe(250); + expect(result?.volumeMl).toBe(5); + }); + + it('rejects invalid dose inputs', () => { + expect(calculateWeightBasedDose({ weightKg: 0, dosePerKg: 10 })).toBeNull(); + expect(calculateWeightBasedDose({ weightKg: 15, dosePerKg: 0 })).toBeNull(); + expect(calculateWeightBasedDose({ weightKg: 15, dosePerKg: 10, frequencyPerDay: 0 })).toBeNull(); + }); +}); + +describe('calculateGcs', () => { + it('scores maximum child/adult defaults as mild GCS 15', () => { + expect(calculateGcs(4, 5, 6)).toEqual({ total: 15, severity: 'Mild' }); + }); + + it('scores 4/5/1 as moderate GCS 10', () => { + expect(calculateGcs(4, 5, 1)).toEqual({ total: 10, severity: 'Moderate' }); + }); + + it('scores 1/1/1 as severe coma', () => { + expect(calculateGcs(1, 1, 1)).toEqual({ total: 3, severity: 'Severe (Coma)' }); + }); + + it('rejects invalid component scores', () => { + expect(calculateGcs(0, 5, 6)).toBeNull(); + expect(calculateGcs(5, 5, 6)).toBeNull(); + expect(calculateGcs(4, 6, 6)).toBeNull(); + expect(calculateGcs(4, 5, 7)).toBeNull(); + }); +}); diff --git a/shared/clinical/calculators.ts b/shared/clinical/calculators.ts new file mode 100644 index 0000000..7ec2330 --- /dev/null +++ b/shared/clinical/calculators.ts @@ -0,0 +1,206 @@ +// ============================================================ +// Pure pediatric calculator helpers. +// +// Keep clinical math out of React components and DOM event handlers. +// High-risk tables such as Rosner BP, Fenton LMS, and bilirubin +// thresholds should be added here only after legacy vectors exist. +// ============================================================ + +export type WeightFormulaBand = + | 'APLS 0-12 mo' + | 'APLS 1-5 yr' + | 'APLS 6-12 yr' + | 'Best Guess 5-14 yr'; + +export interface EstimatedWeight { + weight: number; + formulaLabel: WeightFormulaBand; + all: { + apls: number; + bestGuess: number; + }; +} + +export function parseAgeMonths(input: string | number | null | undefined): number | null { + if (input == null) return null; + const value = String(input).toLowerCase().trim(); + if (!value) return null; + + if (/^[\d.]+$/.test(value)) { + const parsed = Number.parseFloat(value); + return Number.isNaN(parsed) ? null : parsed; + } + + let total = 0; + let matched = false; + const years = value.match(/([\d.]+)\s*(?:years|year|yrs|yr|y)(?![a-z])/); + if (years) { + total += Number.parseFloat(years[1]) * 12; + matched = true; + } + const months = value.match(/([\d.]+)\s*(?:months|month|mos|mo|m)(?![a-z])/); + if (months) { + total += Number.parseFloat(months[1]); + matched = true; + } + const weeks = value.match(/([\d.]+)\s*(?:weeks|week|wks|wk|w)(?![a-z])/); + if (weeks) { + total += Number.parseFloat(weeks[1]) * 7 / 30.4375; + matched = true; + } + const days = value.match(/([\d.]+)\s*(?:days|day|d)(?![a-z])/); + if (days) { + total += Number.parseFloat(days[1]) / 30.4375; + matched = true; + } + + return matched ? total : null; +} + +export function formatAgeMonths(months: number): string { + if (months < 1) { + const days = Math.round(months * 30.4375); + return `${days} day${days === 1 ? '' : 's'} (${months.toFixed(2)} mo)`; + } + if (months < 24) return `${roundTo(months, 1)} months`; + + let years = Math.floor(months / 12); + let remainingMonths = Math.round(months - years * 12); + if (remainingMonths === 12) { + years += 1; + remainingMonths = 0; + } + return `${years} yr${remainingMonths ? ` ${remainingMonths} mo` : ''} (${Math.round(months)} mo total)`; +} + +export interface WeightBasedDoseInput { + weightKg: number; + dosePerKg: number; + frequencyPerDay?: number; + maxSingleDoseMg?: number | null; + concentrationMgPerMl?: number | null; +} + +export interface WeightBasedDoseResult { + singleDoseMg: number; + dailyDoseMg: number; + frequencyPerDay: number; + capped: boolean; + volumeMl: number | null; +} + +export type GcsSeverity = 'Mild' | 'Moderate' | 'Severe (Coma)'; + +export interface GcsResult { + total: number; + severity: GcsSeverity; +} + +export function roundTo(value: number, places: number): number { + const multiplier = Math.pow(10, places); + return Math.round(value * multiplier) / multiplier; +} + +// APLS (Luscombe & Owens 2007 / APLS-UK 2016) and Best Guess +// (Tinning & Acworth 2007). Ported from public/js/calc-math.js. +export function estimateWeightFromAgeMonths(months: number | null | undefined): EstimatedWeight | null { + if (months == null || Number.isNaN(months) || months < 0) return null; + + const years = months / 12; + let apls: number; + if (months < 12) apls = 0.5 * months + 4; + else if (years <= 5) apls = 2 * years + 8; + else apls = 3 * years + 7; + + let bestGuess: number; + if (months < 12) bestGuess = (months + 9) / 2; + else if (years <= 5) bestGuess = 2 * (years + 5); + else bestGuess = 4 * years; + + let formulaLabel: WeightFormulaBand; + if (years < 13) { + if (months < 12) formulaLabel = 'APLS 0-12 mo'; + else if (years <= 5) formulaLabel = 'APLS 1-5 yr'; + else formulaLabel = 'APLS 6-12 yr'; + } else { + formulaLabel = 'Best Guess 5-14 yr'; + } + + const primary = years < 13 ? apls : bestGuess; + return { + weight: Math.max(0.3, roundTo(primary, 1)), + formulaLabel, + all: { + apls: roundTo(apls, 1), + bestGuess: roundTo(bestGuess, 1), + }, + }; +} + +// Mosteller formula used by the legacy BSA calculator: +// BSA (m2) = sqrt(height(cm) * weight(kg) / 3600). +export function calculateMostellerBsa(weightKg: number, heightCm: number): number | null { + if (!Number.isFinite(weightKg) || !Number.isFinite(heightCm) || weightKg <= 0 || heightCm <= 0) { + return null; + } + return Math.sqrt((heightCm * weightKg) / 3600); +} + +// Ported from public/js/calculators.js weight-based dosing panel. +export function calculateWeightBasedDose(input: WeightBasedDoseInput): WeightBasedDoseResult | null { + const frequencyPerDay = input.frequencyPerDay ?? 1; + const maxSingleDoseMg = input.maxSingleDoseMg ?? 0; + const concentrationMgPerMl = input.concentrationMgPerMl ?? 0; + + if ( + !Number.isFinite(input.weightKg) || + !Number.isFinite(input.dosePerKg) || + !Number.isFinite(frequencyPerDay) || + input.weightKg <= 0 || + input.dosePerKg <= 0 || + frequencyPerDay <= 0 + ) { + return null; + } + + let singleDoseMg = input.weightKg * input.dosePerKg; + let capped = false; + if (maxSingleDoseMg > 0 && singleDoseMg > maxSingleDoseMg) { + singleDoseMg = maxSingleDoseMg; + capped = true; + } + + return { + singleDoseMg, + dailyDoseMg: singleDoseMg * frequencyPerDay, + frequencyPerDay, + capped, + volumeMl: concentrationMgPerMl > 0 ? singleDoseMg / concentrationMgPerMl : null, + }; +} + +// Child/adult and infant GCS scoring share the same numeric interpretation. +// Ported from public/js/calculators.js. +export function calculateGcs(eye: number, verbal: number, motor: number): GcsResult | null { + if ( + !Number.isFinite(eye) || + !Number.isFinite(verbal) || + !Number.isFinite(motor) || + eye < 1 || + eye > 4 || + verbal < 1 || + verbal > 5 || + motor < 1 || + motor > 6 + ) { + return null; + } + + const total = eye + verbal + motor; + let severity: GcsSeverity; + if (total <= 8) severity = 'Severe (Coma)'; + else if (total <= 12) severity = 'Moderate'; + else severity = 'Mild'; + + return { total, severity }; +} diff --git a/tsconfig.json b/tsconfig.json index 0ecf971..9b4c924 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -36,6 +36,7 @@ "e2e", "scripts", "test", - "client" + "client", + "**/*.test.ts" ] }