import { useState, type ReactNode } from "react";
import { clinicianApi } from "../../api/client";
import type { CheckRun, PatientDetail } from "../../api/types";
import { doseLabels, formatDate, medicinePlan, patientProfile } from "../../flow";
import { byTier, checkHeadline, latestMarks } from "../../logic/checks";
import { calculatedEgfrLine, doseExtras, findingWording, kidneyLimitLine, notCheckedFor, resultExtras, testMedicines } from "../../logic/engine-extras";
import { factDetail, firstName, listNames, pronouns, whenLabel } from "../../logic/format";
import { urgentGate } from "../../logic/urgent";
import { zoneOffset } from "../../logic/windows";
import { FindingsMix } from "../charts";
import { confirmCall, useCallConfirmation } from "../checkin/confirmations";
import { buttonVariants, Card, cn, Icon, type IconName, Notice, SectionHeader, Segmented, StatusPill, type Tone } from "../kit";
import { tabPath } from "../patient/links";
import { SourceDrawer, type SourceRef } from "../patient/SourceDrawer";
import { Link } from "../router";
import { useClinician } from "../session";
import { FindingCard } from "./FindingCard";
type View = "findings" | "know" | "medicines" | "monitoring";
const views: Record<View, string> = { findings: "Findings", know: "What we know", medicines: "Medicine by medicine", monitoring: "Monitoring" };
const doseLook: Record<string, { tone: Tone; icon: IconName }> = {
within_usual_range: { tone: "green", icon: "checkbox-circle-fill" },
above_limit: { tone: "red", icon: "error-warning-fill" },
check_with_pharmacist: { tone: "amber", icon: "question-fill" },
not_enough_info: { tone: "grey", icon: "information-fill" },
};
const rows = "m-0 flex list-none flex-col p-0";
const row = "flex flex-col gap-1.5 py-3 shadow-[inset_0_-1px_0_var(--color-hairline)] last:shadow-none";
const meta = "text-[12.5px] leading-[18px] text-muted";
const sourceLink =
"inline-flex cursor-pointer items-center gap-1.5 self-start rounded-md border-0 bg-transparent p-0 font-mono text-[12px] text-muted underline decoration-line underline-offset-4 outline-none hover:text-ink focus-visible:shadow-[0_0_0_2px_var(--color-brand)]";
const Panel = ({ title, children, className }: { title: string; children: ReactNode; className?: string }) => (
<section aria-label={title}>
<Card as="div" className={cn("flex flex-col gap-2", className)}>
<SectionHeader title={title} />
{children}
</Card>
</section>
);
/** YCI-1a: an Urgent review finding leads with a call; Ask Yasmar waits for "I will speak to Mariam today". */
export function UrgentBanner({ patient, findings }: { patient: PatientDetail; findings: { id: string; tier: string }[] }) {
const { clinic } = useClinician();
const confirmation = useCallConfirmation(patient.id);
const gate = urgentGate({ findings, patientId: patient.id, patientName: patient.name, confirmation, now: new Date(), clinicOffset: zoneOffset(clinic.timeZone) });
if (gate.mode === "none") return null;
const first = firstName(patient.name);
return (
<Notice
tone="urgent"
role="alert"
icon="alarm-warning-fill"
title={`Urgent review: call ${first} before ${pronouns(patient.pronoun).possessive} next dose.`}
actions={
<>
{patient.phone ? (
<a className={cn(buttonVariants({ variant: "red" }), "no-underline")} href={`tel:${patient.phone.replace(/[^\d+]/g, "")}`}>
<Icon name="phone-fill" /> {gate.callLine} · {patient.phone}
</a>
) : (
<span className="text-[13px] text-red-text">No phone number is shared. Use the number in your own records.</span>
)}
{gate.canAskYasmar ? (
<StatusPill tone="green" icon="checkbox-circle-fill">
You will speak to {first} today
</StatusPill>
) : (
<button type="button" className={cn(buttonVariants(), "bg-page text-red-text hover:bg-white/70")} onClick={() => confirmCall(patient.id)}>
{gate.confirmLine}
</button>
)}
</>
}
>
<p className="m-0">
Yasmar can follow up only after you confirm you will speak to {pronouns(patient.pronoun).object} today. Yasmar is never the only answer to something urgent.
</p>
</Notice>
);
}
export function FindingsView({ patient, check, onCheck }: { patient: PatientDetail; check: CheckRun; onCheck: (c: CheckRun) => void }) {
const { clinic } = useClinician();
const [view, setView] = useState<View>("findings");
const [source, setSource] = useState<SourceRef | null>(null);
const confirmation = useCallConfirmation(patient.id);
const a = check.assessment;
const head = checkHeadline(a, patient.name);
const first = firstName(patient.name);
const marks = latestMarks(check);
const gate = urgentGate({ findings: a.contextual.findings, patientId: patient.id, patientName: patient.name, confirmation, now: new Date(), clinicOffset: zoneOffset(clinic.timeZone) });
const profile = patientProfile(a);
// The same medicine read from two records shows once (it is one medicine in the check).
const plan = medicinePlan(a).filter((m, i, all) => all.findIndex((o) => o.fact.label === m.fact.label && o.fact.detail === m.fact.detail) === i);
const calculated = calculatedEgfrLine(resultExtras(a.contextual).calculated_egfr);
const doseFor = (id: string, name: string) =>
a.contextual.dose_check.find((d) => d.medicine_id === id) ?? a.contextual.dose_check.find((d) => d.medicine.toLowerCase() === name.toLowerCase());
return (
<div className="flex flex-col gap-4">
{patient.newInformation && (
<Notice
role="status"
icon="sparkling-line"
title="New information since your last check"
actions={
<Link className={buttonVariants({ variant: "primary", size: "sm", className: "no-underline" })} to={tabPath(patient.id, "records")}>
See the records
</Link>
}
>
<p className="m-0">{listNames(patient.newInformation.items)}. Past results never change; a new run is kept beside this one.</p>
</Notice>
)}
{check.withheldSome && (
<Notice tone="amber" icon="information-line">
Not everything was shared. {first} held back some medicines or conditions, so this check may be incomplete.
</Notice>
)}
<UrgentBanner patient={patient} findings={a.contextual.findings} />
<section aria-label="Result">
<Card as="div" className="flex flex-col gap-4">
<SectionHeader
title="Result"
action={
<span className="text-right font-mono text-[12px] text-subtle">
Ran {whenLabel(check.ranAt)} · {check.ranBy}
</span>
}
/>
<p className="m-0 text-[15px] leading-[22px] font-medium text-ink">{head.headline}</p>
<div className="grid grid-cols-2 gap-3">
<div className="flex flex-col gap-1 rounded-xl bg-tile px-4 py-3">
<small className="text-[12.5px] text-muted">The list alone</small>
<span className="flex flex-wrap items-baseline gap-x-1.5">
<b className="text-[28px] leading-8 font-semibold tracking-[-0.02em] text-ink tabular-nums">{head.before}</b>
<span className="text-[13px] font-medium text-subtle">{head.before === 1 ? "finding" : "findings"}</span>
</span>
</div>
<div className="flex flex-col gap-1 rounded-xl bg-page px-4 py-3 shadow-[inset_0_0_0_1px_var(--color-line)]">
<small className="text-[12.5px] text-muted">With {first}'s details</small>
<span className="flex flex-wrap items-baseline gap-x-1.5">
<b className="text-[28px] leading-8 font-semibold tracking-[-0.02em] text-ink tabular-nums">{head.after}</b>
<span className="text-[13px] font-medium text-subtle">{head.after === 1 ? "finding to review" : "findings to review"}</span>
</span>
</div>
</div>
<FindingsMix findings={a.contextual.findings} />
<p className={cn("m-0", meta)}>Each finding links to the record lines it came from. A clinician decides what happens next.</p>
</Card>
</section>
<div className="-mx-1 overflow-x-auto px-1 py-0.5">
<Segmented label="Result views" options={(Object.keys(views) as View[]).map((v) => ({ value: v, label: views[v] }))} value={view} onChange={setView} />
</div>
{view === "findings" && (
<section className="flex flex-col gap-3" aria-label="Findings">
{byTier(a.contextual.findings).map((f) => (
<FindingCard key={f.id} finding={f} check={check} patient={patient} mark={marks.get(f.id)} canAskYasmar={gate.canAskYasmar} onSource={setSource} onMarked={(m) => onCheck({ ...check, marks: [...check.marks, m] })} />
))}
{!a.contextual.findings.length && <p className="m-0 rounded-2xl bg-tile px-5 py-6 text-center text-[14px] font-medium text-ink">No findings. This does not mean the prescription is safe.</p>}
</section>
)}
{view === "know" && (
<Panel title={`What we know about ${first}`}>
<ul className={rows}>
{profile.map((item) => (
<li key={item.id} className={row}>
<div className="flex flex-wrap items-center justify-between gap-2">
<span className="flex min-w-0 flex-col">
<span className="text-[12.5px] text-muted">{item.label}</span>
<span className="text-[14px] leading-5 font-medium text-ink">{item.value ?? "Not recorded"}</span>
</span>
<StatusPill
size="sm"
tone={item.status === "used" ? "green" : item.status === "missing" ? "amber" : "grey"}
icon={item.status === "used" ? "checkbox-circle-fill" : item.status === "missing" ? "error-warning-fill" : "information-fill"}
>
{item.status === "used" ? "Used by a rule" : item.status === "missing" ? "Missing" : "Not read by any current rule"}
</StatusPill>
</div>
<small className={meta}>{item.note}</small>
{item.fact && (
<button type="button" className={sourceLink} onClick={() => setSource({ recordId: item.fact!.sourceId, title: check.recordTitles[item.fact!.sourceId] ?? "Record", lines: [item.fact!.line] })}>
<Icon name="file-text-line" size={14} />
{check.recordTitles[item.fact.sourceId] ?? "Record"} · line {item.fact.line}
</button>
)}
</li>
))}
{calculated && (
<li className={row}>
<div className="flex flex-wrap items-center justify-between gap-2">
<span className="flex min-w-0 flex-col">
<span className="text-[12.5px] text-muted">Kidney function (calculated)</span>
<span className="text-[14px] leading-5 font-medium text-ink">{calculated}</span>
</span>
<StatusPill size="sm" tone="amber" icon="error-warning-fill">
Calculated, not measured
</StatusPill>
</div>
</li>
)}
</ul>
</Panel>
)}
{view === "medicines" && (
<Panel title="Medicine by medicine">
<ul className={rows}>
{plan.map((m) => {
const limit = doseExtras(doseFor(m.fact.id, m.fact.label) ?? ({} as never)).kidney_limit;
const line = kidneyLimitLine(limit);
const label = limit && m.status === "within_usual_range" ? "Within the kidney-adjusted limit" : limit && m.status === "above_limit" ? "Above the kidney-adjusted limit" : doseLabels[m.status];
const look = doseLook[m.status] ?? doseLook.not_enough_info;
return (
<li key={m.fact.id} className={row}>
<div className="flex flex-wrap items-start justify-between gap-2">
<span className="flex min-w-0 flex-col gap-0.5">
<span className="flex flex-wrap items-center gap-2">
<b className="text-[14px] leading-5 font-medium text-ink">{m.fact.label}</b>
{check.newMedicineIds.includes(m.fact.id) && (
<StatusPill tone="blue" size="sm" icon="sparkling-line">
New
</StatusPill>
)}
</span>
<small className={meta}>{factDetail(m.fact.detail)}</small>
</span>
<StatusPill size="sm" tone={look.tone} icon={look.icon}>
{label}
</StatusPill>
</div>
{line && <small className="text-[12.5px] leading-[18px] text-body">Kidney-adjusted limit: {line}</small>}
<small className={meta}>{m.findings.length ? `In ${m.findings.length === 1 ? "1 finding" : `${m.findings.length} findings`}: ${m.findings.map((f) => findingWording(f, check.details[f.id]).title).join("; ")}` : "No finding names it"}</small>
</li>
);
})}
</ul>
</Panel>
)}
{view === "monitoring" && (
<Panel title="Monitoring">
<ul className={rows}>
{check.monitoring.map((t) => {
const engineTest = a.contextual.tests_to_ask_about.find((x) => x.test === t.test);
const names = engineTest ? testMedicines(engineTest, t.medicines) : t.medicines;
return (
<li key={t.test} className={cn(row, "sm:flex-row sm:items-center sm:justify-between")}>
<span className="flex min-w-0 flex-col gap-0.5">
<b className="text-[14px] leading-5 font-medium text-ink">{t.test}</b>
<small className={meta}>{names.length ? `Needed with ${listNames(names)}` : t.test === "Weight" ? "Needed for dose limits under 50 kg" : "Needed with the medicines on this list"}</small>
</span>
<StatusPill size="sm" tone={t.onFile ? "green" : "amber"} icon={t.onFile ? "calendar-check-fill" : "error-warning-fill"} className="self-start sm:self-auto">
{t.onFile ? `On file · ${formatDate(t.onFile)}` : "Not recorded"}
</StatusPill>
</li>
);
})}
</ul>
</Panel>
)}
<Panel title="Not checked" className="bg-tile shadow-none">
<ul className="m-0 flex flex-col gap-1.5 pl-5 text-[13px] leading-[19px] text-body">
{notCheckedFor(check).map((l) => (
<li key={l}>{l}</li>
))}
</ul>
<p className={cn("m-0 pt-1", meta)}>
Engine <span className="font-mono">{check.versions.engine}</span> · rules <span className="font-mono">{check.versions.rules}</span>
{check.versions.rulesReviewed ? " (reviewed)" : " (not yet reviewed)"}
</p>
<div className="flex flex-wrap gap-2 pt-2">
{/* The backend sends a printable page for format=pdf; the browser's Print saves it as a PDF. */}
<a className={buttonVariants({ variant: "outline", className: "no-underline" })} href={clinicianApi.exportUrl(check.id, "pdf")} target="_blank" rel="noreferrer">
<Icon name="download-2-line" /> Printable evidence (save as PDF)
</a>
<a className={buttonVariants({ variant: "outline", className: "no-underline" })} href={clinicianApi.exportUrl(check.id, "json")} download>
<Icon name="download-2-line" /> JSON
</a>
</div>
</Panel>
<SourceDrawer source={source} onClose={() => setSource(null)} />
</div>
);
}