import type { ReactNode } from "react";
import { useEffect, useRef } from "react";
import { clinicianApi } from "../../api/client";
import type { PatientDetail, SharedEnvelope, SharedSection } from "../../api/types";
import { formatDate } from "../../flow";
import { asOfLabel, firstName, isNewSince, scheduleLabel, whenLabel } from "../../logic/format";
import { sharedSections } from "../../logic/patients";
import { DoseBars, LabsPanel, NewPill } from "../charts";
import { Card, cn, Icon, type IconName, Notice, SectionHeader, StatusPill } from "../kit";
import { ErrorNote, Spinner, useLoad } from "../ui";
const New = ({ at, since }: { at: string; since: string | null }) => (isNewSince(at, since) ? <NewPill /> : null);
/** Load one shared section, refresh it every minute, and record that the clinician looked (SHR-6). */
function useShared<T extends SharedEnvelope>(patientId: string, section: SharedSection, load: () => Promise<T>) {
const data = useLoad(load, [patientId, section]);
const marked = useRef(false);
useEffect(() => {
const t = setInterval(() => data.reload(true), 60_000);
return () => clearInterval(t);
}, [data.reload]);
useEffect(() => {
if (!data.data || marked.current) return;
marked.current = true;
void clinicianApi.viewed(patientId, section).catch(() => undefined);
}, [data.data, patientId, section]);
return data;
}
function Section<T extends SharedEnvelope>({
title,
icon,
tint,
state,
children,
}: {
title: string;
icon: IconName;
tint: string;
state: ReturnType<typeof useShared<T>>;
children: (d: T) => ReactNode;
}) {
return (
<section aria-label={title}>
<Card as="div" className="flex flex-col gap-4">
<SectionHeader
title={
<>
<span className={cn("flex size-7 items-center justify-center rounded-lg bg-page shadow-card", tint)}>
<Icon name={icon} />
</span>
{title}
</>
}
action={state.data ? <span className="font-mono text-[12px] text-subtle">{asOfLabel(state.data.asOf)}</span> : undefined}
/>
{state.loading && !state.data && <Spinner />}
<ErrorNote error={state.error} onRetry={() => state.reload()} />
{state.data && children(state.data)}
</Card>
</section>
);
}
const rows = "m-0 flex list-none flex-col p-0";
const row = "flex flex-col gap-1 py-3 shadow-[inset_0_-1px_0_var(--color-hairline)] last:shadow-none sm:flex-row sm:items-start sm:justify-between sm:gap-4";
const meta = "text-[12.5px] leading-[18px] text-muted";
const Sub = ({ children }: { children: ReactNode }) => <h3 className="m-0 pt-1 text-[13px] leading-[18px] font-medium text-muted">{children}</h3>;
function Medicines({ patient }: { patient: PatientDetail }) {
const state = useShared(patient.id, "medicines", () => clinicianApi.sharedMedicines(patient.id));
return (
<Section title="Medicines" icon="capsule-line" tint="text-indigo-icon" state={state}>
{(d) => (
<>
{d.withheldSome && (
<Notice tone="amber" icon="information-line">
Not everything was shared. {firstName(patient.name)} held back some medicines or conditions.
</Notice>
)}
<ul className={rows}>
{d.medicines.map((m) => {
const stopped = m.status === "stopped";
return (
<li key={m.id} className={row}>
<div className="flex min-w-0 flex-col gap-1">
<span className="flex flex-wrap items-center gap-2">
<strong className={cn("text-[14px] leading-5 font-medium", stopped ? "text-muted line-through" : "text-ink")}>
{m.name} {m.strength}
</strong>
{stopped && (
<StatusPill tone="grey" size="sm" icon="forbid-fill">
Stopped {m.stoppedAt ? formatDate(m.stoppedAt) : ""}
</StatusPill>
)}
<New at={m.changedAt} since={d.lastLookedAt} />
</span>
<span className={meta}>
{[scheduleLabel(m.schedule), m.purpose && `for ${m.purpose.toLowerCase()}`, m.startDate && `since ${formatDate(m.startDate)}`, m.origin.replace(/_/g, " "), m.prescriber && `prescribed by ${m.prescriber}`].filter(Boolean).join(" · ")}
</span>
</div>
<span className="shrink-0 text-[12px] leading-[18px] text-subtle">
Added by {m.addedBy} · {formatDate(m.addedAt.slice(0, 10))}
</span>
</li>
);
})}
</ul>
<Sub>Allergies</Sub>
{d.allergies.length ? (
<ul className={rows}>
{d.allergies.map((a) => (
<li key={a.id} className={row}>
<div className="flex min-w-0 flex-col gap-1">
<span className="flex flex-wrap items-center gap-2">
<strong className="text-[14px] leading-5 font-medium text-ink">{a.substance}</strong> <New at={a.changedAt} since={d.lastLookedAt} />
</span>
<span className={meta}>
{[a.reaction ? `Reaction: ${a.reaction}` : "Reaction not recorded", a.onset, a.severity, a.year && `in ${a.year}`, a.treatment, a.recordedWhere && `recorded at ${a.recordedWhere}`].filter(Boolean).join(" · ")}
</span>
</div>
<span className="shrink-0 text-[12px] leading-[18px] text-subtle">Added by {a.addedBy}</span>
</li>
))}
</ul>
) : (
<p className={cn("m-0", meta)}>No allergies shared. Unknown is not the same as none.</p>
)}
<Sub>Conditions and details</Sub>
<dl className="m-0 grid grid-cols-2 gap-x-6 gap-y-3 sm:grid-cols-4">
{[
["Conditions", d.conditions.map((c) => c.name).join(", ") || "None shared"],
["Date of birth", d.profile.dateOfBirth ? formatDate(d.profile.dateOfBirth) : d.profile.age ? `Age ${d.profile.age}` : "Not recorded"],
["Sex", d.profile.sex ?? "Not recorded"],
["Weight", d.profile.weightKg ? `${d.profile.weightKg} kg${d.profile.weightRecordedAt ? ` · ${formatDate(d.profile.weightRecordedAt.slice(0, 10))}` : ""}` : "Not recorded"],
...(d.profile.pregnancy ? [["Pregnancy", d.profile.pregnancy.replace("_", " ")]] : []),
].map(([term, value], i) => (
<div key={term} className={cn("flex min-w-0 flex-col gap-1", i === 0 && "col-span-2 sm:col-span-4")}>
<dt className="text-[12.5px] text-muted">{term}</dt>
<dd className="m-0 text-[14px] leading-5 font-medium text-ink tabular-nums">{value}</dd>
</div>
))}
</dl>
</>
)}
</Section>
);
}
function Doses({ patient }: { patient: PatientDetail }) {
const state = useShared(patient.id, "doses", () => clinicianApi.sharedDoses(patient.id));
return (
<Section title="Doses taken" icon="calendar-check-line" tint="text-green-icon" state={state}>
{(d) => (
<DoseBars
doses={d}
notes={
d.patterns.length > 0 && (
<div className="flex flex-col gap-2">
{d.patterns.map((p) => (
<Notice key={p.text} tone="amber" icon="error-warning-line">
<span className="flex flex-wrap items-center gap-2">
{p.text} <New at={p.changedAt} since={d.lastLookedAt} />
</span>
</Notice>
))}
</div>
)
}
/>
)}
</Section>
);
}
function CheckIns({ patient }: { patient: PatientDetail }) {
const state = useShared(patient.id, "check_ins", () => clinicianApi.sharedCheckIns(patient.id));
return (
<Section title="Daily check-ins" icon="chat-1-line" tint="text-blue-icon" state={state}>
{(d) => (
<>
{d.daily.length > 0 && (
<ul className={rows}>
{d.daily.map((c) => (
<li key={c.id} className="flex flex-col gap-1 py-3 shadow-[inset_0_-1px_0_var(--color-hairline)] last:shadow-none">
<span className="flex flex-wrap items-center gap-2">
<strong className="text-[14px] leading-5 font-medium text-ink">{formatDate(c.date)}</strong> <New at={c.changedAt} since={d.lastLookedAt} />
</span>
<span className={meta}>{[c.mood && `Mood ${c.mood.toLowerCase()}`, c.energy && `energy ${c.energy.toLowerCase()}`, c.sleep && `sleep ${c.sleep.toLowerCase()}`].filter(Boolean).join(" · ")}</span>
{c.sideEffects.length > 0 && <span className={meta}>Side effects: {c.sideEffects.join(", ")}</span>}
{c.note && <q className="text-[13px] leading-[19px] text-body">{c.note}</q>}
</li>
))}
</ul>
)}
{!d.daily.length && <p className={cn("m-0", meta)}>No daily check-ins in the last 30 days.</p>}
<p className={cn("m-0", meta)}>Check-ins you asked Yasmar for are on the Check-ins tab. Other clinicians' check-ins are never shown.</p>
</>
)}
</Section>
);
}
function Records({ patient }: { patient: PatientDetail }) {
const state = useShared(patient.id, "records", () => clinicianApi.sharedRecords(patient.id));
return (
<Section title="Records" icon="test-tube-line" tint="text-teal-icon" state={state}>
{(d) => (
<>
<LabsPanel labs={d.labs} lastLookedAt={d.lastLookedAt} />
{d.documents.length > 0 && (
<>
<Sub>Documents</Sub>
<ul className={rows}>
{d.documents.map((doc) => (
<li key={doc.id} className="flex items-center gap-3 py-3 shadow-[inset_0_-1px_0_var(--color-hairline)] last:shadow-none">
<Icon name="file-text-line" className="text-muted" />
<span className="flex min-w-0 flex-col gap-0.5">
<strong className="text-[14px] leading-5 font-medium text-ink">{doc.title}</strong>
<span className={meta}>
{doc.kind}
{doc.date ? ` · ${formatDate(doc.date)}` : ""} · added by {doc.addedBy} {whenLabel(doc.addedAt)}
</span>
</span>
</li>
))}
</ul>
</>
)}
<p className={cn("m-0", meta)}>To use these in a check, read the records on the Check tab: app data goes through review like any record.</p>
</>
)}
</Section>
);
}
/** SHR-2 to SHR-6, read-only (SHR-8). Each area shows only if the patient shares it. */
export function SharedTab({ patient }: { patient: PatientDetail }) {
const sections = sharedSections(patient.sharing);
return (
<div className="flex flex-col gap-4">
<p className="m-0 text-[13.5px] leading-5 text-muted">
What {firstName(patient.name)} shares from MARMAR Health. It is read-only here: changes happen in the app.
{patient.sharing?.status === "paused" ? " Sharing is paused, so this is as it was when it paused." : ""}
</p>
{sections.includes("medicines") && <Medicines patient={patient} />}
{sections.includes("doses") && <Doses patient={patient} />}
{sections.includes("check_ins") && <CheckIns patient={patient} />}
{sections.includes("records") && <Records patient={patient} />}
</div>
);
}