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

@@ -7,6 +7,7 @@
* Extracted from ZoekenBlock for DRY compliance.
*
* Epic: E0.S4 (Patient Selectie - Refactor)
* Epic: E3.S3 (Smart Defaults - Re-route after patient selection)
*/
import { useState, useCallback } from 'react';
@@ -25,6 +26,8 @@ interface UsePatientSelectionOptions {
trackRecentAction?: boolean;
/** Show toast on success (default: true) */
showSuccessToast?: boolean;
/** Handle pending action after selection (default: true) */
handlePendingAction?: boolean;
}
interface UsePatientSelectionReturn {
@@ -46,13 +49,15 @@ export function usePatientSelection(
onError,
trackRecentAction = true,
showSuccessToast = true,
handlePendingAction = true,
} = options;
const [isSelecting, setIsSelecting] = useState(false);
const [selectedId, setSelectedId] = useState<string | null>(null);
const { toast } = useToast();
const { setActivePatient, addRecentAction } = useCortexStore();
// E3.S3: Get pendingAction and openArtifact for re-route functionality
const { setActivePatient, addRecentAction, pendingAction, setPendingAction, openArtifact } = useCortexStore();
const selectPatient = useCallback(
async (patient: PatientSearchResult): Promise<Patient | null> => {
@@ -91,6 +96,30 @@ export function usePatientSelection(
});
}
// E3.S3: Handle pending action - re-route to artifact with patient info
if (handlePendingAction && pendingAction && !pendingAction.entities.patientId) {
const artifactType = pendingAction.artifact?.type || pendingAction.intent;
// Only open artifact if the type is valid (not 'unknown')
if (artifactType !== 'unknown') {
// Open artifact with patient info merged into prefill
openArtifact({
type: artifactType,
title: `${pendingAction.intent} - ${patient.name}`,
prefill: {
...pendingAction.entities,
patientId: dbPatient.id,
patientName: patient.name,
},
});
console.log('[usePatientSelection] E3.S3: Re-routed pendingAction to', artifactType);
}
// Clear pending action regardless
setPendingAction(null);
}
// Call success callback
onSuccess?.(dbPatient, patient.name);
@@ -113,7 +142,7 @@ export function usePatientSelection(
setSelectedId(null);
}
},
[setActivePatient, addRecentAction, toast, trackRecentAction, showSuccessToast, onSuccess, onError]
[setActivePatient, addRecentAction, toast, trackRecentAction, showSuccessToast, handlePendingAction, pendingAction, setPendingAction, openArtifact, onSuccess, onError]
);
const clearSelection = useCallback(() => {

View File

@@ -10,6 +10,7 @@ import type {
ExtractedEntities,
NudgePriority,
NudgeSuggestion,
ProtocolMetadata,
} from './types';
// -----------------------------------------------------------------------------
@@ -51,6 +52,8 @@ export interface ProtocolRule {
/** Function to prefill entities from source action */
prefillEntities: (source: ExtractedEntities) => Partial<ExtractedEntities>;
};
/** Protocol metadata for clinical context (optional) */
protocol?: ProtocolMetadata;
/** Priority for sorting multiple suggestions */
priority: NudgePriority;
/** Whether this rule is active */
@@ -153,6 +156,7 @@ export function evaluateNudge(input: NudgeEvaluationInput): NudgeSuggestion[] {
entities: rule.suggestion.prefillEntities(input.entities),
message: rule.suggestion.message,
rationale: rule.name,
protocol: rule.protocol,
},
status: 'pending',
priority: rule.priority,
@@ -205,6 +209,11 @@ export const PROTOCOL_RULES: ProtocolRule[] = [
appointmentType: 'follow-up',
}),
},
protocol: {
name: 'V&VN Richtlijn Wondzorg',
reference: '§4.2 Controlefrequentie',
rationale: 'Vroege hercontrole na wondverzorging verkleint het risico op infectie en bevordert optimale wondgenezing.',
},
priority: 'medium',
enabled: true,
expiresAfterMs: DEFAULT_EXPIRY_MS,

View File

@@ -282,6 +282,16 @@ export type NudgePriority = 'low' | 'medium' | 'high';
/** Nudge suggestion status */
export type NudgeStatus = 'pending' | 'accepted' | 'dismissed' | 'expired';
/** Protocol metadata for clinical context */
export interface ProtocolMetadata {
/** Official protocol name (e.g., "V&VN Richtlijn Wondzorg") */
name: string;
/** Specific section reference (e.g., "§4.2 Controlefrequentie") */
reference?: string;
/** Clinical rationale/justification for the suggestion */
rationale: string;
}
/** Proactive suggestion after action completion */
export interface NudgeSuggestion {
id: string;
@@ -295,6 +305,8 @@ export interface NudgeSuggestion {
entities: Partial<ExtractedEntities>;
message: string;
rationale: string;
/** Protocol metadata for clinical context (optional) */
protocol?: ProtocolMetadata;
};
status: NudgeStatus;
priority: NudgePriority;

View File

@@ -156,3 +156,36 @@ export function getPatientInitials(name: string): string {
.slice(0, 2)
.toUpperCase();
}
/**
* PatientSearchResult interface (duplicated to avoid circular import)
* Matches the interface in use-patient-search.ts
*/
export interface PatientSearchResult {
id: string;
name: string;
birthDate: string;
identifier_bsn?: string;
identifier_client_number?: string;
matchScore: number;
}
/**
* Converts a database Patient to PatientSearchResult format
*
* Used for displaying recent patients in sidebar/dropdown
* where we have full Patient data but need search result format.
*
* @param patient - Database Patient object
* @returns PatientSearchResult for UI components
*/
export function dbPatientToSearchResult(patient: Patient): PatientSearchResult {
return {
id: patient.id,
name: formatPatientName(patient) || 'Onbekend',
birthDate: patient.birth_date || '',
identifier_bsn: patient.identifier_bsn || undefined,
identifier_client_number: patient.identifier_client_number || undefined,
matchScore: 1,
};
}