import { useEffect, useState } from "react"; import { AlertCircle, Plus, Trash2 } 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 { Alert, AlertDescription } from "../../../../components/ui/alert.tsx"; import { relayServerFailure } from "../../../../utils/fetchFailureMessage"; import { COMMON_SOURCES, LAB_FORMATS, type LabConnection, type LabFieldMapping, type LabProfile, } from "../../../../hooks/useLabConnections.ts"; import "./DiagnosticsPage.css"; // HOW ONE LABORATORY WANTS AN ORDER SAID, described rather than coded. // // EVERY LABORATORY DIFFERS AND NONE OF THEM MAY BE A BRANCH. What changes between them is a row in // this table: where the value goes in their message, which value it is, and how it has to be // written. A laboratory's name must not appear in a conditional anywhere in the product. // // THE SOURCE IS PICKED BY NAME, NOT TYPED. The expression underneath is a plain FHIRPath and the // server validates it, but nobody configuring a laboratory should need to know that the test code // lives at `order.code.coding[0].code`. The typing box is the second option and it is there for the // laboratory that wants something the list does not cover -- the escape hatch, not the front door. // // IT SAYS WHAT IT CANNOT DO. Nothing here sends: this describes the message, and the transport that // carries it is a separate piece. A screen that let a practice believe orders were flowing would be // worse than one that admits they are not. export type ProfileTarget = { connection: LabConnection } | undefined; interface Props { target: ProfileTarget; onClose: () => void; saveProfile: (id: string, profile: LabProfile) => Promise; } const emptyRow = (): LabFieldMapping => ({ target: "", read: COMMON_SOURCES[0].path }); const pathIsCommon = (path?: string) => COMMON_SOURCES.some((each) => each.path === path); const LabProfileEditor = ({ target, onClose, saveProfile }: Props) => { const [fields, setFields] = useState([]); const [saving, setSaving] = useState(false); const [error, setError] = useState(); useEffect(() => { setFields(target?.connection.profile?.fields ?? [emptyRow()]); setError(undefined); }, [target]); if (!target) return null; const connection = target.connection; const set = (at: number, patch: Partial) => // Choosing a source replaces a stored constant, so a row never carries both. setFields((rows) => rows.map((row, index) => (index === at ? { ...row, ...patch, ...('read' in patch ? { constant: undefined } : {}) } : row))); const submit = async () => { const described = fields.filter((row) => row.target.trim()); if (described.length === 0) { setError("Describe at least one field, or this profile says nothing."); return; } setSaving(true); setError(undefined); try { await saveProfile(connection.id as string, { npi: connection.npi as string, name: connection.name, version: connection.profile?.version ?? 0, // Parts of a row this editor does not show (value translations, a constant) are kept as stored. fields: described.map(({ format, whenMissing, ...row }) => ({ ...row, target: row.target.trim(), ...(format ? { format } : {}), ...(whenMissing ? { whenMissing } : {}), })), }); onClose(); } catch (failure) { console.error("Error saving the laboratory profile:", failure); setError(relayServerFailure(failure, "That profile could not be saved. Please try again.")); } finally { setSaving(false); } }; return ( { if (!open && !saving) onClose(); }}> How {connection.name ?? "this laboratory"} wants an order One row per field they expect. Nothing here sends an order yet. {error ? ( {error} ) : null}
{fields.map((row, at) => (
set(at, { target: e.target.value })} />
{/* Only when the list does not cover it. A path box on every row would invite editing the ones that are already right. */} {pathIsCommon(row.read) ? null : (
set(at, { read: e.target.value })} />
)}
set(at, { whenMissing: e.target.checked ? "refuse" : undefined, })} /> {/* Better to stop here than to have the laboratory refuse a message when the specimen has already been drawn. */}
))}
); }; export default LabProfileEditor;