feat: implement patient list and new patient form (E2.S1 & E2.S2)

Epic 2 - Cliëntenbeheer: Complete implementation of patient list with
search/filter functionality and comprehensive new patient form with
John Doe crisis admission support.

E2.S1 - Cliëntenlijst:
- Add client-side search bar with form submission
- Implement status filter dropdown (all/screening/active/finished/cancelled)
- Create StatusBadge component with color-coded status indicators
- Update table columns: Status, Name, BSN, Last Updated
- Add status filtering support to FHIR Patient API route
- Update FHIR patient transform to include episode-status extension
- Sort patients by updated_at descending (newest first)

E2.S2 - Nieuwe Cliënt Flow:
- Complete rewrite of patient form with all FO-required fields
- Implement John Doe checkbox with conditional BSN requirement
- Add BSN validation with Dutch Modulo-11 check algorithm
- Add comprehensive form fields:
  * Name fields: prefix, given name, family name
  * BSN with 9-digit pattern validation
  * Birth date with max date validation
  * Gender selection
  * Address: street, postal code, city
  * Contact: phone, email
  * Insurance: company, policy number
- Add warning messages for John Doe crisis admissions
- Set default episode status to 'planned' for all new patients
- Redirect to patient detail page after successful creation
- Update FHIR transform bidirectional insurance extension support:
  * dbPatientToFHIR: serialize insurance to JSON extension
  * fhirPatientToDB: parse insurance from JSON extension
- Pre-populate form fields when editing existing patients

Technical changes:
- app/api/fhir/Patient/route.ts: Add status query param and sorting
- app/epd/patients/actions.ts: Add status filter parameter
- app/epd/patients/page.tsx: Pass status searchParam
- app/epd/patients/components/patient-list.tsx: Complete UI rewrite
- app/epd/patients/components/patient-form.tsx: Complete form rewrite
- lib/fhir/transforms/patient.ts: Add insurance extension handling
- docs/specs/screening-intake/bouwplan-screening-intake-v1.0.md: Update status

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
colinislit
2025-11-22 13:58:31 +01:00
parent 4caab6a8ca
commit 86995a4466
7 changed files with 556 additions and 161 deletions

View File

@@ -131,6 +131,31 @@ export function dbPatientToFHIR(row: PatientRow): FHIRPatient {
meta: {
lastUpdated: row.updated_at || undefined,
},
// Extension for episode status (non-standard FHIR, but needed for our workflow)
extension: [
row.status
? {
url: 'http://mini-epd.local/fhir/StructureDefinition/episode-status',
valueCode: row.status,
}
: undefined,
row.is_john_doe
? {
url: 'http://mini-epd.local/fhir/StructureDefinition/john-doe',
valueBoolean: true,
}
: undefined,
row.insurance_company
? {
url: 'http://mini-epd.local/fhir/StructureDefinition/insurance',
valueString: JSON.stringify({
company: row.insurance_company,
number: row.insurance_number,
}),
}
: undefined,
].filter((x): x is NonNullable<typeof x> => x !== undefined),
};
}
@@ -172,6 +197,30 @@ export function fhirPatientToDB(fhir: FHIRPatient): PatientInsert {
const gpName = gp?.display;
const gpAgb = gp?.identifier?.value;
// Extract status and john_doe from extensions
const statusExtension = fhir.extension?.find(
(ext) => ext.url === 'http://mini-epd.local/fhir/StructureDefinition/episode-status'
);
const johnDoeExtension = fhir.extension?.find(
(ext) => ext.url === 'http://mini-epd.local/fhir/StructureDefinition/john-doe'
);
const insuranceExtension = fhir.extension?.find(
(ext) => ext.url === 'http://mini-epd.local/fhir/StructureDefinition/insurance'
);
// Parse insurance data from extension
let insuranceCompany: string | undefined;
let insuranceNumber: string | undefined;
if (insuranceExtension?.valueString) {
try {
const insuranceData = JSON.parse(insuranceExtension.valueString);
insuranceCompany = insuranceData.company;
insuranceNumber = insuranceData.number;
} catch (e) {
console.error('Failed to parse insurance extension:', e);
}
}
return {
id: fhir.id,
identifier_bsn: bsn || '999999990', // Default placeholder
@@ -194,5 +243,9 @@ export function fhirPatientToDB(fhir: FHIRPatient): PatientInsert {
general_practitioner_name: gpName || undefined,
general_practitioner_agb: gpAgb || undefined,
active: fhir.active ?? true,
status: (statusExtension?.valueCode as any) || 'planned',
is_john_doe: johnDoeExtension?.valueBoolean || false,
insurance_company: insuranceCompany || undefined,
insurance_number: insuranceNumber || undefined,
};
}