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>
123 lines
3.2 KiB
TypeScript
123 lines
3.2 KiB
TypeScript
'use server';
|
|
|
|
/**
|
|
* Patient CRUD Server Actions (FHIR-based)
|
|
*
|
|
* Server-side actions that interact with FHIR Patient API
|
|
*/
|
|
|
|
import { revalidatePath } from 'next/cache';
|
|
import type { FHIRPatient, FHIRBundle } from '@/lib/fhir';
|
|
|
|
const API_BASE_URL = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
|
|
|
|
/**
|
|
* Get all patients via FHIR API
|
|
*/
|
|
export async function getPatients(filters?: {
|
|
search?: string;
|
|
status?: string;
|
|
}) {
|
|
try {
|
|
const url = new URL(`${API_BASE_URL}/api/fhir/Patient`);
|
|
|
|
if (filters?.search) {
|
|
url.searchParams.set('name', filters.search);
|
|
}
|
|
|
|
if (filters?.status) {
|
|
url.searchParams.set('status', filters.status);
|
|
}
|
|
|
|
const response = await fetch(url.toString(), {
|
|
cache: 'no-store',
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to fetch patients: ${response.statusText}`);
|
|
}
|
|
|
|
const bundle: FHIRBundle<FHIRPatient> = await response.json();
|
|
return bundle.entry?.map((entry) => entry.resource).filter(Boolean) as FHIRPatient[] || [];
|
|
} catch (error) {
|
|
console.error('Error fetching patients:', error);
|
|
throw new Error('Failed to fetch patients');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get single patient by ID via FHIR API
|
|
*/
|
|
export async function getPatient(id: string) {
|
|
try {
|
|
const response = await fetch(`${API_BASE_URL}/api/fhir/Patient/${id}`, {
|
|
cache: 'no-store',
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to fetch patient: ${response.statusText}`);
|
|
}
|
|
|
|
const patient: FHIRPatient = await response.json();
|
|
return patient;
|
|
} catch (error) {
|
|
console.error('Error fetching patient:', error);
|
|
throw new Error('Failed to fetch patient');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create new patient via FHIR API
|
|
*/
|
|
export async function createPatient(fhirPatient: FHIRPatient) {
|
|
try {
|
|
const response = await fetch(`${API_BASE_URL}/api/fhir/Patient`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(fhirPatient),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.issue?.[0]?.diagnostics || 'Failed to create patient');
|
|
}
|
|
|
|
const patient: FHIRPatient = await response.json();
|
|
revalidatePath('/epd/patients');
|
|
return patient;
|
|
} catch (error) {
|
|
console.error('Error creating patient:', error);
|
|
throw error instanceof Error ? error : new Error('Failed to create patient');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update existing patient via FHIR API
|
|
*/
|
|
export async function updatePatient(id: string, fhirPatient: FHIRPatient) {
|
|
try {
|
|
const response = await fetch(`${API_BASE_URL}/api/fhir/Patient/${id}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ ...fhirPatient, id }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.issue?.[0]?.diagnostics || 'Failed to update patient');
|
|
}
|
|
|
|
const patient: FHIRPatient = await response.json();
|
|
revalidatePath('/epd/patients');
|
|
revalidatePath(`/epd/patients/${id}`);
|
|
return patient;
|
|
} catch (error) {
|
|
console.error('Error updating patient:', error);
|
|
throw error instanceof Error ? error : new Error('Failed to update patient');
|
|
}
|
|
}
|