import React, { useEffect, useRef, useState } from 'react'; import { ComboboxControl } from "../ui/combobox-control"; import { ProgressBar } from "../ui/progress-bar"; import { DismissibleAlert } from '../ui/dismissible-alert'; import { Flex, FlexItem, FlexBlock } from '../ui/flex'; import { Text } from '../ui/text'; import { Spacer } from '../ui/spacer'; import { useFormContext, Controller } from 'react-hook-form'; import { Patient } from 'fhir/r4'; import { Check, Trash2 } from 'lucide-react'; import { Badge } from '../ui/badge'; import { usePatientServiceContext, useFhirServiceContext } from '../../services'; import { isRefusedRead } from '../../utils/fetchFailureMessage'; import { FhirHumanNameDisplay, formatFhirHumanName } from '../../utils/fhirHumanName'; interface PatientSearchProps { label?: string; context: string; placeholder?: string; disabled?: boolean; required?: boolean; allowClear?: boolean; onPatientSelected?: (patient: Patient | null) => void; /** Replaces the chart search, for a caller who may only search through a narrower route. */ search?: (searchTerm: string) => Promise; showSelectedPatient?: boolean; selectedPatientStyle?: 'button' | 'text'; } const PatientSearch: React.FC = ({ label = "Patient", context, placeholder = "Search by name or full phone #", disabled = false, required = false, allowClear = true, onPatientSelected, search, showSelectedPatient = true, selectedPatientStyle = 'button' }) => { const { control, watch, setValue } = useFormContext(); const { searchPatients } = usePatientServiceContext(); const { unpackBundle } = useFhirServiceContext(); const [patientSearchResults, setPatientSearchResults] = useState([]); const [patientSearchLoading, setPatientSearchLoading] = useState(false); const [searchFailure, setSearchFailure] = useState(); const selectedPatient = watch(context); // Only the newest search may offer patients, a failure, or the end of loading. const generation = useRef(0); const retireSearch = () => { generation.current += 1; setPatientSearchLoading(false); }; useEffect(() => () => { generation.current += 1; }, []); const searchPatientParticipants = async (searchTerm: string) => { retireSearch(); const mine = generation.current; // The previous answer is not this query's: nothing from it stays to be picked while this one loads. setPatientSearchResults([]); setSearchFailure(undefined); if (!searchTerm || searchTerm.length < 2) return; setPatientSearchLoading(true); try { const patients = search ? await search(searchTerm) : unpackBundle(await searchPatients(searchTerm)); if (generation.current !== mine) return; setPatientSearchResults(patients); setSearchFailure(undefined); } catch (error) { if (generation.current !== mine) return; console.error('Failed to search patients:', error); setPatientSearchResults([]); // A refused or failed search is not an empty one. setSearchFailure(isRefusedRead(error) ? 'You do not have access to search patient records.' : 'Patients could not be searched. Please try again.'); } finally { if (generation.current === mine) setPatientSearchLoading(false); } }; const handlePatientChange = (value: string | null | undefined) => { // A clear arrives as null and is handled once, by onReset. if (value == null) return; const patient = patientSearchResults.find(p => p.id === value) || null; setValue(context, patient); onPatientSelected?.(patient); }; const handlePatientClear = () => { retireSearch(); setValue(context, null); setPatientSearchResults([]); onPatientSelected?.(null); }; const renderSelectedPatient = () => { if (!selectedPatient || !showSelectedPatient) return null; if (selectedPatientStyle === 'button') { return ( <> {/* THIRD badge-shaped button found in this sweep: `disabled` + hardcoded green, displaying a NAME — a status indicator, not a control. Takes Spencer's QuestionnairesPage ruling (squared, font-normal, colours preserved). NOT converted to )} ); } }; return ( <> ( <> ({ value: patient.id ?? '', label: formatFhirHumanName(patient.name) }))} onFilterValueChange={(inputValue) => { searchPatientParticipants(inputValue); }} onChange={handlePatientChange} placeholder={placeholder} disabled={disabled} allowReset={allowClear} onReset={handlePatientClear}/> {patientSearchLoading && ( )} {searchFailure && (

{searchFailure}

)}
{fieldState.error && ( {fieldState.error.message} )} )}/> {renderSelectedPatient()} ); }; export default PatientSearch;