marmar-context / web / context / src / clinician / patients / PatientTile.tsx
PatientTile.tsx
Raw
import type { PatientSummary } from "../../api/types";
import { whenLabel } from "../../logic/format";
import { attentionLabel, sharedByLine, tileValues } from "../../logic/patients";
import { patientStatePill } from "../../logic/pills";
import { cn, Icon, StatusPill } from "../kit";
import { Link } from "../router";
import { attentionPill, PatientAvatar, StatePill } from "./bits";

/** SHR-1 on a phone: name and age, who shares, when it last changed, the state; medicines, doses this week, last check-in. */
export function PatientTile({ patient, link = true }: { patient: PatientSummary; link?: boolean }) {
  const values = tileValues(patient);
  const sharing = patient.sharing;
  const attention = attentionLabel(patient);
  const look = attentionPill(patient);
  const title = `${patient.name}${patient.age !== null ? `, ${patient.age}` : ""}`;
  const head = (
    <div className="flex items-start gap-3">
      <PatientAvatar name={patient.name} size={36} />
      <div className="flex min-w-0 flex-1 flex-col gap-0.5">
        <b className="truncate text-[14.5px] leading-5 font-medium text-ink">{title}</b>
        <small className="text-[12.5px] leading-[17px] text-muted">
          {sharing ? `${sharedByLine(sharing)} · ${whenLabel(sharing.changedAt)}` : "Clinic patient · records you added"}
        </small>
        <span className="mt-1.5 flex flex-wrap gap-1.5">
          {attention && look && (
            <StatusPill tone={look.tone} icon={look.icon} size="sm">
              {attention}
            </StatusPill>
          )}
          <StatePill pill={patientStatePill(patient)} />
        </span>
      </div>
      {link && <Icon name="arrow-right-s-line" size={18} className="mt-2.5 text-subtle" />}
    </div>
  );
  return (
    <article className={cn("rounded-2xl bg-page shadow-card", patient.attention.urgent && "shadow-[0_0_0_1px_var(--color-red-line)]")}>
      {link ? (
        <Link
          className="block rounded-t-2xl p-4 text-inherit no-underline outline-none focus-visible:shadow-[inset_0_0_0_2px_var(--color-brand)]"
          to={`/app/patients/${patient.id}`}
          aria-label={`Open ${patient.name}`}
        >
          {head}
        </Link>
      ) : (
        <div className="p-4">{head}</div>
      )}
      {values ? (
        <dl className="m-0 grid grid-cols-3 gap-3 px-4 py-3 shadow-[inset_0_1px_0_var(--color-hairline)]">
          {([["Medicines", values.medicines], ["Doses this week", values.doses], ["Last check-in", values.lastCheckIn]] as const).map(([label, cell]) => (
            <div className="flex min-w-0 flex-col gap-0.5" key={label}>
              <dt className="truncate text-[12px] text-muted">{label}</dt>
              <dd className="m-0 truncate text-[14px] leading-5 font-medium text-ink tabular-nums">{cell.value}</dd>
              <span className="text-[11.5px] leading-4 text-subtle">{cell.note}</span>
            </div>
          ))}
        </dl>
      ) : (
        <p className="m-0 px-4 py-3 text-[13px] leading-[18px] text-muted shadow-[inset_0_1px_0_var(--color-hairline)]">
          {sharing?.status === "stopped"
            ? `Sharing stopped ${whenLabel(sharing.changedAt)}. You keep the checks and check-ins you saved.`
            : `Invite ${patient.name.split(" ")[0]} to MARMAR Health to see medicines, doses and check-ins from the app.`}
        </p>
      )}
    </article>
  );
}