import { useEffect, useRef, useState } from "react"; import { AlertCircle } from "lucide-react"; import { Alert, AlertDescription } from "../../../components/ui/alert.tsx"; import { Button } from "../../../components/ui/button.tsx"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../../../components/ui/dialog.tsx"; import { Input } from "../../../components/ui/input.tsx"; import { Label } from "../../../components/ui/label.tsx"; import { SelectControl } from "../../../components/ui/select-control.tsx"; import { Textarea } from "../../../components/ui/textarea.tsx"; import { ToggleGroupControl, ToggleGroupControlOption } from "../../../components/ui/toggle-group-control.tsx"; import { newOperationId, useRecordsService, type RecordsBasis, type RecordsDirection, type RecordsRequest, type RecordsScope, } from "../../../services/RecordsService.tsx"; import { relayServerFailure } from "../../../utils/fetchFailureMessage"; import { isSaveRetired, useSaveScope } from "../../../utils/saveOperation"; import { actionAttempt, type ActionAttempt } from "../../../utils/referralRequestScope"; import { SCOPE_LABEL, basisOptions } from "./recordsWords.ts"; const BLANK = { name: "", organization: "", fax: "", email: "", dueAt: "", note: "" }; interface Props { // The practice this opening belongs to; its create is sent under it and nowhere else. organizationId?: string; // Whether this viewer may work records now; losing it retires a create under way for good. mayWork: boolean; onClose: () => void; onCreated: (request: RecordsRequest) => void; } // One operation id per request being typed, reused on a retry, so a lost answer cannot make two. Each opening is its // own mount; an answer that arrives after it closed or its owner changed publishes nothing. const NewRecordsRequestDialog = ({ organizationId, mayWork, onClose, onCreated }: Props) => { const { createRequest } = useRecordsService(); const [direction, setDirection] = useState("incoming"); const [form, setForm] = useState(BLANK); const [scope, setScope] = useState("practice"); const [basis, setBasis] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(); const operation = useRef(); const live = useRef(true); // The create is admitted before it is prepared and again just before it is sent; closing this opening, or a change of // practice or viewer, cancels it, so a create held on a token or proof wait is never sent. const lives = useSaveScope(organizationId); const mayWorkNow = useRef(mayWork); mayWorkNow.current = mayWork; const attempts = useRef(new Set()); useEffect(() => { if (mayWork) return; attempts.current.forEach((attempt) => attempt.retire()); attempts.current.clear(); }, [mayWork]); useEffect(() => { live.current = true; return () => { live.current = false; }; }, []); const field = (key: keyof typeof BLANK) => (event: { target: { value: string } }) => setForm((held) => ({ ...held, [key]: event.target.value })); const close = () => { if (busy) return; setForm(BLANK); setScope("practice"); setBasis(""); setError(undefined); setDirection("incoming"); operation.current = undefined; onClose(); }; const needsNote = basis === "other"; const ready = form.name.trim().length > 0 && (!needsNote || form.note.trim().length > 0); const submit = async () => { setBusy(true); setError(undefined); operation.current = operation.current ?? newOperationId(); const text = (value: string) => value.trim() || undefined; const life = lives.start(); const attempt = actionAttempt({ admit: life.admit, signal: life.transport.signal, organizationId }, () => mayWorkNow.current); attempts.current.add(attempt); attempt.signal.addEventListener("abort", () => attempts.current.delete(attempt), { once: true }); try { const answer = await createRequest(operation.current, { direction, counterpart: { name: form.name.trim(), organization: text(form.organization), fax: text(form.fax), email: text(form.email) }, ...(direction === "incoming" ? { scope } : {}), ...(direction === "incoming" && basis ? { basis: { kind: basis, note: text(form.note) } } : {}), ...(text(form.dueAt) ? { dueAt: form.dueAt } : {}), }, attempt); if (!live.current) return; operation.current = undefined; setBusy(false); onCreated(answer.request); close(); } catch (failure) { if (!live.current) return; if (isSaveRetired(failure)) { setBusy(false); return; } console.error("Error creating a records request:", failure); setError(relayServerFailure(failure, "The request could not be saved. Try again; it will not be logged twice.")); setBusy(false); } }; return ( { if (!next) close(); }}> New records request Log a request for records held here, or ask another practice for theirs.
{error && ( {error} )} setDirection(value === "outgoing" ? "outgoing" : "incoming")}>
{direction === "incoming" && ( <> setScope(value === "all" ? "all" : "practice")}> setBasis(value as RecordsBasis["kind"] | "")} /> {needsNote && (