import { useCallback, useRef } from "react"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "../ui/dialog.tsx"; import TaskPage, { type TaskPageProps } from "../../pages/DashboardPage/HomePage/TaskPage/TaskPage.tsx"; import { useMyOrganizations } from "../../hooks/useMyOrganizations.tsx"; /** * A TASK, OPENED WHERE THE PATIENT WAS ALREADY STANDING. * * The overview exists to stop somebody being sent off to another section for quick things, and the * task row was the last one still doing it: pressing "Complete task" or "Start form" took the patient * off the page they were reading, and coming back meant finding their place again. * * > **Spencer, 2026-08-14:** *"they'd only have to navigate off this page if they actually need the * > full page for that item."* * * Appointments and messages already opened in place. This is tasks joining them, which is what the * approved deck proposes - and the deck is explicit that nothing inside the container is new: it is * what the full task page already renders. * * IT RENDERS THE PAGE ITSELF, NOT A COPY OF IT. A questionnaire task is several hundred lines of form * handling, autosave and submission, and a second rendering of that is a second place for every one of * those behaviours to drift. `TaskPage` takes `taskId` and `onExit` for exactly this, in the shape * `PatientTaskPage` already used. * * THE TITLE IS DELIBERATELY GENERIC, and it is the one thing here that is not the page's own. The * dialog must be labelled before its content has loaded - an unlabelled dialog announces nothing to a * screen reader at the moment it takes focus - and the task's own name is not known until the fetch * inside returns. The page renders its real heading underneath. */ export interface PatientTaskDialogProps { open: boolean; onOpenChange: (open: boolean) => void; taskId: string | undefined; } // The open task, with the patient's own practices so it can name one its owner only references. // Read here, inside the open dialog, so a closed dialog asks for nothing. const OpenTask = ({ taskId, onExit, registerExitGuard }: { taskId: string; onExit: () => void; registerExitGuard: NonNullable }) => { const { organizations } = useMyOrganizations(); return ; }; export const PatientTaskDialog = ({ open, onOpenChange, taskId }: PatientTaskDialogProps) => { const exitGuard = useRef<(() => void) | undefined>(undefined); const registerExitGuard = useCallback((guard: () => void) => { exitGuard.current = guard; return () => { if (exitGuard.current === guard) exitGuard.current = undefined; }; }, []); return ( { if (!nextOpen && exitGuard.current) exitGuard.current(); else onOpenChange(nextOpen); }}> {/* Named for assistive technology before the task loads, so the description holds for any status; the task draws its own status, close and title. */} Your task Shown here, over your overview. {/* * KEYED ON THE TASK, so opening a second task after a first does not hand the new id to a * component still holding the previous one's loaded state, its answers and its autosave timer. * Without the key that is not a stale render that settles - it is one task's form saving into * another task's response. */} {taskId ? onOpenChange(false)} registerExitGuard={registerExitGuard} /> : null} ); }; export default PatientTaskDialog;