'use client'; /** * Zoeken Block * * Block voor het zoeken naar patiënten. * E3.S4: Volledige implementatie met input, resultaten en selectie naar store. */ import { useState, useEffect, useCallback, useRef } from 'react'; import { useToast } from '@/hooks/use-toast'; import { useSwiftStore } from '@/stores/swift-store'; import { BlockContainer } from './block-container'; import type { BlockPrefillData } from '@/stores/swift-store'; import { BLOCK_CONFIGS } from '@/lib/swift/types'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Loader2, Search, User, Check } from 'lucide-react'; import { cn } from '@/lib/utils'; interface ZoekenBlockProps { prefill?: BlockPrefillData; } interface PatientSearchResult { id: string; name: string; birthDate: string; identifier_bsn?: string; identifier_client_number?: string; matchScore: number; } export function ZoekenBlock({ prefill }: ZoekenBlockProps) { const config = BLOCK_CONFIGS.zoeken; const { closeBlock, setActivePatient, addRecentAction } = useSwiftStore(); const { toast } = useToast(); // Search state const [searchQuery, setSearchQuery] = useState(prefill?.patientName || ''); const [patients, setPatients] = useState([]); const [isSearching, setIsSearching] = useState(false); const [selectedPatientId, setSelectedPatientId] = useState(null); const searchTimeoutRef = useRef(); const dropdownRef = useRef(null); // Patient search function const searchPatients = useCallback(async (query: string) => { if (query.length < 2) { setPatients([]); return; } setIsSearching(true); try { const response = await fetch(`/api/patients/search?q=${encodeURIComponent(query)}&limit=10`); if (response.ok) { const data = await response.json(); setPatients(data.patients || []); } else { const errorData = await response.json().catch(() => ({ error: 'Zoeken mislukt' })); throw new Error(errorData.error || 'Zoeken mislukt'); } } catch (error) { console.error('Failed to search patients:', error); toast({ variant: 'destructive', title: 'Zoeken mislukt', description: error instanceof Error ? error.message : 'Er ging iets mis', }); setPatients([]); } finally { setIsSearching(false); } }, [toast]); // Prefill search query useEffect(() => { if (prefill?.patientName) { setSearchQuery(prefill.patientName); // Auto-search if prefill is provided if (prefill.patientName.length >= 2) { searchPatients(prefill.patientName); } } }, [prefill, searchPatients]); // Debounced search useEffect(() => { if (searchTimeoutRef.current) { clearTimeout(searchTimeoutRef.current); } if (searchQuery.length >= 2) { searchTimeoutRef.current = setTimeout(() => { searchPatients(searchQuery); }, 300); } else { setPatients([]); } return () => { if (searchTimeoutRef.current) { clearTimeout(searchTimeoutRef.current); } }; }, [searchQuery, searchPatients]); // Close dropdown on outside click useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { // Don't close if clicking on input const target = event.target as HTMLElement; if (!target.closest('input')) { // Dropdown will close naturally when input loses focus } } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); // Handle patient selection const handleSelectPatient = async (patient: PatientSearchResult) => { setSelectedPatientId(patient.id); try { // Fetch full patient data from FHIR API const response = await fetch(`/api/fhir/Patient/${patient.id}`); if (!response.ok) { throw new Error('Patiënt data ophalen mislukt'); } const fhirPatient = await response.json(); // Map FHIR Patient to database Patient format // Map gender to enum type const genderMap: Record = { male: 'male', female: 'female', other: 'other', unknown: 'unknown', }; const mappedGender = genderMap[fhirPatient.gender?.toLowerCase() || 'unknown'] || 'unknown'; const dbPatient = { id: fhirPatient.id, name_family: fhirPatient.name?.[0]?.family || '', name_given: fhirPatient.name?.[0]?.given || [], birth_date: fhirPatient.birthDate || '', identifier_bsn: fhirPatient.identifier?.find( (id: any) => id.system === 'http://fhir.nl/fhir/NamingSystem/bsn' )?.value || null, identifier_client_number: fhirPatient.identifier?.find( (id: any) => id.system?.includes('client') || id.system?.includes('999.7.6') )?.value || null, gender: mappedGender as 'male' | 'female' | 'other' | 'unknown', active: fhirPatient.active !== false, status: null, created_at: null, updated_at: null, address_line: null, address_city: null, address_postal_code: null, address_country: null, telecom_email: null, telecom_phone: null, name_prefix: null, name_use: null, emergency_contact_name: null, emergency_contact_phone: null, emergency_contact_relationship: null, general_practitioner_name: null, general_practitioner_agb: null, insurance_company: null, insurance_number: null, is_john_doe: null, }; // Set active patient in store setActivePatient(dbPatient); // Add to recent actions addRecentAction({ intent: 'zoeken', label: `Patiënt geselecteerd: ${patient.name}`, patientName: patient.name, }); // Show success toast toast({ title: 'Patiënt geselecteerd', description: `${patient.name} is nu actief`, }); // Close search block and open PatientContextCard closeBlock(); // Open PatientContextCard after a short delay to allow ZoekenBlock to close setTimeout(() => { // PatientContextCard will auto-open when activePatient is set // We don't need to explicitly open it as a block - it's shown automatically }, 100); } catch (error) { console.error('Failed to select patient:', error); toast({ variant: 'destructive', title: 'Selectie mislukt', description: error instanceof Error ? error.message : 'Er ging iets mis', }); setSelectedPatientId(null); } }; const formatPatientDisplay = (patient: PatientSearchResult): string => { let display = patient.name; if (patient.birthDate) { const birthYear = new Date(patient.birthDate).getFullYear(); const currentYear = new Date().getFullYear(); const age = currentYear - birthYear; display += ` (${age} jaar)`; } return display; }; return (
{/* Search Input */}
setSearchQuery(e.target.value)} className="pl-9" autoFocus /> {isSearching && ( )}
{/* Search Results */} {searchQuery.length >= 2 && (
{isSearching ? (
Zoeken...
) : patients.length > 0 ? (
{patients.map((patient) => { const isSelected = selectedPatientId === patient.id; return ( ); })}
) : (

Geen patiënten gevonden

Probeer een andere zoekterm

)}
)} {/* Empty State */} {searchQuery.length < 2 && (

Typ minimaal 2 karakters om te zoeken

)}
); }