import { useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { Search } from "lucide-react"; import { usePatientNav } from "../../patientNav.tsx"; import { PATIENT_RECORD_DESTINATIONS } from "../../patientRecordNav.tsx"; import { PROFILE_SECTION, resolvePatientRecordSections, sectionMatchesQuery, } from "./patientRecordSections.ts"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "../../../../components/ui/accordion.tsx"; import { Input } from "../../../../components/ui/input.tsx"; import { EmptyState } from "../../../../components/ui/empty-state.tsx"; /** * The patient's way around their own record.. * * Until this existed, `PatientChartSidebar` rendered only for STAFF (`!isPatient` in * `PatientPage`), so a patient who opened their own chart could not go anywhere from it: all 36 * record destinations were reachable only through the hamburger - the exact thing design ruling D6 * forbids, for the cohort it names. * * Deliberately NOT the staff sidebar: that one is a clinician scanning a whole patient. This is a * person looking for their own blood test, so the sections are authored in patient words and the * search matches what they would type rather than what we filed it under. * * Two variants, one component, because a phone cannot afford two: * `index` - open, the chart's landing surface. This is what the home's "My visits & results" door * opens onto, and the reason the door stops being a dead end. * `browse` - collapsed to a single row above a record page, so every leaf keeps a way onward * without spending a screen on navigation. */ export const PatientRecordNav = ({ variant = "browse", label = "Your record", }: { variant?: "index" | "browse"; // What this list is called where it sits. It leads the chart as "Your record"; under the visit list // it is the second way in and says so. One component either way, so the two cannot drift apart. label?: string; }) => { const navigate = useNavigate(); const navItems = usePatientNav(); const [query, setQuery] = useState(""); const [expanded, setExpanded] = useState([]); // The authored destinations are handed in as the fallback the resolver uses when the sidebar // offers no record children - which is every patient today. Without it this list is empty and the // component returns null, which is exactly how the record lost its second way in. const sections = useMemo(() => resolvePatientRecordSections(navItems, PATIENT_RECORD_DESTINATIONS), [navItems]); const matching = useMemo( () => sections.filter((section) => sectionMatchesQuery(section, query)), [sections, query], ); // A search that finds a section but leaves it shut has not answered the question. Typing opens // every match; clearing returns to whatever the patient had opened themselves. const searching = query.trim().length > 0; const openSections = searching ? matching.map((section) => section.id) : expanded; // Nothing published (outside the dashboard shell) - render nothing rather than an empty frame // implying the record is empty. if (sections.length === 0) return null; // - "SEARCH YOUR RECORD" WAS A PROMISE THIS BOX CANNOT KEEP, and the old wording made the // product lie to a patient about their own chart. It matches section labels, their keywords and // destination labels: `usePatientNav()` supplies NAVIGATION, and no allergy, condition or result is // loaded here to match against. // Measured on a chart holding one allergy, Peanut: typing `Peanut` returned "Nothing in your record // matches" while the item sat one click away under Allergies & Intolerances. // That is the worst shape a wrong answer can take - confident, specific, and it stops the person // looking. A patient checking whether their allergy is on file was told it is not, which is worse than // having no search at all. // Searching the record's CONTENTS is a real feature and belongs on its own card. Keeping the promise // this box actually makes costs nothing and is the honest half of the fix. const body = (
{matching.length === 0 ? ( // Says what was searched for AND what was searched. "No results" alone leaves a patient unsure // whether they typed the wrong thing or their record is empty - and the second sentence is the // one that stops them concluding the item is missing when it is one click away. // // `compact` and NO ICON: this sits inside the browse panel, above a list, not on a page of its // own. A full-size state with a 40px icon would push the search box it belongs to off a phone. ) : ( {matching.map((section) => ( {section.label} {section.destinations.map((destination) => ( ))} ))} )} {/* The patient's own account. Declared rather than derived — see PROFILE_SECTION: the profile route exists and NOTHING in the nav points at it, which is why it would otherwise be the one part of a patient's record they cannot reach from their own chart. */}
); if (variant === "index") { return (

{label}

{body}
); } return (
Browse your record
{body}
); }; export default PatientRecordNav;