import React, { useEffect, useRef, useState } from 'react'; import type { SaveOperation } from '../../../../utils/saveOperation'; import type { Goal, Observation, Task } from 'fhir/r4'; import { HeartPulse, UserRoundPen } from 'lucide-react'; import { GoalRowCard } from './GoalRowCard'; import { goalHistory, type GoalRow, type GoalSource } from '../../../../utils/goalRow'; import { EmptyState } from '../../../../components/ui/empty-state'; import { chartableReadings } from '../../../../utils/goalChart'; import { formatShortDate } from '../../../../utils/dateUtils'; import { GoalChart } from './GoalChart'; import './GoalDetail.css'; // A goal, opened: where it stands, and the readings that moved it. // // WHAT MOVES IT IS THE POINT OF THE SCREEN. The bar above is derived from exactly this list, so showing // the list is what makes the bar checkable rather than something the patient has to take on trust. // // NEWEST FIRST, and each says where it came from - "a weight the patient logged and one taken at the // practice are not the same kind of fact", the same provenance distinction medications already draw. interface Props { goal: Goal; row: GoalRow; source?: GoalSource; observations: Observation[]; /** The patient's tasks, so a day that passed unanswered appears rather than leaving a gap. */ tasks?: Task[]; /** * Offered only for a goal the patient set themselves. * * A PLAN GOAL IS MOVED BY ANSWERING THE PLAN'S MEASUREMENT, which is the path that records the * reading against the activity that asked for it. A second way in from here would let a patient log * outside the activity, leaving the activity still owed and the plan's own history incomplete. */ onLogReading?: (value: number, opening?: SaveOperation) => Promise | void; /** Starts this detail's opening: choosing another goal or leaving this one retires a reading not yet sent. */ startOpening?: () => SaveOperation; /** An earlier logged reading still to be settled: shown above the form, and nothing more is logged until it is. */ readingNotice?: React.ReactNode; /** The readings are still being read. */ readingsLoading?: boolean; /** The readings could not be read, so no list is not the same as no readings. */ readingsFailed?: boolean; /** * `pane` is the patient's goals deck: a labelled card beside the list on a laptop, under it on a * tablet, and the page itself on a phone, with what moves it as a second card. `sheet` is the * staff sheet it has always been. */ variant?: 'sheet' | 'pane'; } export const GoalDetail: React.FC = ({ goal, row, source, observations, tasks, onLogReading, startOpening, readingNotice, readingsLoading = false, readingsFailed = false, variant = 'sheet', }) => { const pane = variant === 'pane'; const [value, setValue] = useState(''); const [busy, setBusy] = useState(false); const opening = useRef(); useEffect(() => { const mine = startOpening?.(); opening.current = mine; return () => mine?.retire(); }, [startOpening]); const history = goalHistory(goal, observations, tasks ?? []); const readings = chartableReadings(goal, observations); // Spencer 2026-09-03: a line over time here, the bar only on the list. One reading is said, not drawn. const chart = readingsFailed || readings.length === 0 ? null : readings.length === 1 ? (

One reading so far, on {formatShortDate(readings[0].on)}. The line starts with the next one.

) : ; const unusable = value.trim() === '' || !Number.isFinite(Number(value)); const log = async (event: React.FormEvent) => { event.preventDefault(); if (!onLogReading || unusable || busy || readingNotice) return; setBusy(true); try { const mine = opening.current; await onLogReading(Number(value), mine); if (mine && !mine.belongs()) return; setValue(''); } finally { if (!opening.current || opening.current.belongs()) setBusy(false); } }; return (
{pane &&

{row.title}

}
{onLogReading && readingNotice} {onLogReading && (
setValue(event.target.value)} />
)}

What moves it

{readingsFailed ? (

The readings for this goal could not be loaded just now, so its progress may be out of date.

) : history.length === 0 && readingsLoading ? (

Loading readings…

) : history.length === 0 ? ( // Stated rather than drawn as an empty box. A goal with no readings yet is a normal // thing to be looking at, and a blank space says only that something is missing. ) : (
    {history.map((entry, index) => (
  • {entry.date} {/* A MISSED DAY IS THE QUIETEST INK ON THE ROW, and that is a rule rather than taste: amber means OWED, and a missed day is no longer owed. It is history, not a reprimand - it is here so a run of them reads as a pattern instead of a gap somebody has to notice. */} {entry.kind === 'missed' ? ( — missed ) : ( <> {entry.value} {/* A glyph per the deck: the pen is a self-report, the pulse a measurement. The words stay as the name and the tooltip. */} {entry.source === 'patient' ? )}
  • ))}
)}
); }; export default GoalDetail;