import React, { useEffect, useRef, useState } from "react"; import { Label } from "../ui/label"; import { Textarea } from "../ui/textarea"; import EarlierSendCheck from "../messages/EarlierSendCheck"; import type { ScopedSendRecovery } from "../../hooks/useScopedSendRecovery"; /** Sends the patient's note as a correction request; `recipient` names who it went to. */ export type SendCorrectionRequest = (note: string) => Promise<{ ok: boolean; recipient?: string }>; type Phase = "idle" | "composing" | "sent"; export interface CorrectionRequestState { phase: Phase; sentTo?: string; /** Which opening of a record this is; a send only lands on the opening that made it. */ opening: number; isOpening: (opening: number) => boolean; start: () => void; cancel: () => void; sent: (recipient?: string) => void; } /** One request per opened record: it starts over only when a different record is shown. */ export const useCorrectionRequest = (recordKey: unknown): CorrectionRequestState => { const [phase, setPhase] = useState("idle"); const [sentTo, setSentTo] = useState(); const [opening, setOpening] = useState(0); // Read after an await, when the rendered value may already belong to a newer record. const current = useRef(0); useEffect(() => { current.current += 1; setOpening(current.current); setPhase("idle"); setSentTo(undefined); }, [recordKey]); return { phase, sentTo, opening, isOpening: (candidate) => candidate === current.current, start: () => setPhase("composing"), cancel: () => setPhase("idle"), sent: (recipient) => { setSentTo(recipient); setPhase("sent"); }, }; }; export const CorrectionRequestButton: React.FC<{ request: CorrectionRequestState }> = ({ request }) => ( request.phase === "sent" ? ( ) : ( ) ); /** Says what is wrong before anything is sent, sends it once, and says that it went. */ export const CorrectionRequestComposer: React.FC<{ request: CorrectionRequestState; onSend: SendCorrectionRequest; error?: string; idPrefix: string; /** An earlier request to this author not yet confirmed: Check it before another is sent. */ earlier?: ScopedSendRecovery; }> = ({ request, onSend, error, idPrefix, earlier }) => { const [note, setNote] = useState(""); const [sending, setSending] = useState(false); // The opening whose own send failed; the reason is shown there and nowhere else. const [failedIn, setFailedIn] = useState(); // A second press lands before the first re-render; only the ref sees the send already under way. const inFlight = useRef(false); useEffect(() => { if (request.phase !== "composing") setNote(""); }, [request.phase]); // A send still out for an earlier record no longer holds this one. useEffect(() => { inFlight.current = false; setSending(false); }, [request.opening]); if (request.phase === "sent") { return (

Your correction request was sent{request.sentTo ? ` to ${request.sentTo}` : ""}. Their reply will come to Messages.

); } if (request.phase !== "composing") { return null; } const reason = failedIn === request.opening ? error : undefined; const trimmed = note.trim(); const send = async () => { if (!trimmed || inFlight.current || earlier?.waits) return; const owner = request.opening; inFlight.current = true; setSending(true); setFailedIn(undefined); try { const result = await onSend(trimmed); if (!request.isOpening(owner)) return; if (result.ok) request.sent(result.recipient); else setFailedIn(owner); } finally { if (request.isOpening(owner)) { inFlight.current = false; setSending(false); } } }; return (