import { Task } from "fhir/r4"; import { formatCustomTaskDueDate, getAkashicTaskKind, getAppointmentDueLabel, getCustomTaskDueDisplay, getCustomTaskDueMode, isCustomTask, resolveTaskAppointment, useTaskServiceContext, } from "../../../services"; import { useFhirAppointments } from "../../../hooks"; import { formatDateForDisplay } from "../../../lib/utils.ts"; import { getAppointmentPractitionerName } from "./WorkspaceAppointmentDetail.tsx"; // Read-only task detail for the receptionist workspace modal. Renders IN PLACE (no Sheet / WP Modal) so it never // nests an overlay inside the workspace Dialog - that nesting is what broke the old viewTask -> Sheet path. Mirrors // CustomTaskDetails' readout layout but handles every task kind and stays purely presentational. Assign/edit/ // complete stay on the full Tasks page (routed) for now; this is the PHI-limited glance-and-go view. export default function WorkspaceTaskDetail({ task }: { task: Task }) { const { getTaskStatusDisplay } = useTaskServiceContext(); const { appointmentsList } = useFhirAppointments(); const custom = isCustomTask(task); const kind = getAkashicTaskKind(task); const kindLabel = kind === "custom" ? "Custom Task" : kind === "questionnaire" ? "Questionnaire" : "Task"; const isUnable = task.status === "failed"; const isCompleted = task.status === "completed"; const statusClass = isUnable ? "bg-red-600 text-white" : isCompleted ? "bg-green-600 text-white" : "bg-gray-200 text-gray-800"; // When a custom task is anchored to an appointment, show it as three intuitive lines // (label / "On " / "With ") instead of the single dotted string; drop the // appointment type. Everything else (fixed date, recurring, not-scheduled) falls back to the flat helper. const resolvedAppointment = custom ? resolveTaskAppointment(task, appointmentsList) : undefined; const dueMode = custom ? getCustomTaskDueMode(task) : undefined; // ONE definition of this label, shared with the patient's own task row. It used to be spelled out // again here, and the two agreed only by having been typed the same way - so the staff view kept saying // "Soonest upcoming appointment" about a visit the patient had already arrived for, which is the rule // that picked it rather than what it is now. const appointmentModeLabel = getAppointmentDueLabel(dueMode, resolvedAppointment); const appointmentPractitioner = resolvedAppointment ? getAppointmentPractitionerName(resolvedAppointment.participant) : undefined; const structuredAppointmentDue = appointmentModeLabel && resolvedAppointment?.start ? { label: appointmentModeLabel, when: formatCustomTaskDueDate(resolvedAppointment.start), who: appointmentPractitioner && appointmentPractitioner !== "Unknown Practitioner" ? appointmentPractitioner : undefined, } : null; const dueFallback = custom ? getCustomTaskDueDisplay(task, resolvedAppointment) : task.restriction?.period?.end ? formatDateForDisplay(task.restriction.period.end) : "—"; const assignedBy = task.requester?.display || task.owner?.display || "Care team"; const assigned = task.authoredOn ? formatDateForDisplay(task.authoredOn) : "—"; const notes = (task.note ?? []) .map((note) => note.text?.trim()) .filter((text): text is string => Boolean(text)); return (
{kindLabel} {getTaskStatusDisplay(task)}

{task.description || kindLabel}

{task.for?.display && (

Assigned to {task.for.display}.

)} {/* The desk cannot otherwise tell a task voided by a cancelled appointment from one somebody cancelled by hand - both wear the same grey chip, and the reason the server wrote was read by no screen at all. */} {task.status === "cancelled" && task.statusReason?.text && (

Cancelled: {task.statusReason.text}

)}
Assigned by
{assignedBy}
Assigned
{assigned}
Due
{structuredAppointmentDue ? (
{structuredAppointmentDue.label}
On {structuredAppointmentDue.when}
{structuredAppointmentDue.who && (
With {structuredAppointmentDue.who}
)}
) : (
{dueFallback}
)}
{notes.length > 0 && (
Notes
{notes.map((text, index) => (

{text}

))}
)}
); }