marmar-context / web / context / src / clinician / charts / LabsPanel.tsx
LabsPanel.tsx
Raw
import { useId, useMemo, useState } from "react";
import type { SharedLab } from "../../api/types";
import { formatDate } from "../../flow";
import { labsAnswer, labSeries, rangeLabel } from "../../logic/charts";
import { isNewSince } from "../../logic/format";
import { Icon } from "../kit";
import { FlagPill, NewPill } from "./LabBits";
import { LabRows } from "./LabRows";
import { LabTrend } from "./LabTrend";

/** Shared labs: the answer, one trend (a flagged one first), every test's latest, and the full table on request. */
export function LabsPanel({ labs, lastLookedAt }: { labs: SharedLab[]; lastLookedAt: string | null }) {
  const series = useMemo(() => labSeries(labs), [labs]);
  const trends = series.filter((s) => s.trend);
  const [picked, setPicked] = useState<string | null>(null);
  const shown = trends.find((s) => s.key === picked) ?? trends.find((s) => s.latest.flag === "high" || s.latest.flag === "low") ?? trends[0];
  const chartId = useId();
  return (
    <div className="flex flex-col gap-4">
      <p className="m-0 text-[15px] leading-[22px] font-medium text-ink">{labsAnswer(series)}</p>
      {shown && (
        <div className="rounded-2xl bg-tile p-4 sm:px-5">
          <LabTrend key={shown.key} series={shown} id={chartId} />
        </div>
      )}
      {series.length > 0 && <LabRows series={series} lastLookedAt={lastLookedAt} selected={shown?.key ?? null} onSelect={trends.length > 1 ? setPicked : undefined} controls={chartId} />}
      {labs.length > 0 && (
        <details className="group rounded-xl bg-tile">
          <summary className="flex h-10 cursor-pointer list-none items-center gap-2 rounded-xl px-3 text-[13px] font-medium text-body outline-none hover:text-ink focus-visible:shadow-[0_0_0_2px_var(--color-brand)] [&::-webkit-details-marker]:hidden">
            <Icon name="file-list-3-line" className="text-muted" />
            Every result as a table
            <span className="font-mono text-[12px] text-subtle tabular-nums">{labs.length}</span>
            <Icon name="arrow-down-s-line" className="ml-auto text-muted transition-transform group-open:rotate-180" />
          </summary>
          <div className="overflow-x-auto px-3 pb-3">
            <table className="w-full border-collapse text-left text-[13px]">
              <caption className="sr-only">Lab results shared from MARMAR Health</caption>
              <thead>
                <tr className="text-[12px] text-muted">
                  {["Test", "Result", "Range", "Date"].map((h) => (
                    <th key={h} scope="col" className="px-2 py-2 font-medium">
                      {h}
                    </th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {labs.map((l) => (
                  <tr key={l.id} className="shadow-[inset_0_1px_0_var(--color-hairline)]">
                    <th scope="row" className="px-2 py-2 font-medium text-ink">
                      <span className="flex flex-wrap items-center gap-2">
                        {l.name} {isNewSince(l.changedAt, lastLookedAt) && <NewPill />}
                      </span>
                    </th>
                    <td className="px-2 py-2 text-body tabular-nums">
                      <span className="flex flex-wrap items-center gap-2">
                        {l.value} {l.unit} <FlagPill flag={l.flag} />
                      </span>
                    </td>
                    <td className="px-2 py-2 font-mono text-[12px] text-cell">{l.range ? rangeLabel(l.range) : "Not given"}</td>
                    <td className="px-2 py-2 whitespace-nowrap text-cell">{formatDate(l.date)}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </details>
      )}
    </div>
  );
}