import { useEffect, useRef, useState } from "react"; import { AlertCircle } from "lucide-react"; import { IValueSet, useValueSetServiceContext } from "../../services"; import { Input } from "../ui/input.tsx"; import { Alert, AlertDescription } from "../ui/alert.tsx"; import { Button } from "../ui/button.tsx"; /** * Choosing the diagnosis an order answers. * * THE SIBLING OF LabTestPicker, for the same reason it exists: the status-aware search says whether * nothing matched or the lookup is down, and those must never look alike on a clinical screen. * * ICD-10-CM, NOT SNOMED, because a diagnosis on a lab or imaging order is the medical-necessity * code a claim carries, and the server holds the valid (billable) codes only - a category header * such as E11 cannot be offered here, by decision. One code, optional: the order's free-text * "why" stays beside it and a practitioner who cannot look a code up is not blocked. */ const MIN_SEARCH_CHARS = 2; const DEBOUNCE_MS = 300; type SearchState = | { status: "idle" } | { status: "searching" } | { status: "results"; results: IValueSet[] } | { status: "none" } | { status: "unavailable" }; const DiagnosisPicker = ({ value, onChange, disabled = false, }: { value: IValueSet | null; onChange: (diagnosis: IValueSet | null) => void; disabled?: boolean; }) => { const { searchValueSetWithStatus, valueSetUrls } = useValueSetServiceContext(); const [query, setQuery] = useState(""); const [state, setState] = useState({ status: "idle" }); const searchIdRef = useRef(0); useEffect(() => { const trimmed = query.trim(); if (trimmed.length < MIN_SEARCH_CHARS) { setState({ status: "idle" }); return; } const currentId = ++searchIdRef.current; setState({ status: "searching" }); const timer = setTimeout(async () => { const outcome = await searchValueSetWithStatus(valueSetUrls.icd10cmDiagnosis, trimmed); // Race guard: a slower earlier request must not overwrite a later one's answer. if (currentId !== searchIdRef.current) return; if (outcome.outcome === "unavailable") { setState({ status: "unavailable" }); return; } setState( outcome.results.length > 0 ? { status: "results", results: outcome.results } : { status: "none" }, ); }, DEBOUNCE_MS); return () => clearTimeout(timer); }, [query, searchValueSetWithStatus, valueSetUrls.icd10cmDiagnosis]); const choose = (diagnosis: IValueSet) => { searchIdRef.current += 1; setState({ status: "idle" }); setQuery(""); onChange(diagnosis); }; if (value) { return (
{value.code} {value.display}
); } return (
setQuery(event.target.value)} /> {state.status === "searching" && (

Searching…

)} {state.status === "unavailable" && ( The diagnosis lookup is unavailable, so no codes can be searched right now. This is not a search that found nothing. The order can still be placed with the reason written below. )} {state.status === "none" && (

No diagnosis matched that. Check the spelling, or describe the reason below.

)} {state.status === "results" && ( )}
); }; export default DiagnosisPicker;