Files
triqura-ecd/components/cortex/command-center/patient-mention-dropdown.tsx
colinislit 73894ac879 feat(cortex): Epic 2 - @Mention Systeem (E2.S1-S3)
Inline patient selectie via @naam in de chat input.

YAGNI Versimpeling: 8→4 SP, 5→3 stories
- Geen selectedMentions array (activePatient is al beschikbaar)
- Geen API payload wijziging nodig
- Geen meerdere mentions tracking

E2.S1 - @-detectie:
- Detecteer laatste @ in input
- Track query en startIndex
- Actief als geen space na @ en minimaal 1 char

E2.S2 - PatientMentionDropdown:
- Hergebruik usePatientSearch (debounce 150ms, limit 5)
- Hergebruik PatientListItem (size="sm")
- Positioned above input (bottom-full)
- Escape sluit dropdown

E2.S3 - Selectie flow:
- Vervang @query met @naam in input
- Zet activePatient via usePatientSelection
- Sluit dropdown automatisch

Bouwplan v1.4: Epic 2 compleet (14/19 SP - 74%)

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

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

91 lines
2.4 KiB
TypeScript

'use client';
/**
* PatientMentionDropdown Component
*
* Dropdown for @mention patient selection in CommandInput.
* Positioned above the input, shows search results.
*
* Epic: E2.S2 (Patient Selectie - @Mention)
*/
import { useEffect } from 'react';
import { usePatientSearch, type PatientSearchResult } from '@/lib/cortex/hooks/use-patient-search';
import {
PatientListItem,
PatientListEmpty,
PatientListLoading,
} from '@/components/cortex/shared/patient-list-item';
interface PatientMentionDropdownProps {
/** Current search query (text after @) */
query: string;
/** Called when a patient is selected */
onSelect: (patient: PatientSearchResult) => void;
/** Called when dropdown should close */
onClose: () => void;
}
export function PatientMentionDropdown({
query,
onSelect,
onClose,
}: PatientMentionDropdownProps) {
const { setQuery, results, isSearching } = usePatientSearch({
debounceMs: 150,
limit: 5,
minQueryLength: 2,
});
// Sync query to search hook
useEffect(() => {
setQuery(query);
}, [query, setQuery]);
// Close on Escape
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
onClose();
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [onClose]);
return (
<div
className="absolute bottom-full left-0 mb-2 w-72 bg-white border border-slate-200 rounded-lg shadow-lg z-50 overflow-hidden"
role="listbox"
aria-label="Patient zoekresultaten"
>
{isSearching ? (
<PatientListLoading message="Zoeken..." />
) : query.length < 2 ? (
<div className="p-3 text-sm text-slate-500 text-center">
Typ minimaal 2 karakters
</div>
) : results.length > 0 ? (
<div className="p-1 space-y-0.5 max-h-64 overflow-y-auto">
{results.map((patient) => (
<PatientListItem
key={patient.id}
patient={patient}
onClick={() => onSelect(patient)}
size="sm"
/>
))}
</div>
) : (
<PatientListEmpty
message={`Geen resultaten voor "@${query}"`}
submessage="Probeer een andere naam"
/>
)}
</div>
);
}