# E3.S3 - Patient CRUD **Epic:** E3 - Patients & Organizations **Story:** E3.S3 - Patient CRUD **Story Points:** 6 **Status:** ✅ Compleet ## Doel Volledige CRUD operaties voor patiënten met validatie. ## Acceptatiecriteria - [x] **Create:** Nieuwe patiënt aanmaken met validatie - [x] **Read:** Patiënt gegevens ophalen en tonen - [x] **Update:** Bestaande patiënt bijwerken - [x] **Delete:** Patiënt verwijderen met confirmatie - [x] BSN 11-proef validatie - [x] Alle verplichte velden (volgens bouwplan) - [x] John Doe support (crisis opname zonder BSN) ## Implementatie ### 1. Server Actions (actions.ts) 📁 **Locatie:** `app/epd/patients/actions.ts` **CRUD Functies:** ```typescript // Create export async function createPatient(fhirPatient: FHIRPatient): Promise // Read export async function getPatient(id: string): Promise export async function getPatients(filters): Promise<{ patients, total, page, pageSize }> // Update export async function updatePatient(id: string, fhirPatient: FHIRPatient): Promise // Delete export async function deletePatient(id: string): Promise<{ success: boolean }> ``` **FHIR API Integration:** - POST `/api/fhir/Patient` - Create - GET `/api/fhir/Patient/:id` - Read one - GET `/api/fhir/Patient?params` - Read many (met paginatie) - PUT `/api/fhir/Patient/:id` - Update - DELETE `/api/fhir/Patient/:id` - Delete ### 2. Patient Form Component 📁 **Locatie:** `app/epd/patients/components/patient-form.tsx` **Features:** #### A. BSN Validatie **11-proef Check:** ```typescript function validateBSN(bsn: string): boolean { if (!bsn || bsn.length !== 9) return false; const digits = bsn.split('').map(Number); if (digits.some(isNaN)) return false; // Modulo-11 check const sum = digits.reduce((acc, digit, index) => { if (index < 8) { return acc + digit * (9 - index); } return acc - digit; }, 0); return sum % 11 === 0; } ``` **Validatie regels:** - 9 cijfers verplicht - Alleen numerieke karakters - Modulo-11 proef moet kloppen - Optioneel bij John Doe registratie #### B. Form Secties **Stap 1: Persoonlijke gegevens** - ✅ Voorvoegsel (optioneel) - ✅ Voornaam (verplicht) - ✅ Achternaam (verplicht) - ✅ BSN (9 cijfers, 11-proef) - verplicht tenzij John Doe - ✅ Geboortedatum (verplicht, max vandaag) - ✅ Geslacht (verplicht): Man, Vrouw, Anders, Onbekend **Stap 2: Adresgegevens** - ✅ Straat + huisnummer (optioneel) - ✅ Postcode (optioneel) - ✅ Woonplaats (optioneel) **Stap 3: Contactgegevens** - ✅ Telefoonnummer (optioneel) - ✅ E-mail (optioneel, email validatie) **Stap 4: Verzekering** - ✅ Verzekeraar (optioneel) - ✅ Polisnummer (optioneel) - ✅ Huisarts naam (optioneel) - ✅ Huisarts AGB-code (optioneel, 8 cijfers) **Stap 5: Noodcontact** - ✅ Naam contactpersoon (optioneel) - ✅ Relatie (optioneel) - ✅ Telefoonnummer (optioneel) #### C. John Doe Support **Features:** - Checkbox om John Doe aan te geven - BSN wordt optioneel bij John Doe - Warning indicator voor incomplete gegevens - Mogelijkheid om later BSN aan te vullen - Custom FHIR extension: `john-doe` **Use case:** - Crisis opnames waarbij identiteit niet direct bekend is - Patiënt kan later worden geüpdatet met volledige gegevens #### D. FHIR Mapping **Extensions gebruikt:** ```typescript { "extension": [ // Episode status { "url": "http://mini-epd.local/fhir/StructureDefinition/episode-status", "valueCode": "planned" | "active" | "finished" | "cancelled" }, // John Doe flag { "url": "http://mini-epd.local/fhir/StructureDefinition/john-doe", "valueBoolean": true | false }, // Insurance (custom) { "url": "http://mini-epd.local/fhir/StructureDefinition/insurance", "valueString": "{\"company\":\"...\",\"number\":\"...\"}" }, // General Practitioner (custom) { "url": "http://mini-epd.local/fhir/StructureDefinition/general-practitioner", "valueString": "{\"name\":\"...\",\"agb\":\"...\"}" } ], "contact": [ // Emergency contact (FHIR standard) { "relationship": [{ "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/v2-0131", "code": "C", "display": "Emergency Contact" }], "text": "Partner" | "Ouder" | "Kind" | etc. }], "name": { "text": "..." }, "telecom": [{ "system": "phone", "value": "...", "use": "home" }] } ] } ``` ### 3. Delete Patient Component 📁 **Locatie:** `app/epd/patients/components/delete-patient-button.tsx` **Features:** #### A. Two-Step Confirmation **Step 1: Initial Warning** - Rode "Gevaarlijke zone" sectie - Waarschuwing dat actie niet ongedaan gemaakt kan worden - "Patiënt verwijderen" button **Step 2: Final Confirmation** - Gedetailleerde lijst van wat wordt verwijderd: - Alle persoonlijke gegevens - Alle screening en intake informatie - Alle diagnoses en observaties - Alle behandelplannen en doelen - Alle documenten en rapportages - Patiëntnaam wordt getoond - "Ja, verwijder permanent" button - Annuleren button #### B. Safety Features - Disabled state tijdens verwijderen - Loading indicator - Error handling met feedback - Redirect naar overzichtspagina na succes - Cache revalidation ### 4. Page Implementations **Create Page:** - 📁 `app/epd/patients/new/page.tsx` - Gebruikt PatientForm zonder patient prop - "Terug naar patiënten" link - Clear page title en beschrijving **Read Page:** - 📁 `app/epd/patients/[id]/page.tsx` - Toont patiënt detail met tabs - Basisgegevens tab voor view/edit **Update Page:** - 📁 `app/epd/patients/[id]/basisgegevens/page.tsx` - Gebruikt PatientForm met patient prop - John Doe warning indien van toepassing - Missing BSN warning - Delete button onderaan **List Page:** - 📁 `app/epd/patients/page.tsx` - Tabel met alle patiënten (zie E3.S2) - "Nieuwe patiënt" button ## Validatie ### Client-side (HTML5) **Built-in validatie:** - `required` attribute voor verplichte velden - `type="email"` voor email validatie - `type="tel"` voor telefoonnummer - `type="date"` met `max` voor geboortedatum - `maxLength` en `pattern` voor BSN/AGB - `pattern="[0-9]{9}"` voor BSN - `pattern="[0-9]{8}"` voor AGB ### Server-side (Actions) **Validatie in handleSubmit:** - BSN 11-proef check (tenzij John Doe) - Error throwing bij ongeldige data - FHIR API valideert ook server-side **Error handling:** - Try-catch om alle errors - User-friendly error messages - Error state in UI ## UI/UX Details ### Form Layout **Desktop (md+):** - Grid layouts: 2-3 kolommen voor gerelateerde velden - Grouped sections met headers - Visual separation met borders **Mobile:** - Stack layout (1 kolom) - Responsive grid → stack - Touch-friendly input sizes ### Visual Feedback **States:** - Focus ring (teal) op inputs - Hover states op buttons - Loading states tijdens submit/delete - Disabled states - Error states (red border + message) **Colors:** - Teal: Primary actions, focus - Red: Danger (delete), errors - Amber: Warnings (John Doe) - Orange: Info (incomplete BSN) - Slate: Neutral elements ### Accessibility **Features:** - Label voor alle inputs - Aria-labels waar nodig - Keyboard navigation (Tab, Enter) - Clear error messages - Confirmation voor destructieve acties ## Testing Checklist ### Create - [ ] Nieuwe patiënt aanmaken met alle velden - [ ] Nieuwe patiënt aanmaken met minimale velden - [ ] BSN validatie werkt (reject invalid) - [ ] John Doe aanmaken zonder BSN - [ ] Form validatie toont errors - [ ] Redirect naar detail pagina na succes ### Read - [ ] Patiënt detail pagina toont alle gegevens - [ ] Lijst pagina toont alle patiënten - [ ] Missing gegevens worden correct getoond (-) - [ ] John Doe indicator zichtbaar ### Update - [ ] Bestaande patiënt bewerken werkt - [ ] Velden pre-filled met huidige waarden - [ ] John Doe → regulier (BSN toevoegen) - [ ] Update success toont correcte data - [ ] John Doe warning verdwijnt na BSN toevoegen ### Delete - [ ] Delete button zichtbaar op basisgegevens - [ ] First confirmation shows warning - [ ] Second confirmation toont details - [ ] Annuleren werkt op beide stappen - [ ] Delete success redirect naar lijst - [ ] Patiënt is echt verwijderd ### Validation - [ ] BSN 11-proef reject: 123456789 - [ ] BSN 11-proef accept: 111222333 (example valid) - [ ] Email validatie werkt - [ ] Geboortedatum niet in toekomst - [ ] Required velden kunnen niet leeg - [ ] AGB moet 8 cijfers zijn ## Bekende Beperkingen 1. **FHIR API Afhankelijkheid:** - Alle CRUD werkt via `/api/fhir/Patient` - API moet volledig geïmplementeerd zijn - Cascade delete moet in API geregeld zijn 2. **Validatie:** - Momenteel alleen HTML5 + BSN check - Geen Zod schema validatie (toekomstige verbetering) - Server-side validatie in FHIR API 3. **Multi-step Wizard:** - Bouwplan suggereert wizard - Huidige implementatie: single page form - Werkt goed, maar kan verbeterd naar wizard UX 4. **Soft Delete:** - Momenteel hard delete - Toekomstige verbetering: soft delete met archivering ## Volgende Stappen Na E3.S3: - [ ] **E4.S1** - Encounter tijdlijn - [ ] Eventueel: Zod validation schema - [ ] Eventueel: Multi-step wizard UX - [ ] Eventueel: Soft delete / archivering - [ ] Eventueel: Audit trail (wie/wanneer gewijzigd) ## Referenties - **Bouwplan:** `docs/bouwplan-mini-epd.md` (regel 356, E3.S3) - **Patient Form:** `app/epd/patients/components/patient-form.tsx` - **Delete Button:** `app/epd/patients/components/delete-patient-button.tsx` - **Actions:** `app/epd/patients/actions.ts` - **New Page:** `app/epd/patients/new/page.tsx` - **Edit Page:** `app/epd/patients/[id]/basisgegevens/page.tsx` - **FHIR Types:** `lib/fhir/index.ts` --- **Datum voltooid:** 2025-11-22 **Status:** ✅ Compleet **Dependencies voltooid:** E3.S2 (Patients lijst) **Blokkeert:** E4.S1 (Encounter tijdlijn)