Files
triqura-ecd/components/cortex/blocks/zoeken-block.tsx
colinislit 983807d833 refactor(cortex): Epic 0 - Extract patient search hooks & components (E0.S1-S4)
DRY refactor: Extract reusable patient search logic from ZoekenBlock.

New files:
- lib/cortex/hooks/use-patient-search.ts (116 lines)
  Debounced patient search with abort controller
- lib/cortex/hooks/use-patient-selection.ts (98 lines)
  FHIR fetch + store update flow
- lib/fhir/patient-mapper.ts (109 lines)
  FHIR Patient to DB Patient mapping + utilities
- components/cortex/shared/patient-list-item.tsx (114 lines)
  Reusable patient list item with loading states

Refactored:
- ZoekenBlock: 349 -> 127 lines (-64%)

Documentation:
- docs/intent/patient-search/ux-analyse-patient-selectie.md
- docs/intent/patient-search/bouwplan-patient-selectie-v1.md

CLAUDE.md updated with Cortex architecture documentation.

Epic 0 complete: 4/4 stories (4 SP)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 11:40:23 +01:00

128 lines
3.9 KiB
TypeScript

'use client';
/**
* Zoeken Block
*
* Block voor het zoeken naar patienten.
* Refactored to use extracted hooks and components (E0 - DRY).
*
* Epic: E3.S4 (Original), E0 (Refactor)
*/
import { useEffect, useRef } from 'react';
import { useCortexStore } from '@/stores/cortex-store';
import { BlockContainer } from './block-container';
import type { BlockPrefillData } from '@/stores/cortex-store';
import { BLOCK_CONFIGS } from '@/lib/cortex/types';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Search, User } from 'lucide-react';
// Use extracted hooks and components (DRY)
import { usePatientSearch } from '@/lib/cortex/hooks/use-patient-search';
import { usePatientSelection } from '@/lib/cortex/hooks/use-patient-selection';
import {
PatientListItem,
PatientListEmpty,
PatientListLoading,
} from '@/components/cortex/shared/patient-list-item';
interface ZoekenBlockProps {
prefill?: BlockPrefillData;
}
export function ZoekenBlock({ prefill }: ZoekenBlockProps) {
const config = BLOCK_CONFIGS.zoeken;
const { closeBlock, openArtifact } = useCortexStore();
const dropdownRef = useRef<HTMLDivElement>(null);
// Get prefill query from various sources
const prefillQuery = prefill?.patientName || prefill?.query || '';
// Use extracted hooks
const { query, setQuery, results, isSearching, searchPatients } = usePatientSearch({
initialQuery: prefillQuery,
limit: 10,
});
const { selectPatient, selectedId } = usePatientSelection({
onSuccess: (dbPatient, patientName) => {
// Close block and open patient dashboard
closeBlock();
openArtifact({
type: 'patient-dashboard',
title: `Dashboard - ${patientName}`,
prefill: {
patientId: dbPatient.id,
patientName: patientName,
},
});
},
});
// Auto-search on prefill
useEffect(() => {
if (prefillQuery && prefillQuery.length >= 2) {
searchPatients(prefillQuery);
}
}, [prefillQuery, searchPatients]);
const showResults = query.length >= 2;
return (
<BlockContainer title={config.title} size={config.size}>
<div className="space-y-4">
{/* Search Input */}
<div className="space-y-2">
<Label htmlFor="patient-search">Zoek patient</Label>
<div className="relative" ref={dropdownRef}>
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-slate-400" />
<Input
id="patient-search"
type="text"
placeholder="Typ naam, BSN of clientnummer..."
value={query}
onChange={(e) => setQuery(e.target.value)}
className="pl-9"
autoFocus
/>
</div>
</div>
{/* Search Results */}
{showResults && (
<div className="space-y-2">
{isSearching ? (
<PatientListLoading />
) : results.length > 0 ? (
<div className="space-y-1 max-h-96 overflow-y-auto">
{results.map((patient) => (
<PatientListItem
key={patient.id}
patient={patient}
isLoading={selectedId === patient.id}
onClick={() => selectPatient(patient)}
/>
))}
</div>
) : (
<PatientListEmpty
message="Geen patienten gevonden"
submessage="Probeer een andere zoekterm"
/>
)}
</div>
)}
{/* Empty State - waiting for input */}
{!showResults && (
<div className="flex flex-col items-center justify-center py-8 text-slate-400">
<User className="h-8 w-8 mb-2 opacity-50" />
<p className="text-sm">Typ minimaal 2 karakters om te zoeken</p>
</div>
)}
</div>
</BlockContainer>
);
}