Files
triqura-ecd/lib/cortex/hooks/use-patient-selection.ts
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

131 lines
3.7 KiB
TypeScript

'use client';
/**
* usePatientSelection Hook
*
* Handles patient selection flow: fetch FHIR data, map to DB format, update store.
* Extracted from ZoekenBlock for DRY compliance.
*
* Epic: E0.S4 (Patient Selectie - Refactor)
*/
import { useState, useCallback } from 'react';
import { useToast } from '@/hooks/use-toast';
import { useCortexStore } from '@/stores/cortex-store';
import { safeFetch, getErrorInfo } from '@/lib/cortex/error-handler';
import { mapFhirToDbPatient, type Patient } from '@/lib/fhir/patient-mapper';
import type { PatientSearchResult } from './use-patient-search';
interface UsePatientSelectionOptions {
/** Callback after successful selection */
onSuccess?: (patient: Patient, patientName: string) => void;
/** Callback on error */
onError?: (error: Error) => void;
/** Add to recent actions (default: true) */
trackRecentAction?: boolean;
/** Show toast on success (default: true) */
showSuccessToast?: boolean;
}
interface UsePatientSelectionReturn {
/** Select a patient by search result */
selectPatient: (patient: PatientSearchResult) => Promise<Patient | null>;
/** Whether a selection is in progress */
isSelecting: boolean;
/** ID of the patient currently being selected */
selectedId: string | null;
/** Clear selection state */
clearSelection: () => void;
}
export function usePatientSelection(
options: UsePatientSelectionOptions = {}
): UsePatientSelectionReturn {
const {
onSuccess,
onError,
trackRecentAction = true,
showSuccessToast = true,
} = options;
const [isSelecting, setIsSelecting] = useState(false);
const [selectedId, setSelectedId] = useState<string | null>(null);
const { toast } = useToast();
const { setActivePatient, addRecentAction } = useCortexStore();
const selectPatient = useCallback(
async (patient: PatientSearchResult): Promise<Patient | null> => {
setIsSelecting(true);
setSelectedId(patient.id);
try {
// Fetch full FHIR patient data
const response = await safeFetch(
`/api/fhir/Patient/${patient.id}`,
undefined,
{ operation: 'Patient data ophalen' }
);
const fhirPatient = await response.json();
// Map to DB format
const dbPatient = mapFhirToDbPatient(fhirPatient);
// Update store
setActivePatient(dbPatient);
// Track recent action
if (trackRecentAction) {
addRecentAction({
intent: 'zoeken',
label: `Patient geselecteerd: ${patient.name}`,
patientName: patient.name,
});
}
// Show success toast
if (showSuccessToast) {
toast({
title: 'Patient geselecteerd',
description: `${patient.name} is nu actief`,
});
}
// Call success callback
onSuccess?.(dbPatient, patient.name);
return dbPatient;
} catch (error) {
console.error('Failed to select patient:', error);
const errorInfo = getErrorInfo(error, { operation: 'Patient selecteren' });
toast({
variant: 'destructive',
title: errorInfo.title,
description: errorInfo.description,
});
onError?.(error instanceof Error ? error : new Error(String(error)));
return null;
} finally {
setIsSelecting(false);
setSelectedId(null);
}
},
[setActivePatient, addRecentAction, toast, trackRecentAction, showSuccessToast, onSuccess, onError]
);
const clearSelection = useCallback(() => {
setIsSelecting(false);
setSelectedId(null);
}, []);
return {
selectPatient,
isSelecting,
selectedId,
clearSelection,
};
}