feat: migrate clients module to patients + add docs

This commit is contained in:
colinislit
2025-11-23 10:13:00 +01:00
parent 8e3925ea09
commit 6fcb9a0e7b
277 changed files with 9131 additions and 458 deletions

View File

@@ -0,0 +1,170 @@
# E3.S1 - Organization Seed
**Epic:** E3 - Patients & Organizations
**Story:** E3.S1 - Organization seed
**Story Points:** 2
**Status:** 🔄 In Progress (scripts ready, database migration pending)
## Doel
Default organization voor development aanmaken.
## Acceptatiecriteria
- [x] Default organization in seed data
- [x] Organization heeft realistische GGZ data
- [x] Organization is bruikbaar voor development
- [ ] Migratie toegepast op database (wacht op Supabase maintenance)
## Implementatie
### 1. Migratie bestand aangemaakt
📁 **Locatie:** `supabase/migrations/20251122_seed_default_organization.sql`
De migratie maakt een default GGZ organization aan met:
- **ID:** `00000000-0000-0000-0000-000000000001`
- **AGB-code:** `AGB-DEMO-001`
- **KVK-nummer:** `12345678`
- **Naam:** Demo GGZ Instelling
- **Alias:** Demo GGZ, DGGZ
- **Contact:** 030-1234567, info@demo-ggz.nl
- **Adres:** Demonstratiestraat 1, 3511 AB Utrecht
### 2. Scripts aangemaakt
#### TypeScript script (aanbevolen)
📁 **Locatie:** `scripts/seed-organization.ts`
**Gebruik:**
```bash
npx tsx scripts/seed-organization.ts
```
Dit script:
- ✅ Gebruikt Supabase service role key (bypass RLS)
- ✅ Idempotent: kan veilig opnieuw uitgevoerd worden
- ✅ Verificatie na insert/update
- ✅ Duidelijke output en error handling
#### Bash script (alternatief)
📁 **Locatie:** `scripts/apply-organization-seed.sh`
**Gebruik:**
```bash
chmod +x scripts/apply-organization-seed.sh
./scripts/apply-organization-seed.sh
```
### 3. Handmatige toepassing
Als de scripts niet werken, kan de migratie handmatig toegepast worden via:
**Supabase Dashboard:**
1. Ga naar: https://supabase.com/dashboard/project/dqugbrpwtisgyxscpefg/sql
2. Kopieer de SQL uit `supabase/migrations/20251122_seed_default_organization.sql`
3. Plak in SQL Editor
4. Klik "Run"
**Supabase CLI:**
```bash
npx supabase db push
```
**psql (met database password):**
```bash
psql 'postgresql://postgres:[PASSWORD]@db.dqugbrpwtisgyxscpefg.supabase.co:5432/postgres' \
-f supabase/migrations/20251122_seed_default_organization.sql
```
## Verificatie
Na het toepassen van de migratie, controleer:
```sql
SELECT * FROM organizations WHERE identifier_agb = 'AGB-DEMO-001';
```
**Verwacht resultaat:**
```
id | 00000000-0000-0000-0000-000000000001
identifier_agb | AGB-DEMO-001
identifier_kvk | 12345678
name | Demo GGZ Instelling
alias | {Demo GGZ,DGGZ}
type_code | prov
type_display | Healthcare Provider
telecom_phone | 030-1234567
telecom_email | info@demo-ggz.nl
telecom_website | https://demo-ggz.nl
address_line | {Demonstratiestraat 1}
address_city | Utrecht
address_postal_code | 3511 AB
address_country | NL
active | t
```
## Afhankelijkheden
Deze organization wordt gebruikt door:
-**Practitioners** - organizational affiliation
-**Encounters** - organization_id foreign key (zie seed data in `20241121_seed_demo_data.sql`)
-**Care Plans** - care team organization
De bestaande seed data (`20241121_seed_demo_data.sql`) verwijst al naar deze organization via `identifier_agb = 'AGB-DEMO-001'`, maar de organization werd nog niet aangemaakt. Deze story lost dat op.
## Technische details
### Database schema
De `organizations` tabel bestaat al in de database (zie `docs/archive/migrations/20251122-current-db-scheme.sql` - archived snapshot):
```sql
CREATE TABLE public.organizations (
id uuid NOT NULL DEFAULT gen_random_uuid(),
identifier_agb text UNIQUE,
identifier_kvk text,
name text NOT NULL,
alias text[],
type_code text DEFAULT 'prov'::text,
type_display text DEFAULT 'Healthcare Provider'::text,
telecom_phone text,
telecom_email text,
telecom_website text,
address_line text[],
address_city text,
address_postal_code text,
address_country text DEFAULT 'NL'::text,
active boolean DEFAULT true,
created_at timestamp with time zone DEFAULT now(),
updated_at timestamp with time zone DEFAULT now(),
CONSTRAINT organizations_pkey PRIMARY KEY (id)
);
```
### Idempotentie
De migratie gebruikt `ON CONFLICT (id) DO UPDATE` om te zorgen dat:
1. Bij eerste run: organization wordt aangemaakt
2. Bij herhaalde runs: organization wordt geupdatet met nieuwe waardes
3. Geen dubbele entries ontstaan
## Volgende stappen
Na het voltooien van E3.S1:
- [ ] **E3.S2** - Patients lijst pagina (`/clients` met tabel, search, filters)
- [ ] **E3.S3** - Patient CRUD (Create/Update/Delete patient + validatie)
## Referenties
- **Bouwplan:** `docs/bouwplan-mini-epd.md` (regel 352-356)
- **Database schema:** `docs/archive/migrations/20251122-current-db-scheme.sql` (archived snapshot, regel 175-194)
- **Bestaande seed data:** `supabase/migrations/20241121_seed_demo_data.sql` (regel 13-19, 173-188)
---
**Datum aangemaakt:** 2025-11-22
**Laatste update:** 2025-11-22
**Status:** Scripts klaar, database migratie moet handmatig worden toegepast
**Blocker:** Supabase environment variables niet geconfigureerd (NEXT_PUBLIC_SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY)
**Actie vereist:** Voer migratie handmatig uit via Supabase Dashboard SQL Editor

