import { useState, useCallback, useRef } from "react"; import { SignIn, UserButton, OrganizationSwitcher, useAuth } from "@clerk/react"; import { ThemeProvider } from "./ThemeProvider"; import Sidebar from "./components/Sidebar"; import StatCard from "./components/StatCard"; import WorklistTable from "./components/WorklistTable"; import PatientWorklist from "./components/PatientWorklist"; import CSVImport from "./components/CSVImport"; import CSVExport from "./components/CSVExport"; import ThemeToggle from "./components/ThemeToggle"; import Toast from "./components/Toast"; import EmptyState from "./components/EmptyState"; import Privacy from "./Privacy"; import { showToast } from "./lib/toast"; import { apiRecordToLocal } from "./lib/api"; import { parseCSV, processBatch } from "./lib/coverage"; function AppInner() { const { getToken } = useAuth(); const [records, setRecords] = useState([]); const [uploadData, setUploadData] = useState(null); // full API payload (P6) const [batchId, setBatchId] = useState(null); const [activeFilter, setActiveFilter] = useState("all"); const [importLabel, setImportLabel] = useState("No data imported"); const csvImportRef = useRef(null); // The nested patient view is the door (P6, Kisa-approved); ?legacy=1 keeps // the old row-keyed table reachable during the transition (Phase 2b). const useLegacyView = new URLSearchParams(window.location.search).get("legacy") === "1"; const supplyLapsed = records.filter((r) => r.flag === "SUPPLY_LAPSED").length; const visitRequired = records.filter((r) => r.flag === "VISIT_REQUIRED").length; const renewalCritical = records.filter((r) => r.flag === "RENEWAL_CRITICAL").length; const renewalElevated = records.filter((r) => r.flag === "RENEWAL_ELEVATED").length; const renewalSoon = records.filter((r) => r.flag === "RENEWAL_SOON").length; const transferPending = records.filter((r) => r.flag === "TRANSFER_PENDING").length; const refill = records.filter((r) => r.flag === "RESUPPLY_READY" && !(r.cascade?.length > 0)).length; const okCount = records.filter((r) => r.flag === "ACTIVE").length; // escalateCount = VISIT_REQUIRED (qualifying visit past due) + RENEWAL_CRITICAL (<=45 days) const escalateCount = visitRequired + renewalCritical; // outreachCount = RENEWAL_ELEVATED (<=60d) + RENEWAL_SOON (<=90d) const outreachCount = renewalElevated + renewalSoon; const atRiskCount = supplyLapsed + visitRequired + renewalCritical; const actionNeededCount = renewalSoon + renewalElevated + transferPending; // handleResults receives (data, file) from CSVImport after the upload (and optional // mapping review) step is complete. data is the backend API response, or null if // the backend was unreachable, in which case we fall back to local processing. const handleResults = useCallback((data, file) => { const label = new Date().toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", }); if (data) { const results = data.records.map(apiRecordToLocal); setRecords(results); setUploadData(data); setBatchId(data.batch_id || null); setImportLabel(`${file.name} · ${label} · via Signal API`); let msg = `Loaded ${data.total} patient${data.total !== 1 ? "s" : ""} from ${file.name}`; if (data.skipped) { msg += ` · ${data.skipped} row${data.skipped !== 1 ? "s" : ""} skipped`; if (data.skipped_reasons?.length) { const first = data.skipped_reasons[0]; const more = data.skipped_reasons.length - 1; msg += ` — ${first}${more > 0 ? ` (+${more} more)` : ""}`; } } showToast(msg); return; } // Backend unreachable — process locally (legacy table renders; the // nested view needs the API's patients payload) setBatchId(null); setUploadData(null); const reader = new FileReader(); reader.onload = (e) => { const rows = parseCSV(e.target.result); const { results, skipped } = processBatch(rows); setRecords(results); setImportLabel(`${file.name} · ${label}`); let msg = `Loaded ${results.length} patient${results.length !== 1 ? "s" : ""} from ${file.name}`; if (skipped.length) msg += ` · ${skipped.length} skipped`; showToast(msg); }; reader.readAsText(file); }, []); return (