import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { Check, ChevronsDown, Circle } from "lucide-react";
import { useBookAppointmentDialog } from "../../../components/BookAppointmentDialog/BookAppointmentDialogProvider.tsx";
import { type PatientSystemTaskDefinition } from "../../../utils/patientSystemTasks.ts";
import {
chooseBannerOccupant,
offersMessageAction,
recordImportOfferEligible,
type UrgentNotice,
} from "./patientHomeBanner.ts";
import { usePatientUrgentNotice } from "./usePatientUrgentNotice.ts";
import { usePatientSetupSummary } from "./usePatientSetupSummary.ts";
import PatientTaskDialog from "../../../components/tasks/PatientTaskDialog.tsx";
import { useMountedFirstSignInGateStatus } from "../../../components/FirstSignInGate/firstSignInGateStatus.tsx";
import { isOrglessPatientsHost } from "../../../utils/hostedMount";
import { RecordImportOfferSlot } from "./RecordImportOffer.tsx";
import "./PatientHomeBanner.css";
/**
* The patient home's ONE banner slot., design spec §4.
*
* The two states are told apart by STRUCTURE, not by hue: heavier rail, coloured title, no progress
* meter, actions on the face of it. Do not make the urgent state louder - three solid fills were
* tried in the design session and all three were rejected as hard on the eyes. *Nothing in this
* product should be unpleasant to look at, including the alarm*, and a patient reading that their
* procedure is cancelled is at the worst possible moment to also be assaulted.
* The two are never on screen together, so the requirement is recognition over time rather than
* discrimination at a glance - much weaker than it first appears, and what makes a comfortable urgent
* state possible at all.
*
* DEPENDENCY, declared rather than worked around: the setup state's ground and rail are specified
* as `--rose-soft` / `--rose`, and **those tokens do not exist** - the palette landed blue/green/amber/
* red soft only. Defining a token value is lane, so the setup banner renders on the card
* ground with its 4px rail in the ink family until they land. Every STRUCTURAL difference the design
* leans on is built and pinned; only the tint is owed.
*
* R2, SETTLED (Spencer, 2026-08-14): *"The red card (banner) should be the urgent notice piece."* That
* CORRECTS the mock, which showed "Finish setting up - 4 of 7 done" in the rose treatment. Red is reserved
* for the thing that actually needs the patient now; a setup checklist is not that. The assignment below
* already followed the rule - what did not exist was the TREATMENT itself:
* - `border-l-6` is not a Tailwind class, so the urgent rail fell back to 1px;
* - `border-l-destructive` and `border-l-secondary` were never generated, so BOTH banners drew their
* rail in the neutral border colour and the accent that tells the states apart was never on screen.
* Both are fixed - see the rail comment below and the `border-l-*` block in index.css.
*/
const SetupBanner = ({
tasks,
completedCount,
totalCount,
}: {
tasks: PatientSystemTaskDefinition[];
completedCount: number;
totalCount: number;
}) => {
const navigate = useNavigate();
const next = tasks.find((task) => !task.completed);
const [expanded, setExpanded] = useState(completedCount === 0);
return (
{expanded && (
{primary.href ? (
{primary.label}
) : (
)}
{/* 🔑 The "or" is not cosmetic: two stacked buttons read as a SEQUENCE — do this, then that —
and on a cancelled-procedure notice a patient reading it that way either does both or
stalls. The message option exists because a call is not available to everyone at that
moment, a call leaves NO RECORD in a product whose thesis is continuity, and the pooled
front desk is already the correct recipient. */}
{canMessage && offersMessageAction(notice) && (
<>
or
>
)}
!next && setOpenTaskId(undefined)}
taskId={openTaskId}
/>
>
);
};
/** The slot. One occupant, chosen by `chooseBannerOccupant` - never by two components each deciding. */
// `canMessage` is false for a signed-in 12-17 patient: messaging is 18+, so no message action shows.
export const PatientHomeBanner = ({ canMessage = true }: { canMessage?: boolean } = {}) => {
// The three approved triggers, read from records the product already holds.
const urgent = usePatientUrgentNotice();
/**
* THE SETUP SUMMARY IS ASKED FOR, NOT COMPUTED HERE, and the move is what fixed a defect one cell
* below this banner.
*
* It was private to this component, so the home page - the only thing that renders the banner and the
* queue as siblings - could not ask whether the banner was already offering the patient something.
* The cell beneath therefore read "Nothing needs you yet" over a banner listing five outstanding
* setup items. The computation, its starved-inputs history and its not-yet-read guard all moved
* intact into the hook.
*/
const summary = usePatientSetupSummary();
const gate = useMountedFirstSignInGateStatus();
const offer = recordImportOfferEligible({ personalHost: isOrglessPatientsHost(), gate, setup: summary });
const occupant = chooseBannerOccupant(urgent, summary, offer);
if (occupant.kind === "urgent") return ;
if (occupant.kind === "records-offer") {
// Put off, or with no account to key that to, the slot falls back to what it would otherwise hold.
const otherwise = chooseBannerOccupant(undefined, summary);
return (
) : null}
/>
);
}
if (occupant.kind === "setup") {
return (
);
}
return null;
};
export default PatientHomeBanner;