View File

@@ -0,0 +1,238 @@
# E3.S2 - Patients lijst pagina
**Epic:** E3 - Patients & Organizations
**Story:** E3.S2 - Patients lijst pagina
**Story Points:** 5
**Status:** ✅ Compleet
## Doel
Patiënten lijst pagina met tabel, search, filters en paginatie.
## Acceptatiecriteria
- [x] `/patients` route met overzichtelijke tabel
- [x] Search functionaliteit op naam en BSN
- [x] Filters (status, gender)
- [x] Paginatie (50 per pagina)
- [x] Sorteer functionaliteit
## Implementatie
### 1. Server Actions (actions.ts)
📁 **Locatie:** `app/epd/patients/actions.ts`
**Updates:**
- ✅ Paginatie parameters toegevoegd (`page`, `pageSize`)
- ✅ Gender filter parameter
- ✅ Sorteer parameters (`sortBy`, `sortOrder`)
- ✅ Return type aangepast naar object met `patients`, `total`, `page`, `pageSize`
**API parameters:**
```typescript
{
search?: string; // Zoek op naam
status?: string; // Filter: planned, active, finished, cancelled
gender?: string; // Filter: male, female, other, unknown
page?: number; // Pagina nummer (default: 1)
pageSize?: number; // Items per pagina (default: 50)
sortBy?: string; // Sorteer kolom
sortOrder?: 'asc' | 'desc'; // Sorteer richting
}
```
### 2. Page Component (page.tsx)
📁 **Locatie:** `app/epd/patients/page.tsx`
**Updates:**
- ✅ SearchParams interface uitgebreid met nieuwe filters
- ✅ Page parameter parsing
- ✅ Props doorgegeven aan PatientList component
### 3. Patient List Component (patient-list.tsx)
📁 **Locatie:** `app/epd/patients/components/patient-list.tsx`
**Features geïmplementeerd:**
#### A. Filters
**Search bar:**
- Zoek op naam of BSN
- Real-time filtering
- Form submit handler
**Status filter:**
- Alle statussen
- Screening (planned)
- Actief (active)
- Afgerond (finished)
- Afgemeld (cancelled)
**Gender filter:**
- Alle geslachten
- Man (male)
- Vrouw (female)
- Anders (other)
- Onbekend (unknown)
#### B. Paginatie
**Implementatie:**
- 50 patiënten per pagina (configureerbaar)
- Pagina navigatie knoppen (vorige/volgende)
- Genummerde pagina buttons (max 7 zichtbaar)
- Smart pagination logica:
- Bij weinig pagina's: toon alle nummers
- Bij begin: toon eerste 7
- Bij einde: toon laatste 7
- In het midden: toon current ± 3
**UI elementen:**
- "Resultaten X-Y van Z" indicator
- Disabled state voor eerste/laatste pagina
- Active state voor huidige pagina
- Hover states voor interactie
#### C. Sortering
**Sorteerbare kolommen:**
- ✅ Naam (`name`)
- ✅ Geboortedatum (`birthDate`)
- ✅ Status (`status`)
- ✅ Laatst gewijzigd (`_lastUpdated`)
**Visual feedback:**
- Inactieve kolommen: `<ArrowUpDown>` (grijs)
- Actieve kolom ascending: `<ArrowUp>` (teal)
- Actieve kolom descending: `<ArrowDown>` (teal)
- Hover effect op sorteerbare headers
**Functionaliteit:**
- Click om te sorteren
- Click opnieuw om richting te wisselen (asc ↔ desc)
- Sortering reset naar pagina 1
#### D. Helper functions
```typescript
// URL parameters builder
buildUrlParams(updates: Record<string, string | number | undefined>)
// Event handlers
handleSearch(e: React.FormEvent)
handleStatusFilterChange(newStatus: string)
handleGenderFilterChange(newGender: string)
handlePageChange(newPage: number)
handleSort(column: string)
// Render helpers
renderSortIcon(column: string)
```
## UI/UX Details
### Layout
- Responsive design (desktop & mobile)
- Flex layout voor filters (wraps op kleine schermen)
- Sticky header mogelijk (voor lange lijsten)
### Colors & Styling
- Teal als primary color (#0d9488)
- Slate voor neutral elements
- Status badges met kleurcodering:
- Amber: Screening
- Emerald: Actief
- Slate: Afgerond
- Red: Afgemeld
### Empty States
- Verschillende berichten voor:
- Geen patiënten (met CTA om toe te voegen)
- Geen resultaten (met suggestie om filters aan te passen)
## Performance
**Optimalisaties:**
- Server-side paginatie (max 50 geladen per keer)
- URL-based state (deep linking, browser back/forward)
- Suspense voor loading states
- No-cache strategie voor realtime data
**Load times (verwacht):**
- Initial load: < 1s
- Filter/sort/page change: < 500ms
- Search debounce: instant (client-side filter)
## Database Queries
De actions maken gebruik van de FHIR API (`/api/fhir/Patient`) met de volgende query parameters:
```
GET /api/fhir/Patient?
name={search_term}&
status={status_filter}&
gender={gender_filter}&
_count={page_size}&
_offset={offset}&
_sort={sort_field}
```
**FHIR-compliant:**
- Gebruikt FHIR search parameters
- FHIR Bundle response format
- FHIR extension voor episode_status
## Testing Checklist
- [ ] Paginatie werkt correct (vorige/volgende/nummers)
- [ ] Search filtert correct op naam
- [ ] Status filter werkt voor alle statussen
- [ ] Gender filter werkt voor alle geslachten
- [ ] Sortering werkt voor alle kolommen
- [ ] URL parameters worden correct bijgewerkt
- [ ] Browser back/forward werkt met filters
- [ ] Empty state toont bij geen resultaten
- [ ] Responsive layout op mobile
- [ ] Loading states tonen bij data fetch
- [ ] Pagination reset bij filter wijziging
## Bekende Beperkingen
1. **FHIR API Afhankelijkheid:**
- Requires `/api/fhir/Patient` endpoint
- Episode status via extension (custom)
- Supabase moet online zijn voor data
2. **Client-side vs Server-side:**
- Search is momenteel client-side gefilterd
- Kan server-side voor betere performance bij grote datasets
3. **BSN Kolom:**
- Niet sorteerbaar (niet in sortBy options)
- Encryption zou performance impact hebben
## Volgende Stappen
Na E3.S2:
- [ ] **E3.S3** - Patient CRUD (Create/Update/Delete + validatie)
- [ ] Eventueel: Advanced filters (leeftijdsbereik, postcode, etc.)
- [ ] Eventueel: Export functionaliteit (CSV/Excel)
- [ ] Eventueel: Bulk actions (selecteer multiple → wijzig status)
## Referenties
- **Bouwplan:** `docs/bouwplan-mini-epd.md` (regel 352-356, E3.S2)
- **Actions:** `app/epd/patients/actions.ts`
- **Page:** `app/epd/patients/page.tsx`
- **Component:** `app/epd/patients/components/patient-list.tsx`
- **FHIR Types:** `lib/fhir/index.ts`
---
**Datum voltooid:** 2025-11-22
**Status:** ✅ Compleet
**Dependencies voltooid:** E2.S4 (Practitioner profile)
**Blokkeert:** E3.S3 (Patient CRUD)

View File

@@ -0,0 +1,379 @@
# 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<FHIRPatient>
// Read
export async function getPatient(id: string): Promise<FHIRPatient>
export async function getPatients(filters): Promise<{ patients, total, page, pageSize }>
// Update
export async function updatePatient(id: string, fhirPatient: FHIRPatient): Promise<FHIRPatient>
// 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)