import { QueueKindChip } from "./QueueKindChip.tsx"; import "./PatientQueue.css"; import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { CheckCircle2, ChevronRight } from "lucide-react"; import { usePatientQueue, usePatientQueueStatus, queueTotal } from "./usePatientQueue.ts"; import { LIST_ROUTE_FOR_KIND, type RankedItem } from "./patientQueue.ts"; import { EmptyState } from "../../../components/ui/empty-state.tsx"; import { OrgBadge } from "../../../components/ui/org-badge.tsx"; import { DueLine } from "../../../components/ui/row-meta.tsx"; import PatientAppointmentDialog from "../../../components/appointments/PatientAppointmentDialog.tsx"; import PatientMessageDialog from "../../../components/messages/PatientMessageDialog.tsx"; import PatientTaskDialog from "../../../components/tasks/PatientTaskDialog.tsx"; import PatientReferralDialog from "../../../components/tasks/PatientReferralDialog.tsx"; import { usePatientHomeReferrals } from "./patientHomeReferrals.ts"; import type { PatientPacketSummaries } from "../../../hooks/usePatientPacketSummaries.ts"; /** * The patient home's ranked queue., design D1/D2/D4, spec §6. * * One list ordered by urgency - never a section per KIND, because a section can be pushed to the * bottom and the bottom does not get read. * * IT IS NOW ONE LIST ON SCREEN TOO. Spencer's 2026-08-24 mockup replaced the Now / Before end of day / * This week band headings with a single *Needs attention* heading and put the deadline on each row. * The RANKING is untouched - `usePatientQueue` still tiers and still promotes a neglected item to the * top - so what a patient reads first has not moved. Only the headings went, and they went for the same * reason there is no section per kind: a heading is something a reader can scroll past. */ // The per-KIND default for the ACTION. An item may name itself instead (`actionLabel`), which is what // stops every outstanding task being announced as a form - see `TASK_LABELS` in usePatientQueue. // /* * `KIND_LABEL` WAS REMOVED HERE, 2026-09-09, and this is what it leaves behind. * * It held "Form" / "Message" / "Payment" / "Appointment" and was rendered as a text pill above each * title. Spencer replaced that pill with an inline glyph on 2026-09-08, and the map stayed behind - * declared, referenced nowhere, the same shape as the `KIND_CHIP` tone map tombstoned three lines * above it. Deleted rather than left, because a reader looking for "what does this app call a payment * row" would have found a dead answer first. * * The kind is back in words, as the deck's chip (QueueKindChip.tsx). It maps by queue * kind, so a questionnaire reads Task as it does on the deck, and `item.kindLabel` still has no * renderer. */ const ACTION_LABEL: Record = { form: "Start form", message: "Read message", payment: "Pay now", appointment: "View appointment", }; /* * THE `KIND_CHIP` PILL MAP WAS REMOVED HERE, 2026-09-09, and this note is what it leaves behind. * * The pill itself went when Spencer replaced it with an inline icon, but the tone map stayed - * declared, exported to nothing, referenced nowhere. It held `bg-purple-100 text-purple-800`, `bg-blue-100 text-blue-700`, `bg-teal-100 text-teal-700`, * `bg-indigo-100 text-indigo-700`. * * IT IS DELETED RATHER THAN LEFT, because it is dead code carrying the exact four off-palette * colours the palette pass just took out of the icons - purple and indigo are not colours this * product has. A reader looking for "what colour is a payment row" would have found this first, and * the honest reason those hues survived a whole palette review is that nothing connects a Tailwind * colour class to a token. Leaving them here re-arms that. * * The live mapping is QUEUE_KIND_CHIP in QueueKindChip.tsx, on the shared pill tones. */ /* * THE KIND IS A CHIP AGAIN (provisional). The inline kind glyph and its tone map were removed here: * Spencer asked for the queue to be built as the patient-portal deck draws it for now, and the deck * draws a TASK / MESSAGE / PAYMENT chip in the card corner and no glyph. The chip and its map live * in QueueKindChip.tsx, so his later coloured-text refinement is a one-file change. */ const Card = ({ item, onOpen, }: { item: RankedItem; // The WHOLE item, not just what it opens. The row already renders the practice and the practitioner, // and the dialog it opens has fields for both - passing only `opens` was how the detail ended up // saying less than the row that opened it. onOpen: (item: RankedItem) => void; }) => { const navigate = useNavigate(); // R5 (Spencer, 2026-08-14): *"they'd only have to navigate off this page if they actually need the // full page for that item."* So opening in place is the DEFAULT and navigating is the exception - the // row asks what it opens before it asks where it goes. const act = () => (item.opens ? onOpen(item) : navigate(item.to)); /* * THE ROW IS THE CONTROL - there is no button on it. * * Spencer, 2026-08-25: *"those big action buttons don't even exist in the mockups, they need to go * away."* And the deck says why: tapping the item and tapping a button on the item did the same thing. * The button only said so louder, on every row, in the space the next two items would have used. * Measured in the drawing: five rows give back 170px of the 682 they took - a quarter of the list. * * The action's WORD survives as the row's accessible name, so "Complete task" and "Pay now" are * still what a screen reader announces. The label was never the thing that was wrong; the box was. * * The "+ N more" line stays a button of its OWN and must not be nested inside this one - it goes * somewhere else entirely (the list for that kind), and a button inside a button is invalid markup a * browser resolves by dropping one of them. */ return (
{/* THE HIT AREA IS THE WHOLE CARD, and it is a stretched overlay rather than a button wrapped around the content — because the title has to stay an `

`. A heading is flow content and is not valid inside a button, so wrapping would have silently deleted a heading from the page outline to gain a click target. The overlay carries the action's word as its name, the content sits above it and lets clicks fall through, and the "+ N more" line opts back in because it goes somewhere else entirely. */}