import { ChevronRight } from "lucide-react"; import type { CareTeam } from "fhir/r4"; import { Dialog, DialogContent, DialogDescription, DialogTitle } from "../../../../components/ui/dialog"; import { OrgBadge } from "../../../../components/ui/org-badge"; import { Progress } from "../../../../components/ui/progress"; import { Skeleton } from "../../../../components/ui/skeleton"; import type { PlanStep } from "../../../../utils/carePlanSteps"; import type { PlanCard, PlanGoal } from "./carePlanRows"; /** * ONE PLAN, OPENED OVER THE LIST - the deck's "the plan itself", a dialog on a phone and on a laptop * alike, with the trail behind it still reading Care plans because an overlay has no trail of its own. * * KEYED BY THE PLAN. Everything inside is drawn for exactly one plan id, and moving to another remounts * it, so nothing drawn for the last plan can stand under the next one's title. A plan asked for by id * that the list has not delivered yet is still loading; one the answered list does not hold is said to * be missing, never drawn from whatever was open before. * * Its steps are the Tasks based on it; steps that could not be read are never "Nothing is on this plan yet". */ export interface PlanStepsRead { /** The plan's tasks and readings have answered. */ answered: boolean; failed: boolean; stepsFor: (planId: string) => PlanStep[]; } export interface PlanTeamsRead { answered: boolean; failed: boolean; teams: readonly CareTeam[]; } const GoalRow = ({ goal }: { goal: PlanGoal }) => (
{goal.title} {goal.value && {goal.value}}
{goal.percent !== undefined && !goal.reached && } {(goal.reached || goal.detail) && (

{goal.reached ? Reached : null} {goal.reached && goal.detail ? " ยท " : null} {goal.detail}

)}
); /** The readings a measuring step asked for, as the deck's thin line. Two or more, or nothing drawn. */ const StepLine = ({ readings }: { readings: PlanStep["readings"] }) => { if (readings.length < 2) return null; const values = readings.map((reading) => reading.value); const low = Math.min(...values); const span = Math.max(...values) - low || 1; const points = readings .map((reading, index) => `${2 + (index * 236) / (readings.length - 1)},${4 + ((Math.max(...values) - reading.value) * 46) / span}`) .join(" "); return ( reading.value).join(", ")}`}> ); }; /** * Every care team the plan names, by name. Team membership is not an access decision, so nobody is * counted and nothing is said about who can see the plan. These are clinical FHIR CareTeams, never the * product's collaboration teams. */ const LinkedTeams = ({ card, read }: { card: PlanCard; read: PlanTeamsRead }) => { const ids = [...new Set(card.careTeamIds)]; if (ids.length === 0 || (!read.answered && !read.failed)) return null; const label = ids.length === 1 ? "Care team" : "Care teams"; if (read.failed) { return
{label}: {ids.length === 1 ? "its name" : "their names"} could not be read just now
; } return (
{label}:{" "} {ids.map((id, index) => { const team = read.teams.find((candidate) => candidate.id === id); const name = team?.name?.trim(); return ( {index > 0 && ", "} {name ? {name} : team ? "an unnamed team" : "a team we could not read"} ); })}
); }; const PlanBody = ({ card, steps, teams, onOpenActivity, onOpenStep, }: { card: PlanCard; steps: PlanStepsRead; teams: PlanTeamsRead; onOpenActivity: (card: PlanCard) => void; onOpenStep: (taskId: string) => void; }) => { const working = card.goals.filter((goal) => !goal.reached); const reached = card.goals.filter((goal) => goal.reached); const owed = steps.answered && !steps.failed ? steps.stepsFor(card.id) : []; const nothingOn = steps.answered && !steps.failed && owed.length === 0 && card.activities.length === 0 && card.goals.length === 0; return ( <>
{card.title} What this plan asks of you, and its goals.
{card.organization && card.by && ( {card.organization} )} {card.by && {card.by}} {card.period && {card.period}}
{(owed.length > 0 || card.activities.length > 0 || !steps.answered || steps.failed) && (
What you are doing
)} {!steps.answered && !steps.failed && (
)} {steps.failed && (

What this plan asks of you could not be loaded just now. Please try again.

)} {owed.map((step) => ( ))} {card.activities.map((activity) => ( ))} {working.length > 0 && ( <>
Goals
{working.map((goal) => )} )} {reached.length > 0 && ( <>
Reached
{reached.map((goal) => )} )} {nothingOn && (

This plan has no activities or goals yet. Your clinician adds them as you plan your care together.

)}
); }; const CarePlanDialog = ({ planId, cards, loading, steps, teams, onClose, onOpenActivity, onOpenStep, }: { planId?: string; cards: PlanCard[]; loading: boolean; steps: PlanStepsRead; teams: PlanTeamsRead; onClose: () => void; onOpenActivity: (card: PlanCard) => void; onOpenStep: (taskId: string) => void; }) => { const card = planId ? cards.find((candidate) => candidate.id === planId) : undefined; return ( { if (!open) onClose(); }}>
{card ? ( ) : ( <>
{loading ? "A plan from your care team" : "We could not find that plan"} One of your care plans.
{loading ? (
) : (

It may have finished or been withdrawn. Your other plans are still on the list.

)} )}
); }; export default CarePlanDialog;