"use client"; /** * The procedure and surgery history — the third tab of the checkups page. * * It answers the intake-form question "what surgeries have you had" at a * glance, and "what was done on the left knee" with one tap. A procedure is a * visit of kind PROCEDURE, so there is no second record type to keep in step: * the rows here are the same rows the visits tab lists, opened into the same * sheet, and a visit filed before this view existed joins it by switching its * kind. * * The search runs on the server. The body site is encrypted at rest, so the * server decrypts the account's own procedures and matches there; this view * sends the words and renders the answer. The body-site choices and the total * come back with every answer, computed over the whole history, so picking one * never makes the others disappear. */ import { Plus, Scissors, Search } from "lucide-react"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { EmptyState } from "@/components/ui/empty-state"; import { FieldGroup } from "@/components/ui/field-group"; import { Input } from "@/components/ui/input"; import { PageHeader } from "@/components/ui/page-header"; import { QueryErrorCard } from "@/components/ui/query-error-card"; import { Skeleton } from "@/components/ui/skeleton"; import { useDebouncedValue } from "@/hooks/use-debounced-value"; import { useRecordCapabilities } from "@/hooks/use-record-capabilities"; import { useProcedures, type Encounter } from "@/hooks/use-encounters"; import { useTranslations } from "@/lib/i18n/context"; import type { BodySiteFacetDTO } from "@/lib/encounters/dto"; import { bodySiteText } from "./encounter-labels"; import { EncounterSheet } from "./encounter-sheet"; import { VisitCard } from "./visit-card"; type Side = "LEFT" | "RIGHT" | "BOTH" | null; export function ProceduresSection({ enabled = true }: { enabled?: boolean }) { const { t } = useTranslations(); const { canWriteDomain, canManageDomain } = useRecordCapabilities(); // Same grant questions as the visits tab: a procedure is a visit. const canAdd = canWriteDomain("profile"); const canManage = canManageDomain("profile"); const [query, setQuery] = useState(""); const [side, setSide] = useState(null); const q = useDebouncedValue(query.trim(), 250); const list = useProcedures(enabled, { q, laterality: side }); const [editing, setEditing] = useState(null); const [session, setSession] = useState(0); const openSheet = (target: Encounter | "new") => { setEditing(target); setSession((n) => n + 1); }; const procedures = list.data?.procedures ?? []; const bodySites = list.data?.bodySites ?? []; const total = list.data?.total ?? 0; const filtered = query.trim().length > 0 || side !== null; const pick = (facet: BodySiteFacetDTO | null) => { setQuery(facet?.bodySite ?? ""); setSide(facet?.laterality ?? null); }; const isPicked = (facet: BodySiteFacetDTO) => query.trim() === facet.bodySite && side === facet.laterality; const addButton = canAdd ? ( ) : null; return (
{list.isError ? ( void list.refetch()} /> ) : list.isPending ? (
{Array.from({ length: 3 }, (_, index) => ( ))}
) : total === 0 ? ( } title={t("encounters.procedures.emptyTitle")} description={t("encounters.procedures.emptyDescription")} action={addButton ?? undefined} /> ) : (
{ setQuery(event.target.value); // Typing starts a new question; a side picked from a chip // belonged to the old one. setSide(null); }} />
{bodySites.length > 0 ? (
{bodySites.map((facet) => { const picked = isPicked(facet); return ( ); })}
) : null}

{t("encounters.procedures.count", { shown: String(procedures.length), total: String(total), })}

{procedures.length === 0 ? ( } title={t("encounters.procedures.noMatchTitle")} action={ } /> ) : (
{procedures.map((encounter) => ( ))}
)}
)} {/* Remounted per open, like the visits tab's sheet, so a new procedure starts as a procedure and an edit starts from its own row. */} { if (!open) setEditing(null); }} encounter={editing === "new" ? null : editing} kind="PROCEDURE" />
); }