import { useEffect, useRef, useState } from "react"; import { Copy, Phone, Printer, Send } from "lucide-react"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "../../../components/ui/dialog.tsx"; import { Button } from "../../../components/ui/button.tsx"; import { Input } from "../../../components/ui/input.tsx"; import { Textarea } from "../../../components/ui/textarea.tsx"; import { Alert, AlertDescription } from "../../../components/ui/alert.tsx"; import { actionAttempt, stillOpen, type ActionAttempt, type ReferralRequestScope } from "../../../utils/referralRequestScope"; import { useReferralWorkbenchService, type ReferralClaimCode, type ReferralContact, type ReferralContactAttempt, type ReferralFaxResult, } from "../../../services/ReferralWorkbenchService.tsx"; import { useAuthServiceContext } from "../../../services"; import { canSendFax } from "../../../utils/organizationFaxSettings.ts"; import { relayServerFailure } from "../../../utils/fetchFailureMessage"; import { HttpError } from "../../../services/HttpService"; import type { ReferralQueueRow } from "../../../hooks/useReferralQueue.ts"; import { patientBookedLine, referralClosureReasonLabel } from "../../../utils/referralQueue.ts"; import "./referralDialog.css"; import { ReferralContactHistory } from "./ReferralContactHistory"; import { useReferralContactRecovery } from "../../../hooks/useReferralContactRecovery"; import { ReferralContactRecovery } from "./ReferralContactRecovery"; import { outcomeUnknown } from "../../../utils/saveOperation"; // - THE SEND WORKBENCH. Three ways to send a referral, and a phone call is one of them. // // Spencer: *"however they want to do it as long as it's all logged/documented in our system."* // // PHONE IS THE MAJORITY OF REAL-WORLD REFERRAL TRAFFIC, and it is fully buildable today - the fax // vendor is the only part still held on a BAA. A referral worked by phone must leave **as complete a // record as one we faxed**; if the call path records less, the coordinator keeps a private notebook for // the parts that did not fit and we have rebuilt the paper tray this feature replaced. // // AND NOTHING HERE CLAIMS THE SPECIALIST RECEIVED IT. The record says what WE did. A coordinator who // reads "sent" as "they have it" stops chasing - and a referral nobody is chasing is a patient who does // not reach a specialist. // // ZERO WordPress: Tailwind + lucide + the shared ui primitives. type Method = "phone" | "portal" | "fax"; interface Props { row: ReferralQueueRow | null; onOpenChange: (open: boolean) => void; /** Fired after a contact is recorded, so the queue can re-read the row's state. */ onRecorded: () => void; /** Which writes this person may make now, read live: an open form loses a control the moment its authority goes. */ may?: ReferralWorkbenchWrites; /** The opening this dialog belongs to: every request is admitted by it, and nothing is shown or started once it retires. */ scope?: ReferralRequestScope; } export interface ReferralWorkbenchWrites { contact: boolean; state: boolean; claimCode: boolean; fax: boolean; /** The shared contact book a used number is corrected in. */ contactBook: boolean; } const ALL_WRITES: ReferralWorkbenchWrites = { contact: true, state: true, claimCode: true, fax: true, contactBook: true }; const TABS: { key: Method; label: string; icon: typeof Phone }[] = [ { key: "phone", label: "Phone", icon: Phone }, { key: "portal", label: "Akashic", icon: Send }, { key: "fax", label: "Fax", icon: Printer }, ]; const ReferralWorkbenchDialog = ({ row, onOpenChange, onRecorded, may = ALL_WRITES, scope }: Props) => { // The authority as it stands now, not as it stood when a request was sent: a correction is only started under it. const mayNow = useRef(may); mayNow.current = may; // Each write is its own attempt: admitted while the opening holds and that action is still allowed, and retired // for good the first time either fails. const liveAttempts = useRef(new Set<{ action: keyof ReferralWorkbenchWrites; attempt: ActionAttempt }>()); const attempt = (action: keyof ReferralWorkbenchWrites) => { const started = actionAttempt(scope, () => mayNow.current[action]); const entry = { action, attempt: started }; liveAttempts.current.add(entry); started.signal.addEventListener("abort", () => liveAttempts.current.delete(entry), { once: true }); return started; }; // A permission seen to go retires its attempts at once, so its return does not revive them. useEffect(() => { liveAttempts.current.forEach((entry) => { if (!may[entry.action]) entry.attempt.retire(); }); }, [may.contact, may.state, may.claimCode, may.fax, may.contactBook]); const { readContactHistory, recordContact, mintClaimCode, faxReferral, previewReferralPacket, readReferralContact, saveReferralContact, markReferralAccepted, markReferralScheduled } = useReferralWorkbenchService(); /** * WHETHER THIS PRACTICE CAN ACTUALLY SEND, read off the Organization we already hold. * * CHOSEN IS NOT READY. A practice can pick a vendor and never save the account details - the * settings screen writes those in two steps - and offering a button to a half-configured practice * produces exactly the click-and-stop-chasing this tab was built to prevent. */ const { currentOrganization } = useAuthServiceContext(); const faxConfigured = canSendFax(currentOrganization); const [method, setMethod] = useState("phone"); const [recipient, setRecipient] = useState(""); const [spokeWith, setSpokeWith] = useState(""); const [outcome, setOutcome] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [saved, setSaved] = useState(false); const recovery = useReferralContactRecovery(row, scope, may.contact); const contactUnconfirmed = recovery.held; const [recordedContacts, setRecordedContacts] = useState<{ taskId: string; contacts?: ReferralContactAttempt[] }>(); const contacts = recovery.contacts ?? (recordedContacts && recordedContacts.taskId === row?.taskId ? recordedContacts.contacts : row?.contacts); const [claim, setClaim] = useState(null); const [faxNumber, setFaxNumber] = useState(""); const [faxResult, setFaxResult] = useState(null); /** A transition the server confirmed, for the referral it was made on and no other. */ const [recorded, setRecorded] = useState<{ orderId: string; state: "claimed" | "scheduled" } | null>(null); const openOrderId = useRef(row?.orderId); const bodyRef = useRef(null); openOrderId.current = row?.orderId; useEffect(() => setBusy(false), [row?.orderId]); /** * WHAT SOMEBODY ALREADY LEARNED ABOUT REACHING THIS PRACTICE. * * BEST-EFFORT AND NEVER A GATE. A specialist with nothing recorded is the ordinary case on day one, * and the coordinator must still be able to type a number and send. So a failure here is silent: it * costs a prefilled field, not the ability to send a referral. */ const [contact, setContact] = useState(null); const [contactRefusal, setContactRefusal] = useState(null); /** The numbers as the book had them, so an edit can be told from a prefill. */ const [knownFax, setKnownFax] = useState(""); /** Why the packet could not be sent as it stands, learned from a preview rather than a refusal. */ const [previewNote, setPreviewNote] = useState(null); const [knownPhone, setKnownPhone] = useState(""); const [shareCorrection, setShareCorrection] = useState(true); const [sharePhoneCorrection, setSharePhoneCorrection] = useState(true); useEffect(() => { if (!row?.performerNpi) { setContact(null); setKnownFax(""); setKnownPhone(""); return; } let cancelled = false; setContactRefusal(null); void readReferralContact(row.performerNpi, scope) .then((found) => { if (cancelled || !stillOpen(scope)) return; setContact(found); const fax = found.shared?.fax ?? ""; const phone = found.shared?.phone ?? ""; setKnownFax(fax); setKnownPhone(phone); // Prefills only an EMPTY box. A coordinator who has already typed something is mid-thought, // and overwriting them would be the worse failure of the two. setFaxNumber((current) => (current.trim() === "" ? fax : current)); setRecipient((current) => (current.trim() === "" ? phone : current)); }) .catch((err) => { if (cancelled || !stillOpen(scope)) return; // A REFUSAL IS NOT AN EMPTY ADDRESS BOOK. Swallowing this left three blank fields and no // sentence, and a coordinator reads blank as "no contact on file" rather than as "you were // not allowed to look" - empty data standing in for a denial, which is the one thing the // denial rule forbids. A 404 IS a true emptiness and stays silent: every first referral to a // new recipient is one, and crying denial there would teach the desk to ignore the sentence. if (err instanceof HttpError && err.status === 404) return; setContactRefusal(relayServerFailure(err, "We could not read this recipient's saved contact details.")); }); return () => { cancelled = true; }; }, [row?.performerNpi, readReferralContact]); const reset = () => { setMethod("phone"); setRecipient(""); setSpokeWith(""); setOutcome(""); setError(null); setSaved(false); setRecordedContacts(undefined); setClaim(null); setFaxNumber(""); setFaxResult(null); setRecorded(null); setBusy(false); }; const close = (open: boolean) => { if (!open) reset(); onOpenChange(open); }; const logPhoneCall = async () => { if (!row?.taskId || busy || contactUnconfirmed) return; setBusy(true); setError(null); const call = attempt("contact"); // The correction is part of this press: opted into now, and retired for good if its permission goes meanwhile. const correctedPhone = recipient.trim(); const correction = row.performerNpi && sharePhoneCorrection && correctedPhone !== "" && correctedPhone !== knownPhone ? attempt("contactBook") : undefined; try { const contacts = await recovery.record({ method: "phone", recipient: recipient.trim() || undefined, spokeWith: spokeWith.trim() || undefined, outcome: outcome.trim() || undefined, // A FULL ISO INSTANT WITH AN OFFSET. FHIR's dateTime grammar allows a naked local time and the // resource INVARIANT does not, so HealthLake 400s it - `toISOString` is always UTC-qualified //. occurredAt: new Date().toISOString(), }, call, recordContact); if (!stillOpen(call)) return; setRecordedContacts({ taskId: row.taskId, contacts }); setSaved(true); onRecorded(); /* * THE PHONE IS WHERE THE CORRECTION IS ACTUALLY LEARNED, which is why this belongs here and * not only on the fax tab. The server's own note for the book describes this exact moment: * they called, they found out the number was wrong and referrals go to the referral * department, and they type it once so every practice that ever refers here inherits it. * * Best-effort and after the fact, on the same terms as the fax path: the call IS recorded, * and failing the whole action now would tell a coordinator their record did not save when it * did. Only the shared field is written - the private note has its own control and its own * audience. */ if (row.performerNpi && correction && stillOpen(correction)) { try { await saveReferralContact(row.performerNpi, { shared: { phone: correctedPhone } }, correction); if (!stillOpen(correction)) return; setKnownPhone(correctedPhone); } catch { // Deliberately silent - see above. The recorded call is what the coordinator is watching. } } } catch (err) { if (!stillOpen(call)) return; setError(outcomeUnknown(err) ? null : relayServerFailure(err, "That call could not be recorded. Please try again.")); } finally { // Done with this press either way: an unused correction is not left waiting. correction?.retire(); setBusy(false); } }; /* * WHO MAY STATE RECEIPT HERE. A recipient with no Akashic reference cannot claim the referral * itself, so the practice that placed it says so instead - Spencer, 2026-09-16. Terminal states are * excluded because terminal means terminal. */ const acknowledged = recorded && recorded.orderId === row?.orderId ? recorded.state : undefined; const referralState = acknowledged ?? row?.state; const canCloseAsSender = !!row?.orderId && !row?.performerReference && referralState !== "scheduled" && referralState !== "expired"; /* * A FINISHED REFERRAL OPENS AS IT STANDS. Scheduled and closed are terminal, and on staging both * still opened "Send …" with a claim code and a fax, saying neither where they stood nor why one * was closed. What stays on a scheduled one is recording a call, which the server still accepts; * a closed one offers no call form under "Nothing more is sent from here". */ const finished = referralState === "scheduled" || referralState === "expired"; const closed = referralState === "expired"; const closedWith = referralClosureReasonLabel(row?.closureReason); const whereItStands = !finished || acknowledged ? undefined : referralState === "scheduled" ? "Scheduled: they booked the patient. Nothing more is sent from here." : `Closed${closedWith ? `: ${closedWith}` : ""}. Nothing more is sent from here.`; useEffect(() => { if (finished) setMethod("phone"); }, [finished]); // A result that lands after the dialog moved to another referral touches only the queue. const recordOnBehalf = async (state: "claimed" | "scheduled") => { const orderId = row?.orderId; if (!orderId) return; setBusy(true); setError(null); const change = attempt("state"); try { await (state === "claimed" ? markReferralAccepted(orderId, change) : markReferralScheduled(orderId, change)); if (!stillOpen(change)) return; if (openOrderId.current === orderId) setRecorded({ orderId, state }); onRecorded(); } catch (err) { if (openOrderId.current === orderId && stillOpen(change)) { setError(relayServerFailure(err, "That referral could not be updated. Please try again.")); } } finally { if (openOrderId.current === orderId) setBusy(false); } }; const getClaimCode = async () => { if (!row?.orderId) return; setBusy(true); setError(null); const mint = attempt("claimCode"); try { const minted = await mintClaimCode(row.orderId, mint); if (!stillOpen(mint)) return; setClaim(minted); } catch (err) { if (!stillOpen(mint)) return; setError(relayServerFailure(err, "A code could not be created. Please try again.")); } finally { setBusy(false); } }; /** * SEND THE PACKET. * * IT REPORTS WHAT THE VENDOR ACCEPTED, NEVER THAT THE SPECIALIST HAS IT - the same fence the phone * tab carries. A coordinator who reads "sent" as "they have it" stops chasing, and a referral nobody * is chasing is a patient who does not reach a specialist. */ /** * Look at the packet before sending it. * * Ruled 2026-09-14: there was no way to see the page without sending it, so a coordinator faxed a * patient's demographics, insurance and reason for referral to an outside practice without ever * being able to check what was on it. A send cannot be recalled. * * The PDF is opened in a new tab from a blob rather than drawn here. The server composes the same * page the send transmits; a second renderer in the client would be a preview of something else. */ const previewPacket = async () => { if (!row?.orderId) return; // THE TAB IS OPENED IN THE GESTURE, BEFORE ANY AWAIT. A popup blocker allows window.open only // while the click's transient user activation is alive, and awaiting the packet first spends it - // so an ordinary blocker discarded the PDF and the coordinator saw nothing happen. // `noopener` is deliberately absent: passing it makes window.open return NULL, and the handle is // what the blob URL is assigned to below. The opener is severed by hand instead. const tab = window.open('', '_blank'); if (tab) tab.opener = null; // The blank tab belongs to this opening: if it retires before the packet arrives, the tab is closed, not filled. const closeOwnedTab = () => tab?.close(); scope?.signal.addEventListener('abort', closeOwnedTab, { once: true }); setBusy(true); setError(null); try { const preview = await previewReferralPacket(row.orderId, scope); if (!stillOpen(scope)) { tab?.close(); return; } // The reason it could not be sent, said BEFORE the coordinator types a number and presses // Send - the server returns the page either way, so they can see what is missing on it. setPreviewNote(preview.actionable ? null : preview.notActionableReason ?? null); const bytes = Uint8Array.from(atob(preview.pdfBase64), (character) => character.charCodeAt(0)); const url = URL.createObjectURL(new Blob([bytes], { type: preview.contentType })); // The tab already exists; this only points it at the packet. If it was blocked outright there // is no handle, and opening again here would be blocked for the same reason. if (tab) tab.location.href = url; // Released on the next turn rather than immediately: revoking synchronously races the tab that // is still opening and lands it on a blank page. setTimeout(() => URL.revokeObjectURL(url), 60_000); } catch (err) { // The blank tab was opened on the press and the packet never arrived, so it is closed rather // than left as an empty window the coordinator has to tidy up. tab?.close(); if (!stillOpen(scope)) return; setError(relayServerFailure(err, 'That packet could not be prepared. Try again.')); } finally { scope?.signal.removeEventListener('abort', closeOwnedTab); setBusy(false); } }; const sendFax = async () => { if (!row?.orderId || faxNumber.trim() === "") return; setBusy(true); setError(null); const send = attempt("fax"); // The correction is part of this press: opted into now, and retired for good if its permission goes meanwhile. const correctedFax = faxNumber.trim(); const correction = row.performerNpi && shareCorrection && correctedFax !== knownFax ? attempt("contactBook") : undefined; try { // The task id rides along so the SERVER writes the contact entry. It verifies the task is ours, // focuses this referral and is a referral task before touching it. const sent = await faxReferral(row.orderId, { toNumber: faxNumber.trim(), taskId: row.taskId }, send); if (!stillOpen(send)) return; setFaxResult(sent); onRecorded(); /* * SAVE WHAT WAS LEARNED, AFTER THE SEND AND NEVER BEFORE IT. * * Order matters: the number is worth sharing because it was just USED, and recording one that * the vendor then refused would publish a bad number to every practice. So this runs only past * a send the vendor accepted. * * It is also best-effort. The fax has gone; failing the whole action now would tell a * coordinator their referral did not send when it did. Only the shared field is written - the * private note has its own control and its own audience. */ if (row.performerNpi && correction && stillOpen(correction)) { try { await saveReferralContact(row.performerNpi, { shared: { fax: correctedFax } }, correction); if (!stillOpen(correction)) return; setKnownFax(correctedFax); } catch { // Deliberately silent - see above. The send is what the coordinator is watching. } } } catch (err) { /* * A 502 ON THIS ROUTE IS THE FAX VENDOR TALKING, AND ITS WORDS ARE NOT FIT FOR A COORDINATOR. * * MEASURED on journey G4: pressing Send the packet put this on a receptionist's screen - * "The number you set as from does not belong to you, Please use a number that you have in * project ". A sentence about an API project, naming an identifier out of somebody * else's system, in front of somebody holding a patient's referral. * * NARROWED TO 502 ON PURPOSE. `relayServerFailure` is right for every other failure here, * because those come from our own handler and are written for a person to read - "This * practice has not saved its account details yet." is the sentence a coordinator needs, and * a blanket rule would swallow it. 502 is the one status that means the proxy could not get * a good answer out of the third party, so it is the one whose body was never ours. * * IT DOES NOT SAY NOTHING WAS SENT. A 502 is our side failing to hear a clear answer, not a * confirmed refusal, and telling a coordinator their referral definitely did not go is the * same class of mistake in the other direction. */ if (!stillOpen(send)) return; const status = (err as { status?: unknown } | null | undefined)?.status; setError( status === 502 ? "We could not reach the fax service and cannot confirm whether this fax was sent. Try again in a moment." : relayServerFailure(err, "That fax could not be sent. Please try again."), ); } finally { correction?.retire(); setBusy(false); } }; return ( { if (contacts?.length) { event.preventDefault(); bodyRef.current?.querySelector('[data-contact-history-heading]')?.focus(); return; } // Open on the first field. Focus landing on the Phone tab drew its focus ring over the underline. const field = bodyRef.current?.querySelector("input, textarea"); if (field) { event.preventDefault(); field.focus(); } }} > {/* πŸ”‘ THE PATIENT IS IN THE TITLE, and this is the half of the card with the consequence. The dialog named the DESTINATION and nothing else, so a coordinator was about to phone a hospital without being told who they were calling about β€” carrying it from the row they clicked, which is worse than not knowing, because it is exactly the kind of thing people are certain they remembered. ⚠️ Falls back to the old wording when the name is absent rather than printing an empty gap or the word "patient": a row whose subject could not be resolved is the one case where a confident-looking name would be a guess. */} {/* RESERVES THE CLOSE BUTTON'S FOOTPRINT. The dialog is sized by `width: max-content` (index.css:3511), so when the title is the longest line the dialog grows to exactly the title's ink β€” and the close is `absolute right-4`, drawn on top of it. Measured at 1400x950: "Send Zzcard461 Crossorgfixture's referral to State University Of Iowa" ended 8px UNDER the close, while a shorter title cleared it by 1px. The padding travels into the max-content width, so the clearance holds at any title length. */} {finished ? (row?.patientDisplay ? `${row.patientDisplay}'s referral${row.performerDisplay ? ` to ${row.performerDisplay}` : ""}` : `This referral${row?.performerDisplay ? ` to ${row.performerDisplay}` : ""}`) : row?.patientDisplay ? (row.performerDisplay ? `Send ${row.patientDisplay}'s referral to ${row.performerDisplay}` : `Send ${row.patientDisplay}'s referral`) : (row?.performerDisplay ? `Send this referral to ${row.performerDisplay}` : "Send this referral")} {/* 🚨 THE SENTENCE THAT STOPS A COORDINATOR BELIEVING THE REFERRAL LANDED. Everything on this screen is an account of what WE did. */} {/* A closed sheet sends nothing, so its description is where it stands, below. */} {!closed && ( Recording a send is a note of what you did β€” it does not confirm the specialist received it. )} {/* πŸ“ž THE REASON, because relaying WHY is most of what the call is. It sits under the description rather than in the title: the title says who and where, and a reason long enough to matter would push those off a narrow dialog. */} {row?.reasonText && (

