import { useState, useEffect, useRef } from "react"; import { IValueSet, useValueSetServiceContext } from "../../services"; import { Input } from "../ui/input.tsx"; import { isLoincTerm } from "../../utils/observationPanelValue"; interface LoincCodeSearchProps { onSelect: (coding: IValueSet) => void; placeholder?: string; disabled?: boolean; } const MIN_SEARCH_CHARS = 2; const LoincCodeSearch = ({ onSelect, placeholder = "Search LOINC codes (e.g. blood pressure)...", disabled = false, }: LoincCodeSearchProps) => { const { searchLoincCodesWithStatus } = useValueSetServiceContext(); const [query, setQuery] = useState(""); const [results, setResults] = useState([]); const [searching, setSearching] = useState(false); // Its own state, never folded into an empty result list. See the note on the render below. const [unavailable, setUnavailable] = useState(false); const searchIdRef = useRef(0); useEffect(() => { if (query.trim().length < MIN_SEARCH_CHARS) { setResults([]); setSearching(false); setUnavailable(false); return; } const currentId = ++searchIdRef.current; setSearching(true); const timer = setTimeout(async () => { // A code picked here codes a result, so only LOINC terms are offered, never Parts (LP...). const outcome = await searchLoincCodesWithStatus(query.trim(), undefined, true); if (currentId === searchIdRef.current) { setResults(outcome.outcome === "ok" ? outcome.results.filter((result) => isLoincTerm(result.code)) : []); setUnavailable(outcome.outcome === "unavailable"); setSearching(false); } }, 300); return () => clearTimeout(timer); }, [query, searchLoincCodesWithStatus]); const pick = (coding: IValueSet) => { searchIdRef.current += 1; setQuery(""); setResults([]); setSearching(false); setUnavailable(false); onSelect(coding); }; return (
setQuery(e.target.value)} /> {searching && (
Searching…
)} {/* A LOOKUP THAT FAILED IS NOT A CODE THAT DOES NOT EXIST, and this screen used to say the second when the first happened -- the search swallowed its own failure and returned an empty list, which rendered as "No LOINC codes found". THE CONSEQUENCE IS CLINICAL, not cosmetic. A practitioner told the code does not exist records free text instead, and the result is filed uncoded: nothing can match it to the order, trend it, or compare it to a reference range afterwards. Worse, they may conclude the test itself is not one this system knows about. The two messages are therefore separate states, and the failure says what to do next. */} {!searching && unavailable && (
The code service could not be reached, so we cannot say whether a code exists for "{query.trim()}". Try again, or enter the name as free text.
)} {!searching && !unavailable && query.trim().length >= MIN_SEARCH_CHARS && results.length === 0 && (
No LOINC codes found for "{query.trim()}".
)} {!searching && results.length > 0 && ( )}
); }; export default LoincCodeSearch;