feat(swift): Epic 5 Progress - Error Handling & Keyboard Shortcuts (E5.S2-S3)

Epic 5 deels compleet: Error handling en keyboard shortcuts geïmplementeerd.
Voortgang: 62 SP / 72 SP (86%) - Nog 1 story voor MVP compleet!

E5.S2 - Error Handling (2 SP)
- Gecentraliseerde error handler utility (lib/swift/error-handler.ts)
- isOffline(), isNetworkError(), getErrorInfo() functies
- safeFetch() wrapper met 30s timeout en retry logic
- retryFetch() met exponential backoff (max 3 retries)
- User-friendly Nederlandse error messages voor alle HTTP status codes
- OfflineBanner component met online/offline detection
- Alle blocks en CommandInput gebruiken nieuwe error handler
- Context bar margin adjustment voor offline banner
- parseErrorResponse() voor HTML error detection

E5.S3 - Keyboard Shortcuts (2 SP)
- ⌘Enter / Ctrl+Enter quick submit in CommandInput
- ⌘Enter / Ctrl+Enter quick save in DagnotatieBlock
- Visual hints (⌘↵) op submit button in DagnotatieBlock
- Geverifieerd: ⌘K focus, Escape close, 1-3 FallbackPicker
- Cross-platform support (macOS + Windows/Linux)
- preventDefault() voor browser conflict preventie

Components Updated:
- DagnotatieBlock: safeFetch, getErrorInfo, retryFetch, ⌘Enter save
- OverdrachtBlock: safeFetch, retryFetch voor AI generation
- ZoekenBlock: safeFetch, getErrorInfo voor patient search
- CommandInput: safeFetch, getErrorInfo, ⌘Enter submit
- CommandCenter: OfflineBanner integration
- ContextBar: useOffline hook voor margin adjustment

Nieuwe Files:
- lib/swift/error-handler.ts (301 regels) - Error handling utilities
- components/swift/command-center/offline-banner.tsx (72 regels)
- docs/swift/keyboard-shortcuts-reference.md (200+ regels)
- docs/swift/test-plan-e5-s2-error-handling.md (350+ regels)
- docs/swift/test-plan-e5-s3-keyboard-shortcuts.md (350+ regels)
- docs/swift/PROJECT-STATUS-2024-12-27.md (500+ regels) - Status report

Documentatie:
- Bouwplan bijgewerkt naar v2.5
- Epic completion details toegevoegd met progress bars
- Manual test checklist bijgewerkt (17/20 scenarios)
- Risico's sectie bijgewerkt (alle major risks gemitigeerd)
- Sprint planning status: E5 75% compleet (6/8 SP)

Technische verbeteringen:
- Offline detection met visual feedback
- Network error retry met exponential backoff
- HTTP 401/404/500/503 error messages in Nederlands
- Timeout protection (30s) op alle API calls
- Keyboard shortcuts met visual hints
- Cross-platform shortcut support

Testing:
- Error handling test plan met 8 categorieën
- Keyboard shortcuts test plan met 6 categorieën
- 40+ test scenarios gedocumenteerd
- Quick smoke test checklists

Voortgang: 62 SP / 72 SP (86%) - E5.S2 en E5.S3 compleet

🤖 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 09:27:32 +01:00
parent a9d464357b
commit 33c6698870
13 changed files with 1806 additions and 125 deletions

View File

@@ -28,6 +28,7 @@ import { format } from 'date-fns';
import { nl } from 'date-fns/locale/nl';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { safeFetch, getErrorInfo, retryFetch } from '@/lib/swift/error-handler';
interface OverdrachtBlockProps {
prefill?: BlockPrefillData;
@@ -64,10 +65,11 @@ export function OverdrachtBlock({ prefill }: OverdrachtBlockProps) {
const fetchPatients = async () => {
setIsLoadingPatients(true);
try {
const response = await fetch('/api/overdracht/patients');
if (!response.ok) {
throw new Error('Kon patiëntenlijst niet laden');
}
const response = await safeFetch(
'/api/overdracht/patients',
undefined,
{ operation: 'Patiëntenlijst laden' }
);
const data = await response.json();
setPatients(data.patients || []);
@@ -84,10 +86,15 @@ export function OverdrachtBlock({ prefill }: OverdrachtBlockProps) {
setPatientSummaries(summaries);
} catch (error) {
console.error('Failed to fetch patients:', error);
const statusCode = (error as any)?.statusCode;
const errorInfo = getErrorInfo(error, {
operation: 'Patiëntenlijst laden',
statusCode,
});
toast({
variant: 'destructive',
title: 'Laden mislukt',
description: error instanceof Error ? error.message : 'Kon patiëntenlijst niet laden',
title: errorInfo.title,
description: errorInfo.description,
});
} finally {
setIsLoadingPatients(false);
@@ -125,16 +132,20 @@ export function OverdrachtBlock({ prefill }: OverdrachtBlockProps) {
});
try {
const response = await fetch('/api/overdracht/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ patientId, period }),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: 'Genereren mislukt' }));
throw new Error(errorData.error || 'Genereren mislukt');
}
const response = await retryFetch(
() =>
safeFetch(
'/api/overdracht/generate',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ patientId, period }),
},
{ operation: 'Overdracht genereren' }
),
3,
1000
);
const summary: AISamenvatting = await response.json();
@@ -153,6 +164,11 @@ export function OverdrachtBlock({ prefill }: OverdrachtBlockProps) {
});
} catch (error) {
console.error('Failed to generate summary:', error);
const statusCode = (error as any)?.statusCode;
const errorInfo = getErrorInfo(error, {
operation: 'Overdracht genereren',
statusCode,
});
setPatientSummaries((prev) => {
const updated = new Map(prev);
const existing = updated.get(patientId);
@@ -160,7 +176,7 @@ export function OverdrachtBlock({ prefill }: OverdrachtBlockProps) {
updated.set(patientId, {
...existing,
loading: false,
error: error instanceof Error ? error.message : 'Onbekende fout',
error: errorInfo.description,
});
}
return updated;