{row.reasonText}

)} {whereItStands && (closed ? {whereItStands} :

{whereItStands}

)} {patientBookedLine(row?.patientBookedAt) && (

{patientBookedLine(row?.patientBookedAt)}

)}
{ if (!row?.taskId) return; setRecordedContacts({ taskId: row.taskId, contacts }); setRecipient(""); setSpokeWith(""); setOutcome(""); setError(null); setSaved(false); onRecorded(); }} /> {!finished && (
{TABS.map(({ key, label, icon: Icon }) => ( ))}
)} {/* THE REFUSAL, NEXT TO THE FIELDS IT EXPLAINS. Deliberately not the Alert above: that one belongs to an ACTION the coordinator just took, and is cleared by the next one. This is a passive lookup that failed before anybody pressed anything, and it has to survive long enough to be read. It never blocks the form - a denial that takes the screen away is the same defect wearing the other hat. */} {contactRefusal && ( {contactRefusal} )} {error && ( {error} )} {/* ── PHONE ─────────────────────────────────────────────────────────────────────────────── πŸ”¬ THE CARD'S FIRST CONTROL: *"a logged call is a real record, not a note. Who called, who was called, when, and the outcome β€” enough that somebody reading it later can tell whether the referral was accepted, declined or is still open. β›” A free-text box is not this."* β‡’ Separate fields, so the next coordinator can read them rather than parse a paragraph. */} {!may.contact && !may.state && !may.claimCode && !may.fax && (

Your role at this practice can see this referral but not work it.

)} {method === "phone" && !saved && !closed && may.contact && (
setRecipient(e.target.value)} placeholder="(555) 555-0123" /> {/* WHERE THE NUMBER CAME FROM. A prefilled box with no provenance is a number the coordinator cannot judge, and judging it is what makes a correction possible. */} {knownPhone !== "" && recipient.trim() === knownPhone && (

From the shared referral book, recorded by a practice that referred here before.

)} {contact?.shared?.routeTo && (

Ask for {contact.shared.routeTo}.

)} {contact?.shared?.hours && (

Reachable {contact.shared.hours}.

)}
{/* THE CORRECTION, AT THE MOMENT IT IS LEARNED. Opt-out rather than automatic, on the same reasoning as the fax tab: the number dialled for one call may be a direct line somebody was given once, and publishing that to every practice would be wrong. */} {row?.performerNpi && recipient.trim() !== "" && recipient.trim() !== knownPhone && ( )}
{/* ⭐ A PERSON, and the server's own note calls this the single most valuable thing on a phone record β€” it is who the next coordinator rings back and asks for. */} setSpokeWith(e.target.value)} placeholder="Name and role, if they gave one" />