marmar-context / web / context / src / clinician / patients / bits.tsx
bits.tsx
Raw
import type { PatientSummary } from "../../api/types";
import { initials } from "../../flow";
import type { Pill, PillIcon } from "../../logic/pills";
import { cn, Icon, StatusPill, type IconName, type Tone } from "../kit";

const pillIcons: Record<PillIcon, IconName> = {
  sent: "send-plane-fill",
  ringing: "volume-up-line",
  live: "mic-fill",
  completed: "checkbox-circle-fill",
  missed: "phone-fill",
  later: "time-fill",
  not_reached: "hourglass-fill",
  expired: "hourglass-2-fill",
  declined: "forbid-fill",
  identity: "shield-cross-line",
  ended_early: "pause-circle-fill",
  cancelled: "close-circle-fill",
  urgent: "alarm-warning-fill",
  queued: "hourglass-line",
  reading: "loader-4-line",
  read: "file-text-fill",
  failed: "error-warning-fill",
  held_back: "forbid-line",
  connected: "plug-fill",
  paused: "pause-circle-fill",
  stopped: "link-unlink",
  clinic: "hospital-fill",
  draft: "draft-fill",
};

/** A state pill from logic/pills.ts, drawn with the kit: its icon, its colour and its words. */
export function StatePill({ pill, size = "sm" }: { pill: Pill; size?: "sm" | "md" }) {
  const outline = pill.tone === "red-outline";
  return (
    <StatusPill tone={outline ? "red" : (pill.tone as Tone)} outline={outline} icon={pillIcons[pill.icon]} size={size} title={pill.meaning || undefined}>
      {pill.label}
    </StatusPill>
  );
}

/** PAT-2: what needs the clinician, as a pill; null when nothing does. */
export function attentionPill(p: PatientSummary): { tone: Tone; icon: IconName } | null {
  if (p.attention.urgent) return { tone: "red", icon: "alarm-warning-fill" };
  if (p.attention.unreadNotes) return { tone: "blue", icon: "file-text-fill" };
  if (p.attention.newSinceLastCheck) return { tone: "amber", icon: "file-search-fill" };
  return null;
}

export function PatientAvatar({ name, size = 32, className }: { name: string; size?: number; className?: string }) {
  return (
    <span
      aria-hidden="true"
      style={{ width: size, height: size, fontSize: size < 32 ? 11 : 12 }}
      className={cn("inline-flex shrink-0 items-center justify-center rounded-full bg-quiet font-semibold text-body", className)}
    >
      {initials(name)}
    </span>
  );
}

/** A thin bar for doses taken of those due; the words beside it carry the numbers. */
export function DoseBar({ taken, expected, className }: { taken: number; expected: number; className?: string }) {
  const share = expected > 0 ? Math.min(1, taken / expected) : 0;
  return (
    <span aria-hidden="true" className={cn("block h-1.5 w-full overflow-hidden rounded-full bg-quiet", className)}>
      <span className="block h-full rounded-full bg-green-icon" style={{ width: `${Math.round(share * 100)}%` }} />
    </span>
  );
}