feat(swift): implementeer linked evidence UI voor overdracht (E5.S2)

E5.S2 compleet: Bronnotitie links met hover preview in OverdrachtBlock.
Voortgang: 66 SP / 82 SP (80%) - Epic 5 40% compleet!

Implementatie:
- Bronverwijzingen nu klikbaar met hover popover voor volledige content
- Source data enrichment in API response
- Type-specific content display (observaties, rapportages, risico's)
- Visual feedback met icons en kleuren per bron type

Components & Types:
- LinkedEvidence component (160 regels) - Reusable popover voor alle bron types
- Aandachtspunt type extended met optionele sourceData field
- enrichWithSourceData() functie in API route (48 regels)

LinkedEvidence Features:
- Hover popover met volledige bron content
- Type-specific icons: Activity (observatie), FileText (rapportage), AlertTriangle (risico)
- Click/hover trigger met underline-dotted styling
- ExternalLink icon voor visual affordance
- Popover positioning: top-start voor beste UX

Source Data per Type:
- Observaties: value + unit + interpretation (HH/H/N/L/LL) met kleuren
- Rapportages/Verpleegkundig: content + createdBy
- Risico's: riskLevel + rationale met severity kleuren

API Enrichment Logic:
- enrichWithSourceData() lookup in context (vitals, reports, risks)
- Source data toegevoegd aan aandachtspunten vóór response
- Type-safe matching op bron.id en bron.type
- Backwards compatible: werkt met/zonder sourceData

UI/UX Improvements:
- Color-coded interpretations: Red (HH/LL kritiek), Amber (H/L), Teal (N)
- Color-coded risk levels: Red (zeer hoog/hoog), Amber (gemiddeld), Teal (laag)
- Formatted text display met bg-slate-50 border
- Responsive popover met max-width 96 (384px)
- Fallback: geen sourceData = plain text zonder popover

Integration in OverdrachtBlock:
- AandachtspuntItem gebruikt LinkedEvidence component
- Replaced: "bron.label • bron.datum" plain text
- Now: LinkedEvidence met hover preview

Files Changed:
- lib/types/overdracht.ts: +12 lines (sourceData interface)
- app/api/overdracht/generate/route.ts: +58 lines (enrichWithSourceData)
- components/swift/blocks/overdracht-block.tsx: +1/-3 lines (LinkedEvidence import/usage)
- components/swift/shared/linked-evidence.tsx: +160 lines (NEW)

Technical Details:
- Popover from shadcn/ui (@/components/ui/popover)
- Icons from lucide-react (Activity, FileText, AlertTriangle, ExternalLink)
- Type guards voor safe property access (sourceData?.value)
- Underline decoration-dotted cursor-help voor accessibility

Testing:
- Build succesvol (pnpm build)
- /epd/swift: 143 kB (+24 kB door LinkedEvidence component)
- Geen nieuwe type errors
- Popover trigger werkt met keyboard (accessible)

Voortgang: 66 SP / 82 SP (80%) - E5.S2 compleet (3 SP)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
colinislit
2025-12-27 18:38:45 +01:00
parent 422b5bd20d
commit adf6c3fb96
4 changed files with 231 additions and 4 deletions

View File

@@ -219,6 +219,59 @@ async function callClaudeAPI(
return validated;
}
/**
* Enrich aandachtspunten with full source data
*/
function enrichWithSourceData(
aandachtspunten: Aandachtspunt[],
context: OverdrachtContext
): Aandachtspunt[] {
return aandachtspunten.map((punt) => {
const { bron } = punt;
let sourceData: Aandachtspunt['sourceData'];
switch (bron.type) {
case 'observatie': {
const vital = context.vitals.find((v) => v.id === bron.id);
if (vital) {
sourceData = {
value: vital.value_quantity_value?.toString() || undefined,
unit: vital.value_quantity_unit || undefined,
interpretation: vital.interpretation_code || undefined,
};
}
break;
}
case 'rapportage':
case 'verpleegkundig': {
const report = context.reports.find((r) => r.id === bron.id);
if (report) {
sourceData = {
content: report.content,
createdBy: report.created_by || undefined,
};
}
break;
}
case 'risico': {
const risk = context.risks.find((r) => r.id === bron.id);
if (risk) {
sourceData = {
riskLevel: risk.risk_level,
rationale: risk.rationale || undefined,
};
}
break;
}
}
return {
...punt,
sourceData,
};
});
}
/**
* Log AI event to database
*/
@@ -298,13 +351,16 @@ export async function POST(request: NextRequest) {
const durationMs = Date.now() - startTime;
// Enrich aandachtspunten with full source data for linked evidence
const enrichedAandachtspunten = enrichWithSourceData(aiResult.aandachtspunten, context);
// Log AI event
await logAIEvent(supabase, patientId, context, aiResult, durationMs);
// Build response
const response: AISamenvatting = {
samenvatting: aiResult.samenvatting,
aandachtspunten: aiResult.aandachtspunten,
aandachtspunten: enrichedAandachtspunten,
actiepunten: aiResult.actiepunten,
generatedAt: new Date().toISOString(),
durationMs,