import { useEffect, useState } from "react"; import { AlertCircle } 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 { Label } from "../../../components/ui/label.tsx"; import { Textarea } from "../../../components/ui/textarea.tsx"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../components/ui/select.tsx"; import { Alert, AlertDescription } from "../../../components/ui/alert.tsx"; import { relayServerFailure } from "../../../utils/fetchFailureMessage"; import type { Pending } from "./QualityControlStrip.tsx"; import { ActWithdrawn } from "../DiagnosticOrdersPage/ActWithdrawn.tsx"; import { dayAndTime } from "./benchTime.ts"; import "./QualityControlStrip.css"; // A run always names its test; there is no value field because the range lives on the vial. interface Props { pending?: Pending; onClose: () => void; logRun: (run: { orderableId: string; lot: string; level?: string; outcome: "pass" | "fail"; }) => Promise; recordCorrectiveAction: (runId: string, action: string) => Promise; /** The caller may no longer make this write: the dialog stays readable and closable, and cannot send. */ withdrawn?: boolean; } export const ControlRunDialog = ({ pending, onClose, logRun, recordCorrectiveAction, withdrawn }: Props) => { const [lot, setLot] = useState(""); const [level, setLevel] = useState(""); // No outcome until one is chosen, so a run is never saved as passing by default. const [outcome, setOutcome] = useState<"pass" | "fail">(); const [action, setAction] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(); const [chosen, setChosen] = useState(); const opened = pending ? (pending.kind === "run" ? `run:${pending.tests.map((each) => each.orderableId).join(",")}` : `correction:${pending.runId}`) : undefined; useEffect(() => { setChosen(pending?.kind === "run" && pending.tests.length === 1 ? pending.tests[0].orderableId : undefined); setLot(""); setLevel(""); setOutcome(undefined); setAction(""); setError(undefined); }, [opened]); if (!pending) return null; const loggingARun = pending.kind === "run"; const submittable = loggingARun ? Boolean(chosen) && lot.trim().length > 0 && outcome !== undefined : action.trim().length > 0; const testName = pending.kind === "run" ? pending.tests.find((each) => each.orderableId === chosen)?.test : pending.test; const failedAt = pending.kind === "correction" ? dayAndTime(pending.ranAt) : undefined; const failure = pending.kind === "correction" ? [pending.lot ? `Lot ${pending.lot}` : undefined, failedAt ? `Failed ${failedAt}` : undefined].filter(Boolean).join(" · ") : undefined; const submit = async () => { setBusy(true); setError(undefined); try { if (pending.kind === "run" && outcome) { await logRun({ orderableId: chosen as string, lot, level, outcome }); } else if (pending.kind === "correction") { await recordCorrectiveAction(pending.runId, action); } onClose(); } catch (err) { console.error("Error recording quality control:", err); setError(relayServerFailure(err, "Could not record that. Please try again.")); } finally { setBusy(false); } }; return ( { if (!open && !busy) onClose(); }}> {loggingARun ? "Log a control run" : "Record corrective action"}
{testName ?? (loggingARun ? "Choose the test" : "Test not recorded")} {failure ? {failure} : null}
{error ? ( {error} ) : null} {loggingARun ? (
The lot and outcome of a control run for this test. {pending.tests.length > 1 ? (
) : null}
setLot(event.target.value)} />
setLevel(event.target.value)} />
) : (