import React, { useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { usePatientSetupSummary } from "../../../HomePage/usePatientSetupSummary.ts"; import { RowStatus } from "../../../../../components/ui/row-meta.tsx"; /** * THE PATIENT'S OWN SETUP STEPS, ON THE LIST THAT PROMISES TO HOLD EVERYTHING THEY OWE. * * The four global internal items - the profile, medicines or an attestation of none, allergies or an * attestation of none, and the people who help - are derived from what the record HOLDS rather than * assigned by anybody, and they existed only on the home banner. So the one screen whose whole promise * is "what needs doing" could not show them in any state: not outstanding, not finished, not at all. * Measured as Patient One on 2026-09-14: every outstanding group on that page was an appointment. * * IT IS NOT A SECOND COMPUTATION. `usePatientSetupSummary` is the one the banner asks, and it was * lifted out of the banner for exactly this reason - two surfaces that count the same thing separately * are two surfaces that will eventually disagree in front of the patient. * * MOUNTED ONLY FOR THE PATIENT OR THEIR PROXY. The hook reads the patient in context, which on the * staff chart is the chart's patient - but these items are the PATIENT's own housekeeping, not a * coordinator's work, and the chart is not where they are done. Rendering it there would also buy four * FHIR reads for a panel nobody on that surface acts on. * * UNDATED, SO IT SITS LAST, on the same rule the task groups already follow: a thing with no deadline * is not urgent and must never be dropped, so it goes to the end of the list rather than out of it. */ const PatientSetupTasks = ({ tab, onCountsChange, }: { tab: "outstanding" | "completed"; /** * HOW MANY OF THESE ARE OWED AND HOW MANY ARE DONE, told to the host rather than counted twice. * * The host draws an empty state per tab, and each sentence is false while this group contradicts it * a few pixels below: "Nothing to do right now" over five outstanding items, and "Nothing finished * yet" over two completed ones. Both halves are needed because both tabs have an empty state. * * It is the same pairing the home page needed when this computation was lifted out of the banner: * two surfaces on one screen cannot each answer "is anything owed" on their own. */ onCountsChange?: (counts: { outstanding: number; completed: number }) => void; }) => { const navigate = useNavigate(); const summary = usePatientSetupSummary(); // Reported from an effect rather than during render, and BEFORE the early return below, because a // hook that runs conditionally is not a hook. Nothing is reported until the reads answer: not yet // known and nothing owed are the two answers this screen must never blur. const outstanding = summary ? summary.totalCount - summary.completedCount : undefined; const completed = summary ? summary.completedCount : undefined; useEffect(() => { if (outstanding === undefined || completed === undefined) return; onCountsChange?.({ outstanding, completed }); }, [outstanding, completed, onCountsChange]); // `undefined` is "the reads have not answered", which is not the same as "nothing is owed" - and a // heading counting zero of zero would be a claim made from data nobody has read. if (!summary) return null; const shown = summary.tasks.filter((task) => tab === "completed" ? task.completed : !task.completed, ); return (
{/* THE BANNER'S OWN WORDS. One vocabulary: a patient who has read "Finish setting up" on their home page must not meet the same five items under a different name here. */}

Finish setting up

Your account, not a visit - these carry across every practice you use.

{/* Counted over all five rather than over the tab, exactly as the visit groups are: the point of the count is to say how big the job is, which a tab showing one half cannot. */} {summary.completedCount} of {summary.totalCount} done
{shown.length === 0 && ( /* SAID, NEVER LEFT BLANK. An empty group under a heading that counts is the absence carrying a message, and the two absences here mean opposite things. */

{tab === "completed" ? "You have not finished any of these yet." : "Your account is set up. Nothing here needs you."}

)} {shown.map((task) => (
navigate(task.route)} onKeyDown={(event: React.KeyboardEvent) => { if (event.key !== "Enter" && event.key !== " ") return; event.preventDefault(); navigate(task.route); }} >

{task.title}

{/* WHAT IS LEFT, where there is more than one thing in it. The profile step is five fields, and "2 of 5" is the difference between a row a patient opens and one they assume they have already done. */} {task.progressTotal > 1 && !task.completed && (

{task.progressCurrent} of {task.progressTotal} done

)}
{task.completed && ( Completed )}
))}
); }; export default PatientSetupTasks;