import React from "react";
import { FileText, History } from "lucide-react";
import type { SidebarMenuItem } from "./patientNav.tsx";
import { PATIENT_RECORD_SECTIONS } from "./PatientsPage/PatientPage/patientRecordSections.ts";
/**
* ONE CUT THROUGH THE PATIENT'S RECORD, shared by the navigation drawer, the record page's own
* "Browse your record" list, and the breadcrumb.
*
* The drawer used to carry its own grouping - Clinical Data, Diagnostics, Care Provision, Device
* Data, Management - written out as JSX beside the destinations themselves. The record page grouped
* the SAME destinations by `PATIENT_RECORD_SECTIONS`, in the patient's words. So a patient met one
* vocabulary in the drawer and a different one on the page, and the drawer is what they meet first.
*
* The fix is structural rather than a rename: the drawer's groups are BUILT from the sections, so
* there is no second grouping left to disagree with. This file holds the destinations, the sections
* file holds the filing, and neither can drift from the other because neither has a copy.
*/
type PatientRecordDestination = {
id: string;
/** What the patient sees. Their words, never a resource type. */
label: string;
to: string;
/** Defaults to a document icon. Only the two destinations that are not documents override it. */
icon?: React.ReactNode;
/** The breadcrumb parent, where the destination sits outside the record. Defaults to the record. */
crumbParent?: string;
};
export const FAMILY_HISTORY_PATH = "/dashboard/patient/clinical/family-history";
const documentIcon = ;
/**
* Every destination inside the record, flat. The order here does not decide display order - a
* section's `owns` array does - so this list is grouped by route for reading, not for rendering.
*
* Every id must be claimed by exactly one section, either in its `owns` (offered as a row) or in
* its `alsoClaims` (still reachable, but the stream is where the patient meets it).
*/
export const PATIENT_RECORD_DESTINATIONS: PatientRecordDestination[] = [
// Under My Profile in the approved deck: "Dashboard › My Profile › Family health history".
{ id: "family-history", label: "Family health history", to: FAMILY_HISTORY_PATH, crumbParent: "/dashboard/profile" },
{ id: "allergy-intolerances", label: "Allergies", to: "/dashboard/patient/clinical/allergy-intolerances" },
{ id: "conditions", label: "Conditions", to: "/dashboard/patient/clinical/conditions" },
{ id: "procedures", label: "Procedures & surgery", to: "/dashboard/patient/clinical/procedures" },
{ id: "adverse-events", label: "Bad reactions", to: "/dashboard/patient/clinical/adverse-events" },
{ id: "clinical-impressions", label: "Notes from your visits", to: "/dashboard/patient/clinical/clinical-impressions" },
{ id: "detected-issues", label: "Things to check", to: "/dashboard/patient/clinical/detected-issues" },
{ id: "clinical-results", label: "Test results", to: "/dashboard/patient/diagnostics/diagnostics-reports" },
{ id: "personal-readings", label: "Readings & measurements", to: "/dashboard/patient/diagnostics/observations" },
{ id: "imaging-studies", label: "Scans & X-rays", to: "/dashboard/patient/diagnostics/imaging-studies" },
{ id: "media", label: "Photos & recordings", to: "/dashboard/patient/diagnostics/media" },
{ id: "body-structures", label: "Body sites", to: "/dashboard/patient/diagnostics/body-structures" },
{ id: "medication-requests", label: "Prescriptions", to: "/dashboard/patient/medications/medication-requests" },
{ id: "medication-statements", label: "Medicines you told us about", to: "/dashboard/patient/medications/medication-statements" },
{ id: "medication-administrations", label: "Medicines given to you", to: "/dashboard/patient/medications/medication-administrations" },
{ id: "medication-dispenses", label: "Medicines dispensed", to: "/dashboard/patient/medications/medication-dispenses" },
{ id: "immunizations", label: "Vaccinations", to: "/dashboard/patient/medications/immunizations" },
{ id: "immunization-recommendations", label: "Vaccinations due", to: "/dashboard/patient/medications/immunization-recommendations" },
{ id: "immunization-evaluations", label: "Vaccination checks", to: "/dashboard/patient/medications/immunization-evaluations" },
{ id: "care-plans", label: "Care plans", to: "/dashboard/patient/care-provision/care-plans" },
// Its own page in the patient's navigation (Spencer, 2026-09-02: care plans and goals "separated into
// their own pages"), and the deck trails it "Dashboard > Goals".
{ id: "goals", label: "Goals", to: "/dashboard/patient/care-provision/goals", crumbParent: "/dashboard" },
// "CLINICAL care team", not "care team". Collaboration Teams is a different product surface, and
// the two were one word apart in both the label AND the URL until 2026-09-02. Spencer ruled on
// 2026-08-13 that "care-teams should be collaboration-teams unless you're looking at a true FHIR
// care team" - so the collaboration surface now lives at /dashboard/collaboration-teams and THIS
// one, which is the true FHIR CareTeam, keeps the name it earns.
// The label feeds the drawer, the record's own browse list and the breadcrumb from this single
// table, so the three cannot disagree about which of the two a patient is looking at.
{ id: "care-teams", label: "Your clinical care team", to: "/dashboard/patient/care-provision/care-teams" },
{ id: "outside-providers", label: "Other providers you see", to: "/dashboard/patient/care-provision/outside-providers" },
{ id: "nutrition-orders", label: "Diet & nutrition", to: "/dashboard/patient/care-provision/nutrition-orders" },
{ id: "risk-assessments", label: "Risk assessments", to: "/dashboard/patient/care-provision/risk-assessments" },
{ id: "vision-prescriptions", label: "Glasses & contacts", to: "/dashboard/patient/care-provision/vision-prescriptions" },
{ id: "devices", label: "Devices", to: "/dashboard/patient/device-data/devices" },
{ id: "device-use-statements", label: "Devices you use", to: "/dashboard/patient/device-data/device-use-statements" },
{ id: "device-requests", label: "Devices ordered for you", to: "/dashboard/patient/device-data/device-requests" },
// Reachable, but no longer offered as a row: the record's own index IS the stream of visits, and a
// flat list of those same visits with nothing to open is what the stream replaced. Both stay in this
// table so each page still has a name for its breadcrumb rather than a humanised URL slug.
{ id: "encounters", label: "Visits", to: "/dashboard/patient/management/encounters" },
{ id: "episodes-of-care", label: "Courses of care", to: "/dashboard/patient/management/episodes-of-care" },
{ id: "documents", label: "Documents", to: "/dashboard/patient/documents" },
{ id: "questionnaire-responses", label: "Forms you filled in", to: "/dashboard/patient/questionnaire-responses" },
// INSURANCE IS NOT A RECORD DESTINATION, 2026-08-30. "Insurance stops pretending to be part of the
// record" - and Spencer's 08-29 ruling gave it a door of its own on the patient home, which is how a
// patient reaches it now. Leaving it here gave the same screen two routes in with two different names:
// `getBreadcrumbConfig` named /dashboard/patient/insurance as a literal parented to the DASHBOARD and
// the record generator named it again parented to the RECORD, and the literal silently won. Same
// two-vocabularies defect that took "Things to do" out of this list.
// Its route is /dashboard/MY-HISTORY, not /dashboard/patient/... like its siblings.
// `renderPatientRoutes` is shared with the STAFF patient view and this feed is the patient's own
// awareness tool, so it is deliberately mounted outside it. Tidying the path to match would 404.
{
id: "my-history",
label: "My record activity",
to: "/dashboard/my-history",
icon: ,
// The deck crumbs it "Dashboard › My Profile › My record activity".
crumbParent: "/dashboard/profile",
},
];
const byId = new Map(PATIENT_RECORD_DESTINATIONS.map((destination) => [destination.id, destination]));
/** The destinations a section OFFERS, in the section's own order. */
const offeredBy = (owns: string[]): PatientRecordDestination[] =>
owns.map((id) => byId.get(id)).filter((d): d is PatientRecordDestination => Boolean(d));
/**
* The record's children in the navigation drawer.
*
* A section holding several destinations becomes an expandable group. A section holding ONE becomes
* a plain row carrying the SECTION's label, because a disclosure that reveals a single row is a
* click that tells the patient nothing.
*/
export const patientRecordNavItems = (navigate: (to: string) => void): SidebarMenuItem[] =>
PATIENT_RECORD_SECTIONS.flatMap((section): SidebarMenuItem[] => {
const destinations = offeredBy(section.owns);
if (destinations.length === 0) return [];
if (destinations.length === 1) {
const only = destinations[0];
return [
{
id: only.id,
label: section.label,
icon: only.icon ?? documentIcon,
to: only.to,
activeMatch: "prefix",
activePaths: [only.to],
onClick: () => navigate(only.to),
},
];
}
return [
{
id: section.id,
label: section.label,
icon: documentIcon,
// No `to` and no `activePaths`: a section's destinations sit under several different route
// prefixes now, so no single path means "somewhere in this section". The group lights up
// through `hasActiveDescendant` instead, which asks its children rather than guessing.
children: destinations.map((destination) => ({
id: destination.id,
label: destination.label,
icon: destination.icon ?? documentIcon,
to: destination.to,
activeMatch: "prefix",
activePaths: [destination.to],
onClick: () => navigate(destination.to),
})),
},
];
});
/** Section ids that render as an expandable group, so only one of them is open at a time. */
export const PATIENT_RECORD_GROUP_IDS = PATIENT_RECORD_SECTIONS.filter(
(section) => offeredBy(section.owns).length > 1,
).map((section) => section.id);
/**
* Which section group to open for a route, or undefined where none applies. Derived from the
* destinations rather than from a path prefix: one prefix no longer means one section, since the
* destinations under `/dashboard/patient/clinical` alone now belong to four different ones.
*/
export const patientRecordGroupForPath = (pathname: string): string | undefined => {
let best: { section: string; length: number } | undefined;
for (const section of PATIENT_RECORD_SECTIONS) {
const destinations = offeredBy(section.owns);
if (destinations.length < 2) continue;
for (const destination of destinations) {
if (pathname !== destination.to && !pathname.startsWith(destination.to + "/")) continue;
if (!best || destination.to.length > best.length) {
best = { section: section.id, length: destination.to.length };
}
}
}
return best?.section;
};
/**
* Breadcrumb entries for every record destination, built from the same table the drawer renders.
*
* EVERY ONE HANGS DIRECTLY OFF THE RECORD, unless it names a `crumbParent`. The old config carried a crumb for the intermediate route
* segment as well - "My Visits & Results > Conditions & notes > Procedures & surgery" - and that
* middle crumb is now unnameable, because the destinations under one segment belong to several
* different sections. It was also a link to a redirect: /dashboard/patient/clinical sends you to
* Family health history, so the crumb for the page you were standing on went somewhere else.
*/
export const patientRecordBreadcrumbs = (): Record => ({
...Object.fromEntries(
PATIENT_RECORD_DESTINATIONS.map((destination) => [
destination.to,
{ label: destination.label, parent: destination.crumbParent ?? "/dashboard/patient" },
]),
),
// THE TWO CONTAINERS THE STREAM OPENS. They are not destinations - nothing navigates to them from a
// list, a row does - so they are not in the table above. They still need naming here: without an
// entry the crumb falls to the humaniser, which reads "Dashboard > Visit" and drops the record from
// the trail entirely. The deck keeps the record's own trail with one open - "Dashboard > My visits &
// results" beside the list on a laptop and on the phone page alike - so each IS the record's crumb.
"/dashboard/patient/visit/:visitId": { label: "My Visits & Results", parent: "/dashboard" },
"/dashboard/patient/result/:resultId": { label: "My Visits & Results", parent: "/dashboard" },
[`${FAMILY_HISTORY_PATH}/:familyHistoryId`]: { label: "Relative", parent: FAMILY_HISTORY_PATH },
});