diff --git a/docs/audit-rapport.md b/docs/audit-rapport.md deleted file mode 100644 index d87397d..0000000 --- a/docs/audit-rapport.md +++ /dev/null @@ -1,191 +0,0 @@ -# Technische Audit - Mini-EPD Prototype -## Rapport voor Product Owner - -**Datum:** December 2025 -**Doel:** Overzicht van technische staat en aandachtspunten - ---- - -## Context - -Dit is een **prototype/experiment** om te demonstreren hoe ver je komt met AI-tooling in zorgsoftware ontwikkeling. Het systeem bevat **geen echte cliëntgegevens** - alleen testdata voor demonstratiedoeleinden. - -**Doel van dit rapport:** Inzicht geven in de technische staat en wat nodig zou zijn als dit prototype ooit doorontwikkeld wordt naar productie. - ---- - -## Samenvatting - -| Gebied | Status | Relevantie voor Prototype | Bij Doorontwikkeling | -|--------|--------|---------------------------|----------------------| -| Beveiliging | Basis | Voldoende voor demo | Moet verbeterd worden | -| Prestaties | Matig | Acceptabel voor demo | Optimalisatie nodig | -| Onderhoud | Matig | Prima voor experiment | Refactoring wenselijk | -| Schaalbaarheid | Beperkt | Niet relevant nu | Kritiek bij groei | - -**Kernboodschap:** Voor een prototype dat AI-tooling demonstreert is de huidige staat prima. Dit rapport documenteert wat er nodig zou zijn voor eventuele doorontwikkeling. - ---- - -## 1. Beveiliging - -### Huidige staat (acceptabel voor prototype) - -**Punt 1: Next.js versie** -- De huidige versie heeft een bekende kwetsbaarheid -- **Voor prototype:** Geen risico (geen echte data) -- **Bij doorontwikkeling:** Update naar nieuwste versie nodig - -**Punt 2: Gegevensscheiding** -- Alle ingelogde gebruikers kunnen alle testdata zien -- **Voor prototype:** Bewuste keuze voor eenvoud -- **Bij doorontwikkeling:** Rollen en rechten per afdeling/organisatie - -**Punt 3: Geen geautomatiseerde tests** -- Typisch voor een prototype/experiment -- **Voor prototype:** Acceptabel -- **Bij doorontwikkeling:** Testsuite opzetten voor stabiliteit - -### Bij doorontwikkeling nodig - -| Actie | Inspanning | Wanneer | -|-------|------------|---------| -| Next.js updaten | 30 minuten | Voor productie | -| Toegangsrechten implementeren | 1-2 dagen | Voor productie | -| Testsuite opzetten | 2-3 dagen | Voor productie | - ---- - -## 2. Prestaties - -### Huidige staat (acceptabel voor prototype) - -**Punt 1: Laden van grote hoeveelheden data** -- Het systeem haalt alle gegevens in één keer op -- **Voor prototype:** Prima met testdata -- **Bij doorontwikkeling:** Pagination nodig bij veel records - -**Punt 2: Sequentieel laden** -- Pagina's laden gegevens één voor één (sequentieel) -- **Voor prototype:** Merkbaar maar acceptabel -- **Bij doorontwikkeling:** Parallel laden maakt 2-3x sneller - -**Punt 3: Laad-feedback** -- Geen loading indicators of foutmeldingen -- **Voor prototype:** Werkt voor demo's -- **Bij doorontwikkeling:** Professionelere UX wenselijk - -### Bij doorontwikkeling nodig - -| Actie | Inspanning | Effect | -|-------|------------|--------| -| Pagination toevoegen | 4 uur | Schaalbaarheid | -| Parallel laden | 2 uur | 2-3x sneller | -| Loading states | 4 uur | Betere UX | - ---- - -## 3. Schaalbaarheid - -### Huidige capaciteit (voldoende voor prototype) - -| Scenario | Gedrag | Status | -|----------|--------|--------| -| <50 cliënten | Soepel | Prototype | -| 50-200 cliënten | Acceptabel | Lichte optimalisatie | -| >200 cliënten | Aanpassingen nodig | Productie-ready maken | - -**Conclusie:** Voor een demo/experiment met testdata is de huidige capaciteit ruim voldoende. - -### Bij doorontwikkeling nodig - -Als het prototype ooit doorgroeit naar productie: -- Database indexen toevoegen -- Pagination in API's -- Caching strategie - ---- - -## 4. Onderhoudbaarheid - -### Huidige staat (typisch voor prototype) - -**Punt 1: Code duplicatie** -- Sommige logica staat op meerdere plekken -- **Voor prototype:** Normale trade-off voor snelheid van ontwikkeling -- **Bij doorontwikkeling:** Centraliseren voor onderhoudbaarheid - -**Punt 2: Codestructuur** -- Business logica zit verspreid -- **Voor prototype:** Acceptabel - snel itereren was prioriteit -- **Bij doorontwikkeling:** Service-laag introduceren - -**Punt 3: Foutafhandeling** -- Niet overal consistent -- **Voor prototype:** Werkt voor demo's -- **Bij doorontwikkeling:** Standaardiseren - -### Bij doorontwikkeling nodig - -| Actie | Inspanning | Effect | -|-------|------------|--------| -| Code centraliseren | 4 uur | Minder duplicatie | -| Foutafhandeling standaardiseren | 4 uur | Consistentie | -| Service-laag | 2-3 dagen | Betere structuur | - ---- - -## 5. Wat laat dit prototype zien? - -### Succesvol gedemonstreerd met AI-tooling - -Dit prototype toont aan wat mogelijk is met moderne AI-assisted development: - -| Functionaliteit | Status | Opmerking | -|-----------------|--------|-----------| -| Volledige EPD basis | Werkend | Patiëntdossiers, rapportages, intakes | -| AI-gestuurde samenvattingen | Werkend | Overdrachtsrapporten | -| Spraak-naar-tekst | Werkend | Deepgram integratie | -| Agenda & planning | Werkend | FullCalendar | -| Behandelplannen | Werkend | SMART-doelen, interventies | -| Nederlandse UI | Volledig | Alle teksten in het Nederlands | - -### Technische stack - -- **Frontend:** Next.js 14, React, Tailwind CSS -- **Backend:** Supabase (PostgreSQL + Auth) -- **AI:** Claude API, Deepgram -- **Ontwikkeld met:** AI-tooling (Claude Code) - ---- - -## 6. Roadmap bij doorontwikkeling - -Mocht dit prototype doorontwikkeld worden naar productie, dan is dit de aanbevolen volgorde: - -### Fase 1: Productie-ready maken -1. Next.js updaten (30 min) -2. Toegangsrechten implementeren (1-2 dagen) -3. Basis testsuite (2-3 dagen) - -### Fase 2: Schaalbaarheid -4. Pagination in API's (4 uur) -5. Loading states (4 uur) -6. Database optimalisatie (1 dag) - -### Fase 3: Professionalisering -7. Code refactoring (2-3 dagen) -8. Monitoring & logging (1 dag) -9. CI/CD pipeline (1 dag) - ---- - -## 7. Conclusie - -Dit prototype demonstreert succesvol hoe ver je kunt komen met AI-tooling in zorgsoftware ontwikkeling. De technische staat is **passend voor een experiment** - functioneel, demonstreerbaar, maar niet productie-ready. - -**Belangrijkste inzicht:** Met relatief beperkte investering kan dit prototype doorontwikkeld worden naar een productie-waardig systeem. De basis is solide. - ---- - -*Dit rapport is gegenereerd op basis van een technische analyse van de codebase (december 2025).* diff --git a/docs/migratie-clients-naar-patients.md b/docs/migratie-clients-naar-patients.md deleted file mode 100644 index 6313144..0000000 --- a/docs/migratie-clients-naar-patients.md +++ /dev/null @@ -1,655 +0,0 @@ -# 🚀 Migratieplan: `/clients/` → `/patients/` Route Consolidatie - -**Datum:** 2025-11-22 -**Versie:** 1.0 -**Status:** ✅ Voltooid -**Gekozen Strategie:** Custom API (Optie B) - ---- - -## 📋 Executive Summary - -**Doel:** Consolideer beide routes naar één FHIR-compliant `/patients/` route met Custom API voor Intakes. - -**Inschatting:** 10-12 story points (2-3 werkdagen) -**Risico Level:** Medium -**Breaking Changes:** Ja (mitigated door redirects) - ---- - -## 🔧 Fase 0 — Schema & Runbook - -**Status:** ✅ Repo gealigneerd (feb 2026). Alle Supabase-migraties leven nu in `supabase/migrations/` en kunnen in één keer worden toegepast met de standaard CLI. - -### Benodigde migraties -- `20241115000001_create_leads_table.sql` -- `20241115000002_create_epd_core_tables.sql` -- `20241115000004_create_demo_users.sql` -- `20241121_migrate_legacy_to_fhir.sql` -- `20241121_seed_demo_data.sql` -- `20251119094908_auth_hook_duplicate_email.sql` -- `20251122_screening_intake_schema.sql` -- `20251122_seed_default_organization.sql` - -### Runbook -1. Start Supabase lokaal (`supabase start`) of log in op de gewenste omgeving. -2. Draai alle migraties: `supabase db reset --use-migrations` (dev) of `supabase db push` (staging/production). Hiermee wordt het schema gelijkgetrokken met `supabase/migrations/`. -3. Seed basisdata: `pnpm ts-node scripts/seed-organization.ts` (voegt default organisatie + demo practitioners toe) en `pnpm ts-node scripts/apply-organization-seed.sh` indien nodig. -4. Controleer dat er geen drifts zijn: `supabase db diff` mag geen output produceren. -5. Smoke-test FHIR + Custom API: - - `curl -s http://localhost:3000/api/fhir/Patient?_count=1` - - `curl -s http://localhost:3000/api/intakes?patientId=` -6. Handmatige datafix (eenmalig): archiveer legacy `/clients/` records met `UPDATE clients SET archived=true` of verwijder de oude tabellen na validatie. Nieuwe dossiers worden uitsluitend via `patients` beheerd. -7. Documenteer de uitvoering in release-notes (datum, operator, eventuele afwijkingen). - -> Let op: RLS policies staan aan op alle nieuwe tabellen (`screenings`, `screening_activities`, `screening_documents`, `intakes`, enz.). Voer `supabase tests` of `supabase db lint` uit vlak na het toepassen om zeker te zijn dat policies geladen zijn. - ---- - -## 🎯 Probleem Statement - -Momenteel bestaan er twee parallelle implementaties voor patiënt/cliënt beheer: - -1. **`/app/epd/clients/`** - Oudere implementatie (17-19 nov) - - Directe Supabase queries - - Bevat werkende Intake module (Epic 4) - - Niet FHIR-compliant - -2. **`/app/epd/patients/`** - Nieuwere implementatie (21-22 nov) - - FHIR API compliant - - Betere features (BSN validatie, filters, paginatie) - - Intake nog placeholder - -**Impact:** -- Verwarring over canonical route -- Code duplicatie -- Inconsistente architectuur -- Moeilijke maintenance - ---- - -## 📊 Huidige Situatie Analyse - -### `/clients/` Route Inventory - -**✅ Volledig Geïmplementeerd:** - -| Component | LOC | Functionaliteit | -|-----------|-----|-----------------| -| `intakes/components/intake-list.tsx` | 50 | Lijst van alle intakes per cliënt | -| `intakes/components/intake-card.tsx` | 70 | Individuele intake kaart display | -| `intakes/components/new-intake-form.tsx` | 137 | Formulier nieuwe intake (Zod validatie) | -| `intakes/[intakeId]/components/intake-header.tsx` | 65 | Header met titel, status, datums | -| `intakes/[intakeId]/components/intake-tabs.tsx` | 52 | Tab navigatie binnen intake | -| `intakes/[intakeId]/page.tsx` | 52 | Intake detail algemene informatie | -| `intakes/[intakeId]/layout.tsx` | 29 | Layout wrapper voor intake detail | -| `intakes/actions.ts` | 83 | Server actions (CRUD operations) | - -**Total:** ~538 LOC werkende functionaliteit - -**Server Actions (Supabase-based):** -```typescript -export async function getIntakesByClientId(clientId: string) -export async function createIntake(input: CreateIntakeInput) -export async function getIntakeById(intakeId: string) -``` - -**⏳ Placeholders (geen migratie nodig):** -- Profile Tab ("Coming Soon Week 3") -- Plan Tab ("Coming Soon Week 3") -- Reports pagina -- Diagnose pagina - -**📦 Basis Functionaliteit (inferieur aan /patients/):** -- Client list (geen status/gender filters, geen paginatie) -- Client form (geen BSN validatie, geen John Doe) -- Dashboard (basic, geen unique features) - -### `/patients/` Route Inventory - -**✅ Al Geïmplementeerd (Superieur):** -- Patient list met advanced filtering -- Patient form met BSN 11-proef validatie -- John Doe support -- Delete functionaliteit (two-step confirmation) -- Modern layout (ClientHeader + ClientSidebar) -- Dashboard met quick actions - -**❌ Ontbreekt (te migreren van /clients/):** -- Intake module (volledig) - ---- - -## 🎯 Gekozen Architectuur: Custom API (Optie B) - -### Waarom Custom API? - -**✅ Voordelen:** -- Snellere implementatie (1-2 dagen vs 3-4 dagen) -- Eenvoudiger data model (direct mapping naar `intakes` tabel) -- Minder transformatie logica nodig -- Bestaande database schema hergebruiken -- Type safety met TypeScript - -**⚠️ Trade-offs:** -- Niet FHIR Encounter-compliant (maar acceptabel voor MVP) -- Toekomstige refactor naar FHIR mogelijk nodig -- Aparte API naast FHIR Patient/Practitioner - -**🔮 Toekomst Path:** -- Behoud Custom API voor Intakes in MVP -- Plan FHIR Encounter mapping in latere fase (Epic 5/6) -- Incrementele migratie mogelijk zonder breaking changes - -### API Specificatie - -**Endpoint:** `/api/intakes` - -**Routes:** -```typescript -GET /api/intakes?patientId={id} // List intakes for patient -POST /api/intakes // Create new intake -GET /api/intakes/{intakeId} // Get intake by ID -PUT /api/intakes/{intakeId} // Update intake -DELETE /api/intakes/{intakeId} // Delete intake -``` - -**Request/Response Types:** -```typescript -interface Intake { - id: string; - patient_id: string; - title: string; - department: 'Volwassenen' | 'Jeugd' | 'Ouderen'; - status: 'Open' | 'Afgerond'; - start_date: string; - end_date?: string; - notes?: string; - psychologist_id?: string; - created_at: string; - updated_at: string; -} - -interface CreateIntakeInput { - patient_id: string; - title: string; - department: 'Volwassenen' | 'Jeugd' | 'Ouderen'; - start_date: string; -} - -interface IntakeListResponse { - intakes: Intake[]; - total: number; -} -``` - ---- - -## 🗺️ Gedetailleerd Migratieplan - -### Fase 1: Intake API Ontwikkeling (3 SP) - -**Doel:** Bouw Custom API voor Intake operaties - -#### 1.1 API Route Setup (1 SP) - -**Bestanden aan te maken:** -``` -app/api/intakes/ -├── route.ts // GET (list), POST (create) -└── [intakeId]/ - └── route.ts // GET, PUT, DELETE -``` - -**Implementatie:** -- Supabase client met RLS -- Error handling -- Input validatie (Zod schemas) -- Response formatting - -**Acceptatie Criteria:** -- [x] GET `/api/intakes?patientId={id}` retourneert alle intakes -- [x] POST `/api/intakes` creëert nieuwe intake -- [x] GET `/api/intakes/{id}` retourneert specifieke intake -- [x] PUT `/api/intakes/{id}` update intake -- [x] DELETE `/api/intakes/{id}` verwijdert intake -- [x] Alle endpoints hebben error handling -- [x] Input validatie werkt - -#### 1.2 Type Definitions (0.5 SP) ✅ - -**Bestand:** `lib/types/intake.ts` - -**Inhoud:** -- ✅ Intake interface -- ✅ CreateIntakeInput, UpdateIntakeInput types -- ✅ IntakeListResponse type -- ✅ Zod validation schemas - -#### 1.3 Server Actions Refactor (1.5 SP) ✅ - -**Bestand:** `app/epd/patients/[id]/intakes/actions.ts` - -**Wijzigingen:** -```typescript -// VOOR (Supabase direct): -const { data } = await supabase.from('intakes').select('*') - -// NA (API call): -const response = await fetch(`/api/intakes?patientId=${patientId}`) -const data = await response.json() -``` - -**Updates:** -- ✅ `getIntakesByClientId` → `getIntakesByPatientId` -- ✅ `createIntake` - gebruik POST `/api/intakes` -- ✅ `getIntakeById` - gebruik GET `/api/intakes/{id}` -- ✅ Cookies worden doorgegeven aan fetch calls -- ✅ Error handling voor auth redirects - -**Complexiteit:** Medium -**Risico:** Laag - ---- - -### Fase 2: Component Migratie (3 SP) - -**Doel:** Verplaats alle Intake componenten naar `/patients/` - -#### 2.1 Directory Structuur (0.5 SP) ✅ - -**Creëer structuur:** -``` -app/epd/patients/[id]/intakes/ -├── components/ -│ ├── intake-card.tsx ✅ -│ ├── intake-list.tsx ✅ -│ └── new-intake-form.tsx ✅ -├── [intakeId]/ -│ ├── components/ -│ │ ├── intake-header.tsx ✅ -│ │ └── intake-tabs.tsx ✅ -│ ├── layout.tsx ✅ -│ └── page.tsx ✅ -├── new/ -│ └── page.tsx ✅ -└── actions.ts ✅ -``` - -#### 2.2 Kopieer en Pas Aan (2 SP) ✅ - -**Voor elk component:** -1. ✅ Kopieer van `/clients/[id]/intakes/` naar `/patients/[id]/intakes/` -2. ✅ Update imports: - - `clientId` → `patientId` (props en variabelen) - - `/epd/clients/` → `/epd/patients/` (routes) - - Type imports naar `@/lib/types/intake` -3. ✅ Update server action calls (gebruik nieuwe actions.ts) -4. ✅ Import paden gecorrigeerd - -**Specifieke wijzigingen:** - -**intake-list.tsx:** -- ✅ Props: `clientId` → `patientId` -- ✅ Link urls: `/clients/` → `/patients/` - -**new-intake-form.tsx:** -- ✅ Form field: `patient_id` ipv `client_id` -- ✅ Redirect: `/patients/` ipv `/clients/` - -**intake-header.tsx:** -- ✅ Breadcrumb: `/patients/` ipv `/clients/` -- ✅ Type imports gecorrigeerd - -**Complexiteit:** Laag (copy-paste + find/replace) -**Risico:** Laag - -#### 2.3 Verwijder Placeholder (0.5 SP) ✅ - -**Bestand:** `app/epd/patients/[id]/intake/page.tsx` - -**Actie:** ✅ Verwijder placeholder, vervang door redirect: -```typescript -export default async function IntakeRedirect({ params }) { - const { id } = await params; - redirect(`/epd/patients/${id}/intakes`); -} -``` - ---- - -### Fase 3: Navigatie Integratie (2 SP) - -**Doel:** Integreer Intake tab in patient navigatie - -#### 3.1 Update ClientSidebar (1 SP) ✅ - -**Bestand:** `app/epd/patients/[id]/components/client-sidebar.tsx` - -**Wijzigingen:** -- ✅ Maak "Intake" tab interactief (verwijder placeholder styling) -- ✅ Link naar `/patients/{id}/intakes` -- ✅ Active state logic voor subroutes -- Optioneel: Badge met aantal openstaande intakes (nog niet geïmplementeerd) - -#### 3.2 Update Dashboard (1 SP) ✅ - -**Bestand:** `app/epd/patients/[id]/page.tsx` - -**Wijzigingen:** -- ✅ Update "Volgende stappen" sectie -- ✅ Verwijs naar intake functionaliteit -- ✅ Intake quick action card linkt naar nieuwe route -- ✅ Optioneel: Toon recent intake in dashboard (geïmplementeerd) - -**Complexiteit:** Laag -**Risico:** Laag - ---- - -### Fase 4: Route Consolidatie (2 SP) - -**Doel:** Deprecate `/clients/` en setup redirects - -#### 4.1 Catch-all Redirect (1 SP) ✅ - -**Bestand:** `app/epd/clients/[...path]/route.ts` (nieuw) - -**Implementatie:** -```typescript -import { redirect } from 'next/navigation'; -import { NextRequest } from 'next/server'; - -export async function GET( - request: NextRequest, - { params }: { params: Promise<{ path: string[] }> } -) { - const { path } = await params; - const searchParams = request.nextUrl.searchParams; - const newPath = `/epd/patients/${path.join('/')}`; - const newUrl = new URL(newPath, request.url); - searchParams.forEach((value, key) => { - newUrl.searchParams.set(key, value); - }); - redirect(newUrl.toString()); -} -``` - -**Test scenarios:** -- ✅ `/epd/clients` → `/epd/patients` (via `page.tsx` redirect) -- ✅ `/epd/clients/{id}` → `/epd/patients/{id}` -- ✅ `/epd/clients/{id}/intakes` → `/epd/patients/{id}/intakes` -- ✅ `/epd/clients/{id}/intakes/{intakeId}` → `/epd/patients/{id}/intakes/{intakeId}` -- ✅ Query parameters worden behouden - -#### 4.2 Archive Old Code (0.5 SP) ✅ - -**Acties:** -- ✅ `mkdir -p app/epd/_archive` -- ✅ `mv app/epd/clients app/epd/_archive/clients_backup_20251122` -- ✅ Redirect routes teruggeplaatst in `app/epd/clients/` -- ✅ Oude code volledig gearchiveerd - -#### 4.3 Update Documentatie (0.5 SP) ✅ - -**Bestanden bij te werken:** -- ✅ `docs/specs/UI/bouwplan-mini-epd-v1.0.md` - Update `/clients/` naar `/patients/` -- ✅ `CHANGELOG.md` - Entry toegevoegd met breaking changes -- ✅ `docs/migratie-clients-naar-patients.md` - Dit document bijgewerkt -- `docs/bouwplan-mini-epd.md` - Nog te updaten indien aanwezig -- `README.md` - Nog te updaten indien screenshots/links aanwezig - -**Changelog:** -- ✅ Entry toegevoegd aan `CHANGELOG.md` met breaking changes en mitigatie - -**Complexiteit:** Laag -**Risico:** Laag - ---- - -### Fase 5: Testing & Validatie (2 SP) - -**Doel:** Verifieer feature parity en stabiliteit - -#### 5.1 Functionele Tests (1 SP) - -**Test Checklist:** - -**Intake List:** -- [ ] Navigeer naar `/patients/{id}/intakes` -- [ ] Lijst toont alle intakes voor patient -- [ ] Empty state toont bij geen intakes -- [ ] "Nieuwe intake" button werkt - -**Nieuwe Intake:** -- [ ] Formulier opent via "Nieuwe intake" button -- [ ] Alle velden valideren correct -- [ ] Submit creëert intake in database -- [ ] Redirect naar intake lijst na succes -- [ ] Intake verschijnt in lijst - -**Intake Detail:** -- [ ] Klik op intake card opent detail -- [ ] Header toont correcte titel, status, datums -- [ ] Tabs tonen (ook al zijn ze placeholder) -- [ ] Algemene informatie tab toont data -- [ ] Notities sectie werkt - -**Navigatie:** -- [ ] Sidebar "Intake" tab is actief -- [ ] Breadcrumbs kloppen -- [ ] Terug naar patiënten werkt - -**Redirects:** -- [ ] `/clients/{id}` → `/patients/{id}` werkt -- [ ] `/clients/{id}/intakes` → `/patients/{id}/intakes` werkt -- [ ] Query parameters behouden blijven - -#### 5.2 API Tests (0.5 SP) - -**Test alle endpoints:** -```bash -# List intakes -curl http://localhost:3000/api/intakes?patientId={uuid} - -# Create intake -curl -X POST http://localhost:3000/api/intakes \ - -H "Content-Type: application/json" \ - -d '{"patient_id":"uuid","title":"Test","department":"Volwassenen","start_date":"2025-11-22"}' - -# Get intake -curl http://localhost:3000/api/intakes/{intakeId} - -# Update intake -curl -X PUT http://localhost:3000/api/intakes/{intakeId} \ - -d '{"status":"Afgerond"}' - -# Delete intake -curl -X DELETE http://localhost:3000/api/intakes/{intakeId} -``` - -**Verifieer:** -- [ ] Response status codes correct (200, 201, 404, etc.) -- [ ] Response bodies bevatten verwachte data -- [ ] Errors worden netjes afgehandeld -- [ ] RLS policies werken (unauthorized access blocked) - -#### 5.3 Regressie Tests (0.5 SP) - -**Verifieer bestaande functionaliteit:** -- [ ] Patient list werkt nog -- [ ] Patient create/update/delete werkt nog -- [ ] Screening tab (placeholder) werkt nog -- [ ] Andere tabs onveranderd - -**Complexiteit:** Medium -**Risico:** Medium - ---- - -## 📈 Story Point Breakdown - -| Fase | Taak | SP | Complexiteit | Risico | -|------|------|------|--------------|--------| -| 1.1 | API Route Setup | 1 | Medium | Laag | -| 1.2 | Type Definitions | 0.5 | Laag | Laag | -| 1.3 | Server Actions Refactor | 1.5 | Medium | Laag | -| 2.1 | Directory Structuur | 0.5 | Laag | Laag | -| 2.2 | Kopieer en Pas Aan | 2 | Laag | Laag | -| 2.3 | Verwijder Placeholder | 0.5 | Laag | Laag | -| 3.1 | Update ClientSidebar | 1 | Laag | Laag | -| 3.2 | Update Dashboard | 1 | Laag | Laag | -| 4.1 | Catch-all Redirect | 1 | Medium | Laag | -| 4.2 | Archive Old Code | 0.5 | Laag | Laag | -| 4.3 | Update Documentatie | 0.5 | Laag | Laag | -| 5.1 | Functionele Tests | 1 | Medium | Medium | -| 5.2 | API Tests | 0.5 | Medium | Medium | -| 5.3 | Regressie Tests | 0.5 | Laag | Laag | -| **TOTAAL** | | **12 SP** | **Laag-Medium** | **Laag-Medium** | - -**Geschatte Tijdsduur:** 2-3 werkdagen (16-24 uur development tijd) - ---- - -## ⚠️ Risico's en Mitigatie - -| Risico | Impact | Waarschijnlijkheid | Mitigatie | -|--------|--------|-------------------|-----------| -| Breaking changes in productie | Hoog | Laag | Redirects + grondige testing | -| Data inconsistentie (intakes) | Hoog | Laag | Gebruik dezelfde database tabel | -| Gemiste edge cases | Medium | Medium | Uitgebreide test checklist | -| Performance issues (API overhead) | Laag | Laag | Cache strategie, index optimization | -| Type errors na migratie | Medium | Medium | TypeScript strict mode, thorough testing | - ---- - -## 🎯 Acceptatie Criteria (Definition of Done) - -**Functioneel:** -- [x] Alle Intake functionaliteit werkt in `/patients/` route -- [x] Feature parity met originele `/clients/` implementatie -- [x] Redirects werken voor alle `/clients/` URLs -- [x] Geen broken links in applicatie - -**Technisch:** -- [x] API endpoints geïmplementeerd en werkend -- [x] Type definitions compleet (`lib/types/intake.ts`) -- [x] Error handling geïmplementeerd (inclusief auth redirects) -- [x] Cookies worden correct doorgegeven aan API calls -- [ ] RLS policies getest (nog te valideren) - -**Testing:** -- [x] Basis functionele tests uitgevoerd (componenten werken) -- [x] API tests uitgevoerd (endpoints werken) -- [x] Import paden gecorrigeerd -- [x] Geen TypeScript errors -- [ ] Volledige regressie tests (nog te doen) - -**Documentatie:** -- [x] Bouwplannen bijgewerkt (`bouwplan-mini-epd-v1.0.md`) -- [x] CHANGELOG.md entry toegevoegd -- [x] API documentatie beschikbaar (`docs/api/intakes-api.md`) -- [x] Migration guide voor developers (dit document) - -**Cleanup:** -- [x] Oude `/clients/` code gearchiveerd -- [x] Geen duplicate code -- [x] Import paden gecorrigeerd -- [ ] Console warnings/errors check (nog te doen) - ---- - -## 🚀 Implementatie Volgorde (Aanbevolen) - -### Dag 1: API Foundation -1. **Ochtend:** Fase 1.1 + 1.2 (API routes + types) -2. **Middag:** Fase 1.3 (Server actions refactor) -3. **Eind dag:** Fase 5.2 (API tests) - -**Deliverable:** Werkende Intake API - -### Dag 2: Component Migratie -1. **Ochtend:** Fase 2.1 + 2.2 (Kopieer componenten) -2. **Middag:** Fase 2.3 + 3.1 + 3.2 (Navigatie integratie) -3. **Eind dag:** Fase 5.1 (Functionele tests) - -**Deliverable:** Werkende Intake module in `/patients/` - -### Dag 3: Consolidatie & Cleanup -1. **Ochtend:** Fase 4.1 + 4.2 (Redirects + archive) -2. **Middag:** Fase 4.3 (Documentatie) -3. **Eind dag:** Fase 5.3 (Regressie tests) - -**Deliverable:** Volledige migratie afgerond - ---- - -## 📦 Deliverables Checklist - -**Code:** -- [x] `/app/api/intakes/` - API routes (GET, POST) -- [x] `/app/api/intakes/[intakeId]/` - API routes (GET, PUT, DELETE) -- [x] `/app/epd/patients/[id]/intakes/` - Volledige module -- [x] `/app/epd/clients/[...path]/route.ts` - Redirect (catch-all) -- [x] `/app/epd/clients/page.tsx` - Root redirect -- [x] `/lib/types/intake.ts` - Type definitions - -**Documentatie:** -- [x] `docs/migratie-clients-naar-patients.md` - Dit document (bijgewerkt) -- [x] `docs/api/intakes-api.md` - API documentatie -- [x] Updated bouwplannen (`bouwplan-mini-epd-v1.0.md`) -- [x] CHANGELOG.md entry - -**Tests:** -- [ ] Test rapport met resultaten -- [ ] Screenshot van werkende features -- [ ] Performance metrics (optioneel) - -**Archief:** -- [ ] `/app/epd/_archive/clients_backup_20251122/` - Oude code - ---- - -## 🔮 Toekomstige Verbeteringen (Out of Scope) - -**FHIR Compliance:** -- Migreer Custom Intake API naar FHIR Encounter -- Mapping van `intakes` tabel naar FHIR resources -- Implementeer FHIR search parameters - -**Features:** -- Intake status workflow (bezig → afgerond met validaties) -- Intake templates per afdeling -- Notities met rich text editor -- Document attachments per intake -- Intake duplicatie/klonen -- Bulk operations (meerdere intakes tegelijk) - -**Performance:** -- API response caching -- Optimistic UI updates -- Lazy loading van intake details -- Pagination voor intake lists (bij >50 intakes) - -**Analytics:** -- Intake completion metrics -- Average intake duration per afdeling -- Psychologist workload dashboard - ---- - -## 📞 Ondersteuning en Vragen - -**Contact:** -- Developer: Colin Lit -- Email: colin@ikbenlit.nl - -**Resources:** -- Bouwplan Screening & Intake: `docs/specs/screening-intake/bouwplan-screening-intake-v1.0.md` -- FHIR Bouwplan: `docs/bouwplan-pragmatisch-fhir.md` -- Database Schema: `supabase/migrations/20251122_screening_intake_schema.sql` - ---- - -**Versie Historie:** -- v1.0 (2025-11-22): Initial migration plan met Custom API strategie diff --git a/docs/performance/baseline-2025-12-12.md b/docs/performance/baseline-2025-12-12.md deleted file mode 100644 index 6c4db24..0000000 --- a/docs/performance/baseline-2025-12-12.md +++ /dev/null @@ -1,243 +0,0 @@ -=== BASELINE PERFORMANCE METINGEN === -Datum: Fri Dec 12 14:14:19 CET 2025 - -## Server Response Times (TTFB) - -### API Endpoints -``` -GET /api/reports (patient d16935c9...): - TTFB: 1.759250s, Total: 1.759686s, Size: 24 bytes -GET /api/overdracht/patients: - TTFB: 0.535994s, Total: 0.536342s, Size: 45 bytes -``` - -## Bundle Analysis - -### Grootste chunks in build: -``` --rw-r--r-- 1 colin colin 5.9M Dec 12 13:34 .next/static/chunks/main-app.js --rw-r--r-- 1 colin colin 2.7M Dec 12 13:34 .next/static/chunks/app/(marketing)/page.js --rw-r--r-- 1 colin colin 2.3M Dec 12 13:35 .next/static/chunks/app/epd/layout.js --rw-r--r-- 1 colin colin 919K Dec 12 13:34 .next/static/chunks/app/layout.js --rw-r--r-- 1 colin colin 649K Dec 12 13:34 .next/static/chunks/app/(marketing)/layout.js --rw-r--r-- 1 colin colin 502K Dec 12 13:35 .next/static/chunks/app/epd/patients/page.js --rw-r--r-- 1 colin colin 220K Dec 12 13:35 .next/static/chunks/app/epd/patients/[id]/page.js --rw-r--r-- 1 colin colin 220K Dec 12 13:34 .next/static/chunks/app/not-found.js --rw-r--r-- 1 colin colin 143K Dec 12 13:34 .next/static/chunks/app-pages-internals.js --rw-r--r-- 1 colin colin 132K Dec 12 13:35 .next/static/chunks/app/epd/patients/[id]/layout.js -``` - -### Grootste dependencies: -``` -24K node_modules/@fullcalendar -16K node_modules/@tiptap -0 node_modules/three -``` - -## Samenvatting Baseline - -### Kritieke Bevindingen - -| Metric | Waarde | Beoordeling | -|--------|--------|-------------| -| main-app.js | 5.9 MB | ⚠️ Groot (dev mode) | -| epd/layout.js | 2.3 MB | ⚠️ Groot | -| patient/[id]/page.js | 220 KB | ✅ Acceptabel | -| Reports API TTFB | 1.76s | ⚠️ Traag | -| Overdracht API TTFB | 0.54s | ✅ Acceptabel | - -### Opmerkingen - -1. **Bundle sizes zijn development mode** - productie build zal kleiner zijn door minification -2. **API TTFB van 1.76s** suggereert database query optimalisatie nodig -3. **5.9MB main-app.js** bevat alle dependencies - tree shaking en code splitting kan dit verbeteren - -### Aanbevolen optimalisaties - -1. ✅ Parallel data fetching (Promise.all) -2. ✅ Lazy loading van zware componenten (FullCalendar) -3. ✅ Loading states voor betere perceived performance -4. ⚡ Database query optimalisatie (indexes) - ---- - -*Gemeten op: development server (localhost:3000)* -*Let op: Lighthouse kon niet draaien in WSL - Chrome headless issues* - ---- - -## Iteratie 1: Parallel Fetching - -**Datum:** 12 dec 2025 - -### Wijziging - -**Bestand:** `app/epd/patients/[id]/page.tsx` - -**Voor:** 4 sequentiële fetches (elke fetch wacht op vorige) -```typescript -const intakes = await getIntakesByPatientId(id); -const encounters = await getPatientEncounters(id); -const carePlan = await getActiveCarePlan(id); -const intakes = await getPatientIntakes(id); // DUBBEL! -``` - -**Na:** 3 parallelle fetches met Promise.all -```typescript -const [intakes, encounters, carePlan] = await Promise.all([ - getIntakesByPatientId(id).catch(() => []), - getPatientEncounters(id).catch(() => []), - getActiveCarePlan(id).catch(() => null), -]); -``` - -### Verbeteringen - -1. ✅ Verwijderd: dubbele `getPatientIntakes()` call -2. ✅ Parallel fetching met Promise.all -3. ✅ Graceful error handling met .catch() - -### Verwachte impact - -- **Theoretisch:** Van ~800ms sequentieel naar ~250ms parallel (3x sneller) -- **Praktijk:** Test in browser nodig (curl geeft login redirect) - -### Gemeten resultaat (browser test) - -| Pagina | Voor (geschat) | Na | Verbetering | -|--------|----------------|-----|-------------| -| `/epd/patients/[id]` | ~800ms | **440ms** | ~45% sneller | - -### Bevinding: Reports API bottleneck - -De `/api/reports` endpoint is de echte bottleneck: -- **1671ms** response time -- Beïnvloedt `/epd/patients/[id]/rapportage` (2051ms totaal) -- Dit is een database query probleem, niet parallel fetching - -### Status - -✅ Iteratie 1 voltooid - parallel fetching werkt - ---- - -## Iteratie 2: Loading States - -**Datum:** 12 dec 2025 - -### Toegevoegde bestanden - -| Bestand | Doel | -|---------|------| -| `app/epd/loading.tsx` | Algemene EPD loading spinner | -| `app/epd/patients/[id]/loading.tsx` | Patient dashboard skeleton | -| `app/epd/agenda/loading.tsx` | Agenda skeleton | -| `app/epd/patients/[id]/rapportage/loading.tsx` | Rapportage timeline skeleton | - -### Wat dit doet - -Next.js toont automatisch deze loading states terwijl Server Components laden: -- Gebruiker ziet direct visuele feedback (skeleton/spinner) -- Geen "blank screen" meer tijdens laden -- Perceived performance verbetert significant - -### Status - -✅ Iteratie 2 voltooid - loading states toegevoegd - ---- - -## Iteratie 3: Lazy Load FullCalendar - -**Datum:** 12 dec 2025 - -### Wijziging - -**Bestand:** `app/epd/agenda/components/agenda-view.tsx` - -**Voor:** -```typescript -import { AgendaCalendar } from './agenda-calendar'; -``` - -**Na:** -```typescript -import dynamic from 'next/dynamic'; - -const AgendaCalendar = dynamic( - () => import('./agenda-calendar').then((mod) => mod.AgendaCalendar), - { - ssr: false, - loading: () => , - } -); -``` - -### Wat dit doet - -- FullCalendar en alle plugins worden nu **apart gebundeld** -- Component laadt alleen wanneer agenda pagina bezocht wordt -- Gebruiker ziet spinner tijdens laden van agenda -- **Geschatte besparing:** ~150-200KB op initiële bundle - -### Status - -✅ Iteratie 3 voltooid - FullCalendar lazy loaded - ---- - -## Samenvatting Optimalisaties - -| Iteratie | Wijziging | Effect | -|----------|-----------|--------| -| 1 | Parallel Fetching | Patient dashboard: ~800ms → 440ms | -| 2 | Loading States | Betere perceived performance | -| 3 | Lazy Load FullCalendar | ~150KB minder initiële bundle | - -### Resterende bottleneck - -`/api/reports` endpoint: **1671ms** - geoptimaliseerd in Iteratie 4 - ---- - -## Iteratie 4: /api/reports Optimalisatie - -**Datum:** 12 dec 2025 - -### Stap 4.1: Selectieve Kolommen - -**Bestand:** `app/api/reports/route.ts` - -Gewijzigd van `SELECT *` naar selectieve kolommen: -- id, patient_id, type, content, created_at, updated_at, shift_date, include_in_handover, structured_data, created_by - -**Verwacht effect:** -40% databandwidth - ---- - -### Stap 4.2: Server-Side Pagination - -**Bestanden:** -- `app/api/reports/route.ts` - limit/offset parameters toegevoegd -- `app/epd/patients/[id]/rapportage/actions.ts` - `getReportsPaginated()` functie - -Response bevat nu: `{ reports, total, limit, offset, hasMore }` - -**Verwacht effect:** -70% TTFB (alleen eerste 50 reports laden) - ---- - -### Stap 4.3: Database Index - -**Migratie:** `supabase/migrations/20251212_add_reports_timeline_index.sql` - -```sql -CREATE INDEX idx_reports_timeline ON reports(patient_id, created_at DESC) WHERE deleted_at IS NULL; -CREATE INDEX idx_reports_patient_type ON reports(patient_id, type) WHERE deleted_at IS NULL; -``` - -**Let op:** Voer deze migration handmatig uit in Supabase Dashboard of via CLI. - -### Status - -✅ Iteratie 4 volledig voltooid (incl. database indexes) diff --git a/docs/purring-juggling-giraffe.md b/docs/purring-juggling-giraffe.md deleted file mode 100644 index 5b97209..0000000 --- a/docs/purring-juggling-giraffe.md +++ /dev/null @@ -1,464 +0,0 @@ -# Plan: Menu Performance Optimalisatie (<250ms response) - -## Probleem - -Trage menu-reacties bij: -1. **EPD Sidebar** (hoofdnavigatie links) -2. **Patient tabs** (binnen patiënt dossier) - -Huidige performance: ~260ms, doel: <250ms - -## Diagnose - -### EPD Sidebar bottlenecks -- Re-renders bij elke `usePathname()` change -- `.map()` creëert nieuwe array (50+ items) per render -- Geen `React.memo` bescherming -- Regex match bij elke render - -### Patient Tabs bottlenecks -- Re-renders bij elke sub-route navigatie -- Geen memoization van active state - -### Context cascade -- PatientContext reset bij unmount → flashing -- Header herberekent derived state (40+ regels) per render - -## Aanpak - -**Fase 3 (Server Components) zou NIET helpen** - dit zijn client-side React performance issues. - -### Quick wins (hoogste impact) - -1. **EPD Sidebar optimalisatie** (~80ms besparing) -2. **Patient Tabs memoization** (~40ms besparing) -3. **Header derived state memoization** (~30ms besparing) -4. **Context reset pattern verbeteren** (~20ms besparing) - -Totaal: ~170ms besparing → target <90ms - ---- - -## Implementatie - -> **Status Update (26-11-2025):** Alle 4 stappen zijn geïmplementeerd en klaar voor testing. - -### Stap 1: EPD Sidebar optimalisatie ✅ VOLTOOID - -**Bestand:** `app/epd/components/epd-sidebar.tsx` - -**Status:** Geïmplementeerd - alle optimalisaties toegepast - -**Probleem:** -```tsx -const navigationItems = isPatientContext - ? level2NavigationItems.map(item => ({ - ...item, - href: `/epd/patients/${patientId}${item.href}` - })) - : level1NavigationItems; -``` - -**Oplossing:** - -1. Memoize navigation items: -```tsx -const navigationItems = useMemo(() => { - if (isPatientContext) { - return level2NavigationItems.map(item => ({ - ...item, - href: `/epd/patients/${patientId}${item.href}` - })); - } - return level1NavigationItems; -}, [isPatientContext, patientId]); -``` - -2. Maak SidebarItem component met React.memo: -```tsx -const SidebarItem = memo(({ item, isActive, isCollapsed }: Props) => { - return ( - - - {!isCollapsed && {item.label}} - - ); -}, (prev, next) => { - // Shallow compare - return prev.item.href === next.item.href && - prev.isActive === next.isActive && - prev.isCollapsed === next.isCollapsed; -}); -``` - -3. Memoize isActive check: -```tsx -const getIsActive = useCallback((href: string) => { - return pathname === href || pathname?.startsWith(`${href}/`); -}, [pathname]); -``` - -4. Stabilize event handlers: -```tsx -const handleToggle = useCallback(() => { - setIsCollapsed(prev => !prev); -}, []); - -const handleMobileToggle = useCallback(() => { - setIsOpen(prev => !prev); -}, []); -``` - -**Geïmplementeerde wijzigingen:** -- ✅ Toegevoegd: `useMemo`, `useCallback`, `memo` imports -- ✅ Nieuwe `SidebarItem` component met React.memo en custom comparison -- ✅ navigationItems gememoized met useMemo -- ✅ Event handlers gestabiliseerd met useCallback (toggleSidebar, toggleCollapse, handleItemClick) -- ✅ getIsActive functie gememoized -- ✅ Rendering vervangen door SidebarItem component - ---- - -### Stap 2: Patient Tabs optimalisatie ✅ VOLTOOID - -**Bestand:** `app/epd/patients/[id]/intakes/[intakeId]/components/intake-tabs.tsx` - -**Status:** Geïmplementeerd - alle optimalisaties toegepast - -**Probleem:** -- Re-renders bij elke pathname change -- Geen memoization - -**Oplossing:** - -1. Memoize tab items: -```tsx -const tabs = useMemo(() => [ - { href: `/epd/patients/${patientId}/intakes/${intakeId}/anamnese`, label: 'Anamnese' }, - { href: `/epd/patients/${patientId}/intakes/${intakeId}/diagnosis`, label: 'Diagnose' }, - // ... rest -], [patientId, intakeId]); -``` - -2. Maak TabItem component met memo: -```tsx -const TabItem = memo(({ tab, isActive }: { tab: Tab; isActive: boolean }) => { - return ( - - {tab.label} - - ); -}); -``` - -3. Memoize active check: -```tsx -const isTabActive = useCallback((href: string) => { - return pathname === href; -}, [pathname]); -``` - -**Geïmplementeerde wijzigingen:** -- ✅ Toegevoegd: `useMemo`, `useCallback`, `memo` imports -- ✅ Nieuwe `TabItem` component met React.memo -- ✅ tabs array gememoized met useMemo -- ✅ baseUrl gememoized -- ✅ getIsActive functie gememoized met useCallback -- ✅ Rendering vervangen door TabItem component - ---- - -### Stap 3: EPD Header memoization ✅ VOLTOOID - -**Bestand:** `app/epd/components/epd-header.tsx` - -**Status:** Geïmplementeerd - component volledig geoptimaliseerd - -**Probleem:** -- 40+ regels berekeningen per render -- Geen useMemo - -**Oplossing:** - -1. Memoize patient display data: -```tsx -const patientDisplay = useMemo(() => { - if (!patient) return null; - - const name = patient.name?.[0]; - const displayName = name - ? [...(name.prefix || []), ...(name.given || []), name.family] - .filter(Boolean) - .join(' ') - : 'Onbekende patiënt'; - - const birthDate = patient.birthDate - ? new Date(patient.birthDate).toLocaleDateString('nl-NL') - : null; - - const bsn = patient.identifier?.find(id => - id.system === 'http://fhir.nl/fhir/NamingSystem/bsn' - )?.value; - - const statusExtension = patient.extension?.find(ext => - ext.url === 'http://hl7.org/fhir/StructureDefinition/patient-status' - ); - - const isJohnDoe = patient.extension?.some(ext => - ext.url === 'http://hl7.org/fhir/StructureDefinition/data-absent-reason' && - ext.valueCode === 'temp-unknown' - ); - - return { displayName, birthDate, bsn, statusExtension, isJohnDoe }; -}, [patient]); -``` - -2. Stabilize event handlers: -```tsx -const handleNewReportClick = useCallback(() => { - if (!patient?.id) return; - - const rapportagePath = `/epd/patients/${patient.id}/rapportage`; - const onRapportagePage = pathname?.startsWith(rapportagePath); - - if (onRapportagePage) { - const element = document.getElementById('rapportage-composer'); - element?.scrollIntoView({ behavior: 'smooth' }); - } else { - router.push(`${rapportagePath}#rapportage-composer`); - } -}, [patient?.id, pathname, router]); -``` - -3. Wrap component in memo: -```tsx -export const EPDHeader = memo(function EPDHeader() { - // ... component body -}); -``` - -**Geïmplementeerde wijzigingen:** -- ✅ Toegevoegd: `useMemo`, `useCallback`, `memo` imports -- ✅ patientDisplay object gememoized met useMemo (alle 40+ regels berekeningen) -- ✅ handleNewReportClick gestabiliseerd met useCallback -- ✅ Component gewrapped in memo() export -- ✅ Destructuring van gememoized values voor cleaner JSX - ---- - -### Stap 4: PatientContext reset pattern ✅ VOLTOOID - -**Bestand:** `app/epd/components/patient-context.tsx` - -**Status:** Quick fix geïmplementeerd - geen flashing meer - -**Probleem:** -```tsx -useEffect(() => { - setPatient(patient); - return () => setPatient(null); // ← Veroorzaakt flashing -}, [patient, setPatient]); -``` - -**Oplossing 1 (Quick fix):** - -Verwijder cleanup als patient ID niet verandert: -```tsx -export function useSetPatient(patient: FHIRPatient | null) { - const { patient: currentPatient, setPatient } = usePatientContext(); - - useEffect(() => { - // Only update if patient ID changed - if (patient?.id !== currentPatient?.id) { - setPatient(patient); - } - }, [patient?.id, currentPatient?.id, setPatient]); - - // No cleanup - keep patient in context during navigation -} -``` - -**Oplossing 2 (Beter, maar meer werk):** - -Gebruik URL-based patient ID als single source of truth: -```tsx -export function PatientProvider({ children }: { children: ReactNode }) { - const pathname = usePathname(); - const [patients, setPatients] = useState>(new Map()); - - // Extract patient ID from URL - const patientId = useMemo(() => { - const match = pathname?.match(/\/epd\/patients\/([^\/]+)/); - return match?.[1] || null; - }, [pathname]); - - const currentPatient = patientId ? patients.get(patientId) : null; - - const setPatient = useCallback((patient: FHIRPatient | null) => { - if (patient?.id) { - setPatients(prev => new Map(prev).set(patient.id, patient)); - } - }, []); - - return ( - - {children} - - ); -} -``` - -**Aanbeveling:** Start met Oplossing 1 (quick fix), migreer later naar Oplossing 2. - -**Geïmplementeerde oplossing:** Oplossing 1 (Quick fix) -- ✅ Toegevoegd: ID comparison check voordat update -- ✅ Verwijderd: cleanup functie die flashing veroorzaakte -- ✅ Context blijft nu persistent tijdens navigatie - ---- - -## Testing - -> **Status:** Klaar voor performance testing - -### Performance meting - -1. Chrome DevTools Performance profiler: -```bash -# Voor optimalisatie -- Sidebar click: ~260ms -- Tab switch: ~200ms - -# Na optimalisatie -- Sidebar click: <90ms (target: <250ms) ✓ -- Tab switch: <80ms (target: <250ms) ✓ -``` - -2. React DevTools Profiler: -- Meet aantal re-renders per navigatie -- Check "why did this render?" - -### Functionele tests - -1. **EPD Sidebar:** - - [ ] Navigatie tussen Dashboard, Cliënten werkt - - [ ] Context switch Level 1 → Level 2 werkt - - [ ] Mobile hamburger menu werkt - - [ ] Collapsed state persistent - -2. **Patient Tabs:** - - [ ] Alle tabs bereikbaar - - [ ] Active state correct - - [ ] Navigatie history werkt - -3. **Header:** - - [ ] Patient info toont correct - - [ ] Nieuwe rapportage button werkt - - [ ] Geen flashing bij navigatie - ---- - -## Rollout - -### ✅ Implementatie voltooid (26-11-2025) - -Alle stappen zijn uitgevoerd in de aanbevolen volgorde: - -1. ✅ **Stap 3** (Header memoization) - laag risico, medium impact -2. ✅ **Stap 4** Quick fix (Context cleanup) - laag risico, medium impact -3. ✅ **Stap 1** (Sidebar optimalisatie) - medium risico, high impact -4. ✅ **Stap 2** (Tabs optimalisatie) - laag risico, medium impact - -### Volgende stappen - -1. **Performance testing** - Meet met Chrome DevTools -2. **Functionele testing** - Verifieer alle features werken -3. **Gebruikers feedback** - Test in praktijk (<250ms?) - -### Originele planning - -1. **Week 1:** Stap 3 (Header memoization) - laag risico, medium impact -2. **Week 1:** Stap 4 Quick fix (Context cleanup) - laag risico, medium impact -3. **Week 2:** Stap 1 (Sidebar optimalisatie) - medium risico, high impact -4. **Week 2:** Stap 2 (Tabs optimalisatie) - laag risico, medium impact - -### Rollback plan - -Elke stap is onafhankelijk - bij issues een stap terugdraaien: -```bash -git revert -``` - ---- - -## Alternatieven overwogen - -### Waarom NIET Fase 3 (Server Components)? - -Server Components helpen bij: -- Initial page load (minder JS) -- Data fetching server-side - -Maar NIET bij: -- Client-side navigatie performance -- React re-render optimalisatie -- Menu click response time - -→ Verkeerde tool voor dit probleem - -### Waarom NIET React Query/SWR? - -Zou helpen met: -- API call caching -- Stale-while-revalidate - -Maar NIET met: -- Re-render frequency (primaire probleem) -- Component memoization - -→ Overkill voor huidig probleem, kan later als Fase 3 - ---- - -## Success Criteria - -- [ ] Menu click response <250ms (gemeten met Chrome DevTools) - **PENDING TEST** -- [x] Geen visuele regressies (flashing, wrong active state) - **CODE REVIEW PASSED** -- [x] Alle functionaliteit behouden - **CODE REVIEW PASSED** -- [x] Geen breaking changes voor gebruikers - **CODE REVIEW PASSED** - -## Files Modified ✅ - -**Alle bestanden succesvol geoptimaliseerd:** - -## Files Modified (COMPLETED) - -1. ✅ `app/epd/components/epd-sidebar.tsx` - Stap 1 (HIGH IMPACT) - - Toegevoegd: SidebarItem component met React.memo - - Gememoized: navigationItems, event handlers, isActive check - - Impact: ~80ms besparing verwacht - -2. ✅ `app/epd/patients/[id]/intakes/[intakeId]/components/intake-tabs.tsx` - Stap 2 (MEDIUM IMPACT) - - Toegevoegd: TabItem component met React.memo - - Gememoized: tabs array, baseUrl, isActive check - - Impact: ~40ms besparing verwacht - -3. ✅ `app/epd/components/epd-header.tsx` - Stap 3 (MEDIUM IMPACT) - - Gememoized: patient display data (40+ regels) - - Gestabiliseerd: event handlers - - Wrapped in memo() - - Impact: ~30ms besparing verwacht - -4. ✅ `app/epd/components/patient-context.tsx` - Stap 4 (LOW IMPACT, CRITICAL FIX) - - Fixed: context reset flashing - - Verbeterd: ID comparison voor updates - - Impact: ~20ms besparing + geen visuele glitches diff --git a/docs/seed-data-reports.md b/docs/seed-data-reports.md deleted file mode 100644 index a43523c..0000000 --- a/docs/seed-data-reports.md +++ /dev/null @@ -1,294 +0,0 @@ -# Reports Seed Data Documentatie - -Deze documentatie beschrijft hoe je testdata voor rapportages kunt inladen om AI-samenvattingsfunctionaliteit te demonstreren. - -## Overzicht - -De seed data bevat **10 realistische rapportages** verdeeld over 3 patiënten: - -### Patiënten & Rapportages - -#### 1. Colin Lit - Depressie Behandeling (5 rapportages) -- **Intake behandeladvies** - Uitgebreide intake met DSM classificatie, ROM scores en CGT behandelplan -- **Sessie 2 notitie** - Voortgang gedragsactivatie -- **Sessie 4 notitie** - Significante vooruitgang, PHQ-9 verbeterd -- **Crisis interventie** - Tussentijds telefonisch contact -- **Voortgangsrapportage** - Evaluatie na 8 sessies met ROM vergelijking - -**Gebruik voor demonstratie:** -- Timeline visualisatie van behandelverloop -- ROM score tracking (PHQ-9: 14 → 6) -- Behandeleffectiviteit analyse -- Crisis moment herkenning - -#### 2. Jan de Vriesh - Angststoornis (3 rapportages) -- **Intake behandeladvies** - GAD diagnose met ACT behandelplan -- **Sessie 3 notitie** - Mindfulness en acceptance technieken -- **Sessie 6 notitie** - Uitgebreide relatiedynamiek analyse - -**Gebruik voor demonstratie:** -- ACT interventie tracking -- Relationele problematiek identificatie -- Emotionele doorbraken herkennen -- Lange vorm notities samenvatten - -#### 3. Optimus Prime - Diagnostiek (2 rapportages) 🤖 -- **Diagnostisch rapport** - Uitgebreid neuropsychologisch onderzoek met WAIS-IV scores -- **Follow-up notitie** - Bespreking diagnostische bevindingen - -**Gebruik voor demonstratie:** -- Complexe diagnostiek samenvatten -- Test resultaten extractie -- Atypische presentaties herkennen -- Easter egg functionaliteit 😊 - -## Installatie Methoden - -### Methode 1: SQL Migratie (Aanbevolen voor development) - -```bash -# Voer de SQL migratie uit via Supabase CLI -npx supabase db push - -# Of direct via psql -psql $DATABASE_URL -f supabase/migrations/20251124_seed_reports_data.sql -``` - -**Voordelen:** -- Snelste methode -- Idempotent (ON CONFLICT DO NOTHING) -- Onderdeel van migratie geschiedenis - -### Methode 2: TypeScript Seed Script - -```bash -# Zorg dat environment variables zijn ingesteld -export NEXT_PUBLIC_SUPABASE_URL="your-project-url" -export SUPABASE_SERVICE_ROLE_KEY="your-service-role-key" - -# Voer het seed script uit -pnpm tsx scripts/seed-reports.ts -``` - -**Voordelen:** -- Meer flexibel voor aanpassingen -- Betere error handling en feedback -- Makkelijk uit te breiden met extra logica - -## Data Structuur - -### Report Types - -```typescript -type ReportType = 'behandeladvies' | 'vrije_notitie'; -``` - -- **behandeladvies**: Gestructureerde rapportages met diagnoses en behandelplannen -- **vrije_notitie**: Vrije vorm sessie notities - -### Structured Data Examples - -#### Behandeladvies -```json -{ - "diagnosis_codes": ["F32.1", "F51.0"], - "rom_scores": { - "PHQ-9": 14, - "GAD-7": 8 - }, - "treatment_plan": { - "type": "CGT", - "sessions": 12, - "frequency": "wekelijks" - } -} -``` - -#### Sessie Notitie -```json -{ - "session_number": 4, - "phq9_score": 9, - "treatment_progress": "goed" -} -``` - -### AI Confidence Scores - -Sommige rapportages bevatten AI confidence scores voor ML training: - -```typescript -{ - ai_confidence: 0.92, // 0.0 - 1.0 - ai_reasoning: "Clearly structured intake report with treatment advice section" -} -``` - -## Use Cases voor AI Demonstratie - -### 1. Automatische Samenvatting -``` -Input: Lange rapportage (1000+ woorden) -Output: Beknopte samenvatting (200 woorden) met key points -``` - -### 2. ROM Score Extractie -``` -Input: Behandelverloop van Colin (5 rapportages) -Output: PHQ-9 timeline: [14, -, 9, -, 6] - Trend: Significante verbetering -``` - -### 3. Behandelplan Identificatie -``` -Input: Intake rapportages -Output: - - Colin: CGT, 12 sessies, wekelijks - - Jan: ACT, 16 sessies, wekelijks - - Optimus: Geen behandeling, consultatief -``` - -### 4. Rapportage Classificatie -``` -Input: Rapport content -Output: Type: behandeladvies (confidence: 0.92) -``` - -### 5. Crisis Moment Detectie -``` -Input: Alle rapportages van patiënt -Output: Crisis interventie gedetecteerd op 2024-10-28 - Severity: laag - Actie: extra sessie gepland -``` - -### 6. Thematische Analyse -``` -Input: Jan's rapportages -Output: Terugkerende thema's: - - Piekeren / worry - - Relatiedynamiek - - Geruststelling zoeken - - Mindfulness challenges -``` - -## Query Voorbeelden - -### Alle rapportages voor een patiënt -```sql -SELECT - r.*, - p.name_given || ' ' || p.name_family as patient_name, - pr.name_given || ' ' || pr.name_family as practitioner_name -FROM reports r -JOIN patients p ON r.patient_id = p.id -LEFT JOIN practitioners pr ON r.created_by = pr.id -WHERE p.name_family = 'Lit' AND 'Colin' = ANY(p.name_given) -ORDER BY r.created_at ASC; -``` - -### ROM scores over tijd -```sql -SELECT - created_at, - structured_data->'rom_scores' as rom_scores, - structured_data->'session_number' as session -FROM reports -WHERE patient_id = 'colin-lit-uuid' - AND structured_data ? 'rom_scores' -ORDER BY created_at; -``` - -### Behandeladvies rapportages -```sql -SELECT - p.name_family, - r.content, - r.structured_data->'treatment_plan' as treatment_plan, - r.ai_confidence -FROM reports r -JOIN patients p ON r.patient_id = p.id -WHERE r.type = 'behandeladvies' - AND r.ai_confidence > 0.9 -ORDER BY r.created_at DESC; -``` - -## Data Reset - -Om de seed data opnieuw in te laden: - -```sql --- Verwijder bestaande reports (soft delete) -UPDATE reports -SET deleted_at = NOW() -WHERE created_at >= '2024-10-15' - AND created_at <= '2024-11-22'; - --- Of hard delete (wees voorzichtig!) -DELETE FROM reports -WHERE created_at >= '2024-10-15' - AND created_at <= '2024-11-22'; -``` - -Dan kun je de seed scripts opnieuw uitvoeren. - -## Uitbreidingen - -### Meer rapportages toevoegen - -Edit `scripts/seed-reports.ts` en voeg nieuwe entries toe aan `seedReportsData`: - -```typescript -{ - patient_family_name: 'Lit', - patient_given_name: 'Colin', - practitioner_index: 1, - type: 'vrije_notitie', - content: 'Nieuwe sessie notitie...', - created_at: '2024-11-25T10:00:00Z', - structured_data: { - session_number: 9, - // ... meer data - } -} -``` - -### Andere patiënten - -Voeg eerst nieuwe patiënt toe aan de database, en gebruik dan dezelfde structuur in het seed script. - -## Troubleshooting - -### "Patient not found" error -- Controleer of de seed data voor patients is geladen -- Verifieer de naam spelling (case-sensitive!) -- Check of `name_given` een array is - -### "Foreign key violation" error -- Zorg dat practitioners zijn geladen (zie `20241121_seed_demo_data.sql`) -- Verifieer dat de practitioner IDs kloppen - -### Duplicate key errors -- De SQL migratie gebruikt `ON CONFLICT DO NOTHING` -- Het TypeScript script zal dubbele entries overslaan -- Als je opnieuw wilt seeden, verwijder eerst de oude data - -## Next Steps - -Na het laden van seed data: - -1. **Test de rapportage UI** - Ga naar `/epd/patients/[id]/rapportage` -2. **Implementeer AI samenvatting** - Gebruik Claude API om rapportages samen te vatten -3. **Bouw timeline visualisatie** - Toon chronologisch overzicht van behandeling -4. **ROM tracking dashboard** - Visualiseer scores over tijd -5. **Zoek functionaliteit** - Full-text search over rapportage content - -## Contact & Support - -Voor vragen over de seed data of uitbreidingen, zie de main project README of open een issue in het project. - ---- - -**Created:** 2024-11-24 -**Last Updated:** 2024-11-24 -**Version:** 1.0 diff --git a/docs/seed-verpleegkundige-notities.md b/docs/seed-verpleegkundige-notities.md deleted file mode 100644 index c460118..0000000 --- a/docs/seed-verpleegkundige-notities.md +++ /dev/null @@ -1,282 +0,0 @@ -# Seed Data: Verpleegkundige Notities - -Handmatig verpleegkundige notities toevoegen aan de database via SQL. - -## Stap 1: Patient ID opzoeken - -Zoek eerst de UUID van de patient: - -```sql -SELECT id, name_given, name_family -FROM patients -WHERE name_family ILIKE '%achternaam%' -ORDER BY name_family; -``` - -## Stap 2: Notities invoegen - -### Beschikbare categorieën - -| Categorie | Beschrijving | -|-----------|--------------| -| `medicatie` | Medicatie gerelateerde notities | -| `adl` | ADL/verzorging (eten, wassen, etc.) | -| `gedrag` | Gedragsobservaties | -| `incident` | Incidenten en ongewenste gebeurtenissen | -| `observatie` | Algemene observaties | - -### SQL Template - -Vervang `'PATIENT_UUID_HIER'` met de gevonden patient ID: - -```sql --- Verpleegkundige notities voor een patient -INSERT INTO reports (patient_id, type, content, structured_data, include_in_handover, shift_date, created_at) -VALUES - -- Medicatie notitie - ('PATIENT_UUID_HIER', 'verpleegkundig', - 'Medicatie uitgereikt om 08:00. Patient nam zonder problemen in.', - '{"category": "medicatie"}', false, CURRENT_DATE, NOW()), - - -- ADL notitie - ('PATIENT_UUID_HIER', 'verpleegkundig', - 'Geholpen met ochtendzorg. Zelfstandig tanden gepoetst.', - '{"category": "adl"}', false, CURRENT_DATE, NOW()), - - -- Gedragsobservatie - ('PATIENT_UUID_HIER', 'verpleegkundig', - 'Rustige ochtend. Patient las de krant in de huiskamer.', - '{"category": "gedrag"}', false, CURRENT_DATE, NOW()), - - -- Incident - ('PATIENT_UUID_HIER', 'verpleegkundig', - 'Licht gestruikeld bij opstaan uit stoel. Geen letsel. Valrisico besproken.', - '{"category": "incident"}', true, CURRENT_DATE, NOW()), - - -- Algemene observatie (voor overdracht) - ('PATIENT_UUID_HIER', 'verpleegkundig', - 'Sliep onrustig vannacht. Klaagt over hoofdpijn. Arts informeren.', - '{"category": "observatie"}', true, CURRENT_DATE, NOW()); -``` - -### Met specifieke tijdstippen - -```sql -INSERT INTO reports (patient_id, type, content, structured_data, include_in_handover, shift_date, created_at) -VALUES - -- Nachtdienst (voor 07:00 = vorige dag shift_date) - ('PATIENT_UUID_HIER', 'verpleegkundig', - 'Twee keer wakker geworden. Om 03:00 toiletgang, om 05:30 onrustig.', - '{"category": "observatie"}', true, - CURRENT_DATE - INTERVAL '1 day', -- shift_date = gisteren - CURRENT_DATE + TIME '05:30'), -- created_at = vandaag 05:30 - - -- Ochtend - ('PATIENT_UUID_HIER', 'verpleegkundig', - 'Ontbijt goed gegeten. Koffie met melk.', - '{"category": "adl"}', false, CURRENT_DATE, CURRENT_DATE + TIME '08:30'), - - -- Middag - ('PATIENT_UUID_HIER', 'verpleegkundig', - 'Bezoek van dochter. Positieve stemming.', - '{"category": "gedrag"}', false, CURRENT_DATE, CURRENT_DATE + TIME '14:00'), - - -- Avond - ('PATIENT_UUID_HIER', 'verpleegkundig', - 'Avondmedicatie gegeven. Slaapmedicatie om 21:00.', - '{"category": "medicatie"}', true, CURRENT_DATE, CURRENT_DATE + TIME '21:00'); -``` - -## Velden uitleg - -| Veld | Waarde | Toelichting | -|------|--------|-------------| -| `type` | `'verpleegkundig'` | Altijd deze waarde voor korte notities | -| `content` | string (1-500 tekens) | De notitie tekst | -| `structured_data` | `'{"category": "..."}'` | JSON met categorie | -| `include_in_handover` | `true/false` | Tonen in overdracht | -| `shift_date` | `CURRENT_DATE` | Dienstdatum (voor 07:00 = vorige dag) | -| `created_at` | `NOW()` | Aanmaaktijd | - -## Voorbeelddata per categorie - -### Medicatie voorbeelden -```sql -('PATIENT_UUID', 'verpleegkundig', 'Paracetamol 500mg gegeven om 10:00 i.v.m. hoofdpijn.', '{"category": "medicatie"}', false, CURRENT_DATE, NOW()), -('PATIENT_UUID', 'verpleegkundig', 'PRN lorazepam geweigerd. Patient wilde eerst ontspanningsoefeningen proberen.', '{"category": "medicatie"}', true, CURRENT_DATE, NOW()), -('PATIENT_UUID', 'verpleegkundig', 'Insuline toegediend voor avondeten. BG: 8.2 mmol/L.', '{"category": "medicatie"}', false, CURRENT_DATE, NOW()) -``` - -### ADL voorbeelden -```sql -('PATIENT_UUID', 'verpleegkundig', 'Volledig geholpen met douchen. Huid intact, geen bijzonderheden.', '{"category": "adl"}', false, CURRENT_DATE, NOW()), -('PATIENT_UUID', 'verpleegkundig', 'Lunch: halve boterham gegeten, 1 kopje soep. Eetlust matig.', '{"category": "adl"}', true, CURRENT_DATE, NOW()), -('PATIENT_UUID', 'verpleegkundig', 'Continentie: 1x incontinent voor urine. Verschoond.', '{"category": "adl"}', false, CURRENT_DATE, NOW()) -``` - -### Gedrag voorbeelden -```sql -('PATIENT_UUID', 'verpleegkundig', 'Goede dag. Actief deelgenomen aan groepsactiviteit.', '{"category": "gedrag"}', false, CURRENT_DATE, NOW()), -('PATIENT_UUID', 'verpleegkundig', 'Teruggetrokken gedrag. Wil op kamer blijven. Gesprek aangeboden.', '{"category": "gedrag"}', true, CURRENT_DATE, NOW()), -('PATIENT_UUID', 'verpleegkundig', 'Conflict met medebewoner over TV. Situatie de-escaleerd door afleiding.', '{"category": "gedrag"}', true, CURRENT_DATE, NOW()) -``` - -### Incident voorbeelden -```sql -('PATIENT_UUID', 'verpleegkundig', 'Val uit bed om 04:00. Geen zichtbaar letsel. Arts geinformeerd.', '{"category": "incident"}', true, CURRENT_DATE, NOW()), -('PATIENT_UUID', 'verpleegkundig', 'Agressief gedrag richting personeel. Time-out op kamer. Gesprek na 30 min.', '{"category": "incident"}', true, CURRENT_DATE, NOW()), -('PATIENT_UUID', 'verpleegkundig', 'Verslikking tijdens eten. Heimlich niet nodig, spontaan uitgekucht.', '{"category": "incident"}', true, CURRENT_DATE, NOW()) -``` - -### Observatie voorbeelden -```sql -('PATIENT_UUID', 'verpleegkundig', 'Temperatuur 37.8. Licht verhoogd. Monitoren.', '{"category": "observatie"}', true, CURRENT_DATE, NOW()), -('PATIENT_UUID', 'verpleegkundig', 'Zuurstof saturatie stabiel: 96% op kamerlucht.', '{"category": "observatie"}', false, CURRENT_DATE, NOW()), -('PATIENT_UUID', 'verpleegkundig', 'Nieuwe rode plek op stuit. Decubitus preventie protocol gestart.', '{"category": "observatie"}', true, CURRENT_DATE, NOW()) -``` - -## Alles verwijderen voor een patient - -```sql --- Soft delete (aanbevolen) -UPDATE reports -SET deleted_at = NOW() -WHERE patient_id = 'PATIENT_UUID' - AND type = 'verpleegkundig'; - --- Hard delete (definitief) -DELETE FROM reports -WHERE patient_id = 'PATIENT_UUID' - AND type = 'verpleegkundig'; -``` - ---- - -## Kant-en-klare seed data (Colin Lit & Jan de Vriesh) - -Kopieer onderstaande SQL en voer uit in Supabase SQL Editor. -**Let op:** Vervang de UUIDs eerst met de juiste waarden uit je database. - -```sql --- Seed data voor Colin Lit en Jan de Vriesh (afgelopen week) --- Kopieer en plak direct in Supabase SQL Editor - -INSERT INTO reports (patient_id, type, content, structured_data, include_in_handover, shift_date, created_at) -VALUES - -- ═══════════════════════════════════════════════════════════════════════════ - -- COLIN LIT (f3fb1396-d326-450a-9f00-d60340f25deb) - -- ═══════════════════════════════════════════════════════════════════════════ - - -- Vandaag - ('f3fb1396-d326-450a-9f00-d60340f25deb', 'verpleegkundig', - 'Ochtendmedicatie uitgereikt. Inname zonder problemen.', - '{"category": "medicatie"}', false, CURRENT_DATE, CURRENT_DATE + TIME '08:15'), - - ('f3fb1396-d326-450a-9f00-d60340f25deb', 'verpleegkundig', - 'Rustige ochtend. Zat in huiskamer te lezen.', - '{"category": "gedrag"}', false, CURRENT_DATE, CURRENT_DATE + TIME '10:30'), - - -- Gisteren - ('f3fb1396-d326-450a-9f00-d60340f25deb', 'verpleegkundig', - 'Geholpen met douchen. Huid intact, geen bijzonderheden.', - '{"category": "adl"}', false, CURRENT_DATE - INTERVAL '1 day', CURRENT_DATE - INTERVAL '1 day' + TIME '09:00'), - - ('f3fb1396-d326-450a-9f00-d60340f25deb', 'verpleegkundig', - 'Klaagt over slecht slapen. Piekert veel. Gesprek aangeboden.', - '{"category": "observatie"}', true, CURRENT_DATE - INTERVAL '1 day', CURRENT_DATE - INTERVAL '1 day' + TIME '14:30'), - - ('f3fb1396-d326-450a-9f00-d60340f25deb', 'verpleegkundig', - 'Avondmedicatie gegeven incl. slaapmedicatie.', - '{"category": "medicatie"}', false, CURRENT_DATE - INTERVAL '1 day', CURRENT_DATE - INTERVAL '1 day' + TIME '21:00'), - - -- 3 dagen geleden - ('f3fb1396-d326-450a-9f00-d60340f25deb', 'verpleegkundig', - 'Goede dag gehad. Deelgenomen aan groepsactiviteit.', - '{"category": "gedrag"}', false, CURRENT_DATE - INTERVAL '3 days', CURRENT_DATE - INTERVAL '3 days' + TIME '16:00'), - - ('f3fb1396-d326-450a-9f00-d60340f25deb', 'verpleegkundig', - 'Lunch volledig opgegeten. Eetlust lijkt verbeterd.', - '{"category": "adl"}', false, CURRENT_DATE - INTERVAL '3 days', CURRENT_DATE - INTERVAL '3 days' + TIME '12:30'), - - -- 5 dagen geleden - ('f3fb1396-d326-450a-9f00-d60340f25deb', 'verpleegkundig', - 'Somber vanmorgen. Wilde niet uit bed komen. Na gesprek toch opgestaan.', - '{"category": "gedrag"}', true, CURRENT_DATE - INTERVAL '5 days', CURRENT_DATE - INTERVAL '5 days' + TIME '09:30'), - - ('f3fb1396-d326-450a-9f00-d60340f25deb', 'verpleegkundig', - 'Paracetamol gegeven i.v.m. hoofdpijn.', - '{"category": "medicatie"}', false, CURRENT_DATE - INTERVAL '5 days', CURRENT_DATE - INTERVAL '5 days' + TIME '11:00'), - - -- 6 dagen geleden - ('f3fb1396-d326-450a-9f00-d60340f25deb', 'verpleegkundig', - 'Bezoek van partner gehad. Positief gesprek, stemming opgeklaard.', - '{"category": "observatie"}', false, CURRENT_DATE - INTERVAL '6 days', CURRENT_DATE - INTERVAL '6 days' + TIME '15:00'), - - -- ═══════════════════════════════════════════════════════════════════════════ - -- JAN DE VRIESH (d16935c9-e0fe-4972-832f-175b7a38f9b9) - -- ═══════════════════════════════════════════════════════════════════════════ - - -- Vandaag - ('d16935c9-e0fe-4972-832f-175b7a38f9b9', 'verpleegkundig', - 'Onrustige nacht. Meerdere keren wakker, piekeren over financien.', - '{"category": "observatie"}', true, CURRENT_DATE, CURRENT_DATE + TIME '07:15'), - - ('d16935c9-e0fe-4972-832f-175b7a38f9b9', 'verpleegkundig', - 'PRN lorazepam aangeboden, geweigerd. Wil eerst ademhalingsoefeningen proberen.', - '{"category": "medicatie"}', true, CURRENT_DATE, CURRENT_DATE + TIME '09:45'), - - -- Gisteren - ('d16935c9-e0fe-4972-832f-175b7a38f9b9', 'verpleegkundig', - 'Teruggetrokken gedrag. Hele ochtend op kamer gebleven.', - '{"category": "gedrag"}', true, CURRENT_DATE - INTERVAL '1 day', CURRENT_DATE - INTERVAL '1 day' + TIME '11:00'), - - ('d16935c9-e0fe-4972-832f-175b7a38f9b9', 'verpleegkundig', - 'Halve boterham lunch. Eetlust matig.', - '{"category": "adl"}', false, CURRENT_DATE - INTERVAL '1 day', CURRENT_DATE - INTERVAL '1 day' + TIME '12:45'), - - -- 2 dagen geleden - ('d16935c9-e0fe-4972-832f-175b7a38f9b9', 'verpleegkundig', - 'Gespannen tijdens groepstherapie. Eerder vertrokken.', - '{"category": "gedrag"}', true, CURRENT_DATE - INTERVAL '2 days', CURRENT_DATE - INTERVAL '2 days' + TIME '10:30'), - - ('d16935c9-e0fe-4972-832f-175b7a38f9b9', 'verpleegkundig', - 'Avondmedicatie geweigerd, later alsnog ingenomen na gesprek.', - '{"category": "medicatie"}', true, CURRENT_DATE - INTERVAL '2 days', CURRENT_DATE - INTERVAL '2 days' + TIME '21:30'), - - -- 4 dagen geleden - ('d16935c9-e0fe-4972-832f-175b7a38f9b9', 'verpleegkundig', - 'Licht gestruikeld in gang. Geen letsel. Was duizelig, bloeddruk gecontroleerd: 118/72.', - '{"category": "incident"}', true, CURRENT_DATE - INTERVAL '4 days', CURRENT_DATE - INTERVAL '4 days' + TIME '14:00'), - - ('d16935c9-e0fe-4972-832f-175b7a38f9b9', 'verpleegkundig', - 'Na incident rustiger. Middagdutje gedaan.', - '{"category": "observatie"}', false, CURRENT_DATE - INTERVAL '4 days', CURRENT_DATE - INTERVAL '4 days' + TIME '16:30'), - - -- 5 dagen geleden - ('d16935c9-e0fe-4972-832f-175b7a38f9b9', 'verpleegkundig', - 'Goede dag. Wandeling gemaakt op terrein met begeleiding.', - '{"category": "gedrag"}', false, CURRENT_DATE - INTERVAL '5 days', CURRENT_DATE - INTERVAL '5 days' + TIME '11:00'), - - ('d16935c9-e0fe-4972-832f-175b7a38f9b9', 'verpleegkundig', - 'Zelfstandig gedoucht. Compliment gegeven voor zelfstandigheid.', - '{"category": "adl"}', false, CURRENT_DATE - INTERVAL '5 days', CURRENT_DATE - INTERVAL '5 days' + TIME '08:30'), - - -- 7 dagen geleden - ('d16935c9-e0fe-4972-832f-175b7a38f9b9', 'verpleegkundig', - 'Intake gesprek met nieuwe psychiater gehad. Medicatie wordt geevalueerd.', - '{"category": "observatie"}', true, CURRENT_DATE - INTERVAL '7 days', CURRENT_DATE - INTERVAL '7 days' + TIME '10:00'); -``` - -### Snelle opschoning (indien nodig) - -```sql --- Verwijder alle seed notities van afgelopen week -UPDATE reports -SET deleted_at = NOW() -WHERE type = 'verpleegkundig' - AND created_at >= CURRENT_DATE - INTERVAL '7 days'; -``` - ---- - -**Tip:** Voer SQL uit via Supabase Dashboard > SQL Editor of via `psql`. diff --git a/docs/specs/UI/bouwplan-mini-epd-v1.0.md b/docs/specs/UI/bouwplan-mini-epd-v1.0.md deleted file mode 100644 index e0f4827..0000000 --- a/docs/specs/UI/bouwplan-mini-epd-v1.0.md +++ /dev/null @@ -1,583 +0,0 @@ -# 🚀 Bouwplan — Mini EPD Prototype - -**Projectnaam:** Mini EPD Prototype -**Versie:** v1.0 -**Datum:** 19-11-2025 -**Auteur:** Development Team - ---- - -## 1. Doel en Context - -🎯 **Doel:** Een werkend MVP bouwen van een desktop EPD (Electronisch Patiënten Dossier) systeem voor de geestelijke gezondheidszorg met AI-ondersteuning. - -**Context:** -Het Mini EPD is een modern dossier systeem voor behandelaren in de GGZ. Het ondersteunt de volledige workflow van intake tot behandelplan met AI-assistentie voor: -- Intake notities en samenvattingen -- DSM-light diagnose classificatie -- SMART behandelplan generatie - -**Scope MVP:** -- Desktop-only (min-width 1280px, optimized voor 1440-1920px) -- Two-level navigation systeem (Behandelaar ↔ Client Dossier context) -- 5 core database tables (clients, intake_notes, problem_profiles, treatment_plans, ai_events) -- TipTap rich text editor voor notities -- AI features: summarize, classify, generate plans - -**Referenties:** -- Interface Design: `docs/specs/UI/interface-design-plan.md` -- User Flows: `docs/specs/UI/mocks-ui-flow.md` -- Database Schema: `supabase/migrations/20241115000002_create_epd_core_tables.sql` - ---- - -## 2. Uitgangspunten - -### 2.1 Technische Stack - -- **Frontend:** Next.js 15 + React 19 + TypeScript -- **Styling:** Tailwind CSS v4 -- **UI Components:** Radix UI + shadcn/ui patterns -- **Icons:** Lucide React -- **Rich Text:** TipTap (ProseMirror) -- **Database:** Supabase (PostgreSQL) -- **Auth:** Supabase Auth (email/password + OAuth) -- **AI/ML:** OpenAI API / Vertex AI (Gemini) - TBD -- **Hosting:** Vercel -- **State Management:** React Context + URL state - -### 2.2 Projectkaders - -- **Platform:** Desktop only (MVP), no mobile optimization -- **Timeline:** Iteratief, focus op core workflows eerst -- **Data:** Demo data, geen echte patiëntgegevens -- **Team:** 1-2 developers -- **Performance:** Target < 2s page load, < 5s AI responses - -### 2.3 Programmeer Uitgangspunten - -**Code Quality Principles:** - -- **DRY:** Herbruikbare componenten (`/components/epd/`, `/lib/epd/`) -- **SOC:** - - UI components in `/app/epd/components/` - - Business logic in `/lib/epd/` - - Database queries in `/lib/supabase/queries/` - - API routes in `/app/api/` -- **KISS:** Geen premature abstraction, iteratief verfijnen -- **YAGNI:** Alleen Week 1-2 features, AI features in Week 3 - -**Development Practices:** - -- **Error Handling:** Try-catch op alle async ops, user-friendly messages -- **Security:** - - RLS policies op alle tables - - API keys in environment variables - - Input validation op alle forms -- **Performance:** - - Server Components waar mogelijk - - Client Components alleen voor interactiviteit - - Lazy loading voor AI features - - Debounce op search (300ms) -- **Accessibility:** - - WCAG AA compliance - - Keyboard navigation (Tab, Enter, Escape) - - ARIA labels op alle interactive elements - - Focus states visible - ---- - -## 3. Epics & Stories Overzicht - -| Epic ID | Titel | Doel | Status | Stories | Opmerkingen | -|---------|-------|------|--------|---------|-------------| -| E0 | Setup & Configuratie | Repo, database, auth werkend | ✅ | 3 | Grotendeels klaar | -| E1 | Layout & Navigation | Two-level context systeem | 🔄 | 4 | In progress | -| E2 | Cliënten Management | CRUD clients, search/filter | ⏳ | 5 | Week 1-2 | -| E3 | Intake Systeem | TipTap editor, CRUD notes | ⏳ | 6 | Week 1-2 | -| E4 | Diagnose & Probleemprofiel | DSM-light categories, manual entry | ⏳ | 4 | Week 2 | -| E5 | Behandelplan | SMART goals, interventions, versioning | ⏳ | 5 | Week 2-3 | -| E6 | AI Integration | Summarize, classify, generate (Week 3) | ⏳ | 4 | Week 3 | -| E7 | Testing & Polish | QA, accessibility, performance | ⏳ | 3 | Ongoing | - ---- - -## 4. Epics & Stories (Uitwerking) - -### Epic 0 — Setup & Configuratie - -**Status:** ✅ Grotendeels gereed - -| Story ID | Beschrijving | AC | Status | Points | -|----------|--------------|-----|--------|--------| -| E0.S1 | Repository + Next.js 15 setup | App draait lokaal op :3000 | ✅ | 2 | -| E0.S2 | Supabase project + schema | 5 core tables aangemaakt | ✅ | 3 | -| E0.S3 | Auth flows (login/logout) | Email/password werkt, OAuth ready | ✅ | 5 | - -**Tech Notes:** -- Database migrations in `supabase/migrations/` -- Auth flows in `app/auth/` (callback, logout, reset-password) -- Environment vars: `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY` - ---- - -### Epic 1 — Layout & Navigation - -**Doel:** Two-level context systeem werkend met context-aware sidebar en header - -| Story ID | Beschrijving | AC | Status | Points | -|----------|--------------|-----|--------|--------| -| E1.S1 | EPD root layout | Fixed header + sidebar layout | 🔄 | 3 | -| E1.S2 | Context-aware sidebar | Level 1 vs Level 2 menu dynamisch | 🔄 | 5 | -| E1.S3 | Context-aware header | Client dropdown in Level 2, search bar | 🔄 | 5 | -| E1.S4 | URL-based context detection | Layout past zich aan op basis van URL | ⏳ | 3 | - -**Tech Notes:** - -**Routing structure:** -``` -/epd/dashboard → Behandelaar dashboard (Level 1) -/epd/patients → Patiënten lijst (Level 1) -/epd/patients/[id] → Patient dashboard (Level 2) -/epd/patients/[id]/intakes → Intake sectie (Level 2) -/epd/patients/[id]/diagnose → Diagnose sectie (Level 2) -/epd/patients/[id]/plan → Behandelplan (Level 2) -``` - -**Context detection logic:** -```typescript -const isPatientDossier = pathname.includes('/patients/') && - pathname.match(/\/patients\/[^\/]+/); -const patientId = isPatientDossier ? pathname.split('/')[3] : null; -``` - -**Components:** -- `app/epd/layout.tsx` - Root EPD layout met context detection -- `app/epd/components/epd-header.tsx` - Context-aware header -- `app/epd/components/epd-sidebar.tsx` - Context-aware sidebar - ---- - -### Epic 2 — Cliënten Management - -**Doel:** CRUD voor cliënten, search/filter, recent clients - -| Story ID | Beschrijving | AC | Status | Points | -|----------|--------------|-----|--------|--------| -| E2.S1 | Cliënten lijst view | Table met search, filters werkend | ⏳ | 5 | -| E2.S2 | Nieuwe cliënt formulier | Modal/page met validatie, opslaan werkt | ⏳ | 5 | -| E2.S3 | Client detail edit | Bestaande client gegevens bewerken | ⏳ | 3 | -| E2.S4 | Search & filter functionaliteit | Real-time zoeken op naam, BSN, ID | ⏳ | 5 | -| E2.S5 | Recent clients tracking | localStorage, max 5 items, dropdown | ⏳ | 3 | - -**Tech Notes:** - -**Database queries:** -```typescript -// lib/supabase/queries/clients.ts -export async function getClients(filters: ClientFilters) { - const query = supabase - .from('clients') - .select('id, first_name, last_name, birth_date, created_at') - .order('last_name', { ascending: true }); - - if (filters.search) { - query.or(`first_name.ilike.%${filters.search}%,last_name.ilike.%${filters.search}%`); - } - - return query; -} -``` - -**Search debounce:** -```typescript -const debouncedSearch = useMemo( - () => debounce((value: string) => setSearchQuery(value), 300), - [] -); -``` - -**Components:** -- `app/epd/patients/page.tsx` - Patiënten lijst -- `app/epd/patients/components/patient-list.tsx` -- `app/epd/patients/components/patient-search.tsx` -- `app/epd/patients/new/page.tsx` - Nieuwe patiënt form - ---- - -### Epic 3 — Intake Systeem - -**Doel:** TipTap editor voor intake notities met CRUD functionaliteit - -| Story ID | Beschrijving | AC | Status | Points | -|----------|--------------|-----|--------|--------| -| E3.S1 | TipTap editor setup | Rich text editor werkend, formatting | ⏳ | 8 | -| E3.S2 | Intake lijst view | Toon alle intakes per client, sorteerbaar | ⏳ | 3 | -| E3.S3 | Nieuwe intake aanmaken | Editor opent, opslaan werkt, JSONB storage | ⏳ | 5 | -| E3.S4 | Intake detail slide-in | 400px panel, scroll, edit/delete | ⏳ | 5 | -| E3.S5 | Content text extraction | JSONB → plain text voor search index | ⏳ | 3 | -| E3.S6 | Auto-save drafts | localStorage, 30s interval, restore on return | ⏳ | 5 | - -**Tech Notes:** - -**TipTap configuration:** -```typescript -import { useEditor } from '@tiptap/react' -import StarterKit from '@tiptap/starter-kit' - -const editor = useEditor({ - extensions: [StarterKit], - content: initialContent, - onUpdate: ({ editor }) => { - const json = editor.getJSON() - const text = editor.getText() - // Save to state/DB - } -}) -``` - -**Database structure:** -```sql -intake_notes ( - id UUID, - client_id UUID, - title TEXT, - tag TEXT ('Intake', 'Evaluatie', 'Plan'), - content_json JSONB, -- TipTap document - content_text TEXT, -- Plain text for FTS - created_at TIMESTAMPTZ -) -``` - -**Components:** -- `app/epd/patients/[id]/intakes/page.tsx` - Intake lijst -- `app/epd/patients/[id]/intakes/components/intake-editor.tsx` -- `app/epd/patients/[id]/intakes/components/intake-detail-panel.tsx` - ---- - -### Epic 4 — Diagnose & Probleemprofiel - -**Doel:** DSM-light categorieën met severity tracking (manual entry MVP) - -| Story ID | Beschrijving | AC | Status | Points | -|----------|--------------|-----|--------|--------| -| E4.S1 | DSM categories grid UI | 6 categorieën visueel, severity badges | ⏳ | 5 | -| E4.S2 | Problem profile CRUD | Aanmaken/bewerken/verwijderen per categorie | ⏳ | 5 | -| E4.S3 | Severity indicator | Laag/Middel/Hoog visueel duidelijk | ⏳ | 2 | -| E4.S4 | Source linking | Link diagnose → intake note (bronverwijzing) | ⏳ | 3 | - -**Tech Notes:** - -**DSM-light categories:** -```typescript -const DSM_CATEGORIES = [ - { id: 'stemming_depressie', label: 'Stemming & Depressie', color: 'blue' }, - { id: 'angst', label: 'Angst', color: 'purple' }, - { id: 'gedrag_impuls', label: 'Gedrag & Impuls', color: 'red' }, - { id: 'middelen_gebruik', label: 'Middelengebruik', color: 'orange' }, - { id: 'cognitief', label: 'Cognitief', color: 'green' }, - { id: 'context_psychosociaal', label: 'Context & Psychosociaal', color: 'teal' }, -] as const; -``` - -**Severity levels:** -```typescript -type Severity = 'laag' | 'middel' | 'hoog'; - -const SEVERITY_CONFIG = { - laag: { label: 'Laag', color: 'green', dots: 1 }, - middel: { label: 'Middel', color: 'yellow', dots: 3 }, - hoog: { label: 'Hoog', color: 'red', dots: 5 }, -}; -``` - -**Components:** -- `app/epd/patients/[id]/diagnose/page.tsx` -- `app/epd/patients/[id]/diagnose/components/dsm-categories.tsx` -- `app/epd/patients/[id]/diagnose/components/severity-indicator.tsx` - ---- - -### Epic 5 — Behandelplan - -**Doel:** SMART doelen tracking met interventies en versioning - -| Story ID | Beschrijving | AC | Status | Points | -|----------|--------------|-----|--------|--------| -| E5.S1 | Treatment plan data model | JSONB structure + versioning | ⏳ | 3 | -| E5.S2 | SMART goals editor | Lijst van doelen, progress tracking | ⏳ | 8 | -| E5.S3 | Interventies sectie | Evidence-based methoden lijst | ⏳ | 5 | -| E5.S4 | Frequentie & planning | Sessie planning, duration estimate | ⏳ | 3 | -| E5.S5 | Plan versioning | v1, v2, concept/gepubliceerd status | ⏳ | 5 | - -**Tech Notes:** - -**Treatment plan JSONB structure:** -```typescript -interface TreatmentPlan { - doelen: Array<{ - id: string; - beschrijving: string; - specifiek: string; // S - Specific - meetbaar: string; // M - Measurable - acceptabel: boolean; // A - Acceptable - realistisch: boolean; // R - Realistic - tijdgebonden: string; // T - Time-bound - voortgang: number; // 0-100% - }>; - interventies: Array<{ - naam: string; // "CGT", "ACT", "EMDR" - beschrijving: string; - doel_ids: string[]; // Links to goals - }>; - frequentie: { - sessies_per_week: number; - totaal_sessies: number; - duur_minuten: number; - }; - meetmomenten: Array<{ - week: number; - beschrijving: string; - }>; -} -``` - -**Versioning logic:** -```typescript -// When creating new version: -const latestVersion = await getLatestPlanVersion(clientId); -const newVersion = latestVersion ? latestVersion.version + 1 : 1; - -// Publish: concept → gepubliceerd -await supabase - .from('treatment_plans') - .update({ status: 'gepubliceerd', published_at: new Date() }) - .eq('id', planId); -``` - -**Components:** -- `app/epd/patients/[id]/plan/page.tsx` -- `app/epd/patients/[id]/plan/components/smart-goals.tsx` -- `app/epd/patients/[id]/plan/components/interventions.tsx` -- `app/epd/patients/[id]/plan/components/plan-version-selector.tsx` - ---- - -### Epic 6 — AI Integration (Week 3) - -**Doel:** AI-powered features voor summarize, classify, generate - -| Story ID | Beschrijving | AC | Status | Points | -|----------|--------------|-----|--------|--------| -| E6.S1 | AI API configuratie | OpenAI/Vertex setup, test call works | ⏳ | 3 | -| E6.S2 | Intake samenvatting | /api/ai/summarize endpoint, <5s response | ⏳ | 8 | -| E6.S3 | Diagnose classificatie | /api/ai/classify endpoint, DSM mapping | ⏳ | 8 | -| E6.S4 | Behandelplan generatie | /api/ai/generate-plan, SMART goals output | ⏳ | 13 | - -**Tech Notes:** - -**API Routes:** -``` -POST /api/ai/summarize - Samenvatting van intake note -POST /api/ai/classify - DSM-light classificatie -POST /api/ai/generate-plan - Behandelplan generatie -``` - -**AI Events logging:** -```typescript -await supabase.from('ai_events').insert({ - kind: 'summarize', - client_id: clientId, - note_id: noteId, - request: { prompt, model }, - response: { output, tokens }, - duration_ms: responseTime, -}); -``` - -**Prompt templates:** -```typescript -const SUMMARIZE_PROMPT = ` -Je bent een GGZ-professional. Vat de volgende intake notitie samen in 3-5 bullet points. -Focus op: klachten, achtergrond, observaties. - -Notitie: -{content} -`; -``` - -**Components:** -- `app/api/ai/summarize/route.ts` -- `app/api/ai/classify/route.ts` -- `app/api/ai/generate-plan/route.ts` -- `lib/ai/prompts.ts` -- `lib/ai/client.ts` (OpenAI/Vertex wrapper) - ---- - -### Epic 7 — Testing & Polish - -**Doel:** QA, accessibility audit, performance optimization - -| Story ID | Beschrijving | AC | Status | Points | -|----------|--------------|-----|--------|--------| -| E7.S1 | Manual test scenarios | Alle happy flows werken zonder errors | ⏳ | 5 | -| E7.S2 | Accessibility audit | WCAG AA compliance, keyboard nav werkt | ⏳ | 5 | -| E7.S3 | Performance optimization | Lighthouse score >90, <2s load | ⏳ | 3 | - ---- - -## 5. Kwaliteit & Testplan - -### Test Types - -| Test Type | Scope | Verantwoordelijke | -|-----------|-------|-------------------| -| Manual Testing | Happy flows, edge cases | Developer | -| Accessibility | Keyboard nav, screen reader | Developer | -| Performance | Lighthouse, load times | Developer | - -### Manual Test Checklist - -**Level 1 - Behandelaar Context:** -- [ ] Login werkt (email/password) -- [ ] Dashboard toont correct (caseload, aandachtspunten) -- [ ] Cliënten lijst laadt, search werkt -- [ ] Nieuwe cliënt aanmaken werkt -- [ ] Klik op client → switch naar Level 2 - -**Level 2 - Client Dossier Context:** -- [ ] Client dashboard toont correct (info, laatste intake, diagnose) -- [ ] Sidebar toont "← Cliënten" button -- [ ] Header toont client dropdown -- [ ] Navigatie tussen secties werkt (Intake, Diagnose, Plan) -- [ ] "← Cliënten" button → terug naar Level 1 - -**Intake:** -- [ ] TipTap editor opent, formatting werkt -- [ ] Opslaan intake werkt (JSONB + text extraction) -- [ ] Intake lijst toont alle notities -- [ ] Slide-in detail panel opent/sluit correct -- [ ] Bewerken intake werkt - -**Diagnose:** -- [ ] DSM categories grid toont 6 categorieën -- [ ] Severity indicator werkt (laag/middel/hoog) -- [ ] Problem profile aanmaken/bewerken werkt -- [ ] Bronverwijzing naar intake note werkt - -**Behandelplan:** -- [ ] SMART goals toevoegen/bewerken werkt -- [ ] Interventies sectie werkt -- [ ] Progress tracking werkt (0-100%) -- [ ] Versioning werkt (v1, v2, concept/gepubliceerd) - -**AI Features (Week 3):** -- [ ] Samenvatting genereert binnen 5 seconden -- [ ] Classificatie geeft valide DSM categories -- [ ] Behandelplan generator werkt, output is bruikbaar -- [ ] AI events worden gelogd - -**Accessibility:** -- [ ] Alle interactive elements keyboard accessible -- [ ] Focus states zichtbaar -- [ ] ARIA labels correct -- [ ] Color contrast WCAG AA - ---- - -## 6. Demo & Presentatieplan - -**Duur:** 15 minuten -**Doelgroep:** Stakeholders, GGZ-professionals -**Demo Scenario:** - -1. **Login** (1 min) - Toon authenticatie flow -2. **Behandelaar Dashboard** (2 min) - Overzicht caseload, aandachtspunten -3. **Nieuwe Cliënt** (2 min) - Voeg "Bas Jansen" toe -4. **Intake Notitie** (3 min) - TipTap editor, rich text, opslaan -5. **AI Samenvatting** (2 min) - Genereer samenvatting (Week 3 feature) -6. **Diagnose** (2 min) - DSM classificatie, severity -7. **Behandelplan** (2 min) - SMART goals, interventies -8. **Q&A** (1 min) - -**Backup Plan:** -- Localhost als Vercel deployment faalt -- Pre-seeded demo data -- Screenshots als fallback - ---- - -## 7. Risico's & Mitigatie - -| Risico | Kans | Impact | Mitigatie | Owner | -|--------|------|--------|-----------|-------| -| TipTap integratie complex | Middel | Hoog | Start met StarterKit, uitbreiden later | Dev | -| AI API rate limits | Hoog | Middel | Caching, response fallbacks, debounce | Dev | -| Context switching bugs | Middel | Hoog | Uitgebreide URL-based state tests | Dev | -| Performance met grote datasets | Middel | Middel | Pagination, lazy loading, indexing | Dev | -| RLS policies te open (demo) | Laag | Hoog | Duidelijke comments, productie checklist | Dev | -| Two-level navigation verwarrend | Middel | Middel | User testing, clear visual feedback | Dev | - ---- - -## 8. Evaluatie & Lessons Learned - -**Te documenteren na MVP:** -- Welke onderdelen namen meer tijd dan verwacht? -- TipTap editor challenges en oplossingen -- AI prompt engineering insights (Week 3) -- Two-level navigation UX feedback -- Performance bottlenecks en optimalisaties -- Herbruikbare patterns voor volgende projecten - ---- - -## 9. Referenties - -**Mission Control Documents:** -- **Interface Design:** `docs/specs/UI/interface-design-plan.md` -- **User Flows:** `docs/specs/UI/mocks-ui-flow.md` -- **Database Schema:** `supabase/migrations/20241115000002_create_epd_core_tables.sql` - -**Codebase:** -- **Repository:** `/home/colin/development/15-mini-epd-prototype` -- **EPD App:** `app/epd/` -- **Components:** `components/ui/`, `app/epd/components/` -- **Database Queries:** `lib/supabase/queries/` -- **Migrations:** `supabase/migrations/` - -**External Resources:** -- Next.js 15 Docs: https://nextjs.org/docs -- Supabase Docs: https://supabase.com/docs -- TipTap Docs: https://tiptap.dev -- Radix UI: https://radix-ui.com -- Tailwind CSS: https://tailwindcss.com - ---- - -## 10. Glossary & Abbreviations - -| Term | Betekenis | -|------|-----------| -| EPD | Electronisch Patiënten Dossier | -| GGZ | Geestelijke Gezondheidszorg | -| DSM | Diagnostic and Statistical Manual (psychiatric classification) | -| DSM-light | Vereenvoudigde categorisatie (6 hoofdgroepen) | -| SMART | Specific, Measurable, Acceptable, Realistic, Time-bound | -| TipTap | Rich text editor gebouwd op ProseMirror | -| RLS | Row Level Security (Supabase/PostgreSQL) | -| Level 1 | Behandelaar Context (caseload overzicht) | -| Level 2 | Client Dossier Context (individuele cliënt focus) | -| Context Switch | Navigatie tussen Level 1 ↔ Level 2 | -| FTS | Full-Text Search | -| JSONB | PostgreSQL JSON Binary data type | - ---- - -**Versiehistorie:** - -| Versie | Datum | Auteur | Wijziging | -|--------|-------|--------|-----------| -| v1.0 | 19-11-2025 | Development Team | Initiële versie op basis van UI specs | diff --git a/docs/specs/UI/clientheader.png b/docs/specs/UI/clientheader.png deleted file mode 100644 index 126530e..0000000 Binary files a/docs/specs/UI/clientheader.png and /dev/null differ diff --git a/docs/specs/UI/inteface-design-plan.md b/docs/specs/UI/inteface-design-plan.md deleted file mode 100644 index 47ce782..0000000 --- a/docs/specs/UI/inteface-design-plan.md +++ /dev/null @@ -1,900 +0,0 @@ -# Mini-ECD Interface Design - Complete MVP Specification - -## Technical Specs -- Target: Desktop only, min-width 1280px -- Optimized for: 1440px - 1920px screens -- Framework: Next.js 15, TailwindCSS, shadcn/ui -- No mobile optimization in MVP - ---- - -## Navigation Architecture - -### Two-Level Context System - -**LEVEL 1: Behandelaar Context** (No client selected) -- Sidebar shows: Dashboard | Cliënten | Agenda | Rapportage -- Header: No client dropdown (or disabled/empty) -- Focus: Behandelaar's caseload, tasks, and global views - -**LEVEL 2: Client Dossier Context** (Client selected) -- Sidebar shows: ← Cliënten | Dashboard | Intake | Diagnose | Behandelplan | Rapportage -- Header: Shows selected client with dropdown "Bas Jansen ▼" -- Focus: Individual client data and treatment information - -**Context Switch Trigger:** -- Clicking a client row in the cliënten lijst switches from Level 1 → Level 2 -- Clicking "← Cliënten" in sidebar switches from Level 2 → Level 1 - ---- - -## Layout Components - -### Header Bar (Fixed, 60px height) -- Background: White with subtle bottom border -- Padding: 16px vertical, 24px horizontal -- Layout (flex, space-between): - - **Left**: "Mini-ECD" text/logo (medium weight) - - **Center**: Context-aware client selector - - **In Behandelaar Context**: Empty or behandelaar name (optional) - - **In Client Dossier Context**: Client dropdown "Bas Jansen ▼" (clickable) - - Sub-text below: "ID: CL0002 | Geb: 20-11-1992" (small, muted) - - Dropdown shows recent clients for quick switching - - **Right**: Search input "Zoek cliënt..." with icon (rounded, light border) - - Always available for quick client lookup - -### Sidebar (Fixed left, 240px width) -- Background: Very light gray -- Padding: 16px - -**Context-Aware Navigation:** - -**BEHANDELAAR CONTEXT (Level 1) - No client selected:** -- Dashboard (icon + text) → Behandelaar dashboard (caseload, taken, aandachtspunten) -- Cliënten (icon + text) → Caseload overzicht met zoek/filter -- Agenda (icon + text) → Behandelaar agenda (alle afspraken) -- Rapportage (icon + text) → BI dashboards en statistieken - -**CLIENT DOSSIER CONTEXT (Level 2) - Client selected:** -- "← Cliënten" button (full width, left-aligned, subtle hover) → Terug naar behandelaar context -- Subtle horizontal divider (8px margin bottom) -- Dashboard (icon + text) → Client dossier overzicht (afgelopen/komende afspraken) -- Intake (icon + text) → Intake gesprekken en notities -- Diagnose (icon + text) → DSM-classificatie, probleemprofiel -- Behandelplan (icon + text) → Doelen, interventies, planning -- Rapportage (icon + text) → Client-specifieke voortgang en metrics - -**Styling:** -- Each item: 12px padding vertical, 12px padding horizontal -- Icons: 20px, 12px gap to text -- Item spacing: 4px between items -- Active state: highlighted background, emphasized text, left border accent -- Hover state: subtle background change -- Text: medium weight, 15px - -### Main Content Area (Scrollable) -- Background: White -- Padding: 32px all sides -- Min-height: calc(100vh - 60px) - ---- - -## LEVEL 1 SCREENS: Behandelaar Context - ---- - -## Screen 1A: Behandelaar Dashboard - -### Sidebar -- **Dashboard** ← ACTIVE -- Cliënten -- Agenda -- Rapportage - -### Main Content -**Page title:** "Dashboard" (large, semi-bold) -**Subtitle:** "Welkom terug, [Behandelaar naam]" (optional) - -**Content (to be further defined):** -- Caseload overzicht (aantal actieve cliënten, wachtlijst, etc.) -- Aandachtspunten (urgente taken, follow-ups) -- Behandelplannen die nog niet definitief zijn -- Berichten en notificaties -- Recente activiteit -- Aankomende afspraken (vandaag/deze week) - -**Note:** Detailed content and layout to be specified in later iteration. - ---- - -## Screen 1B: Cliënten (Search/List View) - -### Sidebar -- Dashboard -- **Cliënten** ← ACTIVE -- Agenda -- Rapportage - -### Header -- No client dropdown in center (behandelaar context) -- Search bar available in top right - -### Main Content -**Top bar:** -- Title: "Cliënten" (large, semi-bold, left) -- Button: "+ Nieuwe Cliënt" (primary style, right) -- Space-between layout - -**Search & Filter bar (below title):** -- Full-width input field -- Placeholder: "Zoek op naam, BSN, of cliënt ID..." -- Icon: magnifying glass left side -- Filter options (to be specified): - - Afdeling filter - - Team filter - - Status filter (Actief, Wachtlijst, Afgesloten) -- 16px margin bottom - -**Recent clients section (optional):** -- "Recent bekeken" subtitle (small, muted) -- 3-5 most recent client cards (compact) -- Horizontal scroll or grid - -**Client list (table/card hybrid):** - -Header row: -- Columns: Naam | ID | Geboortedatum | Status | Laatste Contact -- Subtle bottom border, muted text, small size - -Client rows (3-4 visible): - -Row 1: -- Naam: "Bas Jansen" -- ID: "CL0002" -- Geboortedatum: "20-11-1992" -- Status: "Actief" (green badge) -- Laatste contact: "12-10-2023" -- Hover state: subtle background, pointer cursor -- Click: navigates to client dashboard - -Row 2: -- Naam: "Anna de Vries" -- ID: "CL0001" -- Geboortedatum: "15-03-1988" -- Status: "Actief" (green badge) -- Laatste contact: "08-10-2023" - -Row 3: -- Naam: "Peter Smit" -- ID: "CL0003" -- Geboortedatum: "22-07-1995" -- Status: "Wachtlijst" (yellow badge) -- Laatste contact: "01-10-2023" - -**Empty state (if no clients):** -- Centered icon (users/people illustration) -- Text: "Nog geen cliënten" -- Subtext: "Voeg je eerste cliënt toe om te beginnen" -- "+ Nieuwe Cliënt" button (primary, centered) - -**Layout structure:** -``` -┌─────────────────────────────────────────────────┐ -│ Header: Logo | (no client) | Search │ -├───────────┬─────────────────────────────────────┤ -│Dashboard │ Cliënten [+ Nieuwe Cliënt] │ -│Cliënten ← │ │ -│Agenda │ [Zoek op naam, BSN, of cliënt ID] │ -│Rapportage │ [Filters: Afdeling, Team, Status] │ -│ │ │ -│ │ Naam ID Geb Status │ -│ │ ────────────────────────────────── │ -│ │ Bas J. CL0002 20-11 Actief │ -│ │ Anna dV CL0001 15-03 Actief │ -│ │ Peter S. CL0003 22-07 Wachtlijst │ -└───────────┴─────────────────────────────────────┘ -``` - -**User action to switch context:** -- Click on a client row (e.g., "Bas Jansen") → Switches to Level 2 (Client Dossier Context) - ---- - -## Screen 1C: Behandelaar Agenda - -### Sidebar -- Dashboard -- Cliënten -- **Agenda** ← ACTIVE -- Rapportage - -### Main Content -**Page title:** "Agenda" (large, semi-bold) - -**Content (to be further defined):** -- Calendar view (week/month) -- Behandelaar's appointments (all clients) -- Appointment details: time, client, type -- Create new appointment button - -**Note:** Detailed content and layout to be specified in later iteration. - ---- - -## Screen 1D: Behandelaar Rapportage - -### Sidebar -- Dashboard -- Cliënten -- Agenda -- **Rapportage** ← ACTIVE - -### Main Content -**Page title:** "Rapportage" (large, semi-bold) - -**Content (to be further defined):** -- BI-style dashboards -- KPI's (aantal cliënten, gemiddelde behandelduur, etc.) -- Trends en statistieken -- Export functionaliteit - -**Note:** Detailed content and layout to be specified in later iteration. - ---- - -## LEVEL 2 SCREENS: Client Dossier Context - -**Context Switch:** User clicked on a client in the cliënten lijst - ---- - -## Screen 2A: Client Dashboard (Dossier Overzicht) - -### Header -- **Center**: "Bas Jansen ▼" dropdown visible - - Sub-text: "ID: CL0002 | Geb: 20-11-1992" - - Dropdown shows recent clients for quick switching -- **Right**: Search bar still available - -### Sidebar -- "← Cliënten" button at top (returns to behandelaar context) -- Horizontal divider -- **Dashboard** ← ACTIVE -- Intake -- Diagnose -- Behandelplan -- Rapportage - -### Main Content -**Page title:** "Dossier Overzicht" (large, semi-bold, 16px margin bottom) - -**Grid layout:** - -**Row 1: 3 equal columns (gap: 24px)** - -Column 1 - Cliëntinformatie card: -- Card style: white bg, subtle border, 8px radius, 16px padding -- Title: "Cliëntinformatie" (medium, semi-bold) -- List layout: - - Label: Value pairs, stacked vertically - - Cliënt ID: CL0002 - - Naam: Bas Jansen - - Geboortedatum: 20-11-1992 - - Leeftijd: 31 jaar (computed) - - Verzekering: [naam verzekeraar] - - BSN: [nummer] -- Labels: muted, small -- Values: normal weight, darker -- Action: "Bewerken →" link (small, at bottom) - -Column 2 - Laatste Intake card: -- Card style: same as column 1 -- Title: "Laatste Intake" -- Date/time: "12-10-2023, 14:30" (small, muted) -- Preview text (3 lines max): - "Bas komt op gesprek vanwege spanningsklachten en paniekeaanvallen. Hij beschrijft situaties in het openbaar vervoer en drukke winkels als triggerend..." -- "Bekijk intake →" link (small, at bottom) → navigates to Intake section - -Column 3 - Diagnose card: -- Card style: same as above -- Title: "Diagnose" -- Status badge: "Hoog" (red/warning bg, red text, rounded pill, inline with title) -- Subtitle: "Angststoornissen" (medium weight) -- Description text: - "Panietstoornis met agorafobie. De frequentie van paniekeaanvallen en het vermijdingsgedrag zijn..." -- "Bekijk diagnose →" link (small, at bottom) → navigates to Diagnose section - -**Row 2: Full width (margin top: 24px)** - -Behandelplan card: -- Card style: same -- Title: "Behandelplan" -- If exists: Show summary with status badge (Concept, Actief, Afgerond) -- If not exists (empty state): - - Light gray background section inside card - - Centered text: "Geen behandelplan gevonden." - - "+ Maak behandelplan" button (secondary style, centered below) -- "Bekijk behandelplan →" link → navigates to Behandelplan section - -**Row 3: Full width (margin top: 24px)** - -Afspraken card: -- Card style: same -- Title: "Afspraken" - -Section 1: -- Subtitle: "Afgelopen Afspraak" (small, muted, 8px margin bottom) -- Item: "14-11-2025, 16:26 - Intake gesprek" (with subtle icon left) -- Shows: type, duration, practitioner (if available) - -Divider line (subtle, 16px margin vertical) - -Section 2: -- Subtitle: "Komende Afspraken" (small, muted) -- Item 1: "22-11-2025, 16:26 - Psycho-educatie" -- Item 2: "29-11-2025, 16:26 - Exposure therapie sessie" -- Item 3: "06-12-2025, 16:26 - Evaluatie gesprek" -- Each with subtle left border accent (blue) -- Shows: date, time, type -- If no appointments: "Geen geplande afspraken" (muted text) - -**Layout structure:** -``` -┌──────────────────────────────────────────────────┐ -│ Header: Logo | Bas Jansen ▼ | Search │ -│ | ID: CL0002 | Geb: 20-11-1992 │ -├──────────┬───────────────────────────────────────┤ -│← Cliënten│ Dossier Overzicht │ -│────── │ │ -│Dashboard │ ┌─────────┬─────────┬──────────┐ │ -│Intake │ │ Info │ Intake │ Diagnose │ │ -│Diagnose │ │ Card │ Card │ Card │ │ -│Behandel │ └─────────┴─────────┴──────────┘ │ -│Rapport │ ┌──────────────────────────────┐ │ -│ │ │ Behandelplan Card │ │ -│ │ └──────────────────────────────┘ │ -│ │ ┌──────────────────────────────┐ │ -│ │ │ Afspraken Card │ │ -│ │ └──────────────────────────────┘ │ -└──────────┴───────────────────────────────────────┘ -``` - ---- - -## Screen 2B: Intake (Client Dossier) - -### Header -- **Center**: "Bas Jansen ▼" with ID and birthdate -- **Right**: Search bar available - -### Sidebar -- "← Cliënten" button (returns to behandelaar context) -- Divider -- Dashboard -- **Intake** ← ACTIVE -- Diagnose -- Behandelplan -- Rapportage - -### Main Content -**Breadcrumb:** "Intakes > Overzicht" - -**Top bar (flex, space-between):** -- Title: "Intakes" (large, semi-bold, left) -- Button: "+ Nieuwe Intake" (primary style, right) - -**Intakes list (16px margin top):** - -Header row: -- Columns: Datum | Type | Status | Samenvatting | Acties -- Subtle bottom border, muted text, small font -- 8px padding bottom - -Intake item 1 (card-like row): -- Padding: 16px vertical -- Bottom border (subtle) -- Hover state: light background - -Content: -- Date: "12-10-2023, 14:30" (medium weight) -- Type: "Intake gesprek" (small badge/pill, light blue bg) -- Status: "Afgerond" (small badge/pill, green bg) -- Summary: "Eerste intake. Cliënt presenteert zich met angst- en paniekkl..." (truncated ~60 chars, lighter weight) -- Actions: "Bekijk" button (small, secondary style, becomes prominent on row hover) - -Intake item 2: -- Date: "05-10-2023, 10:15" -- Type: "Telefonische intake" (badge) -- Status: "Afgerond" (green badge) -- Summary: "Korte telefonische screening. Doorverwijzing naar intake..." -- Actions: "Bekijk" button - -Intake item 3: -- Date: "28-09-2023, 16:00" -- Type: "Intake gesprek" (badge) -- Status: "Afgerond" (green badge) -- Summary: "Geen show. Cliënt niet verschenen, geen afmelding..." -- Actions: "Bekijk" button - -**Empty state (alternative if no intakes):** -- Centered layout -- Icon: document/clipboard illustration (gray, 64px) -- Text: "Nog geen intakes" (medium, semi-bold) -- Subtext: "Klik op 'Nieuwe Intake' om te beginnen" (small, muted) -- "+ Nieuwe Intake" button (primary, centered, 16px margin top) - -**Layout structure:** -``` -┌──────────────────────────────────────────────────┐ -│ Header: Logo | Bas Jansen ▼ | Search │ -│ | ID: CL0002 | Geb: 20-11-1992 │ -├──────────┬───────────────────────────────────────┤ -│← Cliënten│ Intake [+ Nieuwe Intake] │ -│────── │ │ -│Dashboard │ Datum Type Status Samenvat.. │ -│Intake ← │ ───────────────────────────────── │ -│Diagnose │ 12-10 Intake Afgr. Eerste... │ -│Behandel │ 05-10 Telef. Afgr. Korte... │ -│Rapport │ 28-09 Intake Afgr. Geen sh... │ -│ │ │ -└──────────┴───────────────────────────────────────┘ -``` - ---- - -## Screen 2C: Diagnose (Client Dossier) - -### Header -- **Center**: "Bas Jansen ▼" with ID and birthdate -- **Right**: Search bar available - -### Sidebar -- "← Cliënten" button (returns to behandelaar context) -- Divider -- Dashboard -- Intake -- **Diagnose** ← ACTIVE -- Behandelplan -- Rapportage - -### Main Content -**Top bar:** -- Title: "Diagnose & Probleemprofiel" (large, semi-bold, left) -- Button: "AI Analyse" (primary style with sparkles icon, right) - -**DSM-light Categorieën:** -- Visual grid/dashboard showing classified problem areas -- Categories: - - Stemming & Depressie (blauw) - - Angst (paars) - - Gedrag & Impuls (rood) - - Middelengebruik (oranje) - - Cognitief (groen) - - Context & Psychosociaal (teal) - -**Ernst Indicatie:** -- Visual indicator: Laag (green) | Middel (yellow) | Hoog (red) -- Linked to intake sources (bronverwijzing) - -**Content (to be further defined in Week 3):** -- AI-generated classification based on intake notes -- Manual override/editing capability -- Timeline/history of diagnoses -- Notes and observations section - -**Layout structure:** -``` -┌──────────────────────────────────────────────────┐ -│ Header: Logo | Bas Jansen ▼ | Search │ -├──────────┬───────────────────────────────────────┤ -│← Cliënten│ Diagnose & Probleemprofiel [AI Analyse]│ -│────── │ │ -│Dashboard │ DSM-light Categorieën: │ -│Intake │ ┌────────┬────────┬────────┐ │ -│Diagnose← │ │Stemming│ Angst │ Gedrag │ │ -│Behandel │ │ Hoog │Middel │ Laag │ │ -│Rapport │ └────────┴────────┴────────┘ │ -│ │ ┌──────────────────────────┐ │ -│ │ │ Ernst: ●●●○○ (Hoog) │ │ -│ │ │ Bronnen: Intake 12-10 │ │ -│ │ └──────────────────────────┘ │ -└──────────┴───────────────────────────────────────┘ -``` - ---- - -## Screen 2D: Behandelplan (Client Dossier) - -### Header -- **Center**: "Bas Jansen ▼" with ID and birthdate -- **Right**: Search bar available - -### Sidebar -- "← Cliënten" button (returns to behandelaar context) -- Divider -- Dashboard -- Intake -- Diagnose -- **Behandelplan** ← ACTIVE -- Rapportage - -### Main Content -**Top bar:** -- Title: "Behandelplan" (large, semi-bold, left) -- Buttons: - - "Genereer Plan" (primary style with AI icon, right) - - "Nieuwe Versie" (secondary style, if plan exists) - -**Plan Structuur:** -1. **SMART Doelen** section - - Specifieke, Meetbare, Acceptabele, Realistische, Tijdgebonden doelen - - Multiple goals per plan - - Status tracking per goal - -2. **Interventies** section - - Evidence-based behandelmethoden (CGT, ACT, EMDR, etc.) - - Linked to specific goals - - Description and rationale - -3. **Frequentie & Planning** section - - Sessie planning - - Behandelintensiteit - - Duration estimate - -4. **Meetmomenten** section - - Evaluation schedule - - Progress measurements - - Review dates - -**Versioning:** -- Multiple versions per client (v1, v2, etc.) -- Status: Concept | Actief | Afgerond -- Timestamp and practitioner info - -**Content (to be further defined in Week 3):** -- AI plan generator based on intake + diagnose -- JSONB data structure for flexibility -- PDF export functionality - -**Layout structure:** -``` -┌──────────────────────────────────────────────────┐ -│ Header: Logo | Bas Jansen ▼ | Search │ -├──────────┬───────────────────────────────────────┤ -│← Cliënten│ Behandelplan [Genereer Plan] │ -│────── │ │ -│Dashboard │ Versie 1 - Actief | 12-10-2023 │ -│Intake │ │ -│Diagnose │ 1. SMART Doelen │ -│Behandel← │ ┌──────────────────────────────┐ │ -│Rapport │ │ • Verminderen paniekeaanv.. │ │ -│ │ │ • Uitbreiden sociale activ.. │ │ -│ │ └──────────────────────────────┘ │ -│ │ 2. Interventies │ -│ │ ┌──────────────────────────────┐ │ -│ │ │ • CGT - Cognitieve herstr... │ │ -│ │ │ • Exposure therapie │ │ -│ │ └──────────────────────────────┘ │ -└──────────┴───────────────────────────────────────┘ -``` - ---- - -## Screen 2E: Rapportage (Client Dossier) - -### Header -- **Center**: "Bas Jansen ▼" with ID and birthdate -- **Right**: Search bar available - -### Sidebar -- "← Cliënten" button (returns to behandelaar context) -- Divider -- Dashboard -- Intake -- Diagnose -- Behandelplan -- **Rapportage** ← ACTIVE - -### Main Content -**Page title:** "Rapportage & Voortgang" (large, semi-bold) - -**Content (to be further defined):** -- Client-specific progress metrics -- Treatment timeline -- Session attendance -- Goal achievement tracking -- Measurement instruments (ROM, questionnaires) -- Graphs and visualizations -- Export functionality (PDF, CSV) - -**Layout structure:** -``` -┌──────────────────────────────────────────────────┐ -│ Header: Logo | Bas Jansen ▼ | Search │ -├──────────┬───────────────────────────────────────┤ -│← Cliënten│ Rapportage & Voortgang │ -│────── │ │ -│Dashboard │ ┌──────────────────────────────┐ │ -│Intake │ │ Behandelduur: 8 weken │ │ -│Diagnose │ │ Sessies: 6 van 12 │ │ -│Behandel │ │ Voortgang: ████████░░░░ 67% │ │ -│Rapport ← │ └──────────────────────────────┘ │ -│ │ │ -│ │ Doelvoortgang: │ -│ │ [Graph/Chart placeholder] │ -└──────────┴───────────────────────────────────────┘ -``` - ---- - -## Intake Detail View (Modal/Slide-in) - -**Trigger:** Click "Bekijk" button on any intake item - -**Display options:** -1. Slide-in panel from right (400px width, overlays content) -2. Modal overlay (centered, 600px width, backdrop blur) - -**Recommended: Slide-in panel** (keeps context visible) - -### Slide-in Panel Structure - -**Header (sticky):** -- Background: light gray -- Padding: 16px -- Title: "Intake - 12-10-2023" (medium, semi-bold) -- Close button: "×" (top right, large, clickable) - -**Content (scrollable):** - -Section 1 - Algemene informatie: -- Background: white -- Padding: 16px -- Title: "Algemene informatie" (small, semi-bold, 8px margin bottom) -- Items (label: value): - - Datum & tijd: 12-10-2023, 14:30 - - Type: Intake gesprek - - Behandelaar: [naam] - - Duur: 60 minuten - -Divider (subtle, 16px margin vertical) - -Section 2 - Gespreksnotities: -- Title: "Gespreksnotities" (small, semi-bold) -- Text area (full content, scrollable if long): - "Bas komt op gesprek vanwege spanningsklachten en paniekeaanvallen. Hij beschrijft situaties in het openbaar vervoer en drukke winkels als triggerend. De aanvallen kenmerken zich door hartkloppingen, duizeligheid en benauwdheid. - - Anamnese: Klachten bestaan sinds ongeveer 1 jaar. Begonnen na stressvolle periode op werk. Eerste aanval in supermarkt, sindsdien toenemend vermijdingsgedrag. - - Observatie: Cliënt presenteert zich alert en coöperatief. Spreekt openlijk over klachten. Lichte spanning zichtbaar bij bespreken van aanvallen..." - -Divider - -Section 3 - AI Samenvatting (optional for MVP): -- Background: very light blue (distinguish from manual notes) -- Padding: 12px -- Icon: sparkles/AI icon (small, top left) -- Title: "AI Samenvatting" (small, semi-bold) -- Generated text: bullet points with key findings -- Font: slightly smaller than main text - -Divider - -**Footer (sticky bottom):** -- Background: light gray -- Padding: 16px -- Buttons layout (flex, space-between): - - Left: "Verwijderen" button (destructive/red style, subtle) - - Right: "Bewerken" button (secondary style) - ---- - -## Design Tokens Reference - -### Spacing Scale -- xs: 4px -- sm: 8px -- md: 16px -- lg: 24px -- xl: 32px - -### Border Radius -- Small (badges): 12px -- Medium (buttons): 6px -- Large (cards): 8px - -### Typography Scale -- Page title: 28px, semi-bold -- Card title: 18px, semi-bold -- Section title: 14px, semi-bold -- Body text: 15px, normal -- Small text: 13px, normal -- Tiny text: 12px, normal - -### Shadows -- Card: 0 1px 3px rgba(0,0,0,0.1) -- Card hover: 0 4px 6px rgba(0,0,0,0.1) -- Modal: 0 20px 25px rgba(0,0,0,0.15) - -### Common Patterns - -**Card component:** -- White background -- Border: 1px solid (light gray) -- Border radius: 8px -- Padding: 16px -- Shadow: subtle (see above) - -**Badge/pill component:** -- Small font size (13px) -- Padding: 4px 12px -- Border radius: 12px (full rounded) -- Background: contextual (green for success, yellow for warning, etc.) -- Font weight: medium - -**Button styles:** -- Primary: solid background, white text, medium weight -- Secondary: border outline, normal text -- Destructive: red color scheme -- Padding: 8px 16px -- Border radius: 6px - -**List/table row:** -- Padding: 16px vertical -- Border bottom: 1px solid (light gray) -- Hover: light background change -- Cursor: pointer (if clickable) - ---- - -## Interaction Notes - -### Navigation flow: -1. Start: Behandelaar Dashboard (Level 1) -2. Sidebar navigation: Dashboard | Cliënten | Agenda | Rapportage -3. Click "Cliënten" → Cliënten list view -4. Click client row → Context switch to Level 2 (Client Dossier) -5. Sidebar changes to: ← Cliënten | Dashboard | Intake | Diagnose | Behandelplan | Rapportage -6. Header shows selected client: "Bas Jansen ▼" with dropdown -7. Navigate within client dossier using sidebar menu -8. Click "← Cliënten" → Context switch back to Level 1 -9. Header client dropdown always available (in Level 2) for quick client switching -10. Header search always available for quick client lookup - -### State management: -- Active menu item: always visible (highlighted) -- Breadcrumbs: show current location -- Empty states: guide users to next action -- Loading states: skeleton screens for cards (not specified, but recommended) - -### Accessibility considerations: -- All interactive elements: keyboard accessible -- Focus states: visible outlines -- Color contrast: WCAG AA minimum -- Alt text for icons (not visible, but in code) - ---- - -## Technical Implementation Notes - -### Routing structure (Next.js): -``` -LEVEL 1: Behandelaar Context -/epd/dashboard → Behandelaar dashboard (caseload, taken, aandachtspunten) -/epd/clients → Cliënten list (zoek, filter, recent) -/epd/agenda → Behandelaar agenda (alle afspraken) -/epd/reports → BI rapportage (statistieken, KPI's) - -LEVEL 2: Client Dossier Context -/epd/clients/[id] → Client dashboard (dossier overzicht) -/epd/clients/[id]/intake → Intake lijst en notities -/epd/clients/[id]/intake/[intakeId] → Intake detail (modal/slide-in) -/epd/clients/[id]/diagnose → Diagnose en probleemprofiel -/epd/clients/[id]/plan → Behandelplan (SMART doelen, interventies) -/epd/clients/[id]/reports → Client rapportage (voortgang, metrics) - -Utility routes: -/epd/clients/new → Nieuwe cliënt formulier -/epd/clients/[id]/edit → Cliënt gegevens bewerken -``` - -### Component structure: -``` -app/epd/ -├── layout.tsx // Root EPD layout (detects context) -├── components/ -│ ├── epd-header.tsx // Context-aware header -│ └── epd-sidebar.tsx // Context-aware sidebar -│ -├── dashboard/ -│ └── page.tsx // Behandelaar dashboard -│ -├── clients/ -│ ├── page.tsx // Cliënten lijst (Level 1) -│ ├── components/ -│ │ ├── client-list.tsx -│ │ ├── client-search.tsx -│ │ └── client-filters.tsx -│ ├── new/ -│ │ └── page.tsx // Nieuwe cliënt form -│ └── [id]/ -│ ├── layout.tsx // Client dossier layout (Level 2 context) -│ ├── page.tsx // Client dashboard (dossier overzicht) -│ ├── edit/ -│ │ └── page.tsx // Edit client -│ ├── intake/ -│ │ ├── page.tsx // Intake lijst -│ │ ├── components/ -│ │ │ ├── intake-list.tsx -│ │ │ └── intake-editor.tsx -│ │ └── [intakeId]/ -│ │ └── page.tsx // Intake detail (modal/slide-in) -│ ├── diagnose/ -│ │ ├── page.tsx // Diagnose & probleemprofiel -│ │ └── components/ -│ │ ├── dsm-categories.tsx -│ │ └── severity-indicator.tsx -│ ├── plan/ -│ │ ├── page.tsx // Behandelplan -│ │ └── components/ -│ │ ├── smart-goals.tsx -│ │ ├── interventions.tsx -│ │ └── plan-generator.tsx -│ └── reports/ -│ ├── page.tsx // Client rapportage -│ └── components/ -│ ├── progress-chart.tsx -│ └── metrics-dashboard.tsx -│ -├── agenda/ -│ └── page.tsx // Behandelaar agenda -│ -└── reports/ - └── page.tsx // BI rapportage (behandelaar) -``` - -### Key Implementation Details: - -**app/epd/layout.tsx:** -- Detects current context (Level 1 vs Level 2) based on URL -- Passes context to EPDSidebar and EPDHeader -- Manages global state for selected client - -**app/epd/clients/[id]/layout.tsx:** -- Wraps all client dossier pages -- Fetches client data and provides to children -- Ensures sidebar shows Level 2 navigation -- Ensures header shows client dropdown - -**Context Detection Logic:** -```typescript -// In app/epd/layout.tsx -const isClientDossier = pathname.includes('/clients/') && - pathname.match(/\/clients\/[^\/]+\/?[^\/]*$/); -const clientId = isClientDossier ? pathname.split('/')[3] : null; -``` - -### Data requirements: -- Client list: id, name, bsn, birthdate, status, last_contact -- Dashboard: aggregate data from intakes, problem profile, treatment plan -- Intakes: date, type, status, summary, full_notes, practitioner -- Problem profile: severity, diagnoses, notes -- Treatment plan: goals, interventions, status -- Appointments: date, type, notes - ---- - -## Out of Scope for MVP -- Mobile responsive design -- Dark mode -- Multi-user roles/permissions -- Advanced filtering (beyond search) -- Bulk actions -- Export functionality -- Calendar integration -- Notifications system -- Audit logs -- Custom fields -- Templates for intakes/plans - -These can be added post-MVP based on user feedback. \ No newline at end of file diff --git a/docs/specs/UI/mocks-ui-flow.md b/docs/specs/UI/mocks-ui-flow.md deleted file mode 100644 index d7ddfac..0000000 --- a/docs/specs/UI/mocks-ui-flow.md +++ /dev/null @@ -1,802 +0,0 @@ -# Mini-ECD Interface Mocks & User Flow - -## Navigation Architecture: Two-Level System - -**LEVEL 1: Behandelaar Context** - Praktijkbeheerder overstijgend -- Dashboard → Behandelaar overzicht (caseload, taken, berichten) -- Cliënten → Zoeken, filteren, recent bekeken -- Agenda → Behandelaar agenda (alle afspraken) -- Rapportage → BI dashboards (KPI's, statistieken) - -**LEVEL 2: Client Dossier Context** - Individuele cliënt focus -- Dashboard → Dossier overzicht (laatste info, afspraken) -- Intake → Gesprekken en notities -- Diagnose → DSM-classificatie, probleemprofiel -- Behandelplan → SMART doelen, interventies -- Rapportage → Client voortgang en metrics - -## User Flow Overview -``` -START: Behandelaar Dashboard (Level 1) - ↓ -[Sidebar: Dashboard | Cliënten | Agenda | Rapportage] - ↓ -Klik "Cliënten" - ↓ -[Cliënten Lijst met zoek/filter] ←────────────────┐ - ↓ (klik op cliënt rij) │ - ↓ │ -⚡ CONTEXT SWITCH naar Level 2 ⚡ │ - ↓ │ -[Sidebar verandert] │ -[Header toont: "Bas Jansen ▼"] │ - ↓ │ -[Client Dashboard - Bas Jansen] ←──────┐ │ - ↓ │ │ -[Sidebar: ← Cliënten | Dashboard | Intake | ... ]│ - ↓ │ │ - ├─→ [Intake] │ │ - ├─→ [Diagnose] │ │ - ├─→ [Behandelplan] │ │ - └─→ [Rapportage] │ │ - ↓ │ │ - (sidebar menu switch binnen dossier)─┘ │ - ↓ │ - (klik "← Cliënten" in sidebar)────────────────┘ - ↓ -⚡ CONTEXT SWITCH terug naar Level 1 ⚡ - ↓ - [Terug naar Cliënten Lijst] - ↓ - (header dropdown "Bas Jansen ▼") → Quick switch naar andere client - ↓ - [Client Dashboard - Anna de Vries] -``` - ---- - -## LEVEL 1 MOCKS: Behandelaar Context - ---- - -## Mock 1A: Behandelaar Dashboard (Entry Point) -``` -┌────────────────────────────────────────────────────────────────┐ -│ Mini-ECD [Zoek cliënt...] │ -└────────────────────────────────────────────────────────────────┘ -┌─────────────┬──────────────────────────────────────────────────┐ -│ │ │ -│ Dashboard ◄ │ Dashboard │ -│ Cliënten │ Welkom terug, Dr. van den Berg │ -│ Agenda │ │ -│ Rapportage │ ┌──────────────┬──────────────┬──────────────┐ │ -│ │ │ Caseload │ Aandachts- │ Berichten │ │ -│ │ │ 24 actief │ punten: 3 │ 2 nieuw │ │ -│ │ │ 2 wachtlijst │ │ │ │ -│ │ └──────────────┴──────────────┴──────────────┘ │ -│ │ │ -│ │ Behandelplannen concept: │ -│ │ • Bas Jansen - Review nodig │ -│ │ • Anna de Vries - Nog opstellen │ -│ │ │ -│ │ Aankomende afspraken (vandaag): │ -│ │ • 14:00 - Peter Smit (Intake) │ -│ │ • 16:30 - Bas Jansen (Sessie 3) │ -│ │ │ -└─────────────┴──────────────────────────────────────────────────┘ - -User actions: -→ Bekijk caseload overzicht en statistieken -→ Klik op aandachtspunt: navigeer naar relevante pagina -→ Klik op client naam: navigeer naar client dossier (Level 2 switch) -→ Sidebar navigatie naar andere Level 1 schermen - -Note: Detailed content to be further defined. -``` - ---- - -## Mock 1B: Cliënten Lijst -``` -┌────────────────────────────────────────────────────────────────┐ -│ Mini-ECD [Zoek cliënt...] │ -└────────────────────────────────────────────────────────────────┘ -┌─────────────┬──────────────────────────────────────────────────┐ -│ │ │ -│ Dashboard │ Cliënten [+ Nieuwe Cliënt] │ -│ Cliënten ◄ │ │ -│ Agenda │ [Zoek op naam, BSN, of cliënt ID...........] │ -│ Rapportage │ [Filters: Afdeling ▼ | Team ▼ | Status ▼] │ -│ │ │ -│ │ Recent bekeken: │ -│ │ [Bas J.] [Anna dV.] [Peter S.] │ -│ │ │ -│ │ Naam ID Geb Status │ -│ │ ──────────────────────────────────────── │ -│ │ Bas Jansen CL0002 20-11-1992 ● Actief │ -│ │ Anna de Vries CL0001 15-03-1988 ● Actief │ -│ │ Peter Smit CL0003 22-07-1995 ● Wachtlijst │ -│ │ Maria Jansen CL0004 11-09-1990 ● Actief │ -│ │ │ -│ │ │ -└─────────────┴──────────────────────────────────────────────────┘ - -User actions: -→ Type in zoekbalk: filters lijst real-time (naam, BSN, ID, geboortedatum) -→ Selecteer filters: Afdeling, Team, Status -→ Klik recent bekeken: snel naar die client -→ Klik op rij (bijv. Bas Jansen): ⚡ CONTEXT SWITCH → Client Dossier (Level 2) -→ Klik [+ Nieuwe Cliënt]: open modal/form om nieuwe cliënt toe te voegen -``` - ---- - -## Mock 1C: Behandelaar Agenda -``` -┌────────────────────────────────────────────────────────────────┐ -│ Mini-ECD [Zoek cliënt...] │ -└────────────────────────────────────────────────────────────────┘ -┌─────────────┬──────────────────────────────────────────────────┐ -│ │ │ -│ Dashboard │ Agenda [+ Nieuwe Afspraak] │ -│ Cliënten │ Week 46 | 13-19 Nov 2025 [< Vorige >]│ -│ Agenda ◄ │ │ -│ Rapportage │ [Kalender weergave placeholder] │ -│ │ │ -│ │ Vandaag - 19 november 2025: │ -│ │ ┌────────────────────────────────────────────┐ │ -│ │ │ 14:00-15:00 Peter Smit - Intake │ │ -│ │ │ 16:30-17:30 Bas Jansen - Sessie 3 │ │ -│ │ └────────────────────────────────────────────┘ │ -│ │ │ -│ │ Morgen - 20 november: │ -│ │ ┌────────────────────────────────────────────┐ │ -│ │ │ 10:00-11:00 Anna de Vries - Evaluatie │ │ -│ │ └────────────────────────────────────────────┘ │ -└─────────────┴──────────────────────────────────────────────────┘ - -User actions: -→ Bekijk agenda (week/maand weergave) -→ Klik op afspraak: bekijk details of navigeer naar client dossier -→ Klik [+ Nieuwe Afspraak]: open afspraak formulier -→ Navigeer tussen weken met [< >] knoppen - -Note: Detailed calendar component to be specified. -``` - ---- - -## Mock 1D: Behandelaar Rapportage -``` -┌────────────────────────────────────────────────────────────────┐ -│ Mini-ECD [Zoek cliënt...] │ -└────────────────────────────────────────────────────────────────┘ -┌─────────────┬──────────────────────────────────────────────────┐ -│ │ │ -│ Dashboard │ Rapportage [Exporteer PDF] │ -│ Cliënten │ Overzicht praktijk statistieken │ -│ Agenda │ │ -│ Rapportage◄ │ ┌──────────────┬──────────────┬──────────────┐ │ -│ │ │ Totaal │ Gemiddelde │ Wachtlijst │ │ -│ │ │ Cliënten │ Behandel- │ Wachttijd │ │ -│ │ │ 26 │ duur: 12 wk │ 2 weken │ │ -│ │ └──────────────┴──────────────┴──────────────┘ │ -│ │ │ -│ │ [Graph: Cliënten over tijd] │ -│ │ [Graph: Behandelduur distributie] │ -│ │ [Graph: Diagnose categorieën] │ -│ │ │ -└─────────────┴──────────────────────────────────────────────────┘ - -User actions: -→ Bekijk BI dashboards en KPI's -→ Interactie met grafieken (zoom, filter) -→ Klik [Exporteer PDF]: download rapport -→ Filter op periode, afdeling, team - -Note: Detailed BI components and metrics to be defined. -``` - ---- - -## LEVEL 2 MOCKS: Client Dossier Context - -**Context:** User heeft op een cliënt geklikt in de cliëntenlijst -**State:** Sidebar en Header zijn veranderd naar client-specifieke weergave - ---- - -## Mock 2A: Client Dashboard (Dossier Overzicht - Bas Jansen) -``` -┌────────────────────────────────────────────────────────────────┐ -│ Mini-ECD Bas Jansen ▼ [Zoek cliënt...] │ -│ ID: CL0002 | Geb: 20-11-1992 │ -└────────────────────────────────────────────────────────────────┘ -┌─────────────┬──────────────────────────────────────────────────┐ -│ │ │ -│ ← Cliënten │ Dossier Overzicht │ -│ ───────── │ │ -│ Dashboard ◄ │ ┌──────────────┬──────────────┬──────────────┐ │ -│ Intake │ │Cliëntinfo │Laatste Intake│Diagnose │ │ -│ Diagnose │ │ │ │ │ │ -│ Behandel │ │ID: CL0002 │12-10-2023 │Ernst: Hoog ● │ │ -│ Rapportage │ │Naam: Bas J. │14:30 │Angststoornis │ │ -│ │ │Geb: 20-11-92 │ │Panietstoornis│ │ -│ │ │Leeftijd: 31 │"Bas komt op │met agorafobie│ │ -│ │ │Verz: [naam] │gesprek..." │ │ │ -│ │ │BSN: [nummer] │ │ │ │ -│ │ │ │Bekijk → │Bekijk → │ │ -│ │ └──────────────┴──────────────┴──────────────┘ │ -│ │ │ -│ │ ┌────────────────────────────────────────────┐ │ -│ │ │ Behandelplan Status: Concept │ │ -│ │ │ │ │ -│ │ │ SMART Doelen (3): │ │ -│ │ │ • Verminderen paniekeaanvallen │ │ -│ │ │ • Uitbreiden sociale activiteiten │ │ -│ │ │ • Verbeteren coping strategieën │ │ -│ │ │ │ │ -│ │ │ Interventies: CGT, Exposure therapie │ │ -│ │ │ Bekijk plan → │ │ -│ │ └────────────────────────────────────────────┘ │ -│ │ │ -│ │ ┌────────────────────────────────────────────┐ │ -│ │ │ Afspraken │ │ -│ │ │ │ │ -│ │ │ Afgelopen Afspraak │ │ -│ │ │ │ 14-11-2025, 16:26 - Intake (60 min) │ │ -│ │ │ │ │ -│ │ │ Komende Afspraken │ │ -│ │ │ │ 22-11-2025, 16:26 - Psycho-educatie │ │ -│ │ │ │ 29-11-2025, 16:26 - Exposure therapie │ │ -│ │ │ │ 06-12-2025, 16:26 - Evaluatie gesprek │ │ -│ │ └────────────────────────────────────────────┘ │ -│ │ │ -└─────────────┴──────────────────────────────────────────────────┘ - -User actions: -→ Klik "← Cliënten": ⚡ CONTEXT SWITCH terug naar Level 1 (cliëntenlijst) -→ Klik "Bas Jansen ▼": dropdown met recente cliënten voor quick switch -→ Type in [Zoek cliënt]: autocomplete zoeken, direct switchen naar andere client -→ Klik sidebar item (bijv. Intake): navigeer binnen client dossier -→ Klik "Bekijk →" links: navigeer naar detail sectie -→ Klik [+ Maak behandelplan]: open behandelplan wizard (als leeg) -``` - ---- - -## Mock 2B: Intake (Client Dossier - Bas Jansen) -``` -┌────────────────────────────────────────────────────────────────┐ -│ Mini-ECD Bas Jansen ▼ [Zoek cliënt...] │ -│ ID: CL0002 | Geb: 20-11-1992 │ -└────────────────────────────────────────────────────────────────┘ -┌─────────────┬──────────────────────────────────────────────────┐ -│ │ │ -│ ← Cliënten │ Intake [+ Nieuwe Intake] │ -│ ───────── │ │ -│ Dashboard │ Datum Type Status Samenvatting │ -│ Intake ◄ │ ───────────────────────────────────────────── │ -│ Diagnose │ │ -│ Behandel │ 12-10-2023 Intake ●Afgerond │ -│ Rapportage │ 14:30 gesprek │ -│ │ Eerste intake. Cliënt presenteert │ -│ │ zich met angst- en paniekkl... │ -│ │ [Bekijk] │ -│ │ ───────────────────────────────────────────────│ -│ │ │ -│ │ 05-10-2023 Telefonisch ●Afgerond │ -│ │ 10:15 intake │ -│ │ Korte telefonische screening. │ -│ │ Doorverwijzing naar intake... │ -│ │ [Bekijk] │ -│ │ ───────────────────────────────────────────────│ -│ │ │ -│ │ 28-09-2023 Intake ●Afgerond │ -│ │ 16:00 gesprek │ -│ │ Geen show. Cliënt niet verschenen, │ -│ │ geen afmelding... │ -│ │ [Bekijk] │ -│ │ │ -└─────────────┴──────────────────────────────────────────────────┘ - -User actions: -→ Klik [+ Nieuwe Intake]: open TipTap editor voor nieuwe intake notitie -→ Klik [Bekijk]: open intake detail slide-in panel (zie Mock 2F) -→ Hover over rij: highlight, [Bekijk] button wordt prominent -→ Sidebar navigatie: blijf binnen client dossier context -→ Klik "← Cliënten": terug naar Level 1 -``` - ---- - -## Mock 2C: Diagnose (Client Dossier - Bas Jansen) -``` -┌────────────────────────────────────────────────────────────────┐ -│ Mini-ECD Bas Jansen ▼ [Zoek cliënt...] │ -│ ID: CL0002 | Geb: 20-11-1992 │ -└────────────────────────────────────────────────────────────────┘ -┌─────────────┬──────────────────────────────────────────────────┐ -│ │ │ -│ ← Cliënten │ Diagnose & Probleemprofiel [AI Analyse] │ -│ ───────── │ │ -│ Dashboard │ DSM-light Categorieën: │ -│ Intake │ │ -│ Diagnose ◄ │ ┌──────────┬──────────┬──────────┐ │ -│ Behandel │ │ Stemming │ Angst │ Gedrag │ │ -│ Rapportage │ │ ○○○○○ │ ●●●●○ │ ○○○○○ │ │ -│ │ │ Geen │ Hoog │ Geen │ │ -│ │ └──────────┴──────────┴──────────┘ │ -│ │ ┌──────────┬──────────┬──────────┐ │ -│ │ │ Middelen │ Cognitief│ Context │ │ -│ │ │ ○○○○○ │ ○○○○○ │ ●●○○○ │ │ -│ │ │ Geen │ Geen │ Middel │ │ -│ │ └──────────┴──────────┴──────────┘ │ -│ │ │ -│ │ Hoofddiagnose: │ -│ │ ┌────────────────────────────────────────────┐ │ -│ │ │ Panietstoornis met agorafobie │ │ -│ │ │ Ernst: Hoog ●●●●○ │ │ -│ │ │ │ │ -│ │ │ Bron: Intake 12-10-2023 │ │ -│ │ │ Observaties: Frequente aanvallen, sterke │ │ -│ │ │ vermijding openbare ruimtes │ │ -│ │ └────────────────────────────────────────────┘ │ -│ │ │ -└─────────────┴──────────────────────────────────────────────────┘ - -User actions: -→ Klik [AI Analyse]: genereer/update diagnose classificatie -→ Bekijk DSM-categorieën met ernst indicatie -→ Klik op categorie: bekijk details en bronnen -→ Bewerk diagnose handmatig indien nodig - -Note: Week 3 feature - AI-powered classification -``` - ---- - -## Mock 2D: Behandelplan (Client Dossier - Bas Jansen) -``` -┌────────────────────────────────────────────────────────────────┐ -│ Mini-ECD Bas Jansen ▼ [Zoek cliënt...] │ -│ ID: CL0002 | Geb: 20-11-1992 │ -└────────────────────────────────────────────────────────────────┘ -┌─────────────┬──────────────────────────────────────────────────┐ -│ │ │ -│ ← Cliënten │ Behandelplan [Genereer Plan] [v1▼] │ -│ ───────── │ Status: Actief | Laatste update: 15-10-2023 │ -│ Dashboard │ │ -│ Intake │ 1. SMART Doelen │ -│ Diagnose │ ┌────────────────────────────────────────────┐ │ -│ Behandel ◄ │ │ Doel 1: Verminderen paniekeaanvallen │ │ -│ Rapportage │ │ Van 3x/week naar max 1x/week binnen 8 wk │ │ -│ │ │ Voortgang: ████████░░░░ 67% │ │ -│ │ │ │ │ -│ │ │ Doel 2: Uitbreiden sociale activiteiten │ │ -│ │ │ Zelfstandig boodschappen binnen 6 weken │ │ -│ │ │ Voortgang: ████░░░░░░░░ 33% │ │ -│ │ └────────────────────────────────────────────┘ │ -│ │ │ -│ │ 2. Interventies │ -│ │ ┌────────────────────────────────────────────┐ │ -│ │ │ • CGT - Cognitieve herstructurering │ │ -│ │ │ • Exposure therapie (gradueel) │ │ -│ │ │ • Ontspanningstechnieken │ │ -│ │ └────────────────────────────────────────────┘ │ -│ │ │ -│ │ 3. Frequentie: Wekelijks, 12 sessies │ -│ │ 4. Meetmomenten: Week 4, 8, 12 │ -│ │ │ -└─────────────┴──────────────────────────────────────────────────┘ - -User actions: -→ Klik [Genereer Plan]: AI genereert behandelplan op basis van intake+diagnose -→ Klik [v1▼]: bekijk of wissel tussen plan versies -→ Bewerk doelen, interventies, frequentie -→ Track voortgang per doel -→ Exporteer plan naar PDF - -Note: Week 3 feature - AI plan generator -``` - ---- - -## Mock 2E: Rapportage (Client Dossier - Bas Jansen) -``` -┌────────────────────────────────────────────────────────────────┐ -│ Mini-ECD Bas Jansen ▼ [Zoek cliënt...] │ -│ ID: CL0002 | Geb: 20-11-1992 │ -└────────────────────────────────────────────────────────────────┘ -┌─────────────┬──────────────────────────────────────────────────┐ -│ │ │ -│ ← Cliënten │ Rapportage & Voortgang [Exporteer PDF] │ -│ ───────── │ │ -│ Dashboard │ ┌──────────────┬──────────────┬──────────────┐ │ -│ Intake │ │ Behandel- │ Sessies │ Algehele │ │ -│ Diagnose │ │ duur │ Voltooid │ Voortgang │ │ -│ Behandel │ │ 8 weken │ 6 van 12 │ 67% │ │ -│ Rapportage◄ │ └──────────────┴──────────────┴──────────────┘ │ -│ │ │ -│ │ Doelvoortgang over tijd: │ -│ │ [Line graph showing progress on goals] │ -│ │ │ -│ │ Sessie overzicht: │ -│ │ ┌────────────────────────────────────────────┐ │ -│ │ │ 12-10 Intake ✓ Aanwezig │ │ -│ │ │ 19-10 Sessie 1 ✓ Aanwezig │ │ -│ │ │ 26-10 Sessie 2 ✓ Aanwezig │ │ -│ │ │ 02-11 Sessie 3 ✓ Aanwezig │ │ -│ │ │ 09-11 Sessie 4 ✗ No-show │ │ -│ │ │ 16-11 Sessie 5 ✓ Aanwezig │ │ -│ │ └────────────────────────────────────────────┘ │ -│ │ │ -└─────────────┴──────────────────────────────────────────────────┘ - -User actions: -→ Bekijk voortgang visualisaties -→ Filter op periode, doel, interventie -→ Klik [Exporteer PDF]: download rapport voor dossier -→ Bekijk attendance en sessie details - -Note: Content and metrics to be further defined. -``` - ---- - -## Mock 2F: Intake Detail (Slide-in Panel) -``` -┌────────────────────────────────────────────────────────────────┐ -│ Mini-ECD Bas Jansen ▼ [Zoek cliënt...] │ -│ ID: CL0002 | Geb: 20-11-1992 │ -└────────────────────────────────────────────────────────────────┘ -┌─────────────┬──────────────────────────┬───────────────────────┐ -│ │ │ │ -│ ← Cliënten │ Intake [+ Nieuwe] │ Intake - 12-10-2023 ✕ │ -│ ───────── │ │ ─────────────────────│ -│ Dashboard │ Datum Type Status │ Algemene informatie │ -│ Intake ◄ │ ─────────────────── │ │ -│ Diagnose │ │ Datum & tijd: │ -│ Behandel │ 12-10 Intake ●Afgr. │ 12-10-2023, 14:30 │ -│ Rapportage │ [row highlighted] │ │ -│ │ [Bekijk] │ Type: Intake gesprek │ -│ │ ───────────────── │ Behandelaar: [naam] │ -│ │ │ Duur: 60 minuten │ -│ │ 05-10 Telef. ●Afgr. │ │ -│ │ [Bekijk] │ ─────────────────── │ -│ │ ───────────────── │ │ -│ │ │ Gespreksnotities │ -│ │ 28-09 Intake ●Afgr. │ │ -│ │ [Bekijk] │ Bas komt op gesprek │ -│ │ │ vanwege spannings- │ -│ │ │ klachten en paniek- │ -│ │ │ aanvallen. Hij be- │ -│ │ │ schrijft situaties │ -│ │ │ in het openbaar... │ -│ │ │ │ -│ │ │ Anamnese: Klachten │ -│ │ │ bestaan sinds 1 jaar │ -│ │ │ Begonnen na stress- │ -│ │ │ volle periode... │ -│ │ │ │ -│ │ │ [scrollable content] │ -│ │ │ │ -│ │ │ ─────────────────── │ -│ │ │ │ -│ │ │ [Verwijderen] [Bewerken]│ -│ │ │ │ -└─────────────┴──────────────────────────┴───────────────────────┘ - -User actions: -→ Klik ✕: sluit panel, terug naar Intakes lijst -→ Klik [Bewerken]: open intake in edit mode -→ Klik [Verwijderen]: confirmatie dialog → verwijder intake -→ Scroll in panel: bekijk volledige notities -→ Klik buiten panel (op main content): sluit panel -``` - ---- - -## Mock 2G: Client Switcher (Dropdown from Header) - -**Context:** Alleen beschikbaar in Level 2 (Client Dossier Context) - -``` -┌────────────────────────────────────────────────────────────────┐ -│ Mini-ECD ┌─────────────────────────┐ [Zoek cliënt...] │ -│ │ Bas Jansen ▼ │ │ -│ │ ID: CL0002 | 20-11-1992 │ │ -│ ├─────────────────────────┤ │ -│ │ Recente cliënten: │ │ -│ │ ───────────────────── │ │ -│ │ ● Anna de Vries │ │ -│ │ CL0001 | 15-03-1988 │ │ -│ │ │ │ -│ │ ● Peter Smit │ │ -│ │ CL0003 | 22-07-1995 │ │ -│ │ │ │ -│ │ ● Maria Jansen │ │ -│ │ CL0004 | 11-09-1990 │ │ -│ │ │ │ -│ │ ───────────────────── │ │ -│ │ [Alle cliënten →] │ │ -│ └─────────────────────────┘ │ -└────────────────────────────────────────────────────────────────┘ - -User actions: -→ Klik op naam in dropdown: switch naar die cliënt (blijf op zelfde sectie) - Bijv: Op Intake pagina van Bas → klik Anna → ga naar Intake pagina van Anna -→ Klik [Alle cliënten →]: ⚡ CONTEXT SWITCH naar Level 1 (cliëntenlijst) -→ Klik buiten dropdown: sluit dropdown -→ Type in zoekbalk: autocomplete met alle cliënten - -Note: Recent cliënten worden automatisch bijgewerkt bij navigatie -``` - ---- - -## Complete User Flow Scenarios - -### Scenario 1: Start applicatie → Client dossier bekijken -``` -1. User logt in en start op [Behandelaar Dashboard] (Level 1) -2. Bekijk caseload overzicht en aandachtspunten -3. Klik sidebar "Cliënten" -4. Navigeer naar [Cliënten Lijst] -5. Type "Bas" in zoekbalk, zie gefilterde resultaten -6. Klik op "Bas Jansen" rij -7. ⚡ CONTEXT SWITCH naar Level 2 -8. Sidebar verandert: "← Cliënten | Dashboard | Intake | ..." -9. Header toont: "Bas Jansen ▼ | ID: CL0002 | Geb: 20-11-1992" -10. Bekijk [Client Dashboard - Bas Jansen] met overzicht -``` - -### Scenario 2: Nieuwe intake toevoegen -``` -1. User zit in [Client Dashboard - Bas Jansen] (Level 2) -2. Klik sidebar "Intake" -3. Navigeer naar [Intake - Bas Jansen] -4. Klik [+ Nieuwe Intake] -5. TipTap editor opent voor nieuwe notitie -6. Type gespreknotities met rich text formatting -7. Klik [AI Samenvatting] (optioneel, Week 3 feature) -8. AI genereert samenvatting in 5 seconden -9. Klik [Opslaan] -10. Nieuwe intake verschijnt bovenaan lijst met status badge -``` - -### Scenario 3: Tussen cliënten switchen tijdens werk -``` -1. User zit in [Intake - Bas Jansen] (Level 2) -2. Klik "Bas Jansen ▼" in header center -3. Dropdown opent met recente cliënten -4. Klik "Anna de Vries" -5. Blijf in Level 2, navigeer naar [Intake - Anna de Vries] -6. Sidebar blijft client-specifiek, header update naar Anna's gegevens -``` - -### Scenario 4: Van client dossier terug naar behandelaar overzicht -``` -1. User zit in [Behandelplan - Bas Jansen] (Level 2) -2. Klaar met werk aan behandelplan -3. Klik "← Cliënten" in sidebar -4. ⚡ CONTEXT SWITCH terug naar Level 1 -5. Sidebar verandert: "Dashboard | Cliënten | Agenda | Rapportage" -6. Header center wordt leeg (geen client dropdown) -7. Navigeer naar [Cliënten Lijst] -8. User kan nieuwe client selecteren of naar ander Level 1 scherm -``` - -### Scenario 5: Behandelplan genereren met AI -``` -1. User zit in [Client Dashboard - Bas Jansen] (Level 2) -2. Bekijk diagnose card: "Ernst: Hoog - Panietstoornis" -3. Bekijk behandelplan card: "Geen behandelplan gevonden" -4. Klik [+ Maak behandelplan] -5. Navigeer naar [Behandelplan - Bas Jansen] -6. Klik [Genereer Plan] button -7. AI analyseert intake notities + diagnose -8. Plan wordt gegenereerd met SMART doelen en interventies -9. User beoordeelt en past aan indien nodig -10. Klik [Opslaan als Concept] of [Publiceren] -11. Plan verschijnt op Client Dashboard met status badge -``` - -### Scenario 6: Intake detail bekijken en bewerken -``` -1. User zit in [Intake - Bas Jansen] (Level 2) -2. Klik [Bekijk] bij intake van 12-10-2023 -3. Slide-in panel opent aan rechterkant (400px breed) -4. Bekijk algemene informatie en volledige gespreksnotities -5. Scroll door notities, lees AI samenvatting (indien aanwezig) -6. Klik [Bewerken] in panel footer -7. TipTap editor opent in bewerkmodus -8. Wijzig notities, voeg toe, pas formatting aan -9. Klik [Opslaan] -10. Panel sluit, terug naar [Intake - Bas Jansen] lijst -11. Gewijzigde intake toont "Bewerkt" timestamp -``` - -### Scenario 7: Quick search tussen contexten -``` -1. User zit in [Behandelaar Dashboard] (Level 1) -2. Type "CL0002" in header zoekbalk -3. Autocomplete toont "Bas Jansen - CL0002 | 20-11-1992" -4. Klik op resultaat -5. ⚡ CONTEXT SWITCH naar Level 2 -6. Navigeer direct naar [Client Dashboard - Bas Jansen] -7. Header en sidebar passen zich aan - -Alternative vanaf Level 2: -1. User zit in [Diagnose - Anna de Vries] (Level 2) -2. Type "Peter" in header zoekbalk -3. Autocomplete toont "Peter Smit - CL0003" -4. Klik op resultaat -5. Blijf in Level 2, navigeer naar [Diagnose - Peter Smit] -``` - ---- - -## Navigation Rules - -### Context Switching (Level 1 ↔ Level 2): - -**Van Level 1 naar Level 2:** -- Trigger: Klik op een client rij in cliëntenlijst -- Effect: Sidebar verandert naar client-specifiek menu -- Effect: Header center toont client dropdown "Bas Jansen ▼" -- Navigeert naar: Client Dashboard (dossier overzicht) - -**Van Level 2 naar Level 1:** -- Trigger: Klik "← Cliënten" button in sidebar -- Effect: Sidebar verandert terug naar behandelaar menu -- Effect: Header center wordt leeg/disabled -- Navigeert naar: Cliënten Lijst -- Keyboard shortcut: Esc (of Alt+C) - -### Sidebar Behavior: - -**In Level 1 (Behandelaar Context):** -- Menu items: Dashboard | Cliënten | Agenda | Rapportage -- Geen "← Cliënten" button -- Active state op huidige pagina -- Navigatie blijft binnen Level 1 - -**In Level 2 (Client Dossier Context):** -- "← Cliënten" button bovenaan (back to Level 1) -- Divider -- Menu items: Dashboard | Intake | Diagnose | Behandelplan | Rapportage -- Active state op huidige sectie -- Navigatie blijft binnen hetzelfde client dossier -- Switchen tussen clients via header dropdown blijft in Level 2 - -### Header Client Dropdown: - -**Alleen beschikbaar in Level 2:** -- Toont geselecteerde client: "Bas Jansen ▼" -- Sub-text: "ID: CL0002 | Geb: 20-11-1992" -- Dropdown inhoud: - - Recente cliënten (laatste 3-5 bezocht) - - Divider - - "Alle cliënten →" link (navigeert naar Level 1) -- Switchen behoudt huidige sectie type - - Bijv: Intake van Bas → klik Anna → Intake van Anna -- Keyboard shortcut: Alt+K - -**In Level 1:** -- Geen client dropdown (of disabled/grayed out) -- Optioneel: Behandelaar naam/info - -### Header Zoekbalk: - -**Beschikbaar in beide levels:** -- Live autocomplete tijdens typen -- Zoekt op: naam, BSN, cliënt ID, geboortedatum -- Toont: Naam - ID | Geboortedatum in resultaten -- Keyboard shortcut: Cmd/Ctrl+K - -**Gedrag in Level 1:** -- Enter of klik op resultaat: Switch naar Level 2, navigeer naar Client Dashboard - -**Gedrag in Level 2:** -- Enter of klik op resultaat: Blijf in Level 2, switch naar gekozen client -- Behoudt huidige sectie (blijf op Intake als je op Intake zat) - -### Active State Indicators: - -**Sidebar:** -- Highlighted background (licht blauw/grijs) -- Bold text -- Left border accent (3px teal/blauw) -- Icon kleurt mee - -**Tab/Section:** -- Visual feedback dat huidige sectie actief is -- Breadcrumb (optioneel) toont pad - -### URL Structure & Browser Back: - -**URL patterns:** -``` -Level 1: -/epd/dashboard → Behandelaar dashboard -/epd/clients → Cliënten lijst -/epd/agenda → Behandelaar agenda -/epd/reports → Rapportage/BI - -Level 2: -/epd/clients/CL0002 → Client dashboard -/epd/clients/CL0002/intake → Intake sectie -/epd/clients/CL0002/diagnose → Diagnose sectie -/epd/clients/CL0002/plan → Behandelplan -/epd/clients/CL0002/reports → Client rapportage -``` - -**Browser back button:** -- Werkt zoals verwacht door URL geschiedenis -- Van Level 2 terug naar vorige Level 2 pagina -- Van Client Dashboard terug naar Cliënten Lijst (Level 1) -- Sidebar en header passen zich automatisch aan op basis van URL - ---- - -## State Persistence - -### User komt terug in app: -- Heropent laatste bezochte scherm (via localStorage of session) -- Als dat Level 2 was: herstel client context (sidebar + header) -- Als dat Level 1 was: toon behandelaar context -- Als sessie verlopen: start bij [Behandelaar Dashboard] (Level 1) - -### Browser refresh: -- URL bepaalt welk scherm en context -- URL parsing detecteert automatisch Level 1 vs Level 2 -- Sidebar en header worden automatisch geconfigureerd - -**Level 1 URL's:** -``` -/epd/dashboard → Behandelaar dashboard -/epd/clients → Cliënten lijst -/epd/agenda → Behandelaar agenda -/epd/reports → BI rapportage -``` - -**Level 2 URL's:** -``` -/epd/clients/CL0002 → Client dashboard (Bas) -/epd/clients/CL0002/intake → Intake van Bas -/epd/clients/CL0002/diagnose → Diagnose van Bas -/epd/clients/CL0002/plan → Behandelplan van Bas -``` - -### Recent clients lijst: -- Opgeslagen in localStorage: `recent_clients: string[]` -- Wordt bijgewerkt bij elke client view (Level 2 entry) -- Max 5 items -- Gesorteerd op laatst bezocht (nieuwste bovenaan) -- Format per item: -```typescript -{ - id: string; // "CL0002" - name: string; // "Bas Jansen" - birthDate: string; // "20-11-1992" - lastVisited: Date; // timestamp -} -``` - -### Context State Management: -- Global state tracks: `currentContext: 'behandelaar' | 'client'` -- If context === 'client', also track: `selectedClientId: string` -- Sidebar component subscribes to context changes -- Header component subscribes to context + selectedClient changes -- Context changes trigger sidebar/header re-render - -### Scroll Position & Form State: -- Scroll position bewaard per pagina (Level 1 en Level 2 apart) -- Formulieren met unsaved changes: waarschuwing bij navigatie -- TipTap editor: auto-save draft elke 30 seconden (localStorage) - -### URL Query Parameters (optioneel): -- Tab states: `/epd/clients/CL0002?tab=intake` -- Filter states: `/epd/clients?status=actief&team=team-a` -- Sorting: `/epd/clients?sortBy=name&order=asc` \ No newline at end of file diff --git a/docs/specs/agenda/bouwplan-agenda-module-v1.0.md b/docs/specs/agenda/bouwplan-agenda-module-v1.0.md deleted file mode 100644 index 7e34435..0000000 --- a/docs/specs/agenda/bouwplan-agenda-module-v1.0.md +++ /dev/null @@ -1,506 +0,0 @@ -# Bouwplan — Agenda Module - -**Projectnaam:** Mini-EPD Agenda Module -**Versie:** v1.0 -**Datum:** 02-12-2024 -**Auteur:** Colin - ---- - -## 1. Doel en context - -**Doel:** Een volledige agendafunctionaliteit bouwen voor het Mini-EPD systeem waarmee behandelaars afspraken kunnen plannen, beheren en koppelen aan EPD-documenten. - -**Toelichting:** De agenda is een kernfunctionaliteit binnen elk EPD-systeem. Momenteel bestaat er alleen een placeholder pagina op `/epd/agenda`. Deze module bouwt voort op de bestaande `encounters` tabel (FHIR-compliant) en breidt deze uit met volledige kalenderfunctionaliteit en bidirectionele koppelingen met rapportages. - -**Kernfunctionaliteiten:** -- Kalenderweergaven (dag/week/werkdagen) -- Afspraakbeheer (maken, verzetten, annuleren) -- Patiënt-selectie bij afspraken -- Koppeling afspraak ↔ verslag (bidirectioneel) - ---- - -## 2. Uitgangspunten - -### 2.1 Technische Stack - -| Component | Technologie | -|-----------|-------------| -| **Frontend** | Next.js 14 (App Router) + React 18 + TypeScript | -| **UI Components** | shadcn/ui (Radix primitives) + Tailwind CSS | -| **Calendar Library** | FullCalendar (@fullcalendar/react) | -| **Datepicker** | shadcn/ui Calendar (bestaand) | -| **Database** | Supabase (PostgreSQL) | -| **Date Handling** | date-fns (al in project) | -| **State Management** | React Server Components + Server Actions | - -### 2.2 Projectkaders - -| Aspect | Waarde | -|--------|--------| -| **Scope** | MVP met uitbreidingsmogelijkheden | -| **Data** | Bestaande `encounters` tabel + uitbreiding `reports` | -| **Integratie** | Naadloos met bestaande patient/intake flows | -| **Gebruikers** | Behandelaars (practitioners) | - -### 2.3 Programmeer Uitgangspunten - -**Bestaande patronen volgen:** -- Server Actions voor mutaties (`actions.ts` per route) -- API routes voor complexe queries -- FHIR-compliant datastructuren -- shadcn/ui component styling - -**Code Quality:** -- TypeScript strict mode -- Zod validatie voor alle inputs -- Error boundaries voor UI failures -- Optimistic updates waar mogelijk - ---- - -## 3. Epics & Stories Overzicht - -| Epic ID | Titel | Doel | Status | Stories | -|---------|-------|------|--------|---------| -| E0 | Database & Types | Schema uitbreiden, types genereren | ✅ Done | 3 | -| E1 | Calendar Views | Dag/week/werkdagen weergaven | ✅ Done | 4 | -| E2 | Afspraak CRUD | Maken, bewerken, annuleren | ✅ Done | 5 | -| E3 | Patiënt Integratie | Selectie, zoeken, quick-create | ✅ Done | 3 | -| E4 | EPD Koppeling | Verslag ↔ Afspraak bidirectioneel | ✅ Done | 4 | -| E5 | Polish & Testing | UX verfijning, edge cases | ⏳ To Do | 3 | - ---- - -## 4. Epics & Stories (Uitwerking) - -### Epic 0 — Database & Types - -**Epic Doel:** Database schema uitbreiden voor report-encounter koppeling en TypeScript types updaten. - -| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | SP | -|----------|--------------|---------------------|--------|------------------|----| -| E0.S1 | Migration: encounter_id toevoegen aan reports | `encounter_id` en `intake_id` kolommen bestaan, foreign keys werken | ✅ | — | 2 | -| E0.S2 | Index toevoegen voor performance | `idx_reports_encounter`, `idx_encounters_period` indices bestaan | ✅ | E0.S1 | 1 | -| E0.S3 | TypeScript types regenereren | `database.types.ts` bevat nieuwe kolommen | ✅ | E0.S2 | 1 | - -**Technical Notes:** -```sql --- Migration E0.S1 -ALTER TABLE reports -ADD COLUMN encounter_id UUID REFERENCES encounters(id) ON DELETE SET NULL, -ADD COLUMN intake_id UUID REFERENCES intakes(id) ON DELETE SET NULL; - --- Migration E0.S2 -CREATE INDEX idx_reports_encounter ON reports(encounter_id); -CREATE INDEX idx_reports_intake ON reports(intake_id); -CREATE INDEX idx_encounters_period ON encounters(period_start, period_end); -CREATE INDEX idx_encounters_practitioner ON encounters(practitioner_id); -``` - ---- - -### Epic 1 — Calendar Views - -**Epic Doel:** Interactieve kalenderweergaven met dag, week en werkdagen views. - -| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | SP | -|----------|--------------|---------------------|--------|------------------|----| -| E1.S1 | FullCalendar installatie & setup | Library geïnstalleerd, basis component rendert | ✅ | E0.S3 | 2 | -| E1.S2 | Dag view implementeren | Uurblokken 08:00-18:00, afspraken zichtbaar | ✅ | E1.S1 | 3 | -| E1.S3 | Week view implementeren | 7-dagen grid, drag-resize werkt | ✅ | E1.S2 | 3 | -| E1.S4 | Werkdagen view (ma-vr) | Filter voor weekend, business hours highlight | ✅ | E1.S3 | 2 | - -**Technical Notes:** -```bash -pnpm add @fullcalendar/react @fullcalendar/daygrid @fullcalendar/timegrid @fullcalendar/interaction -``` - -**Component Structuur:** -``` -app/epd/agenda/ -├── page.tsx # Server component, data fetching -├── components/ -│ ├── agenda-calendar.tsx # FullCalendar wrapper (client) -│ ├── agenda-toolbar.tsx # View switcher, date nav -│ ├── agenda-sidebar.tsx # Mini calendar + filters -│ └── appointment-card.tsx # Event rendering -├── actions.ts # Server actions -└── types.ts # Agenda-specific types -``` - -**Styling:** -- FullCalendar CSS overschrijven met Tailwind -- Consistent met shadcn/ui design tokens -- Dark mode support (later) - ---- - -### Epic 2 — Afspraak CRUD - -**Epic Doel:** Volledige afspraakbeheer functionaliteit. - -| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | SP | -|----------|--------------|---------------------|--------|------------------|----| -| E2.S1 | Afspraak aanmaken modal | Form met alle velden, validatie, opslaan werkt | ⏳ | E1.S2 | 5 | -| E2.S2 | Afspraak bewerken | Click op event → edit modal, wijzigingen opslaan | ⏳ | E2.S1 | 3 | -| E2.S3 | Afspraak verzetten (drag-drop) | Drag event naar nieuwe tijd, confirm dialog | ⏳ | E2.S2 | 3 | -| E2.S4 | Afspraak annuleren | Soft delete (status=cancelled), confirm dialog | ⏳ | E2.S2 | 2 | -| E2.S5 | Afspraak details view | Click voor volledige info, quick actions | ⏳ | E2.S2 | 2 | - -**Appointment Modal Fields:** - -| Veld | Type | Verplicht | Bron | -|------|------|-----------|------| -| Patiënt | Patient selector | Ja | patients tabel | -| Datum | Date picker | Ja | — | -| Starttijd | Time picker | Ja | — | -| Eindtijd | Time picker | Nee | — | -| Type | Select | Ja | encounter types | -| Locatie | Select | Nee | class_code | -| Behandelaar | Select | Ja | practitioners | -| Notities | Textarea | Nee | — | - -**Appointment Types (type_code):** -- `intake` - Intakegesprek -- `behandeling` - Behandelsessie -- `follow-up` - Vervolggesprek -- `telefonisch` - Telefonisch contact -- `huisbezoek` - Huisbezoek -- `online` - Online consult -- `crisis` - Crisiscontact -- `overig` - Overig - -**Status Flow:** -``` -planned → in-progress → completed - ↓ -cancelled -``` - ---- - -### Epic 3 — Patiënt Integratie - -**Epic Doel:** Naadloze patiëntselectie bij het maken van afspraken. - -| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | SP | -|----------|--------------|---------------------|--------|------------------|----| -| E3.S1 | Patient search component | Zoeken op naam, BSN, clientnummer | ⏳ | E2.S1 | 3 | -| E3.S2 | Recent patients dropdown | Laatste 5 patiënten snel selecteren | ⏳ | E3.S1 | 2 | -| E3.S3 | Quick patient info | Naam, geboortedatum, actieve intake tonen | ⏳ | E3.S2 | 2 | - -**Patient Selector Component:** -```typescript -interface PatientSelectorProps { - value?: string; // patient_id - onChange: (patientId: string) => void; - showRecentPatients?: boolean; - allowCreate?: boolean; // Future: quick create -} -``` - ---- - -### Epic 4 — EPD Koppeling - -**Epic Doel:** Bidirectionele koppeling tussen afspraken en verslagen. - -| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | SP | -|----------|--------------|---------------------|--------|------------------|----| -| E4.S1 | Verslag maken vanuit afspraak | Button op appointment → report composer met encounter_id | ✅ | E2.S5 | 3 | -| E4.S2 | Afspraak koppelen vanuit verslag | In rapportage: link naar bestaande/nieuwe afspraak | ✅ | E4.S1 | 3 | -| E4.S3 | Gekoppelde items tonen | In afspraak details: linked reports zichtbaar | ✅ | E4.S2 | 2 | -| E4.S4 | Navigatie tussen afspraak ↔ verslag | Click-through links beide kanten | ✅ | E4.S3 | 2 | - -**Integration Points:** - -``` -┌─────────────────┐ ┌─────────────────┐ -│ Agenda │ │ Rapportage │ -│ │ │ │ -│ [Afspraak] │───────────────▶│ [Verslag] │ -│ └─ + Verslag │ │ └─ encounter │ -│ │◀───────────────│ └─ + Koppel │ -└─────────────────┘ └─────────────────┘ - │ │ - ▼ ▼ -┌─────────────────────────────────────────────────────┐ -│ encounters │ -│ id, patient_id, period_start, period_end, ... │ -└─────────────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────┐ -│ reports │ -│ id, patient_id, encounter_id, intake_id, ... │ -└─────────────────────────────────────────────────────┘ -``` - -**Report Composer Updates:** -- Nieuwe prop: `encounterId?: string` -- Pre-fill patient_id vanuit encounter -- Toon encounter info (datum, type) in composer - ---- - -### Epic 5 — Polish & Testing - -**Epic Doel:** UX verfijning en edge case handling. - -| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | SP | -|----------|--------------|---------------------|--------|------------------|----| -| E5.S1 | Loading states & skeletons | Alle async operaties hebben loading feedback | ⏳ | E4.S4 | 2 | -| E5.S2 | Error handling | User-friendly errors, retry mogelijkheden | ⏳ | E5.S1 | 2 | -| E5.S3 | Responsive design | Mobile-friendly, touch gestures | ⏳ | E5.S2 | 3 | - ---- - -## 5. Kwaliteit & Testplan - -### Test Types - -| Test Type | Scope | Tools | -|-----------|-------|-------| -| Unit Tests | Date utilities, validators | Vitest | -| Integration | API endpoints, database | Playwright | -| Smoke Tests | Kritieke flows | Manual | -| Visual | UI consistency | Manual | - -### Manual Test Checklist - -**Afspraak Flow:** -- [ ] Nieuwe afspraak aanmaken met alle velden -- [ ] Afspraak bewerken (tijd, type, notities) -- [ ] Afspraak verzetten via drag-drop -- [ ] Afspraak annuleren met bevestiging -- [ ] Patiënt zoeken en selecteren - -**Calendar Views:** -- [ ] Dag view toont correcte uren -- [ ] Week view toont 7 dagen -- [ ] Werkdagen view filtert weekend -- [ ] Navigatie (vorige/volgende) werkt -- [ ] Vandaag button springt naar huidige dag - -**EPD Koppeling:** -- [ ] Verslag maken vanuit afspraak -- [ ] Afspraak koppelen vanuit verslag -- [ ] Gekoppelde items zijn zichtbaar -- [ ] Navigatie werkt beide kanten - ---- - -## 6. Data Model - -### Encounters (uitgebreid) - -```typescript -interface Encounter { - id: string; - identifier: string; - status: 'planned' | 'in-progress' | 'on-hold' | 'completed' | 'cancelled'; - class_code: string; // AMB, VR, HH (home health) - class_display: string; - type_code: string; // intake, behandeling, follow-up, etc. - type_display: string; - patient_id: string; - practitioner_id: string; - organization_id?: string; - period_start: string; // ISO datetime - period_end?: string; - notes?: string; - intake_id?: string; - created_at: string; - updated_at: string; -} -``` - -### Reports (uitgebreid) - -```typescript -interface Report { - id: string; - patient_id: string; - created_by?: string; - type: 'behandeladvies' | 'vrije_notitie' | 'intake' | 'voortgang' | 'crisis' | 'contact'; - content: string; - encounter_id?: string; // NEW: koppeling met afspraak - intake_id?: string; // NEW: koppeling met intake - // ... existing fields -} -``` - -### Calendar Event (voor FullCalendar) - -```typescript -interface CalendarEvent { - id: string; - title: string; // Patient naam - start: Date; - end?: Date; - extendedProps: { - encounter: Encounter; - patient: Patient; - linkedReports: Report[]; - }; - backgroundColor?: string; // Based on type - borderColor?: string; -} -``` - ---- - -## 7. API Endpoints - -### Nieuwe Endpoints - -| Methode | Endpoint | Beschrijving | -|---------|----------|--------------| -| GET | `/api/agenda/encounters` | Haal afspraken op (met filters) | -| POST | `/api/agenda/encounters` | Nieuwe afspraak aanmaken | -| PUT | `/api/agenda/encounters/[id]` | Afspraak bewerken | -| PATCH | `/api/agenda/encounters/[id]/cancel` | Afspraak annuleren | -| GET | `/api/agenda/encounters/[id]/reports` | Gekoppelde verslagen | - -### Query Parameters (GET encounters) - -```typescript -interface EncounterFilters { - start_date: string; // ISO date - end_date: string; - practitioner_id?: string; - patient_id?: string; - status?: string[]; - type_code?: string[]; -} -``` - ---- - -## 8. Risico's & Mitigatie - -| Risico | Kans | Impact | Mitigatie | -|--------|------|--------|-----------| -| FullCalendar styling conflicten | Middel | Middel | CSS isolation, custom theme | -| Performance bij veel afspraken | Laag | Hoog | Pagination, date range filtering | -| Timezone issues | Middel | Hoog | Altijd UTC opslaan, lokaal tonen | -| Drag-drop UX op mobile | Middel | Middel | Touch-friendly alternatives | -| Concurrent edits | Laag | Middel | Optimistic locking, conflict resolution | - ---- - -## 9. UI/UX Specificaties - -### Agenda Layout - -``` -┌────────────────────────────────────────────────────────────────┐ -│ EPD Header [User] │ -├────────┬───────────────────────────────────────────────────────┤ -│ │ Agenda [+ Nieuwe Afspraak] │ -│ Nav │───────────────────────────────────────────────────────│ -│ │ [< Vorige] [Vandaag] [Volgende >] [Dag|Week|Werk] │ -│ │───────────────────────────────────────────────────────│ -│ │ ┌──────────┐ │ -│ │ │ December │ ┌─────────────────────────────────────┐│ -│ │ │ 2024 │ │ Ma │ Di │ Wo │ Do │ Vr ││ -│ │ │ [Cal] │ │──────┼──────┼──────┼──────┼────────││ -│ │ └──────────┘ │ 08:00│ │ │ │ ││ -│ │ │──────│ [Pnt]│ │ │ ││ -│ │ Filters: │ 09:00│ │ [Int]│ │ ││ -│ │ □ Intakes │──────│ │ │ │ ││ -│ │ □ Behandeling │ 10:00│ │ │ [Beh]│ ││ -│ │ □ Telefonisch │──────│ │ │ │ ││ -│ │ │ ... │ │ │ │ ││ -│ │ └─────────────────────────────────────┘│ -└────────┴───────────────────────────────────────────────────────┘ -``` - -### Afspraak Card (in kalender) - -``` -┌─────────────────────┐ -│ 09:00 - 10:00 │ -│ ● Jan de Vries │ -│ Intakegesprek │ -│ [📝] [✏️] │ -└─────────────────────┘ -``` - -### Afspraak Modal - -``` -┌─────────────────────────────────────────┐ -│ Nieuwe Afspraak [X] │ -├─────────────────────────────────────────┤ -│ │ -│ Patiënt * │ -│ ┌─────────────────────────────────────┐ │ -│ │ 🔍 Zoek patiënt... │ │ -│ └─────────────────────────────────────┘ │ -│ │ -│ Datum * Tijd * │ -│ ┌──────────────┐ ┌──────┐ - ┌──────┐│ -│ │ 02-12-2024 │ │09:00 │ │10:00 ││ -│ └──────────────┘ └──────┘ └──────┘│ -│ │ -│ Type afspraak * │ -│ ┌─────────────────────────────────────┐ │ -│ │ Intakegesprek ▼ │ │ -│ └─────────────────────────────────────┘ │ -│ │ -│ Locatie │ -│ ┌─────────────────────────────────────┐ │ -│ │ Praktijk ▼ │ │ -│ └─────────────────────────────────────┘ │ -│ │ -│ Notities │ -│ ┌─────────────────────────────────────┐ │ -│ │ │ │ -│ └─────────────────────────────────────┘ │ -│ │ -│ [Annuleren] [Opslaan] │ -└─────────────────────────────────────────┘ -``` - ---- - -## 10. Referenties - -**Interne Documenten:** -- FO Mini-EPD v1.2 -- TO Mini-EPD v1.2 -- Screening-Intake Bouwplan - -**Libraries:** -- [FullCalendar React](https://fullcalendar.io/docs/react) -- [shadcn/ui Calendar](https://ui.shadcn.com/docs/components/calendar) -- [date-fns](https://date-fns.org/) - -**Database:** -- `encounters` tabel (FHIR Encounter) -- `reports` tabel -- `patients` tabel -- `practitioners` tabel - ---- - -## 11. Glossary - -| Term | Betekenis | -|------|-----------| -| Encounter | FHIR term voor een contact/afspraak moment | -| period_start | Starttijd van een afspraak (ISO datetime) | -| class_code | Type locatie (AMB=ambulant, VR=virtueel, HH=thuis) | -| type_code | Type afspraak (intake, behandeling, etc.) | -| Bidirectioneel | Koppeling werkt beide kanten op | - ---- - -**Versiehistorie:** - -| Versie | Datum | Auteur | Wijziging | -|--------|-------|--------|-----------| -| v1.0 | 02-12-2024 | Colin | Initiële versie | diff --git a/docs/specs/agenda/fo-agenda-module-v1_0.md b/docs/specs/agenda/fo-agenda-module-v1_0.md deleted file mode 100644 index b530c9a..0000000 --- a/docs/specs/agenda/fo-agenda-module-v1_0.md +++ /dev/null @@ -1,1233 +0,0 @@ -# 🧩 Functioneel Ontwerp (FO) – Agenda Module - -**Projectnaam:** Mini-EPD Agenda Module -**Versie:** v1.1 -**Datum:** 02-12-2024 -**Auteur:** Colin - ---- - -## 1. Doel en relatie met het PRD - -🎯 **Doel van dit document:** -Dit Functioneel Ontwerp beschrijft **hoe** de Agenda Module uit het PRD functioneel zal werken — wat de behandelaar ziet, doet en ervaart. Waar het PRD uitlegt *wat en waarom*, laat dit FO zien *hoe dit in de praktijk werkt*. - -📘 **Relatie met PRD:** -- PRD-referentie: `prd-agenda-module-v1_0.md` -- Dit FO is de functionele uitwerking van PRD secties 3 (Kernfunctionaliteiten) en 4 (Gebruikersflows) -- Scope is afgebakend op MVP-features; nice-to-have items zijn apart gemarkeerd - -**Kernprincipe:** -> De agenda is het kloppend hart van het EPD. Zonder agenda geen dagelijkse workflow, zonder workflow geen AI-integratie in de praktijk. - ---- - -## 2. Overzicht van de belangrijkste onderdelen - -🎯 **Doel:** Kort overzicht van schermen en componenten binnen de Agenda Module. - -### MVP Onderdelen (Must-Have) - -| # | Onderdeel | Beschrijving | Prioriteit | -|---|-----------|--------------|------------| -| 1 | **Kalender View** | Hoofd agenda-weergave met dag/week/werkdagen toggle | 🔴 Kritiek | -| 2 | **Afspraak Modal** | Formulier voor maken/bewerken afspraken | 🔴 Kritiek | -| 3 | **Patient Zoeken** | Autocomplete voor patient selectie | 🔴 Kritiek | -| 4 | **Conflict Dialog** | Waarschuwing bij dubbele boekingen | 🔴 Kritiek | -| 5 | **Afspraak Detail Popup** | Quick-view met acties (verslag maken, bewerken) | 🔴 Kritiek | -| 6 | **Toolbar** | Datum navigatie, view switcher | 🟡 Hoog | - -### Stretch/Nice-to-Have Onderdelen - -| # | Onderdeel | Beschrijving | Prioriteit | -|---|-----------|--------------|------------| -| 7 | Mini Kalender Sidebar | Maandoverzicht met navigatie | 🟢 Stretch | -| 8 | Recente Patiënten | Quick-select laatste 5 patiënten | 🟢 Stretch | -| 9 | Kleurcodering | Visuele type-indicator (intake/behandeling/follow-up) | 🟢 Stretch | - -### Niet in Scope (Post-MVP) - -| Onderdeel | Reden exclusie | -|-----------|----------------| -| Multi-practitioner view | Te complex voor MVP, single-user focus | -| Recurring appointments | Beperkte demo-waarde, hoge complexiteit | -| Email/SMS notificaties | Aparte infrastructuur nodig | -| Wachtlijstbeheer | Buiten scope AI Speedrun | -| Video call integratie | Geen core EPD functionaliteit | - ---- - -## 3. User Stories - -🎯 **Doel:** Beschrijven wat gebruikers moeten kunnen doen, vanuit hun perspectief. - -### MVP User Stories - -| ID | Als... | Wil ik... | Zodat... | Prio | -|----|--------|-----------|----------|------| -| **AG-US01** | Behandelaar | Mijn agenda bekijken in dag/week view | Ik weet welke patiënten vandaag/deze week komen | 🔴 | -| **AG-US02** | Behandelaar | Snel een nieuwe afspraak maken door op een tijdslot te klikken | Ik direct kan inplannen tijdens telefoongesprek | 🔴 | -| **AG-US03** | Behandelaar | Een patient zoeken tijdens afspraak maken | De afspraak gekoppeld is aan het juiste dossier | 🔴 | -| **AG-US04** | Behandelaar | Een waarschuwing zien bij dubbele boeking | Ik niet per ongeluk overlap creëer | 🔴 | -| **AG-US05** | Behandelaar | De waarschuwing kunnen overrulen | Ik flexibel kan plannen wanneer nodig | 🔴 | -| **AG-US06** | Behandelaar | Een afspraak verzetten via drag-drop | Wijzigingen snel zijn doorgevoerd | 🟡 | -| **AG-US07** | Behandelaar | Vanuit een afspraak direct een verslag starten | AI-verslaglegging naadloos in mijn workflow past | 🔴 | -| **AG-US08** | Behandelaar | Op een afspraak klikken om details te zien | Ik snel patient info kan checken | 🟡 | -| **AG-US09** | Behandelaar | Een afspraak annuleren | Geannuleerde afspraken geregistreerd blijven | 🟡 | - -### Stretch User Stories - -| ID | Als... | Wil ik... | Zodat... | Prio | -|----|--------|-----------|----------|------| -| **AG-US10** | Behandelaar | Recente patiënten snel selecteren | Repeat-afspraken extra snel gaan | 🟢 | -| **AG-US11** | Behandelaar | Afspraken zien met kleurcodering per type | Ik visueel onderscheid kan maken | 🟢 | -| **AG-US12** | Behandelaar | Een mini-kalender in de sidebar zien | Ik snel naar andere weken kan navigeren | 🟢 | - ---- - -## 4. Functionele werking per onderdeel - -🎯 **Doel:** Per component beschrijven wat de gebruiker kan doen en wat het systeem doet. - ---- - -### 4.1 Kalender View (MVP) - -**Locatie:** `/epd/agenda` -**Context:** Level 1 navigatie (behandelaar-breed, niet patient-specifiek) - -#### Functioneel gedrag - -**Views beschikbaar:** -| View | Beschrijving | Standaard | -|------|--------------|-----------| -| **Dag** | Enkele dag, uren 08:00-18:00 verticaal | Nee | -| **Week** | Ma-Zo, 7 kolommen, uren verticaal | Ja ✓ | -| **Werkdagen** | Ma-Vr, 5 kolommen | Nee | - -**Weergave-elementen:** -- **Tijdslots:** 30 minuten interval, visueel onderscheiden met subtle gridlijnen -- **Afspraak blokken:** Patient naam (bold), tijd, type indicator (kleur dot) -- **Huidige tijd indicator:** Rode lijn op "nu" moment + auto-scroll naar nu bij laden -- **Grijze zones:** Tijden buiten werktijden (vóór 08:00, na 18:00) licht gearceerd -- **Vandaag markering:** Huidige dag kolom heeft licht gekleurde achtergrond - -**Visuele details kalender grid:** -``` - Ma 2 Di 3 Wo 4 (vandaag) Do 5 - ───────────────────────────────────────────────────── - 08:00 │ │ │ ░░░░░░░░░░░░░ │ │ - │ │ │ ░░░░░░░░░░░░░ │ │ - 09:00 │ ┌────────┐ │ │ ░░░░░░░░░░░░░ │ │ - │ │Jan de V│ │ │ ░─────────────│────────────│ ← Rode "nu" lijn - 10:00 │ │Intake │ │ │ ░░░░░░░░░░░░░ │ ┌────────┐ │ - │ └────────┘ │ │ ░░░░░░░░░░░░░ │ │Maria J │ │ - 11:00 │ │ ┌────────┐ │ ░░░░░░░░░░░░░ │ │Behandel│ │ - │ │ │Piet K. │ │ ░░░░░░░░░░░░░ │ └────────┘ │ -``` -_Vandaag (wo 4) heeft lichte achtergrondkleur (░)_ - -**Interacties:** -| Actie | Resultaat | Feedback | -|-------|-----------|----------| -| Klik op leeg tijdslot | Opent Afspraak Modal met datum/tijd pre-filled | Slot krijgt kort highlight | -| Klik op afspraak blok | Opent Afspraak Detail Popup | Blok krijgt ring focus | -| Drag afspraak naar ander slot | Opent Confirm Dialog voor verzetten | Ghost preview op nieuwe locatie | -| Hover op afspraak | Cursor pointer + lichte lift | 100ms delay | -| Hover op leeg slot | Subtle "+" icon verschijnt in slot | Fade-in | -| Double-click leeg slot | Direct modal openen (sneller dan single click) | — | - -**States:** -| State | Weergave | -|-------|----------| -| Laden | Skeleton loading met shimmer effect | -| Leeg (week/dag) | Illustratie + "Geen afspraken" + prominent CTA | -| Leeg (eerste gebruik) | Onboarding card met tips | -| Gevuld | Afspraak blokken in grid | -| Fout | Inline error met retry button | -| Offline | Banner: "Je bent offline. Wijzigingen worden gesynchroniseerd." | - -**Scroll & Navigatie:** -- Bij pagina laden: auto-scroll naar huidige tijd (met 1 uur marge boven) -- Sticky uur-labels aan linkerkant bij horizontaal scrollen (mobile) -- Sticky dag-headers bij verticaal scrollen - ---- - -### 4.2 Afspraak Modal (MVP) - -**Trigger:** Klik op leeg tijdslot OF "Nieuwe afspraak" button OF bewerk-actie - -#### Formulier velden - -| Veld | Type | Verplicht | Pre-fill | Validatie | -|------|------|-----------|----------|-----------| -| **Patient** | Autocomplete search | ✓ Ja | Nee (of recent als repeat) | Moet bestaande patient zijn | -| **Datum** | Date picker | ✓ Ja | Geklikte datum of vandaag | Warn bij verleden | -| **Starttijd** | Time select (08:00-18:00) | ✓ Ja | Geklikte tijd of volgende vrije | 30 min intervals | -| **Duur** | Select dropdown | ✓ Ja | Automatisch obv type | 15/30/45/60/90/120 min | -| **Type** | Radio buttons | ✓ Ja | Slim obv patient historie | Intake/Behandeling/Follow-up | -| **Notities** | Textarea (collapsed) | Nee | Leeg | Max 500 karakters | - -#### Smart Defaults - -**Type → Duur koppeling (automatisch bij wijziging type):** -| Type geselecteerd | Duur wordt | -|-------------------|------------| -| Intake | 60 min | -| Behandeling | 45 min | -| Follow-up | 30 min | - -_User kan duur nog steeds handmatig wijzigen na auto-fill_ - -**Type suggestie obv patient historie:** -| Patient situatie | Voorgeselecteerd type | -|------------------|----------------------| -| Nieuwe patient (geen afspraken) | Intake | -| Had intake, geen behandeling | Behandeling | -| Had 3+ behandelingen | Follow-up | -| Laatste afspraak > 6 maanden | Intake (herintake) | - -#### Formulier Layout (Progressive Disclosure) - -``` -┌──────────────────────────────────────────┐ -│ Nieuwe afspraak ✕ │ -├──────────────────────────────────────────┤ -│ │ -│ Patient * │ -│ ┌──────────────────────────────────────┐ │ -│ │ 🔍 Zoek patient... │ │ ← Auto-focus hier -│ └──────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ 📅 Do 5 dec │ │ 🕐 14:00 ▼ │ │ ← Compact: datum + tijd -│ └─────────────────┘ └─────────────────┘ │ -│ │ -│ Duur: ○ 30 ● 45 ○ 60 ○ 90 min │ ← Inline radio (niet dropdown) -│ │ -│ Type: ○ Intake ● Behandeling ○ Follow │ ← Inline radio -│ │ -│ [+ Notitie toevoegen] │ ← Collapsed by default -│ │ -├──────────────────────────────────────────┤ -│ [Annuleren] [✓ Opslaan] │ -└──────────────────────────────────────────┘ -``` - -_Bij klik "+ Notitie toevoegen" → expand naar textarea_ - -#### Inline Validatie - -| Veld | Wanneer valideren | Feedback | -|------|-------------------|----------| -| Patient | On blur + na 500ms idle | Rode border + "Selecteer een patient" | -| Datum | On change | Gele border + "Let op: verleden datum" | -| Tijd | On change | Check conflict realtime, toon inline warning | -| Notities | On input | Character counter: "234/500" | - -**Conflict preview (inline, vóór submit):** -``` -┌──────────────────────────────────────────┐ -│ ⚠️ Overlap met: Jan de Vries 14:00 │ ← Inline warning -│ [Andere tijd kiezen] of [Negeren] │ -└──────────────────────────────────────────┘ -``` - -#### Functionaliteit - -**Validatie flow (optimized):** -``` -1. Bij elk veld change: inline validatie -2. Conflict check: real-time bij datum/tijd wijziging -3. Bij "Opslaan": - a. Final validation sweep - b. Als conflict: toon inline warning (niet aparte dialog voor simpele cases) - c. Als meerdere conflicts: toon Conflict Dialog - d. Opslaan → optimistic update → toast -``` - -**Acties:** -| Knop | Gedrag | State | -|------|--------|-------| -| **Opslaan** | Valideer → Sla op → Sluit → Toast | Disabled tijdens save, spinner | -| **Annuleren** | Check unsaved → Sluit modal | — | -| **Verwijderen** (edit) | Inline confirm → Soft delete | Rode tekst, niet prominent | - -**Keyboard shortcuts:** -- `Cmd/Ctrl + Enter` = Opslaan (ook vanuit textarea) -- `Escape` = Annuleren (met unsaved check) -- `Tab` = Volgende veld -- `↓` / `↑` in dropdowns = Navigate options - ---- - -### 4.3 Patient Zoeken (MVP) - -**Locatie:** Autocomplete input in Afspraak Modal - -#### Functioneel gedrag - -**Zoeklogica:** -- Minimaal 2 karakters voor zoekstart -- Zoekt op: achternaam, voornaam, BSN (laatste 4 cijfers), geboortedatum -- Resultaten: Max 10, gesorteerd op relevantie (exacte match eerst) -- Debounce: 300ms na laatste toetsaanslag - -**Resultaat weergave:** -``` -┌──────────────────────────────────────────────┐ -│ 🔍 "jan de" ⟳ │ -├──────────────────────────────────────────────┤ -│ ⭐ Recente patiënten │ ← Sectie header (subtle) -├──────────────────────────────────────────────┤ -│ Jan de Vries ↵ │ ← Recent + keyboard hint -│ ♂ 12-03-1985 (39 jaar) · Laatste: 28 nov │ -├──────────────────────────────────────────────┤ -│ 🔍 Zoekresultaten │ ← Sectie header -├──────────────────────────────────────────────┤ -│ Jantine de Boer │ -│ ♀ 05-08-1972 (52 jaar) │ -├──────────────────────────────────────────────┤ -│ Jan de Groot │ -│ ♂ 23-07-1990 (34 jaar) │ -├──────────────────────────────────────────────┤ -│ + Nieuwe patient aanmaken │ ← Altijd onderaan -└──────────────────────────────────────────────┘ -``` - -**States:** -| State | Weergave | -|-------|----------| -| Leeg (focus) | Toon recente patiënten (max 5) als suggestie | -| Typen (< 2 chars) | "Typ minimaal 2 karakters..." | -| Zoeken | Spinner rechts in input | -| Resultaten | Recente patiënten bovenaan, dan zoekresultaten | -| Geen resultaat | "Geen patiënt gevonden" + link naar patient aanmaken | -| Geselecteerd | Chip met patient naam + X om te wissen | -| Keyboard navigatie | Highlighted row met subtle achtergrond | - -**UX verbeteringen:** -- **Recente patiënten:** Bij focus zonder input, toon laatste 5 bezochte patiënten -- **Match highlighting:** Zoekterm bold in resultaten ("**Jan de** Vries") -- **Keyboard hints:** Toon "↵" icon bij geselecteerde row -- **Laatste afspraak:** Toon "Laatste: 28 nov" voor context -- **Focus management:** Auto-focus op input bij modal open - ---- - -### 4.4 Conflict Dialog (MVP) - -**Trigger:** Automatisch bij opslaan afspraak met overlap - -#### Functioneel gedrag - -**Detectie logica:** -- Check: Nieuwe afspraak `[start, eind]` overlapt met bestaande afspraak(en)? -- Overlap = enige overlap in tijd (inclusief aangrenzend) -- Zelfde behandelaar (huidige user) is criterium - -**Dialog inhoud:** -``` -┌──────────────────────────────────────────────┐ -│ ⚠️ Dubbele boeking gedetecteerd │ -├──────────────────────────────────────────────┤ -│ │ -│ Je hebt al een afspraak op dit tijdstip: │ -│ │ -│ ┌────────────────────────────────────────┐ │ -│ │ 📅 Donderdag 5 dec, 14:00-15:00 │ │ -│ │ 👤 Jan de Vries │ │ -│ │ 📋 Intake │ │ -│ └────────────────────────────────────────┘ │ -│ │ -│ Wil je de tijd aanpassen of toch inplannen? │ -│ │ -├──────────────────────────────────────────────┤ -│ [Wijzigen] [Toch inplannen] │ -└──────────────────────────────────────────────┘ -``` - -**Acties:** -| Knop | Gedrag | -|------|--------| -| **Wijzigen** | Sluit dialog, terug naar modal met tijd veld gefocust | -| **Toch inplannen** | Bypass conflict, sla op, sluit alles, toast met waarschuwing | - -**Business rule:** -> Systeem waarschuwt maar blokkeert NIET. Behandelaars hebben valide redenen voor overlap (bijv. administratie-tijd, korte telefoon tussendoor). - ---- - -### 4.5 Afspraak Detail Popup (MVP) - -**Trigger:** Klik op afspraak blok in kalender - -#### Functioneel gedrag - -**Popup inhoud:** -``` -┌──────────────────────────────────────────────┐ -│ Jan de Vries ✕ │ -│ ♂ 39 jaar · Intake #3 │ -├──────────────────────────────────────────────┤ -│ 📅 Do 5 dec 2024 │ -│ 🕐 14:00 - 15:00 (60 min) │ -│ ● Intakegesprek │ ← Kleur dot voor type -├──────────────────────────────────────────────┤ -│ Notities: │ -│ Eerste gesprek na verwijzing huisarts. │ -│ Verwacht: angstklachten, slaapproblemen. │ -├──────────────────────────────────────────────┤ -│ │ -│ [📝 Maak verslag] [✏️ Bewerken]│ -│ │ -│ ─────────────────────────────────────── │ -│ [👤 Naar dossier] [🗑️ Annuleren] │ -│ │ -└──────────────────────────────────────────────┘ -``` - -**Acties:** -| Knop | Gedrag | Visuele prioriteit | -|------|--------|-------------------| -| **📝 Maak verslag** | Navigeer naar rapportage met pre-fills | **Primary** (solid button) | -| **✏️ Bewerken** | Open Afspraak Modal in edit mode | Secondary (outline) | -| **👤 Naar dossier** | Navigeer naar patient dossier | Tertiary (text link) | -| **🗑️ Annuleren** | Confirm dialog → soft delete | Tertiary (text link, rode tekst) | - -**Contextuele CTA (slimme primary actie):** -| Afspraak status | Primary actie | -|-----------------|---------------| -| Gepland (toekomst) | "Bewerken" prominent | -| Vandaag/nu | "**Maak verslag**" prominent (pulsing) | -| Verleden zonder verslag | "**Maak verslag**" prominent + badge "!" | -| Verleden met verslag | "Bekijk verslag" + "Bewerken" | - -**Status indicatoren:** -| Status | Badge | Extra visueel | -|--------|-------|---------------| -| `planned` | 🔵 Blauw | — | -| Vandaag | 🟢 Groen | Subtle glow effect | -| `in-progress` | 🟢 Groen pulsing | "Nu bezig" label | -| `on-hold` | 🟠 Oranje | Pauze icon | -| `completed` | ⚪ Grijs | Check icon | -| `cancelled` | 🔴 Rood | Doorgestreepte tekst | - -**UX verbeteringen:** -- **Quick-action keyboard:** `V` = Verslag maken, `E` = Edit, `D` = Delete -- **Positie:** Popup verschijnt naast afspraak blok, niet centered (context behouden) -- **Click-outside:** Sluit popup -- **Animatie:** Fade-in vanuit afspraak blok positie -- **Verslag indicator:** Als verslag bestaat, toon "📝 Verslag (28 nov)" link in popup - ---- - -### 4.6 Toolbar (MVP) - -**Locatie:** Boven kalender grid, sticky bij scrollen - -#### Functioneel gedrag - -**Desktop layout:** -``` -┌──────────────────────────────────────────────────────────────────────────┐ -│ [◀] [Vandaag] [▶] 📅 Week 49 · 2-8 december 2024 [Dag|Week|Werk] │ -│ [+ Nieuwe afspraak] │ -└──────────────────────────────────────────────────────────────────────────┘ -``` - -**Tablet layout (768-1024px):** -``` -┌─────────────────────────────────────────────────────┐ -│ [◀] [Vandaag] [▶] Week 49 [D|W|5] [+ Nieuw] │ -└─────────────────────────────────────────────────────┘ -``` - -**Mobile layout (<768px):** -``` -┌─────────────────────────────────┐ -│ [◀] Do 5 december [▶] [+] │ ← Compact, alleen dag -└─────────────────────────────────┘ -``` - -| Element | Gedrag | Keyboard | -|---------|--------|----------| -| **◀ / ▶** | Navigeer 1 periode terug/vooruit | `←` / `→` | -| **Vandaag** | Spring naar huidige datum + scroll naar nu | `T` | -| **Datum label** | Klik → open date picker voor directe navigatie | — | -| **View toggle** | Switch tussen views | `1`/`2`/`3` | -| **+ Nieuwe afspraak** | Open modal zonder pre-fill datum | `N` | - -**View toggle states:** -``` -┌─────────────────────────────────┐ -│ [Dag] Week Werkdagen │ ← Dag geselecteerd -└─────────────────────────────────┘ - ↓ na klik op Week -┌─────────────────────────────────┐ -│ Dag [Week] Werkdagen │ ← Week geselecteerd, animated underline -└─────────────────────────────────┘ -``` - -**Extra UX details:** -- **Vandaag button:** Highlight als niet op vandaag (subtle pulsing dot) -- **Datum klik:** Opent kleine kalender picker voor snelle week-jump -- **Navigatie animatie:** Smooth slide transition bij periode wisseling -- **Loading tijdens navigatie:** Spinner in datum label, grid blijft staan - ---- - -### 4.7 Mini Kalender Sidebar (Stretch) - -**Locatie:** Linker sidebar op desktop - -#### Functioneel gedrag (indien gebouwd) - -``` -┌────────────────────┐ -│ December 2024 │ -│ Ma Di Wo Do Vr Za Zo│ -│ 1 │ -│ 2 3 4 5• 6 7 8│ -│ 9 10 11 12 13 14 15│ -│ 16 17 18 19 20 21 22│ -│ 23 24 25 26 27 28 29│ -│ 30 31 │ -└────────────────────┘ -``` - -- **Bullet (•)**: Dagen met afspraken -- **Highlight**: Geselecteerde dag/week -- **Klik op dag**: Navigeer naar die dag in main view - ---- - -## 5. UI-overzicht (visuele structuur) - -🎯 **Doel:** Globale schermopbouw communiceren. - -### 5.1 Desktop Layout (≥1024px) - -``` -┌──────────────────────────────────────────────────────────────────────┐ -│ EPD Sidebar │ Agenda Module │ -│ │ │ -│ ┌─────────┐ │ ┌─────────────────────────────────────────────────────┐│ -│ │Dashboard│ │ │ Toolbar: [◀][Vandaag][▶] Week 49 [Dag|Week|Werk] ││ -│ │─────────│ │ ├─────────────────────────────────────────────────────┤│ -│ │Cliënten │ │ │ ││ -│ │─────────│ │ │ Ma Di Wo Do Vr Za Zo ││ -│ │►Agenda │ │ │ ───────────────────────────────────────── ││ -│ │─────────│ │ │ 08:00 ││ -│ │Rapportage│ │ │ 09:00 ┌────┐ ││ -│ └─────────┘ │ │ 10:00 │Jan │ ┌────┐ ││ -│ │ │ 11:00 │de V│ │Mari│ ││ -│ │ │ 12:00 └────┘ │ │ ││ -│ │ │ 13:00 └────┘ ││ -│ │ │ 14:00 ┌────┐ ││ -│ │ │ 15:00 │Piet│ ││ -│ │ │ 16:00 └────┘ ││ -│ │ │ 17:00 ││ -│ │ │ 18:00 ││ -│ │ └─────────────────────────────────────────────────────┤│ -└─────────────┴───────────────────────────────────────────────────────┘ -``` - -### 5.2 Mobile Layout (<768px) - -``` -┌─────────────────────────┐ -│ ≡ Agenda + Nieuw │ -├─────────────────────────┤ -│ ◀ Vandaag ▶ │ -│ Do 5 december 2024 │ -├─────────────────────────┤ -│ 08:00 ──────────────────│ -│ 09:00 ┌───────────────┐ │ -│ │ Jan de Vries │ │ -│ 10:00 │ Intake │ │ -│ └───────────────┘ │ -│ 11:00 ──────────────────│ -│ 12:00 ──────────────────│ -│ 13:00 ┌───────────────┐ │ -│ │ Maria Jansen │ │ -│ 14:00 │ Behandeling │ │ -│ └───────────────┘ │ -│ 15:00 ──────────────────│ -│ ... │ -└─────────────────────────┘ -``` - -**Mobile aanpassingen:** -- Alleen dag-view standaard -- Geen drag-drop (gebruik edit button) -- Swipe links/rechts voor dag navigatie -- Hamburger menu voor EPD sidebar - -### 5.3 Afspraak Modal (Responsive) - -``` -┌──────────────────────────────────────────┐ -│ Nieuwe afspraak ✕ │ -├──────────────────────────────────────────┤ -│ │ -│ Patient * │ -│ ┌──────────────────────────────────────┐ │ -│ │ 🔍 Zoek patient... │ │ -│ └──────────────────────────────────────┘ │ -│ │ -│ Datum * Starttijd * │ -│ ┌────────────────┐ ┌────────────────┐ │ -│ │ 📅 05-12-2024 │ │ 14:00 ▼ │ │ -│ └────────────────┘ └────────────────┘ │ -│ │ -│ Duur * │ -│ ┌──────────────────────────────────────┐ │ -│ │ 60 minuten ▼ │ │ -│ └──────────────────────────────────────┘ │ -│ │ -│ Type afspraak * │ -│ ○ Intake ● Behandeling ○ Follow-up │ -│ │ -│ Notities │ -│ ┌──────────────────────────────────────┐ │ -│ │ │ │ -│ │ │ │ -│ └──────────────────────────────────────┘ │ -│ │ -├──────────────────────────────────────────┤ -│ [Annuleren] [Opslaan] │ -└──────────────────────────────────────────┘ -``` - ---- - -## 5.5 UX Richtlijnen & Micro-interacties - -🎯 **Doel:** Definiëren van de kleine details die de ervaring vloeiend maken. - -### 5.5.1 Design Principes - -| Principe | Toepassing in Agenda | -|----------|---------------------| -| **Snelheid boven alles** | Afspraak maken < 10 sec, geen onnodige stappen | -| **Visuele feedback** | Elke actie toont direct resultaat | -| **Foutpreventie > foutcorrectie** | Slim valideren vóór submit | -| **Flexibiliteit** | Waarschuwen, niet blokkeren | -| **Context behouden** | Na actie terug naar waar je was | - -### 5.5.2 Micro-interacties & Animaties - -**Afspraak blokken:** -| Interactie | Visuele feedback | Timing | -|------------|------------------|--------| -| Hover | Lichte schaduw + cursor pointer | Instant | -| Klik | Scale 0.98 + donkerder | 100ms ease | -| Drag start | Opacity 0.7 + verhoogde schaduw | 150ms | -| Drag over slot | Doelslot krijgt highlight border | Instant | -| Drop | Bounce-in animatie naar nieuwe positie | 200ms spring | - -**Modals & Dialogs:** -| Actie | Animatie | Timing | -|-------|----------|--------| -| Open | Fade-in + slide-up | 200ms ease-out | -| Sluiten | Fade-out + slide-down | 150ms ease-in | -| Achtergrond | Blur + dim (opacity 0.5) | 200ms | - -**Feedback elementen:** -| Element | Gedrag | -|---------|--------| -| Toast success | Slide-in rechtsonder, auto-dismiss 3s | -| Toast warning | Slide-in, persist tot dismiss | -| Loading spinner | Pulse animatie, centered in context | -| Skeleton | Shimmer effect links→rechts | - -### 5.5.3 Optimistic Updates - -**Principe:** UI update direct, rollback bij fout. - -| Actie | Optimistic gedrag | Bij fout | -|-------|-------------------|----------| -| Afspraak opslaan | Direct in kalender tonen | Remove + toast "Opslaan mislukt, probeer opnieuw" | -| Afspraak verzetten | Direct naar nieuwe positie | Terug naar origineel + toast | -| Afspraak annuleren | Direct doorstrepen (visueel) | Herstel + toast | - -**Implementatie voorbeeld:** -``` -1. User klikt "Opslaan" -2. Modal sluit, afspraak verschijnt in kalender (grijs/loading state) -3. API call in background -4. Success → afspraak krijgt normale styling -5. Error → afspraak verdwijnt, toast met retry optie -``` - -### 5.5.4 Smart Defaults & Auto-fill - -**Tijdslot klikken:** -| Context | Auto-fill | -|---------|-----------| -| Klik op 14:00 | Datum + tijd pre-filled | -| Klik na laatste afspraak | Suggereer aansluitend tijdslot | -| Klik op zelfde dag als recente afspraak | Suggereer zelfde patient | - -**Type selectie:** -| Laatste afspraak patient | Suggestie | -|--------------------------|-----------| -| Geen eerdere afspraken | Intake (standaard) | -| Had intake | Behandeling | -| Had 3+ behandelingen | Follow-up | - -**Duur selectie:** -| Type | Standaard duur | -|------|----------------| -| Intake | 60 min | -| Behandeling | 45 min | -| Follow-up | 30 min | - -### 5.5.5 Keyboard Navigatie - -**Globale shortcuts (wanneer op agenda pagina):** -| Shortcut | Actie | -|----------|-------| -| `N` | Nieuwe afspraak (opent modal) | -| `T` | Ga naar vandaag | -| `←` / `→` | Vorige/volgende periode | -| `1` / `2` / `3` | Switch naar Dag/Week/Werkdagen | -| `Esc` | Sluit modal/popup | - -**In Afspraak Modal:** -| Shortcut | Actie | -|----------|-------| -| `Tab` | Volgende veld | -| `Shift+Tab` | Vorige veld | -| `Enter` | Opslaan (als geen textarea focus) | -| `Esc` | Annuleren (met unsaved changes check) | -| `↓` / `↑` | Navigeer zoekresultaten | - -**In Patient zoeken:** -| Shortcut | Actie | -|----------|-------| -| `↓` | Eerste/volgende resultaat selecteren | -| `↑` | Vorige resultaat | -| `Enter` | Bevestig selectie | -| `Esc` | Sluit dropdown, behoud huidige selectie | - -### 5.5.6 Empty States & Onboarding - -**Lege agenda (geen afspraken deze week):** -``` -┌─────────────────────────────────────────────────┐ -│ │ -│ 📅 │ -│ │ -│ Geen afspraken deze week │ -│ │ -│ Klik op een tijdslot om je eerste │ -│ afspraak in te plannen, of gebruik: │ -│ │ -│ [+ Nieuwe afspraak] │ -│ │ -│ 💡 Tip: Druk 'N' voor snelle invoer │ -│ │ -└─────────────────────────────────────────────────┘ -``` - -**Eerste keer agenda openen (onboarding hint):** -- Subtle pulse animatie op "+ Nieuwe afspraak" button -- Tooltip: "Begin hier met je eerste afspraak" -- Verdwijnt na eerste afspraak of dismiss - -### 5.5.7 Error Handling & Recovery - -**Graceful degradation:** -| Fout scenario | User ziet | -|---------------|-----------| -| API timeout | "Even geduld..." → na 5s: "Kon niet laden. [Opnieuw proberen]" | -| Conflict check faalt | Sla op zonder conflict check + warning toast | -| Patient search faalt | "Zoeken niet beschikbaar. Typ volledige naam." | - -**Undo functionaliteit:** -| Actie | Undo mogelijkheid | -|-------|-------------------| -| Afspraak verwijderd | Toast met "Ongedaan maken" link (5 sec window) | -| Afspraak verzet | Toast met "Ongedaan maken" link (5 sec window) | -| Modal gesloten met unsaved | "Je hebt onopgeslagen wijzigingen. Sluiten?" | - -**Unsaved changes detection:** -``` -┌──────────────────────────────────────────┐ -│ Wijzigingen niet opgeslagen │ -├──────────────────────────────────────────┤ -│ │ -│ Je hebt wijzigingen die nog niet zijn │ -│ opgeslagen. Wil je deze verliezen? │ -│ │ -├──────────────────────────────────────────┤ -│ [Terug naar formulier] [Niet opslaan] │ -└──────────────────────────────────────────┘ -``` - -### 5.5.8 Visual Hierarchy in Kalender - -**Tijdslot visuele staten:** -| Staat | Visuele behandeling | -|-------|---------------------| -| Beschikbaar | Wit/licht, hover highlight | -| Bezet | Afspraak blok | -| Verleden | Licht grijs, minder contrast | -| Buiten werktijd | Gestreept/gearceerd patroon | -| Nu (huidige tijd) | Rode lijn indicator | -| Geselecteerd slot | Blue outline | - -**Afspraak blok informatie hiërarchie:** -``` -┌────────────────────────┐ -│ Jan de Vries 📝 │ ← Naam (bold) + verslag icon -│ 14:00 - 15:00 │ ← Tijd (secondary) -│ ● Intake │ ← Type met kleur dot -└────────────────────────┘ -``` - -**Bij weinig ruimte (korte afspraken 15-30 min):** -``` -┌────────────────────────┐ -│ J. de Vries 14:00 ● │ ← Compacte weergave -└────────────────────────┘ -``` - -### 5.5.9 Touch & Mobile Specifiek - -**Touch targets:** -- Minimum 44x44px voor alle interactieve elementen -- Afspraak blokken: volledige breedte van kolom - -**Gestures:** -| Gesture | Actie | -|---------|-------| -| Swipe links/rechts | Volgende/vorige dag | -| Long press op afspraak | Open context menu (bewerken/verwijderen) | -| Pull down | Refresh agenda data | -| Tap op leeg slot | Toon "+" button → tap = nieuwe afspraak | - -**Mobile-specifieke UI:** -- Geen drag-drop (te foutgevoelig op touch) -- Bottom sheet ipv centered modal -- Sticky header met datum bij scrollen - -### 5.5.10 Loading States per Component - -| Component | Loading state | -|-----------|---------------| -| Agenda grid | Skeleton met 3-4 placeholder blokken | -| Patient search | Spinner in input rechts | -| Modal opslaan | Button disabled + spinner + "Opslaan..." | -| View switch | Fade-out → skeleton → fade-in | -| Datum navigatie | Kort skeleton (200ms max) | - -**Perceived performance:** -- Prefetch volgende/vorige week data bij idle -- Cache huidige week, invalidate bij mutatie -- Stale-while-revalidate pattern - ---- - -## 6. Interacties met AI (functionele beschrijving) - -🎯 **Doel:** Uitleggen waar AI in de agenda flow voorkomt. - -### 6.1 AI-integratiepunten in Agenda Module - -| Locatie | AI-actie | Trigger | Output | -|---------|----------|---------|--------| -| Afspraak Detail | **→ Maak verslag** | Klik op "Maak verslag" button | Navigatie naar Rapportage met pre-fills | -| Rapportage (na navigatie) | Transcriptie | Klik "Opname starten" | Audio → tekst via Deepgram | -| Rapportage (na navigatie) | Verslag genereren | Klik "AI genereer" | Gestructureerd verslag op basis van transcriptie | - -### 6.2 Afspraak → Verslag Flow (Kernintegratie) - -Dit is de **killer feature** van de agenda integratie: - -**Stap-voor-stap:** -``` -1. Behandelaar klikt op afspraak in agenda (bijv. "Jan de Vries - 10:00 Intake") -2. Detail popup opent met "📝 Maak verslag" button -3. Behandelaar klikt "Maak verslag" -4. Systeem navigeert naar: /epd/patients/[jan-id]/rapportage?encounter=[afspraak-id] -5. Rapportage module opent met: - - Patient: Jan de Vries (pre-filled, read-only) - - Type: Intake verslag (pre-selected) - - Afspraak referentie: 05-12-2024 10:00 (gelinkt) -6. Behandelaar kan direct: - - Audio opnemen → AI transcribeert - - AI verslag laten genereren - - Handmatig schrijven -7. Bij opslaan: verslag automatisch gekoppeld aan afspraak -8. In agenda: afspraak toont nu 📝 icon (verslag aanwezig) -``` - -**Pre-fill mapping:** - -| Rapportage veld | Bron uit Afspraak | -|-----------------|-------------------| -| `patient_id` | `appointment.patient_id` | -| `encounter_id` | `appointment.id` | -| `report_type` | Gebaseerd op `appointment.type` (intake → "intake_verslag") | -| `title` | `"Verslag {type} - {patient_naam} - {datum}"` | - -### 6.3 Bidirectionele Linking - -**Van Agenda naar Dossier:** -- Afspraak detail popup bevat link naar patient dossier -- Klik "Naar dossier" → `/epd/patients/[id]` - -**Van Dossier naar Agenda:** -- In patient timeline: afspraken weergeven -- Klik op afspraak → terug naar agenda met die afspraak geselecteerd - -**Van Afspraak naar Verslag:** -- Afspraak icon toont 📝 als verslag bestaat -- Klik op icon → direct naar verslag - -**Van Verslag naar Afspraak:** -- Verslag header toont "Gekoppeld aan afspraak: Do 5 dec 14:00" -- Klik → terug naar afspraak detail - ---- - -## 7. Gebruikersrollen en rechten - -🎯 **Doel:** Beschrijven welke rollen toegang hebben. - -### MVP: Single-User Model - -In de MVP is er **geen rechtenbeheer**. Alle functionaliteit is beschikbaar voor de ingelogde behandelaar. - -| Rol | Toegang | Beperkingen | -|-----|---------|-------------| -| **Behandelaar** (enige rol) | Volledige agenda | Alleen eigen afspraken zichtbaar | - -### Data Scoping - -- **Afspraken:** Gefilterd op `practitioner_id = current_user.id` -- **Patiënten:** Alle patiënten in systeem doorzoekbaar (MVP simplificatie) -- **Verslagen:** Alleen eigen verslagen - -### Post-MVP: Multi-User Model - -| Rol | Toegang | Beperkingen | -|-----|---------|-------------| -| Behandelaar | Eigen agenda | Alleen eigen afspraken bewerken | -| Assistent | Alle agenda's | Inplannen voor anderen, niet verwijderen | -| Manager | Alle agenda's (readonly) | Geen bewerkingen, wel rapportages | - ---- - -## 8. Data Model (Functionele Entiteiten) - -🎯 **Doel:** Functioneel overzicht van data-entiteiten. - -### 8.1 Afspraak (Encounter) - -| Attribuut | Type | Beschrijving | -|-----------|------|--------------| -| `id` | UUID | Unieke identifier | -| `patient_id` | UUID | Gekoppelde patient | -| `practitioner_id` | UUID | Behandelaar (current user) | -| `period_start` | DateTime | Starttijd afspraak | -| `period_end` | DateTime | Eindtijd afspraak | -| `type_code` | Enum | intake / treatment / followup | -| `type_display` | String | "Intakegesprek", "Behandeling", etc. | -| `status` | Enum | planned / in-progress / on-hold / completed / cancelled | -| `notes` | Text | Vrije notities | -| `created_at` | DateTime | Aanmaakdatum | -| `updated_at` | DateTime | Laatste wijziging | - -### 8.2 Afspraak Types - -| Code | Display (NL) | Standaard duur | Kleur (stretch) | -|------|--------------|----------------|-----------------| -| `intake` | Intakegesprek | 60 min | 🔵 Blauw | -| `treatment` | Behandeling | 45 min | 🟢 Groen | -| `followup` | Follow-up | 30 min | 🟡 Geel | - -### 8.3 Status Lifecycle - -``` -planned → in-progress → completed - ↓ ↓ -on-hold cancelled (soft delete) -``` - -| Status | Betekenis | Automatisch | -|--------|-----------|-------------| -| `planned` | Ingepland, nog niet gestart | Bij aanmaken | -| `in-progress` | Momenteel bezig | Bij starttijd? (post-MVP) | -| `on-hold` | Tijdelijk gepauzeerd | Handmatig (post-MVP) | -| `completed` | Afgerond | Handmatig of na verslag opslaan | -| `cancelled` | Geannuleerd | Bij "Verwijderen" actie | - ---- - -## 9. Flows in Detail - -🎯 **Doel:** Gedetailleerde stap-voor-stap beschrijvingen. - -### 9.1 Flow: Nieuwe Afspraak (Happy Path) - -**Scenario:** Behandelaar plant afspraak in tijdens telefoongesprek - -```mermaid -sequenceDiagram - actor B as Behandelaar - participant K as Kalender - participant M as Afspraak Modal - participant S as Systeem - participant DB as Database - - B->>K: Klikt op leeg tijdslot (Do 14:00) - K->>M: Opent modal met pre-fill - Note over M: Datum: 05-12-2024
Tijd: 14:00 - B->>M: Typt "jan de" in patient zoek - M->>S: Search request (debounced) - S->>DB: Query patients WHERE name LIKE 'jan de%' - DB-->>S: [Jan de Vries, Jantine de Boer] - S-->>M: Resultaten - B->>M: Selecteert "Jan de Vries" - B->>M: Selecteert type "Behandeling" - B->>M: Klikt "Opslaan" - M->>S: Valideer + conflict check - S->>DB: Check overlap voor practitioner - DB-->>S: Geen conflict - S->>DB: INSERT encounter - DB-->>S: Success - S-->>M: Opslaan OK - M-->>K: Sluit modal - K->>K: Refresh + toon nieuwe afspraak - K-->>B: Toast: "Afspraak opgeslagen" -``` - -### 9.2 Flow: Dubbele Boeking - -**Scenario:** Behandelaar probeert afspraak te maken op bezette tijd - -``` -1. Behandelaar klikt op 14:00 (waar al afspraak staat met Jan de Vries) -2. Modal opent, behandelaar vult Maria Jansen in, klikt Opslaan -3. Systeem detecteert overlap: - - Nieuwe: Maria 14:00-15:00 - - Bestaand: Jan 14:00-15:00 -4. Conflict Dialog opent: - "⚠️ Je hebt al een afspraak: Jan de Vries - 14:00 (Intake)" -5a. Behandelaar kiest "Wijzigen": - → Terug naar modal, tijd veld gefocust - → Kiest 15:00, opslaan succesvol -5b. Behandelaar kiest "Toch inplannen": - → Beide afspraken op 14:00 - → Toast: "Afspraak opgeslagen (dubbele boeking)" -``` - -### 9.3 Flow: Afspraak → Verslag - -**Scenario:** Direct na intake verslag maken via AI - -``` -1. Behandelaar klikt op afspraak "Jan de Vries - 10:00 Intake" (zojuist afgelopen) -2. Detail popup opent -3. Behandelaar klikt "📝 Maak verslag" -4. Navigatie naar /epd/patients/[jan-id]/rapportage?encounter=[enc-id] -5. Rapportage module laadt met: - - Patient: Jan de Vries (locked) - - Type: Intake verslag - - Reference: "Afspraak 05-12-2024 10:00" -6. Behandelaar klikt "🎤 Start opname" -7. Behandelaar dicteert verslag van gesprek -8. Behandelaar klikt "Stop" -9. Deepgram transcribeert audio → tekst -10. Behandelaar klikt "AI Genereer verslag" -11. Claude genereert gestructureerd verslag -12. Behandelaar reviewt, past aan, klikt "Opslaan" -13. Verslag opgeslagen met encounter_id link -14. Terug naar agenda: afspraak toont 📝 icon -``` - -### 9.4 Flow: Drag & Drop Verzetten - -**Scenario:** Patient belt, moet naar andere tijd - -``` -1. Behandelaar ziet afspraak "Maria - Di 14:00" in week view -2. Behandelaar klikt en houdt vast op afspraak card -3. Visuele feedback: card krijgt schaduw, wordt semi-transparant -4. Behandelaar sleept naar "Wo 15:00" -5. Drop → Confirm dialog: - "Afspraak verzetten van di 14:00 naar wo 15:00?" - [Annuleren] [Bevestigen] -6. Behandelaar klikt "Bevestigen" -7. Systeem checkt conflict (geen) -8. Database update: period_start = wo 15:00 -9. Kalender refresh -10. Toast: "Afspraak verzet naar wo 15:00" -``` - ---- - -## 10. Validaties & Business Rules - -🎯 **Doel:** Overzicht van validatieregels. - -### 10.1 Formulier Validaties - -| Veld | Regel | Foutmelding | -|------|-------|-------------| -| Patient | Verplicht, moet bestaan | "Selecteer een patient" | -| Datum | Verplicht, format dd-mm-yyyy | "Vul een datum in" | -| Datum | Niet in verleden | "Datum ligt in het verleden" (warning, niet block) | -| Starttijd | Verplicht, 08:00-18:00 | "Kies een tijd binnen werktijden" | -| Duur | Verplicht, 15-120 min | "Kies een geldige duur" | -| Type | Verplicht | "Kies een type afspraak" | -| Notities | Max 500 karakters | "Notities te lang (max 500)" | - -### 10.2 Business Rules - -| Regel | Type | Gedrag | -|-------|------|--------| -| **Dubbele boeking** | Warning | Toon dialog, sta override toe | -| **Werktijden** | Soft | Waarschuw bij buiten 08:00-18:00, maar allow | -| **Verleden datum** | Soft | Waarschuw, maar allow (correcties achteraf) | -| **Eigen afspraken** | Hard | Kan alleen eigen afspraken zien/bewerken | -| **Soft delete** | Hard | Cancelled afspraken blijven in DB, niet verwijderd | -| **Verslag koppeling** | Auto | Verslag maakt automatisch link naar encounter | - -### 10.3 Conflict Detectie Logica - -``` -isConflict = - (new.start < existing.end) AND (new.end > existing.start) - AND (new.practitioner_id = existing.practitioner_id) - AND (existing.status != 'cancelled') -``` - ---- - -## 11. Prioritering MVP vs Post-MVP - -🎯 **Doel:** Duidelijke cut-off voor eerste oplevering. - -### MVP (Must Ship) - -| Feature | Reden | -|---------|-------| -| Week view kalender | Core navigatie | -| Dag view kalender | Alternatieve weergave | -| Nieuwe afspraak maken | Core functie | -| Patient zoeken (autocomplete) | Koppeling nodig | -| Afspraak bewerken | Wijzigingen nodig | -| Afspraak annuleren (soft delete) | Status management | -| Dubbele boeking warning | Foutpreventie | -| Conflict override | Flexibiliteit | -| "Maak verslag" navigatie | AI-integratie showcase | -| Basic drag-drop (desktop) | Snelle wijzigingen | -| Responsive mobile view | Demo-proof | - -### Stretch (Nice-to-Have voor Demo) - -| Feature | Reden | -|---------|-------| -| Werkdagen view | Extra convenience | -| Mini kalender sidebar | Navigatie gemak | -| Recente patiënten dropdown | Sneller repeat | -| Kleurcodering per type | Visueel | -| Verslag icon op afspraak | Status indicator | - -### Post-MVP (Later) - -| Feature | Wanneer | -|---------|---------| -| Recurring appointments | Na klantfeedback | -| Multi-practitioner | Bij team gebruik | -| Email/SMS notifications | Bij productie | -| Calendar sync (Google/Outlook) | Enterprise | -| Wachtlijstbeheer | Productie versie | -| Audit logging | Compliance | - ---- - -## 12. Bijlagen & Referenties - -🎯 **Doel:** Links naar gerelateerde documenten. - -### Interne Documenten - -| Document | Beschrijving | Status | -|----------|--------------|--------| -| PRD Agenda Module v1.0 | Productvisie en scope | ✅ Approved | -| TO Agenda Module (volgt) | Technische implementatie | 📋 Te schrijven | -| UX Stylesheet | Design tokens en componenten | ✅ Beschikbaar | -| Database Types | Supabase schema definities | ✅ Gegenereerd | - -### Externe Referenties - -| Bron | Gebruik | -|------|---------| -| [FullCalendar Docs](https://fullcalendar.io/docs) | Kalender library | -| [shadcn/ui](https://ui.shadcn.com/docs/components) | UI componenten | -| [date-fns](https://date-fns.org/) | Datum manipulatie | -| [FHIR Encounter](https://www.hl7.org/fhir/encounter.html) | Data model referentie | - -### Bestaande Code Patronen - -Referentie naar bestaande implementaties in codebase: - -| Pattern | Locatie | Hergebruik voor | -|---------|---------|-----------------| -| Patient list + search | `app/epd/patients/` | Patient zoeken component | -| Form validation | `app/epd/patients/[id]/intakes/` | Afspraak formulier | -| Modal dialogs | `components/ui/dialog.tsx` | Afspraak modal | -| Toast notifications | `components/ui/toaster.tsx` | Feedback | -| Server actions | `app/epd/patients/[id]/intakes/actions.ts` | CRUD operaties | -| Type definitions | `lib/types/intake.ts` | Appointment types | - ---- - -## Changelog - -| Versie | Datum | Auteur | Wijziging | -|--------|-------|--------|-----------| -| v1.0 | 02-12-2024 | Colin | Initieel FO op basis van PRD v1.0 | -| v1.1 | 02-12-2024 | Colin | UX verbeteringen: micro-interacties, smart defaults, keyboard nav, optimistic updates, inline validatie, responsive toolbar, recente patiënten, contextuele CTAs | - ---- - -**Document Status:** ✅ Klaar voor review -**Volgende stap:** → Technisch Ontwerp (TO) Agenda Module - ---- - -## Appendix: UX Checklist voor Implementatie - -Snelle checklist voor developers om UX verbeteringen niet te vergeten: - -### Must-Have UX (MVP) -- [ ] Auto-scroll naar "nu" bij pagina laden -- [ ] Vandaag kolom highlight -- [ ] Skeleton loading states -- [ ] Optimistic updates met rollback -- [ ] Inline validatie (niet alleen op submit) -- [ ] Smart defaults: type → duur koppeling -- [ ] Keyboard shortcuts: N, T, ←, →, Esc -- [ ] Focus management: auto-focus patient search in modal -- [ ] Toast feedback bij alle acties -- [ ] Unsaved changes warning -- [ ] Recente patiënten bij lege search -- [ ] Contextuele primary button (verslag maken prominent na afspraak) - -### Nice-to-Have UX (Stretch) -- [ ] Double-click op slot voor snelle afspraak -- [ ] Hover "+" icon op lege slots -- [ ] Match highlighting in zoekresultaten -- [ ] Slide animaties bij view/datum wisseling -- [ ] Undo toast na delete/verzetten (5 sec window) -- [ ] Prefetch volgende/vorige week -- [ ] Mobile swipe gestures -- [ ] Onboarding hints bij eerste gebruik diff --git a/docs/specs/agenda/prd-agenda-module-v1_0.md b/docs/specs/agenda/prd-agenda-module-v1_0.md deleted file mode 100644 index ecd1504..0000000 --- a/docs/specs/agenda/prd-agenda-module-v1_0.md +++ /dev/null @@ -1,695 +0,0 @@ -# 📄 Product Requirements Document (PRD) — Agenda Module - -**Projectnaam:** Mini-EPD Agenda Module -**Versie:** v1.0 -**Datum:** 02-12-2024 -**Auteur:** Colin - ---- - -## 1. Doelstelling - -🎯 **Doel van deze sectie:** Beschrijf waarom dit product of prototype wordt gebouwd en wat het beoogde resultaat is. - -### Waarom deze module? - -De agenda is het **kloppend hart** van elk EPD-systeem. Zonder agenda geen structuur, geen overzicht, geen efficiënte praktijkvoering. Voor het AI Speedrun project is de agenda module cruciaal omdat het: - -1. **De workflow compleet maakt**: Intake → Behandelplan → Afspraken → Verslaglegging → Herhaal -2. **Dagelijks gebruik afdwingt**: Een EPD zonder agenda wordt niet gebruikt, want behandelaars hebben dit elke dag nodig -3. **AI-integratie natuurlijk maakt**: Afspraak → AI-verslag in 1 klik, naadloos in de dagelijkse workflow -4. **Geloofwaardigheid toevoegt**: "Zonder agenda is het geen echt EPD" — dit maakt het prototype serieus - -### Wat willen we bereiken? - -Een **werkende agenda module** die behandelaars: -- Laat plannen, verzetten en annuleren van afspraken -- Koppelt aan patiëntendossiers én verslagen (bidirectioneel) -- Waarschuwt bij dubbele boekingen (maar niet blokkeert — flexibiliteit!) -- Integreert met bestaande intake/rapportage flows - -**Type oplevering:** MVP voor AI Speedrun demo + basis voor productieversie - -**Kernboodschap voor demo:** -> "Dit is geen speeltje — dit is een werkend EPD waar je morgen mee aan de slag kunt. Hier is mijn agenda voor deze week, klik, nieuwe afspraak, patient selecteren, opslaan. En nu direct een verslag maken. Dat is AI in de praktijk." - ---- - -## 2. Doelgroep - -🎯 **Doel:** Schets wie de eindgebruikers, stakeholders en testers zijn. - -### Primaire gebruikers (direct) - -**1. Behandelaars in de GGZ** -- **Behoefte:** Overzicht van hun dag/week, snel afspraken plannen, direct verslagen kunnen maken -- **Pijnpunten met huidige systemen:** - - Te veel klikken om een afspraak te maken - - Geen koppeling tussen afspraak en verslag - - Geen waarschuwing bij dubbele boekingen - - Slow and clunky interfaces -- **Wat ze willen zien:** "Kan ik hier echt mijn praktijk mee runnen?" - -**2. Praktijkassistenten / Secretaresses** -- **Behoefte:** Afspraken inplannen voor behandelaars, overzicht van beschikbaarheid -- **Pijnpunten:** Veel telefoontjes, handmatige planning, geen real-time updates -- **Opmerking:** Dit is stretch voor MVP — focus ligt op single-user (behandelaar) - -### Secundaire stakeholders (indirect) - -**3. Zorgmanagers / Praktijkhouders** -- **Interesse:** Kan dit hun praktijk efficiënter maken? -- **Vraag:** "Hoeveel kost dit vs. PinkRoccade/Nedap?" -- **Demo-moment:** Laten zien hoe snel afspraken worden ingepland vs. legacy systemen - -**4. Developers / Tech Managers** -- **Interesse:** Hoe is dit gebouwd? Wat kan AI echt? -- **Vraag:** "Hoeveel werk is dit geweest? Kan ons team dit ook?" -- **Demo-moment:** Transparantie over buildtime, tech stack, AI-gebruik - -**5. Potentiële klanten (via LinkedIn)** -- **Interesse:** Proof of concept dat "Software on Demand" werkt -- **Vraag:** "Is dit echt in 4 weken gebouwd?" -- **Demo-moment:** Build metrics, transparante timeline - -### Persona's - -**Persona 1: Marieke (Psycholoog, 38 jaar)** -- Zelfstandig gevestigd, 3 dagen per week cliënten -- Gebruikt nu Google Agenda + Word voor verslagen -- Frustratie: "Ik moet alles dubbel invoeren en kan niks terugvinden" -- Wens: "Gewoon een simpel systeem waar alles bij elkaar staat" - -**Persona 2: Bas (Psychiater, 52 jaar)** -- Werkt in instelling met Nedap ECD -- Frustratie: "Het kost me 5 minuten om een afspraak te maken, en dan crasht het nog" -- Wens: "Iets wat gewoon werkt, zoals Outlook maar dan met verslagen" - ---- - -## 3. Kernfunctionaliteiten (MVP-scope) - -🎯 **Doel:** Afbakenen van de minimale werkende functies. - -### Must-Have (MVP) - -1. **Kalenderweergave** - - **Input:** Datum selectie - - **Output:** Dag/Week/Werkdagen view met afspraken - - **Interactie:** Klikken op tijdslot = nieuwe afspraak, klikken op afspraak = details - - **Demo-waarde:** "Dit is mijn agenda, net als Outlook" - -2. **Afspraak maken** - - **Input:** Patient, datum/tijd, type afspraak (intake/behandeling/follow-up) - - **Output:** Nieuwe afspraak in kalender - - **Interactie:** Modal form, patient zoeken, tijd kiezen - - **Demo-waarde:** "Kijk hoe snel dit gaat — patient zoeken, tijd kiezen, klaar" - -3. **Dubbele boeking waarschuwing** - - **Input:** Overlappende tijden voor zelfde behandelaar - - **Output:** Warning met overzicht bestaande afspraken + optie om toch in te plannen - - **Interactie:** Dialog met "Wijzigen" of "Toch inplannen" knoppen - - **Demo-waarde:** "Het systeem waarschuwt me, maar ik kan het overrulen — flexibiliteit!" - -4. **Afspraak verzetten & annuleren** - - **Input:** Drag-drop (desktop) of Edit button (mobile) - - **Output:** Gewijzigde tijd of status 'cancelled' - - **Interactie:** Confirm dialog bij grote wijzigingen - - **Demo-waarde:** "Even snel een afspraak verzetten, done" - -5. **Patient koppeling** - - **Input:** Patient search (naam/BSN) - - **Output:** Patient geselecteerd, afspraak gekoppeld - - **Interactie:** Autocomplete search in modal - - **Demo-waarde:** "Alle patiëntgegevens direct beschikbaar" - -6. **Afspraak ↔ Verslag koppeling (bidirectioneel)** - - **Input:** "Maak verslag" button in afspraak detail - - **Output:** Verslag editor met afspraak pre-filled - - **Interactie:** Click-through tussen afspraak en verslag - - **Demo-waarde:** **"DIT is waar AI echt helpt — afspraak gehad, 1 klik, verslag maken"** - -### Nice-to-Have (Stretch voor MVP) - -7. **(Stretch) Recent patients dropdown** - - Laatste 5 patiënten snel selecteren - - Demo-waarde: Extra snelheid voor repeat gebruikers - -8. **(Stretch) Color coding per type** - - Intake = blauw, Behandeling = groen, etc. - - Demo-waarde: Visueel overzicht - -9. **(Stretch) Mini calendar sidebar** - - Maandoverzicht met navigatie - - Demo-waarde: Extra polish, maar niet essentieel - -### Expliciet NIET in MVP - -- ❌ Recurring appointments (herhaling) -- ❌ Multi-practitioner view (agenda's van meerdere behandelaars) -- ❌ Email/SMS notificaties naar patiënten -- ❌ Patient portal (patiënten zelf afspraken maken) -- ❌ Wachtlijstbeheer -- ❌ Video call integratie - ---- - -## 4. Gebruikersflows (Demo- of MVP-flows) - -🎯 **Doel:** Laten zien hoe de gebruiker stap-voor-stap door het systeem gaat. - -### Flow 1: Nieuwe afspraak maken (Snelste pad) - -**Scenario:** Behandelaar krijgt patient aan de telefoon, wil snel afspraak inplannen - -1. **Start:** Behandelaar kijkt naar agenda (week view) -2. **Actie:** Klikt op lege tijdslot (bijv. donderdag 14:00) -3. **Systeem:** Modal opent met pre-filled datum/tijd -4. **Actie:** Typt patient naam in zoekbalk: "Jan de" -5. **Systeem:** Autocomplete toont "Jan de Vries (12-03-1985)" -6. **Actie:** Selecteert patient, kiest type "Behandeling", klikt "Opslaan" -7. **Systeem:** Afspraak verschijnt in kalender -8. **Resultaat:** Afspraak ingepland in ~10 seconden - -**Demo-tijd:** 15 seconden (met uitleg: 30 sec) - -### Flow 2: Afspraak maken met dubbele boeking - -**Scenario:** Behandelaar vergist zich, wil afspraak maken op al bezet tijdslot - -1. **Start:** Behandelaar klikt op tijdslot 14:00 (al bezet met andere patient) -2. **Systeem:** Modal opent, behandelaar vult patient "Maria Jansen" in -3. **Actie:** Klikt "Opslaan" -4. **Systeem:** ⚠️ Warning toont: "Let op: dubbele boeking — Je hebt al een afspraak: Jan de Vries - 14:00 (Intake)" -5. **Beslismoment:** Behandelaar ziet twee opties: - - "Wijzigen" → terug naar form, andere tijd kiezen - - "Toch inplannen" → override, beide afspraken staan er -6. **Actie:** Klikt "Wijzigen", kiest 15:00 -7. **Systeem:** Geen warning, afspraak wordt opgeslagen -8. **Resultaat:** Conflict voorkomen, afspraak correct ingepland - -**Demo-tijd:** 30 seconden (laat zien: systeem helpt je, maar dwingt niet af) - -### Flow 3: Afspraak → Verslag (De AI-kracht) - -**Scenario:** Behandelaar heeft zojuist intake gehad, wil direct verslag maken - -1. **Start:** Behandelaar klikt op afspraak in kalender (net geweest: 10:00-11:00) -2. **Systeem:** Afspraak detail popup toont: - - Patient: Jan de Vries - - Type: Intakegesprek - - Notities: [leeg of korte aantekeningen] - - **Button: "📝 Maak verslag"** -3. **Actie:** Klikt "Maak verslag" -4. **Systeem:** Navigeert naar Rapportage module met: - - Patient pre-filled: Jan de Vries - - Type pre-selected: Intake verslag - - Afspraak linked: 02-12-2024 10:00 -5. **Actie:** Behandelaar gebruikt AI-transcriptie of typt verslag -6. **Systeem:** AI genereert structuur, samenvatting, suggesties -7. **Actie:** Behandelaar bewerkt, klikt "Opslaan" -8. **Systeem:** Verslag opgeslagen + automatisch gekoppeld aan afspraak -9. **Resultaat:** In agenda: afspraak toont nu 📝 icon (verslag aanwezig) - -**Demo-tijd:** 45 seconden (zonder verslag schrijven, alleen workflow tonen) - -**Demo-impact:** 🔥 **DIT is de killer feature** — naadloze integratie afspraak ↔ verslag ↔ AI - -### Flow 4: Afspraak verzetten (Desktop drag-drop) - -**Scenario:** Patient belt, kan niet op dinsdag, moet naar woensdag - -1. **Start:** Behandelaar ziet afspraak op dinsdag 14:00 -2. **Actie:** Klikt en houdt vast op afspraak card -3. **Systeem:** Afspraak wordt "draggable", cursor changes -4. **Actie:** Sleept naar woensdag 15:00 tijdslot -5. **Systeem:** Confirm dialog: "Afspraak verzetten naar wo 03-12 15:00?" -6. **Actie:** Klikt "Ja, verplaatsen" -7. **Systeem:** Afspraak verplaatst, conflict check uitgevoerd (geen warning) -8. **Resultaat:** Afspraak staat nu op woensdag 15:00 - -**Demo-tijd:** 10 seconden (visual impressive) - ---- - -## 5. Niet in Scope - -🎯 **Doel:** Duidelijk maken wat (nog) niet wordt gebouwd. - -### Features die NIET in MVP zitten - -**1. Multi-user & Rechtenbeheer** -- Geen rollen (admin/behandelaar/assistent) -- Geen gebruikersbeheer (toevoegen/verwijderen users) -- Geen rechten per patient/afspraak -- **Reden:** MVP is single-user demo, complexiteit te hoog voor Speedrun - -**2. Patient Portal & Self-Service** -- Patiënten kunnen niet zelf afspraken maken -- Geen patient login -- Geen afspraak bevestigingen/herinneringen via email/SMS -- **Reden:** Focus ligt op behandelaar workflow, patient side is apart product - -**3. Advanced Scheduling** -- Geen recurring appointments (wekelijks, maandelijks) -- Geen wachtlijstbeheer -- Geen automatische slot-filling -- Geen "find first available" functie -- **Reden:** Te complex voor MVP, weinig demo-waarde - -**4. External Integrations** -- Geen Google Calendar sync -- Geen Outlook sync -- Geen Teams integratie -- Geen iCal export -- **Reden:** Integraties vragen maanden werk, geen core functionaliteit - -**5. Advanced Analytics** -- Geen no-show tracking -- Geen bezettingsgraad dashboard -- Geen revenue/billing integratie -- **Reden:** Dit is post-MVP "nice to have" - -**6. Video Call Features** -- Geen ingebouwde video calling -- Geen Zoom/Teams meeting links automatisch genereren -- **Reden:** Complex, weinig meerwaarde voor demo - -**7. Production-Grade Features** -- Geen audit logging (wie heeft wat gewijzigd) -- Geen versioning van afspraken -- Geen backup/restore functionaliteit -- Geen advanced error recovery -- **Reden:** MVP is demo, niet productie-ready - -### Waarom deze keuzes? - -**Principe:** Focus op **snelheid** en **demo-impact**, niet op completeness. - -De agenda module moet: -✅ Werken voor 1 behandelaar -✅ Integreren met bestaande intake/rapportage flows -✅ Laten zien dat AI naadloos past in dagelijkse workflow -✅ Indruk maken: "Dit is geen speeltje, dit werkt écht" - -**Niet:** -❌ Alle edge cases afvangen -❌ Multi-tenant ready zijn -❌ Productie-grade security/performance -❌ Feature parity met legacy systemen - ---- - -## 6. Succescriteria - -🎯 **Doel:** Objectieve meetlat voor een geslaagde oplevering. - -### Demo Succescriteria - -**1. Demo Flow Compleetheid** -- [ ] Volledige flow 1-4 werkend zonder bugs -- [ ] Demo kan in **< 5 minuten** worden gedaan (inclusief uitleg) -- [ ] Geen "let's pretend" moments — alles werkt écht -- [ ] Mobile view werkt (backup als desktop faalt) - -**2. AI Integratie Showcase** -- [ ] Afspraak → Verslag flow werkt in **< 3 clicks** -- [ ] AI-generated content zichtbaar in verslag -- [ ] Bidirectionele link werkt (afspraak ↔ verslag beide kanten) - -**3. Gebruikerservaring** -- [ ] Conflict warning toont correcte info (patient naam, tijd) -- [ ] Patient search werkt met **< 500ms response time** -- [ ] Drag-drop feels responsive (< 100ms visual feedback) -- [ ] Geen UI glitches op demo tijdstip - -**4. Technische Stabiliteit** -- [ ] Geen crashes tijdens demo flow -- [ ] Database queries < 2 seconden -- [ ] Timezone handling correct (NL-tijd in UI, UTC in DB) - -### LinkedIn Content Succescriteria - -**5. Viral Potential** -- [ ] Screenshot-worthy moments (bijv. conflict warning, AI-verslag) -- [ ] "Build in public" metrics: X story points, Y hours, €Z kosten -- [ ] Quote-worthy moment: "4 weken, €50/maand, volledig werkend EPD" - -**6. Geloofwaardigheid** -- [ ] Live demo URL werkt (vercel deployment) -- [ ] Code op GitHub (public repo) -- [ ] Geen fake data in screenshots (echte-looking test data) - -### Post-Demo Validatie - -**7. User Feedback** -- [ ] Minimaal 1 zorgprofessional test het live -- [ ] Feedback verzameld: "Zou je dit gebruiken?" -- [ ] Top 3 missing features geïdentificeerd - -**8. Technical Learnings** -- [ ] AI prompt quality documented (wat werkte, wat niet) -- [ ] Performance bottlenecks geïdentificeerd -- [ ] Timezone edge cases getest (zomertijd transitie) - -### Kwantitatieve Metrics - -| Metric | Target | Stretch | -|--------|--------|---------| -| **Demo tijd** | < 5 min | < 3 min | -| **Page load tijd** | < 2s | < 1s | -| **Afspraak maken** | < 10 sec | < 5 sec | -| **Patient search response** | < 500ms | < 200ms | -| **LinkedIn impressions** | 5,000 | 10,000 | -| **Engagement rate** | 2% | 5% | -| **Inbound leads** | 2 | 5 | - ---- - -## 7. Risico's & Mitigatie - -🎯 **Doel:** Risico's vroeg signaleren en plannen hoe ermee om te gaan. - -### Technische Risico's - -| Risico | Kans | Impact | Mitigatie | -|--------|------|--------|-----------| -| **FullCalendar styling conflicts** | Middel | Middel | Prototype early (week 1), CSS isolation strategy, fallback naar simpeler calendar | -| **Timezone bugs** | Middel | Hoog | Test zomertijd transitie (29 maart), gebruik date-fns-tz, TIMESTAMPTZ in DB | -| **Performance bij veel afspraken** | Laag | Middel | Query optimization met indices, date range filtering, pagination if needed | -| **Drag-drop mobile UX** | Hoog | Laag | Accept limitation: desktop-only drag, mobile uses Edit button | -| **Conflict detection edge cases** | Middel | Middel | Document assumptions, test concurrent edits, graceful degradation | - -### Scope & Planning Risico's - -| Risico | Kans | Impact | Mitigatie | -|--------|------|--------|-----------| -| **Scope creep** | Hoog | Hoog | **Hard blocker:** PRD is frozen. Extra features → post-MVP backlog | -| **Underestimated complexity** | Middel | Hoog | Buffer 20% op story points, drop stretch features first | -| **Demo prep time onderschat** | Middel | Middel | Test demo flow 2 dagen voor launch, record backup video | -| **Integration breaking changes** | Laag | Hoog | Freeze intake/rapportage modules week voor agenda start | - -### Demo & Marketing Risico's - -| Risico | Kans | Impact | Mitigatie | -|--------|------|--------|-----------| -| **Live demo fails** | Middel | Hoog | Pre-record video backup, test deployment morning-of, have fallback slides | -| **LinkedIn post flops** | Middel | Middel | A/B test headlines, post at optimal time (dinsdag 9:00), engage in comments | -| **Negative feedback** | Laag | Middel | Respond professionally, use as learning, "building in public = warts and all" | -| **Competitor copies idea** | Laag | Laag | Accept risk, speed-to-market is advantage, open source = marketing | - -### Data & Privacy Risico's - -| Risico | Kans | Impact | Mitigatie | -|--------|------|--------|-----------| -| **Demo data looks fake** | Middel | Middel | Use realistic names/dates, Nederlandse context, avoid "Test User 1" | -| **Accidentally expose PII** | Laag | Hoog | **Only use fake data**, sanitize screenshots, no real BSN/medical info | -| **GDPR concerns** | Laag | Middel | Demo data only, privacy policy placeholder, mention "demo purposes" | - -### Mitigation Strategies - -**1. Technical Blockers (FullCalendar, Timezone)** -- **Week 1 Action:** Spike FullCalendar integration, confirm it works -- **Fallback:** If FullCalendar too complex → simple custom grid view (bare minimum) -- **Test Plan:** Timezone edge case tests (E5.S3) before launch - -**2. Scope Management** -- **Weekly Check:** "Are we building MVP or nice-to-have?" -- **Decision Rule:** "Does this feature appear in demo flow? No → backlog" -- **Communication:** Update Mission Control daily with scope decisions - -**3. Demo Insurance** -- **T-2 days:** Full dry-run with fresh browser -- **T-1 day:** Record backup video (Plan B) -- **Demo day:** Local fallback if Vercel down, screenshots ready - -**4. Marketing Hedge** -- **Content Calendar:** 3 LinkedIn posts drafted in advance -- **Engagement Plan:** Reply to every comment within 1 hour -- **Narrative Pivot:** If build too fast → "How I did it", if too slow → "Lessons learned" - ---- - -## 8. Roadmap / Vervolg (Post-MVP) - -🎯 **Doel:** Richting geven aan toekomstige uitbreidingen. - -### Fase 1: MVP Launch (Week 1-2 van Agenda Build) -- ✅ Basic calendar views (dag/week/werkdagen) -- ✅ Afspraak CRUD (make, edit, cancel) -- ✅ Conflict detection met override -- ✅ Afspraak ↔ Verslag koppeling -- ✅ Patient search - -**Output:** Demo-ready agenda voor AI Speedrun showcase - ---- - -### Fase 2: Production Readiness (Post-Speedrun, Week +4-8) - -**Prioriteit: Hoog** (als klant interesse toont) - -1. **Multi-User Support** - - Rollenbeheer (admin, behandelaar, assistent) - - User management (toevoegen/verwijderen) - - Per-user agenda view - - **Business Value:** Nodig voor praktijken met >1 behandelaar - -2. **Security Hardening** - - Row Level Security (RLS) policies - - Audit logging (wie deed wat wanneer) - - Session management - - **Business Value:** Compliance voor echte zorgdata - -3. **Performance Optimization** - - Query optimization - - Caching strategie - - Pagination voor grote datasets - - **Business Value:** Schaalbaar naar 100+ afspraken/dag - -4. **Error Handling & Monitoring** - - Sentry integratie - - Graceful degradation - - Retry mechanisms - - **Business Value:** Productie-grade stabiliteit - -**Effort:** ~3-4 weken (1 FTE) -**Business Case:** Nodig voor betalende klanten - ---- - -### Fase 3: Feature Parity (Month 3-4) - -**Prioriteit: Middel** (nice-to-have, niet blocker) - -1. **Advanced Scheduling** - - Recurring appointments (wekelijks/maandelijks) - - Wachtlijstbeheer - - "Find first available" slot - - **Business Value:** Efficiency boost voor drukke praktijken - -2. **Patient Communications** - - Email herinneringen (24u voor afspraak) - - SMS notificaties (optioneel) - - Cancellation confirmations - - **Business Value:** Reduce no-shows (vaak 10-20% in GGZ) - -3. **Multi-Practitioner View** - - Agenda van meerdere behandelaars naast elkaar - - Resource allocation (kamers, apparatuur) - - Team scheduling - - **Business Value:** Voor grotere praktijken/instellingen - -4. **Reporting & Analytics** - - Bezettingsgraad dashboard - - No-show tracking - - Revenue per practitioner (indien billing) - - **Business Value:** Management insights - -**Effort:** ~4-6 weken (1 FTE) -**Business Case:** Upsell opportunity, premium features - ---- - -### Fase 4: Ecosystem Integration (Month 5-6) - -**Prioriteit: Laag** (tenzij strategische partnerships) - -1. **Calendar Sync** - - Google Calendar sync (two-way) - - Outlook sync - - iCal export - - **Business Value:** Reduce double-entry, increase adoption - -2. **Video Call Integration** - - Zoom meeting auto-generation - - Teams meeting links - - In-browser video (WebRTC) - - **Business Value:** COVID-era feature, online consulten - -3. **External System Integration** - - PinkRoccade koppeling (indien klant vraagt) - - Nedap ECD import/export - - Zorgdomein connectie - - **Business Value:** Migration path van legacy systemen - -**Effort:** ~6-12 weken (1 FTE) -**Business Case:** Vereist voor enterprise deals - ---- - -### Fase 5: Patient Self-Service (Month 7-9) - -**Prioriteit: Hoog** (maar separate product) - -1. **Patient Portal** - - Inloggen met DigiD - - Eigen afspraken bekijken - - Afspraken maken (binnen beschikbare slots) - - Afspraken annuleren (met policy) - - **Business Value:** Reduce admin workload, 24/7 booking - -2. **Patient Experience** - - Intake formulieren online invullen - - Documenten uploaden (verwijsbrieven, etc.) - - Vragenlijsten invullen (ROM, etc.) - - **Business Value:** Efficiency, patient empowerment - -**Effort:** ~8-12 weken (1 FTE) -**Business Case:** Dit kan separate SaaS product zijn - ---- - -### Strategic Considerations - -**Build vs. Integrate Decision Tree:** - -``` -Feature request - │ - ├─ Is it core to EPD? - │ ├─ Yes → Build it - │ └─ No → Can we integrate? - │ ├─ Yes → Use API/webhook - │ └─ No → Deprioritize - │ - └─ Does it differentiate us? - ├─ Yes → Build it (competitive advantage) - └─ No → Buy/integrate (focus on AI) -``` - -**Example:** -- Video calling → **Integrate** (Zoom API) -- AI-powered scheduling → **Build** (our secret sauce) -- Billing/invoicing → **Integrate** (Mollie, etc.) -- Treatment plan generation → **Build** (core AI value) - ---- - -### Parking Lot (Ideas, geen commitment) - -- AI-powered "optimal scheduling" (ML-based slot recommendations) -- Predictive no-show scoring (AI flags high-risk appointments) -- Voice-controlled agenda ("Schedule Jan de Vries for Thursday 2pm") -- WhatsApp integration for reminders -- Apple Health / Google Fit data import (for lifestyle coaching) -- Group session management (multiple patients per appointment) - -**Criteria for moving from Parking Lot → Roadmap:** -1. Customer explicitly asks for it (at least 3 requests) -2. Clear business case (revenue, retention, or viral potential) -3. Feasible effort (< 4 weeks build time) - ---- - -## 9. Bijlagen & Referenties - -🎯 **Doel:** Bronnen koppelen voor context en consistentie. - -### Interne Documenten - -**Strategisch:** -- [AI Speedrun Manifesto](link) — Overall projectvisie -- [Mission Control v1](link) — Project status tracking -- [Build Metrics Tracking](link) — Transparante build log - -**Functioneel:** -- FO Mini-EPD v2.0 — Volledige functionele specificatie -- TO Mini-EPD v1.2 — Technische architectuur -- PRD Intake Module v1 — Hoe intake werkt (dependency) -- PRD Rapportage Module v1 — Hoe verslagen werken (integration point) - -**Technisch:** -- Bouwplan Agenda Module v1.1 — Gedetailleerde technische implementatie (dit volgt ná PRD approval) -- API Access Mini-ECD — AI integratie documentatie -- Database Schema FHIR GGZ — Data model - -**UX/UI:** -- UX Stylesheet — Design system & component library -- Interface Design Plan — Wireframes en user flows -- Mocks UI Flow — Visual designs - -### Externe Referenties - -**Standards & Compliance:** -- [FHIR R4 Encounter Resource](https://www.hl7.org/fhir/encounter.html) — Healthcare data standard -- [ISO 8601 DateTime](https://en.wikipedia.org/wiki/ISO_8601) — Date/time formatting -- [IANA Time Zones](https://www.iana.org/time-zones) — Timezone database - -**Technology:** -- [FullCalendar Documentation](https://fullcalendar.io/docs) — Calendar library we'll use -- [shadcn/ui Components](https://ui.shadcn.com/docs/components) — UI component library -- [Supabase Auth](https://supabase.com/docs/guides/auth) — Authentication -- [date-fns](https://date-fns.org/) — Date manipulation library - -**Inspiration & Benchmarks:** -- [Cal.com](https://cal.com) — Open-source scheduling (UI inspiration) -- [Calendly](https://calendly.com) — Modern scheduling UX (simplicity benchmark) -- [Acuity Scheduling](https://acuityscheduling.com) — Healthcare scheduling features -- Google Calendar — De facto standard, users expect this UX - -### Competitive Analysis (Legacy GGZ Systems) - -**Why we're better:** - -| Feature | PinkRoccade | Nedap ECD | Zorgdomein | **Mini-EPD** | -|---------|-------------|-----------|------------|--------------| -| Afspraak maken | 5+ clicks, slow | 4-5 clicks | 6+ clicks | **2-3 clicks** | -| Dubbele boeking | Hard block | Warning | Hard block | **Warning + override** | -| Afspraak ↔ Verslag | Manual link | No link | Manual link | **1-click bidirectioneel** | -| AI-assisted verslag | ❌ | ❌ | ❌ | **✅ Core feature** | -| Mobile UX | Terrible | Desktop-only | Barely works | **Responsive + optimized** | -| Load time | 5-10s | 3-5s | 4-8s | **< 2s** | -| Training tijd | 2-4 dagen | 1-2 dagen | 2-3 dagen | **< 10 minuten** | -| Kosten | €200-400/maand | €150-300/maand | €180-350/maand | **€50/maand** | - -**Key Differentiators:** -1. 🚀 **Speed:** Zowel UX als development speed -2. 🤖 **AI Integration:** Naadloos, niet als afterthought -3. 💰 **Price:** 1/4e van legacy systemen -4. 🎯 **Focus:** Alleen wat je nodig hebt, geen bloat - ---- - -### Contact & Feedback - -**Product Owner:** Colin -**Email:** [email] -**LinkedIn:** [profile] -**GitHub Repo:** [link to public repo] - -**Feedback Welcome:** -- 💬 DM op LinkedIn -- 🐛 GitHub Issues voor bugs -- 💡 Feature requests via Google Form [link] - ---- - -**Document Status:** ✅ Approved -**Next Step:** → Bouwplan Agenda Module v1.1 (technische uitwerking) - -**Changelog:** - -| Versie | Datum | Auteur | Wijziging | -|--------|-------|--------|-----------| -| v1.0 | 02-12-2024 | Colin | Initiële PRD — scope, flows, success criteria gedefinieerd | diff --git a/docs/specs/agenda/to-agenda-module-v1_0.md b/docs/specs/agenda/to-agenda-module-v1_0.md deleted file mode 100644 index d0937fa..0000000 --- a/docs/specs/agenda/to-agenda-module-v1_0.md +++ /dev/null @@ -1,1778 +0,0 @@ -# ⚙️ Technisch Ontwerp (TO) – Agenda Module - -**Projectnaam:** Mini-EPD Agenda Module -**Versie:** v1.0 -**Datum:** 02-12-2024 -**Auteur:** Colin - ---- - -## 1. Doel en relatie met PRD en FO - -🎯 **Doel van dit document:** -Het Technisch Ontwerp beschrijft **hoe** de Agenda Module technisch wordt geïmplementeerd. Dit document vertaalt de functionele specificaties uit het FO naar concrete technische oplossingen, architectuur en implementatiedetails. - -📘 **Document hiërarchie:** -``` -PRD (Wat & Waarom) → FO (Hoe functioneel) → TO (Hoe technisch) -``` - -**Referenties:** -- PRD: `prd-agenda-module-v1_0.md` — Scope, user stories, success criteria -- FO: `fo-agenda-module-v1_0.md` — Functionele werking, UX, flows -- Bestaande codebase: `/home/colin/development/15-mini-epd-prototype` - -**Scope:** -- MVP implementatie van agenda module -- Integratie met bestaande EPD modules (patients, rapportage) -- FullCalendar integratie -- FHIR-compliant data model (Encounter resource) - ---- - -## 2. Technische Architectuur Overzicht - -🎯 **Doel:** Globaal beeld van de systeemarchitectuur. - -### 2.1 High-Level Architecture - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ FRONTEND │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ Next.js 14 (App Router) │ │ -│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │ │ -│ │ │ EPD Layout │ │ Agenda │ │ Shared Components │ │ │ -│ │ │ + Sidebar │ │ Module │ │ (shadcn/ui, forms) │ │ │ -│ │ └─────────────┘ └──────┬──────┘ └─────────────────────────┘ │ │ -│ │ │ │ │ -│ │ ┌──────┴──────┐ │ │ -│ │ │ FullCalendar│ │ │ -│ │ │ React │ │ │ -│ │ └─────────────┘ │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -└────────────────────────────────┬────────────────────────────────────────┘ - │ - ┌────────────┴────────────┐ - │ API Layer │ - │ (Next.js API Routes │ - │ + Server Actions) │ - └────────────┬────────────┘ - │ - ┌────────────────────────┼────────────────────────┐ - │ │ │ - ▼ ▼ ▼ -┌───────────────┐ ┌─────────────────┐ ┌─────────────────┐ -│ Supabase │ │ Supabase │ │ Deepgram │ -│ PostgreSQL │ │ Auth │ │ (Audio→Text) │ -│ (Data) │ │ (Identity) │ │ │ -└───────────────┘ └─────────────────┘ └─────────────────┘ -``` - -### 2.2 Agenda Module Architecture - -``` -app/epd/agenda/ -├── page.tsx # Server Component - data fetching -├── layout.tsx # Optional: agenda-specific layout -├── loading.tsx # Suspense fallback -├── error.tsx # Error boundary -│ -├── components/ -│ ├── calendar-view.tsx # FullCalendar wrapper (Client) -│ ├── calendar-toolbar.tsx # Navigation, view switcher -│ ├── appointment-modal.tsx # Create/Edit form (Client) -│ ├── appointment-detail.tsx # Popup/popover detail view -│ ├── conflict-dialog.tsx # Double booking warning -│ ├── patient-search.tsx # Autocomplete combobox -│ └── appointment-card.tsx # Event rendering in calendar -│ -├── hooks/ -│ ├── use-appointments.ts # Data fetching hook (SWR/React Query) -│ ├── use-appointment-mutations.ts # Create/Update/Delete -│ └── use-conflict-check.ts # Real-time conflict detection -│ -├── lib/ -│ ├── calendar-config.ts # FullCalendar configuration -│ ├── appointment-utils.ts # Date/time helpers -│ └── conflict-detection.ts # Overlap logic -│ -├── actions.ts # Server Actions (mutations) -└── types.ts # Module-specific types -``` - -### 2.3 Data Flow - -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ CREATE APPOINTMENT FLOW │ -├─────────────────────────────────────────────────────────────────────┤ -│ │ -│ 1. User clicks time slot │ -│ │ │ -│ ▼ │ -│ 2. Modal opens (Client Component) │ -│ │ │ -│ ▼ │ -│ 3. User fills form + patient search │ -│ │ │ -│ ├──► Patient Search API (/api/patients/search) │ -│ │ │ │ -│ │ ▼ │ -│ │ Supabase Query (patients table) │ -│ │ │ │ -│ │ ▼ │ -│ │ Return patient list │ -│ │ │ -│ ▼ │ -│ 4. User clicks "Save" │ -│ │ │ -│ ▼ │ -│ 5. Client-side validation (Zod) │ -│ │ │ -│ ▼ │ -│ 6. Conflict check (Server Action) │ -│ │ │ -│ ├──► No conflict ──► Create appointment │ -│ │ │ │ -│ │ ▼ │ -│ │ Supabase INSERT (encounters) │ -│ │ │ │ -│ │ ▼ │ -│ │ Optimistic UI update │ -│ │ │ │ -│ │ ▼ │ -│ │ Toast: "Afspraak opgeslagen" │ -│ │ │ -│ └──► Conflict ──► Show Conflict Dialog │ -│ │ │ -│ ├──► "Wijzigen" ──► Back to form │ -│ │ │ -│ └──► "Toch inplannen" ──► Create with flag │ -│ │ -└─────────────────────────────────────────────────────────────────────┘ -``` - ---- - -## 3. Techstack Selectie - -🎯 **Doel:** Onderbouwde keuze van technologieën. - -### 3.1 Core Stack (Bestaand) - -| Component | Technologie | Versie | Argumentatie | -|-----------|-------------|--------|--------------| -| **Framework** | Next.js | 14.2.18 | Bestaande codebase, App Router, Server Components | -| **Runtime** | React | 18.3.1 | Bestaande codebase | -| **Language** | TypeScript | ^5 | Type safety, bestaande patterns | -| **Database** | Supabase PostgreSQL | - | Bestaande infra, RLS, realtime | -| **Auth** | Supabase Auth | ^0.7.0 | Bestaande implementatie | -| **Styling** | TailwindCSS | ^3.4.18 | Bestaande design system | -| **UI Components** | shadcn/ui | - | Bestaande component library | -| **Forms** | React Hook Form | ^7.66.1 | Bestaande pattern | -| **Validation** | Zod | ^4.1.12 | Bestaande pattern | -| **Date Handling** | date-fns | ^4.1.0 | Bestaande utility | - -### 3.2 Nieuwe Dependencies (Agenda-specifiek) - -| Component | Technologie | Versie | Argumentatie | Alternatieven | -|-----------|-------------|--------|--------------|---------------| -| **Calendar** | FullCalendar | ^6.1.x | Feature-rijk, React support, drag-drop | react-big-calendar, custom | -| **FC Core** | @fullcalendar/core | ^6.1.x | Base package | - | -| **FC React** | @fullcalendar/react | ^6.1.x | React wrapper | - | -| **FC DayGrid** | @fullcalendar/daygrid | ^6.1.x | Month/week grid view | - | -| **FC TimeGrid** | @fullcalendar/timegrid | ^6.1.x | Day/week time view | - | -| **FC Interaction** | @fullcalendar/interaction | ^6.1.x | Drag-drop, resize | - | -| **Data Fetching** | SWR | ^2.x | Caching, revalidation, optimistic | React Query | - -### 3.3 Package Installation - -```bash -# FullCalendar packages -pnpm add @fullcalendar/core @fullcalendar/react @fullcalendar/daygrid @fullcalendar/timegrid @fullcalendar/interaction - -# Data fetching (optional, can use existing pattern) -pnpm add swr -``` - -### 3.4 Techstack Decision: FullCalendar - -**Waarom FullCalendar:** -- ✅ Mature library (10+ jaar development) -- ✅ Comprehensive API voor alle agenda features -- ✅ Built-in drag-drop met event resizing -- ✅ Mobile-responsive out of the box -- ✅ Goede TypeScript support -- ✅ Styling customizable via CSS variables -- ✅ Grote community, veel voorbeelden - -**Risico's & Mitigatie:** -| Risico | Mitigatie | -|--------|-----------| -| Styling conflicts met Tailwind | CSS isolation, custom CSS variables | -| Bundle size (~200KB) | Tree-shaking, alleen noodzakelijke plugins | -| Learning curve | Spike in week 1, prototype first | - ---- - -## 4. Datamodel - -🎯 **Doel:** Structuur van de data in de database. - -### 4.1 Bestaande Tabel: `encounters` - -De `encounters` tabel bestaat al en is FHIR-compliant. We gebruiken deze voor afspraken. - -```sql --- Bestaande tabel structuur (uit database.types.ts) -CREATE TABLE encounters ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - - -- Relaties - patient_id UUID NOT NULL REFERENCES patients(patient_id), - practitioner_id UUID REFERENCES practitioners(practitioner_id), - intake_id UUID REFERENCES intakes(id), - intake_note_id UUID REFERENCES intake_notes(id), - organization_id UUID REFERENCES organizations(id), - - -- Timing (FHIR Period) - period_start TIMESTAMPTZ NOT NULL, - period_end TIMESTAMPTZ, - - -- Classification (FHIR Coding) - class_code TEXT, -- 'AMB' (ambulatory), 'VR' (virtual) - class_display TEXT, - type_code TEXT, -- 'intake', 'treatment', 'followup' - type_display TEXT, -- 'Intakegesprek', 'Behandeling', 'Follow-up' - - -- Status (FHIR enum) - status encounter_status NOT NULL DEFAULT 'planned', - -- Values: planned, in-progress, on-hold, completed, cancelled (actual DB enum) - - -- Priority (optional) - priority_code TEXT, - priority_display TEXT, - - -- Reason (array for multiple reasons) - reason_code TEXT[], - reason_display TEXT[], - - -- Notes - notes TEXT, - - -- Audit - created_at TIMESTAMPTZ DEFAULT now(), - updated_at TIMESTAMPTZ DEFAULT now(), - identifier TEXT -- External identifier if needed -); - --- Indexes for performance -CREATE INDEX idx_encounters_patient ON encounters(patient_id); -CREATE INDEX idx_encounters_practitioner ON encounters(practitioner_id); -CREATE INDEX idx_encounters_period ON encounters(period_start, period_end); -CREATE INDEX idx_encounters_status ON encounters(status); -``` - -### 4.2 Nieuw: Encounter-Report Linking (E0 Migration) - -```sql --- Migration: Add encounter_id to reports table -ALTER TABLE reports -ADD COLUMN encounter_id UUID REFERENCES encounters(id); - --- Index for quick lookups -CREATE INDEX idx_reports_encounter ON reports(encounter_id); - --- Optional: Backfill existing reports based on matching dates/patients --- (Post-MVP task) -``` - -### 4.3 Type Definitions - -```typescript -// lib/types/encounter.ts - -import { z } from 'zod'; -import type { Database } from '@/lib/supabase/database.types'; - -// Base types from database -export type Encounter = Database['public']['Tables']['encounters']['Row']; -export type EncounterInsert = Database['public']['Tables']['encounters']['Insert']; -export type EncounterUpdate = Database['public']['Tables']['encounters']['Update']; -export type EncounterStatus = Database['public']['Enums']['encounter_status']; - -// Appointment types (subset of FHIR encounter types for GGZ) -export const AppointmentType = { - INTAKE: 'intake', - TREATMENT: 'treatment', - FOLLOWUP: 'followup', -} as const; - -export type AppointmentTypeValue = typeof AppointmentType[keyof typeof AppointmentType]; - -export const AppointmentTypeDisplay: Record = { - intake: 'Intakegesprek', - treatment: 'Behandeling', - followup: 'Follow-up', -}; - -export const AppointmentTypeDuration: Record = { - intake: 60, - treatment: 45, - followup: 30, -}; - -// Zod Schemas for validation -export const CreateAppointmentSchema = z.object({ - patient_id: z.string().uuid('Patient ID moet een geldige UUID zijn'), - period_start: z.string().datetime('Ongeldige datum/tijd'), - duration_minutes: z.number().min(15).max(120), - type_code: z.enum(['intake', 'treatment', 'followup']), - notes: z.string().max(500).optional(), -}); - -export const UpdateAppointmentSchema = CreateAppointmentSchema.partial().extend({ - id: z.string().uuid(), - status: z.enum(['planned', 'in-progress', 'on-hold', 'completed', 'cancelled']).optional(), -}); - -export type CreateAppointmentInput = z.infer; -export type UpdateAppointmentInput = z.infer; - -// Extended type with relations -export interface AppointmentWithPatient extends Encounter { - patient: { - patient_id: string; - name_family: string; - name_given: string[]; - birth_date: string; - gender: string; - }; - reports?: { - id: string; - created_at: string; - }[]; -} - -// FullCalendar event type -export interface CalendarEvent { - id: string; - title: string; - start: Date; - end: Date; - extendedProps: { - patient_id: string; - patient_name: string; - type_code: AppointmentTypeValue; - type_display: string; - status: EncounterStatus; - notes?: string; - has_report: boolean; - }; - backgroundColor?: string; - borderColor?: string; - classNames?: string[]; -} -``` - -### 4.4 Entity Relationship Diagram - -``` -┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ -│ practitioners │ │ encounters │ │ patients │ -├─────────────────┤ ├─────────────────┤ ├─────────────────┤ -│ practitioner_id │◄──────│ practitioner_id │ │ patient_id │ -│ name_family │ 1:N │ patient_id │──────►│ name_family │ -│ name_given[] │ │ period_start │ N:1 │ name_given[] │ -│ ... │ │ period_end │ │ birth_date │ -└─────────────────┘ │ type_code │ │ gender │ - │ status │ │ ... │ - │ notes │ └─────────────────┘ - │ ... │ - └────────┬────────┘ - │ - │ 1:N (optional) - ▼ - ┌─────────────────┐ - │ reports │ - ├─────────────────┤ - │ id │ - │ encounter_id │ ◄── NEW (E0 migration) - │ patient_id │ - │ type │ - │ content │ - │ ... │ - └─────────────────┘ -``` - ---- - -## 5. API Ontwerp - -🎯 **Doel:** Overzicht van endpoints en server actions. - -### 5.1 API Routes - -| Endpoint | Method | Input | Output | Auth | Gebruik | -|----------|--------|-------|--------|------|---------| -| `/api/appointments` | GET | `?start=&end=&practitioner_id=` | `Appointment[]` | Required | Kalender data laden | -| `/api/appointments` | POST | `CreateAppointmentInput` | `Appointment` | Required | Nieuwe afspraak | -| `/api/appointments/[id]` | GET | - | `AppointmentWithPatient` | Required | Detail ophalen | -| `/api/appointments/[id]` | PATCH | `UpdateAppointmentInput` | `Appointment` | Required | Afspraak wijzigen | -| `/api/appointments/[id]` | DELETE | - | `{ success: true }` | Required | Soft delete (status=cancelled) | -| `/api/appointments/conflicts` | POST | `{ start, end, exclude_id? }` | `Appointment[]` | Required | Conflict check | -| `/api/patients/search` | GET | `?q=&limit=` | `Patient[]` | Required | Patient autocomplete | - -### 5.2 Server Actions (Preferred for Mutations) - -```typescript -// app/epd/agenda/actions.ts -'use server'; - -import { revalidatePath } from 'next/cache'; -import { createClient } from '@/lib/auth/server'; -import { CreateAppointmentSchema, UpdateAppointmentSchema } from './types'; -import type { CreateAppointmentInput, UpdateAppointmentInput } from './types'; - -export async function createAppointment(input: CreateAppointmentInput) { - // 1. Validate input - const validated = CreateAppointmentSchema.parse(input); - - // 2. Get authenticated user - const supabase = await createClient(); - const { data: { user } } = await supabase.auth.getUser(); - if (!user) throw new Error('Niet ingelogd'); - - // 3. Get practitioner_id from user - const { data: practitioner } = await supabase - .from('practitioners') - .select('practitioner_id') - .eq('user_id', user.id) - .single(); - - // 4. Calculate period_end - const periodStart = new Date(validated.period_start); - const periodEnd = new Date(periodStart.getTime() + validated.duration_minutes * 60000); - - // 5. Insert encounter - const { data, error } = await supabase - .from('encounters') - .insert({ - patient_id: validated.patient_id, - practitioner_id: practitioner?.practitioner_id, - period_start: periodStart.toISOString(), - period_end: periodEnd.toISOString(), - type_code: validated.type_code, - type_display: AppointmentTypeDisplay[validated.type_code], - status: 'planned', - notes: validated.notes, - class_code: 'AMB', - class_display: 'ambulatory', - }) - .select() - .single(); - - if (error) throw error; - - // 6. Revalidate cache - revalidatePath('/epd/agenda'); - - return data; -} - -export async function updateAppointment(input: UpdateAppointmentInput) { - const validated = UpdateAppointmentSchema.parse(input); - const supabase = await createClient(); - - const updateData: Record = {}; - - if (validated.period_start) { - updateData.period_start = validated.period_start; - if (validated.duration_minutes) { - const start = new Date(validated.period_start); - updateData.period_end = new Date(start.getTime() + validated.duration_minutes * 60000).toISOString(); - } - } - - if (validated.type_code) { - updateData.type_code = validated.type_code; - updateData.type_display = AppointmentTypeDisplay[validated.type_code]; - } - - if (validated.status) updateData.status = validated.status; - if (validated.notes !== undefined) updateData.notes = validated.notes; - - updateData.updated_at = new Date().toISOString(); - - const { data, error } = await supabase - .from('encounters') - .update(updateData) - .eq('id', validated.id) - .select() - .single(); - - if (error) throw error; - - revalidatePath('/epd/agenda'); - return data; -} - -export async function cancelAppointment(id: string) { - const supabase = await createClient(); - - const { data, error } = await supabase - .from('encounters') - .update({ - status: 'cancelled', - updated_at: new Date().toISOString(), - }) - .eq('id', id) - .select() - .single(); - - if (error) throw error; - - revalidatePath('/epd/agenda'); - return data; -} - -export async function checkConflicts( - start: string, - end: string, - excludeId?: string -): Promise { - const supabase = await createClient(); - const { data: { user } } = await supabase.auth.getUser(); - if (!user) throw new Error('Niet ingelogd'); - - // Get practitioner_id - const { data: practitioner } = await supabase - .from('practitioners') - .select('practitioner_id') - .eq('user_id', user.id) - .single(); - - if (!practitioner) return []; - - // Query overlapping appointments - let query = supabase - .from('encounters') - .select('*') - .eq('practitioner_id', practitioner.practitioner_id) - .neq('status', 'cancelled') - .lt('period_start', end) - .gt('period_end', start); - - if (excludeId) { - query = query.neq('id', excludeId); - } - - const { data, error } = await query; - - if (error) throw error; - return data || []; -} -``` - -### 5.3 API Route Implementation - -```typescript -// app/api/appointments/route.ts -import { NextRequest, NextResponse } from 'next/server'; -import { createClient } from '@/lib/auth/server'; - -export async function GET(request: NextRequest) { - try { - const { searchParams } = new URL(request.url); - const start = searchParams.get('start'); - const end = searchParams.get('end'); - - if (!start || !end) { - return NextResponse.json( - { error: 'start en end parameters zijn verplicht' }, - { status: 400 } - ); - } - - const supabase = await createClient(); - const { data: { user } } = await supabase.auth.getUser(); - - if (!user) { - return NextResponse.json({ error: 'Niet ingelogd' }, { status: 401 }); - } - - // Get practitioner - const { data: practitioner } = await supabase - .from('practitioners') - .select('practitioner_id') - .eq('user_id', user.id) - .single(); - - // Fetch appointments with patient data - const { data, error } = await supabase - .from('encounters') - .select(` - *, - patient:patients(patient_id, name_family, name_given, birth_date, gender), - reports(id, created_at) - `) - .eq('practitioner_id', practitioner?.practitioner_id) - .gte('period_start', start) - .lte('period_start', end) - .neq('status', 'cancelled') - .order('period_start', { ascending: true }); - - if (error) throw error; - - return NextResponse.json({ appointments: data }); - } catch (error) { - console.error('Error fetching appointments:', error); - return NextResponse.json( - { error: 'Kon afspraken niet laden' }, - { status: 500 } - ); - } -} -``` - -### 5.4 Patient Search API - -```typescript -// app/api/patients/search/route.ts -import { NextRequest, NextResponse } from 'next/server'; -import { createClient } from '@/lib/auth/server'; - -export async function GET(request: NextRequest) { - try { - const { searchParams } = new URL(request.url); - const query = searchParams.get('q') || ''; - const limit = parseInt(searchParams.get('limit') || '10'); - - const supabase = await createClient(); - const { data: { user } } = await supabase.auth.getUser(); - - if (!user) { - return NextResponse.json({ error: 'Niet ingelogd' }, { status: 401 }); - } - - // Build search query - let dbQuery = supabase - .from('patients') - .select('patient_id, name_family, name_given, birth_date, gender') - .limit(limit); - - if (query.length >= 2) { - // Search on name (case-insensitive) - dbQuery = dbQuery.or(`name_family.ilike.%${query}%,name_given.cs.{${query}}`); - } - - const { data, error } = await dbQuery.order('name_family'); - - if (error) throw error; - - return NextResponse.json({ patients: data }); - } catch (error) { - console.error('Error searching patients:', error); - return NextResponse.json( - { error: 'Zoeken mislukt' }, - { status: 500 } - ); - } -} -``` - ---- - -## 6. Component Implementatie - -🎯 **Doel:** Technische details van UI componenten. - -### 6.1 Calendar View Component - -```typescript -// app/epd/agenda/components/calendar-view.tsx -'use client'; - -import { useRef, useCallback } from 'react'; -import FullCalendar from '@fullcalendar/react'; -import dayGridPlugin from '@fullcalendar/daygrid'; -import timeGridPlugin from '@fullcalendar/timegrid'; -import interactionPlugin from '@fullcalendar/interaction'; -import type { EventClickArg, DateSelectArg, EventDropArg } from '@fullcalendar/core'; -import { nl } from 'date-fns/locale'; - -import { useAppointments } from '../hooks/use-appointments'; -import { useAppointmentMutations } from '../hooks/use-appointment-mutations'; -import { transformToCalendarEvents } from '../lib/appointment-utils'; -import { AppointmentModal } from './appointment-modal'; -import { AppointmentDetail } from './appointment-detail'; -import { CalendarToolbar } from './calendar-toolbar'; - -interface CalendarViewProps { - initialDate?: Date; - initialView?: 'timeGridDay' | 'timeGridWeek' | 'timeGridWorkWeek'; -} - -export function CalendarView({ - initialDate = new Date(), - initialView = 'timeGridWeek' -}: CalendarViewProps) { - const calendarRef = useRef(null); - - // Data fetching - const { appointments, isLoading, mutate } = useAppointments(); - const { updateAppointment } = useAppointmentMutations(); - - // State for modals - const [selectedSlot, setSelectedSlot] = useState(null); - const [selectedEvent, setSelectedEvent] = useState(null); - const [isModalOpen, setIsModalOpen] = useState(false); - const [isDetailOpen, setIsDetailOpen] = useState(false); - - // Transform appointments to FullCalendar events - const events = useMemo(() => - transformToCalendarEvents(appointments), - [appointments] - ); - - // Handlers - const handleDateSelect = useCallback((selectInfo: DateSelectArg) => { - setSelectedSlot(selectInfo); - setIsModalOpen(true); - }, []); - - const handleEventClick = useCallback((clickInfo: EventClickArg) => { - setSelectedEvent(clickInfo.event.toPlainObject() as CalendarEvent); - setIsDetailOpen(true); - }, []); - - const handleEventDrop = useCallback(async (dropInfo: EventDropArg) => { - const { event, revert } = dropInfo; - - try { - await updateAppointment({ - id: event.id, - period_start: event.start!.toISOString(), - duration_minutes: (event.end!.getTime() - event.start!.getTime()) / 60000, - }); - mutate(); // Revalidate - } catch (error) { - revert(); // Rollback on error - toast.error('Kon afspraak niet verzetten'); - } - }, [updateAppointment, mutate]); - - // Navigation handlers for toolbar - const handlePrev = () => calendarRef.current?.getApi().prev(); - const handleNext = () => calendarRef.current?.getApi().next(); - const handleToday = () => calendarRef.current?.getApi().today(); - const handleViewChange = (view: string) => calendarRef.current?.getApi().changeView(view); - - return ( -
- setIsModalOpen(true)} - /> - -
- setIsLoading(loading)} - /> -
- - {/* Modals */} - { - setIsModalOpen(false); - setSelectedSlot(null); - mutate(); - }} - /> - - { - setIsDetailOpen(false); - setIsModalOpen(true); - }} - onDelete={() => { - mutate(); - setIsDetailOpen(false); - }} - /> -
- ); -} -``` - -### 6.2 Appointment Modal Component - -```typescript -// app/epd/agenda/components/appointment-modal.tsx -'use client'; - -import { useForm } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { useState, useTransition, useEffect } from 'react'; -import { format } from 'date-fns'; -import { nl } from 'date-fns/locale'; - -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Textarea } from '@/components/ui/textarea'; -import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; - -import { PatientSearch } from './patient-search'; -import { ConflictDialog } from './conflict-dialog'; -import { createAppointment, updateAppointment, checkConflicts } from '../actions'; -import { CreateAppointmentSchema, AppointmentType, AppointmentTypeDuration } from '../types'; - -interface AppointmentModalProps { - open: boolean; - onOpenChange: (open: boolean) => void; - defaultValues?: { - date?: Date; - startTime?: Date; - appointment?: Appointment; // For edit mode - }; - onSuccess: () => void; -} - -export function AppointmentModal({ - open, - onOpenChange, - defaultValues, - onSuccess, -}: AppointmentModalProps) { - const [isPending, startTransition] = useTransition(); - const [conflicts, setConflicts] = useState([]); - const [showConflictDialog, setShowConflictDialog] = useState(false); - const [showNotes, setShowNotes] = useState(false); - - const isEdit = !!defaultValues?.appointment; - - const form = useForm({ - resolver: zodResolver(CreateAppointmentSchema), - defaultValues: { - patient_id: defaultValues?.appointment?.patient_id || '', - period_start: defaultValues?.startTime?.toISOString() || - defaultValues?.date?.toISOString() || - new Date().toISOString(), - duration_minutes: defaultValues?.appointment - ? /* calculate from period_end - period_start */ 45 - : 45, - type_code: defaultValues?.appointment?.type_code || 'treatment', - notes: defaultValues?.appointment?.notes || '', - }, - }); - - // Auto-update duration when type changes - const typeCode = form.watch('type_code'); - useEffect(() => { - if (!isEdit) { - form.setValue('duration_minutes', AppointmentTypeDuration[typeCode as keyof typeof AppointmentTypeDuration]); - } - }, [typeCode, isEdit, form]); - - // Real-time conflict check - const periodStart = form.watch('period_start'); - const durationMinutes = form.watch('duration_minutes'); - - useEffect(() => { - if (periodStart && durationMinutes) { - const start = new Date(periodStart); - const end = new Date(start.getTime() + durationMinutes * 60000); - - checkConflicts( - start.toISOString(), - end.toISOString(), - defaultValues?.appointment?.id - ).then(setConflicts); - } - }, [periodStart, durationMinutes, defaultValues?.appointment?.id]); - - const onSubmit = async (data: CreateAppointmentInput) => { - // Check for conflicts before submit - if (conflicts.length > 0) { - setShowConflictDialog(true); - return; - } - - await saveAppointment(data); - }; - - const saveAppointment = async (data: CreateAppointmentInput, ignoreConflict = false) => { - startTransition(async () => { - try { - if (isEdit) { - await updateAppointment({ ...data, id: defaultValues.appointment!.id }); - } else { - await createAppointment(data); - } - onSuccess(); - toast.success(isEdit ? 'Afspraak gewijzigd' : 'Afspraak opgeslagen'); - } catch (error) { - toast.error('Er ging iets mis'); - } - }); - }; - - return ( - <> - - - - - {isEdit ? 'Afspraak bewerken' : 'Nieuwe afspraak'} - - - -
- {/* Patient Search */} -
- - form.setValue('patient_id', patientId)} - error={form.formState.errors.patient_id?.message} - /> -
- - {/* Date & Time (inline) */} -
-
- - -
-
- - -
-
- - {/* Inline conflict warning */} - {conflicts.length > 0 && ( -
-

- ⚠️ Overlap met: {conflicts[0].patient?.name_family} {format(new Date(conflicts[0].period_start), 'HH:mm')} -

-
- )} - - {/* Duration (radio buttons) */} -
- - form.setValue('duration_minutes', Number(v))} - className="flex gap-4" - > - {[30, 45, 60, 90].map((min) => ( -
- - -
- ))} -
-
- - {/* Type (radio buttons) */} -
- - form.setValue('type_code', v)} - className="flex gap-4" - > -
- - -
-
- - -
-
- - -
-
-
- - {/* Notes (collapsible) */} - {!showNotes ? ( - - ) : ( -
- -