feat(cortex): implement activePatient fallback in various components

- Updated DagnotatieBlock to use activePatient for prefill if no patientId is provided.
- Enhanced PatientDashboardBlock to fallback to activePatient for patientId and name.
- Adjusted usePatientSelection to handle pending actions and re-route after patient selection.
- Refined chat components to manage nudge messages and integrate them into the chat flow.
- Improved UI elements for better patient selection experience.

This update enhances user experience by ensuring that the active patient context is utilized across multiple components, streamlining workflows and reducing manual input.
This commit is contained in:
colinislit
2026-01-03 22:18:30 +01:00
parent 7040241f7d
commit 05836c5c6a
18 changed files with 472 additions and 93 deletions

View File

@@ -26,6 +26,7 @@ import { Loader2, Search, User, RefreshCw } from 'lucide-react';
import { cn } from '@/lib/utils';
import { safeFetch, getErrorInfo, retryFetch } from '@/lib/cortex/error-handler';
import { evaluateNudge } from '@/lib/cortex/nudge';
import { formatPatientName as formatPatientNameFromDb } from '@/lib/fhir/patient-mapper';
interface DagnotitieBlockProps {
prefill?: BlockPrefillData;
@@ -40,20 +41,25 @@ interface Patient {
export function DagnotatieBlock({ prefill }: DagnotitieBlockProps) {
const config = BLOCK_CONFIGS.dagnotitie;
const { closeBlock, addSuggestion } = useCortexStore();
const { closeBlock, addChatMessage, activePatient } = useCortexStore();
const { toast } = useToast();
// Form state
const [patientId, setPatientId] = useState<string>(prefill?.patientId || '');
const [patientName, setPatientName] = useState<string>(prefill?.patientName || '');
// E3.S1: Determine initial patient from prefill OR activePatient
const initialPatientId = prefill?.patientId || activePatient?.id || '';
const initialPatientName = prefill?.patientName || (activePatient ? formatPatientNameFromDb(activePatient) : '');
const hasPrefillPatient = Boolean(prefill?.patientId);
// Form state - E3.S1: Use activePatient as fallback
const [patientId, setPatientId] = useState<string>(initialPatientId);
const [patientName, setPatientName] = useState<string>(initialPatientName);
const [category, setCategory] = useState<VerpleegkundigCategory>(
prefill?.category || 'observatie'
);
const [content, setContent] = useState<string>(prefill?.content || '');
const [includeInHandover, setIncludeInHandover] = useState<boolean>(false);
// Patient search state
const [searchQuery, setSearchQuery] = useState<string>(prefill?.patientName || '');
// Patient search state - E3.S1: Use activePatient as fallback
const [searchQuery, setSearchQuery] = useState<string>(initialPatientName);
const [patients, setPatients] = useState<Patient[]>([]);
const [isSearching, setIsSearching] = useState(false);
const [showPatientDropdown, setShowPatientDropdown] = useState(false);
@@ -61,18 +67,33 @@ export function DagnotatieBlock({ prefill }: DagnotitieBlockProps) {
const searchTimeoutRef = useRef<NodeJS.Timeout>();
const dropdownRef = useRef<HTMLDivElement>(null);
// Prefill patient if patientId is provided
// E3.S1: Prefill patient from prefill OR activePatient
useEffect(() => {
// Priority 1: Explicit prefill
if (prefill?.patientId && prefill?.patientName) {
setPatientId(prefill.patientId);
setPatientName(prefill.patientName);
setSearchQuery(prefill.patientName);
setSelectedPatient({
id: prefill.patientId,
name_family: prefill.patientName.split(' ').pop(),
name_given: prefill.patientName.split(' ').slice(0, -1),
});
}
}, [prefill]);
// Priority 2: activePatient (only if no prefill patient)
else if (!hasPrefillPatient && activePatient) {
const name = formatPatientNameFromDb(activePatient);
setPatientId(activePatient.id);
setPatientName(name);
setSearchQuery(name);
setSelectedPatient({
id: activePatient.id,
name_family: activePatient.name_family || undefined,
name_given: activePatient.name_given || [],
identifier_bsn: activePatient.identifier_bsn || undefined,
});
}
}, [prefill, activePatient, hasPrefillPatient]);
// Patient search with debouncing
const searchPatients = useCallback(async (query: string) => {
@@ -223,6 +244,7 @@ export function DagnotatieBlock({ prefill }: DagnotitieBlockProps) {
});
// E4: Evaluate nudge after successful save
// Now adds nudges as chat messages instead of toast
const nudges = evaluateNudge({
intent: 'dagnotitie',
actionId: data.id || `dagnotitie-${Date.now()}`,
@@ -233,10 +255,14 @@ export function DagnotatieBlock({ prefill }: DagnotitieBlockProps) {
content: content.trim(),
});
// Add nudge suggestions to store
// Add nudge suggestions as chat messages
nudges.forEach((nudge) => {
addSuggestion(nudge);
console.log('[DagnotatieBlock] Nudge triggered:', nudge.suggestion.message);
addChatMessage({
type: 'nudge',
content: nudge.suggestion.message,
nudge: nudge,
});
console.log('[DagnotatieBlock] Nudge chat message:', nudge.suggestion.message);
});
// Close block after short delay
@@ -259,7 +285,7 @@ export function DagnotatieBlock({ prefill }: DagnotitieBlockProps) {
} finally {
setIsSubmitting(false);
}
}, [patientId, content, category, includeInHandover, patientName, toast, closeBlock, addSuggestion]);
}, [patientId, content, category, includeInHandover, patientName, toast, closeBlock, addChatMessage]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();

View File

@@ -23,9 +23,11 @@ import { safeFetch, getErrorInfo } from '@/lib/cortex/error-handler';
import { useToast } from '@/hooks/use-toast';
import { BLOCK_CONFIGS } from '@/lib/cortex/types';
import type { BlockPrefillData } from '@/stores/cortex-store';
import { useCortexStore } from '@/stores/cortex-store';
import type { FHIRPatient } from '@/lib/fhir';
import type { Intake } from '@/lib/types/intake';
import { cn } from '@/lib/utils';
import { formatPatientName } from '@/lib/fhir/patient-mapper';
interface PatientDashboardBlockProps {
prefill?: BlockPrefillData;
@@ -103,9 +105,13 @@ function getPatientBsn(patient?: FHIRPatient): string | null {
export function PatientDashboardBlock({ prefill }: PatientDashboardBlockProps) {
const config = BLOCK_CONFIGS['patient-dashboard'];
const patientId = prefill?.patientId;
const { activePatient } = useCortexStore();
const { toast } = useToast();
// E3.S2: Use activePatient as fallback for patientId
const patientId = prefill?.patientId || activePatient?.id;
const patientNameFromPrefill = prefill?.patientName || (activePatient ? formatPatientName(activePatient) : undefined);
const [data, setData] = useState<PatientDashboardResponse | null>(null);
const [isLoading, setIsLoading] = useState(Boolean(patientId));
const [error, setError] = useState<string | null>(null);
@@ -178,8 +184,9 @@ export function PatientDashboardBlock({ prefill }: PatientDashboardBlockProps) {
? data?.carePlan?.activities.length
: 0;
const title = prefill?.patientName
? `${config.title} - ${prefill.patientName}`
// E3.S2: Use patientNameFromPrefill for title
const title = patientNameFromPrefill
? `${config.title} - ${patientNameFromPrefill}`
: config.title;
return (