feat(verpleegrapportage): Nieuwe module + opruiming codebase

Verpleegrapportage module:
- Nieuwe /epd/verpleegrapportage met patiëntenoverzicht
- Rapportage invoer workspace met timeline view
- Overdracht overzicht met AI-samenvatting
- API endpoints voor verpleegrapportage data

Opruiming:
- Oude /epd/overdracht en /epd/dagregistratie verwijderd (vervangen)
- Oude /api/nursing-logs verwijderd (geconsolideerd naar reports)
- Verouderde design docs en reports verwijderd
- Fonts verplaatst van docs/ naar public/fonts/

Bugfixes:
- Risk-manager: fix constraint violation (db values vs display labels)
- Overdracht API: filter op rapportages i.p.v. encounters

Database:
- Migratie voor consolidatie nursing_logs naar reports tabel

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
colinislit
2025-12-08 11:19:48 +01:00
parent d9340ba296
commit 75c7e284b9
222 changed files with 5473 additions and 7494 deletions

View File

@@ -1,114 +0,0 @@
import { z } from 'zod';
import type { Database } from '@/lib/supabase/database.types';
// Database types
export type NursingLog = Database['public']['Tables']['nursing_logs']['Row'];
export type NursingLogInsert = Database['public']['Tables']['nursing_logs']['Insert'];
export type NursingLogUpdate = Database['public']['Tables']['nursing_logs']['Update'];
// Category enum
export const NURSING_LOG_CATEGORIES = [
'medicatie',
'adl',
'gedrag',
'incident',
'observatie',
] as const;
export type NursingLogCategory = (typeof NURSING_LOG_CATEGORIES)[number];
// Zod schemas for validation
export const NursingLogCategorySchema = z.enum(NURSING_LOG_CATEGORIES);
export const CreateNursingLogSchema = z.object({
patient_id: z.string().uuid('Patient ID moet een geldige UUID zijn'),
category: NursingLogCategorySchema,
content: z
.string()
.min(1, 'Notitie mag niet leeg zijn')
.max(500, 'Notitie mag maximaal 500 karakters bevatten'),
timestamp: z.string().datetime().optional(),
include_in_handover: z.boolean().default(false),
});
export type CreateNursingLogInput = z.infer<typeof CreateNursingLogSchema>;
export const UpdateNursingLogSchema = z.object({
category: NursingLogCategorySchema.optional(),
content: z
.string()
.min(1, 'Notitie mag niet leeg zijn')
.max(500, 'Notitie mag maximaal 500 karakters bevatten')
.optional(),
timestamp: z.string().datetime().optional(),
include_in_handover: z.boolean().optional(),
});
export type UpdateNursingLogInput = z.infer<typeof UpdateNursingLogSchema>;
// Response types
export interface NursingLogListResponse {
logs: NursingLog[];
total: number;
}
// Category display configuration
export const CATEGORY_CONFIG: Record<
NursingLogCategory,
{
label: string;
icon: string;
color: string;
bgColor: string;
textColor: string;
}
> = {
medicatie: {
label: 'Medicatie',
icon: 'Pill',
color: 'blue',
bgColor: 'bg-blue-100',
textColor: 'text-blue-700',
},
adl: {
label: 'ADL/verzorging',
icon: 'Utensils',
color: 'green',
bgColor: 'bg-green-100',
textColor: 'text-green-700',
},
gedrag: {
label: 'Gedragsobservatie',
icon: 'User',
color: 'purple',
bgColor: 'bg-purple-100',
textColor: 'text-purple-700',
},
incident: {
label: 'Incident',
icon: 'AlertTriangle',
color: 'red',
bgColor: 'bg-red-100',
textColor: 'text-red-700',
},
observatie: {
label: 'Algemene observatie',
icon: 'FileText',
color: 'gray',
bgColor: 'bg-gray-100',
textColor: 'text-gray-700',
},
};
// Helper function to calculate shift_date from timestamp
export function calculateShiftDate(timestamp: Date | string): string {
const date = typeof timestamp === 'string' ? new Date(timestamp) : timestamp;
const hours = date.getHours();
// Night shift (before 7:00) belongs to previous day
if (hours < 7) {
date.setDate(date.getDate() - 1);
}
return date.toISOString().split('T')[0];
}

View File

@@ -1,5 +1,5 @@
import { z } from 'zod';
import type { NursingLog } from './nursing-log';
import type { Json } from '@/lib/supabase/database.types';
// Patient overview for list view
export interface PatientOverzicht {
@@ -34,7 +34,6 @@ export interface PatientDetail {
};
vitals: VitalSign[];
reports: Report[];
nursingLogs: NursingLog[];
risks: RiskAssessment[];
conditions: Condition[];
}
@@ -49,13 +48,16 @@ export interface VitalSign {
effective_datetime: string;
}
// Report (rapportage)
// Report (rapportage) - now includes verpleegkundig type (was nursing_logs)
export interface Report {
id: string;
type: string;
content: string;
created_at: string;
created_by: string | null;
structured_data?: Json | null; // Contains category for verpleegkundig
include_in_handover?: boolean | null;
shift_date?: string | null;
}
// Risk assessment
@@ -88,7 +90,7 @@ export interface Aandachtspunt {
tekst: string;
urgent: boolean;
bron: {
type: 'observatie' | 'rapportage' | 'dagnotitie' | 'risico';
type: 'observatie' | 'rapportage' | 'verpleegkundig' | 'risico';
id: string;
datum: string;
label: string;
@@ -98,6 +100,7 @@ export interface Aandachtspunt {
// Zod schema for generate request
export const GenerateOverdrachtSchema = z.object({
patientId: z.string().uuid('Patient ID moet een geldige UUID zijn'),
period: z.enum(['1d', '3d', '7d', '14d']).optional().default('1d'),
});
export type GenerateOverdrachtInput = z.infer<typeof GenerateOverdrachtSchema>;

View File

@@ -15,9 +15,79 @@ export const REPORT_TYPES = [
'intake',
'behandeladvies',
'vrije_notitie',
'verpleegkundig', // Korte verpleegkundige notities (was nursing_logs)
] as const;
export type ReportType = (typeof REPORT_TYPES)[number];
// Types die getoond worden in het verpleegkundig overzicht
export const VERPLEEG_REPORT_TYPES = [
'verpleegkundig',
'observatie',
'incident',
'medicatie',
'crisis',
] as const;
export type VerpleegReportType = (typeof VERPLEEG_REPORT_TYPES)[number];
// Categorieën voor verpleegkundige notities (in structured_data.category)
export const VERPLEEGKUNDIG_CATEGORIES = [
'medicatie',
'adl',
'gedrag',
'incident',
'observatie',
] as const;
export type VerpleegkundigCategory = (typeof VERPLEEGKUNDIG_CATEGORIES)[number];
// Category display configuration voor verpleegkundige notities
export const CATEGORY_CONFIG: Record<
VerpleegkundigCategory,
{
label: string;
icon: string;
color: string;
bgColor: string;
textColor: string;
}
> = {
medicatie: {
label: 'Medicatie',
icon: 'Pill',
color: 'blue',
bgColor: 'bg-blue-100',
textColor: 'text-blue-700',
},
adl: {
label: 'ADL/verzorging',
icon: 'Utensils',
color: 'green',
bgColor: 'bg-green-100',
textColor: 'text-green-700',
},
gedrag: {
label: 'Gedragsobservatie',
icon: 'User',
color: 'purple',
bgColor: 'bg-purple-100',
textColor: 'text-purple-700',
},
incident: {
label: 'Incident',
icon: 'AlertTriangle',
color: 'red',
bgColor: 'bg-red-100',
textColor: 'text-red-700',
},
observatie: {
label: 'Algemene observatie',
icon: 'FileText',
color: 'gray',
bgColor: 'bg-gray-100',
textColor: 'text-gray-700',
},
};
// Schema voor standaard rapportages (20-5000 karakters)
export const CreateReportSchema = z.object({
patient_id: z.string().uuid('Patient ID moet een geldige UUID zijn'),
type: z.enum(REPORT_TYPES),
@@ -33,6 +103,33 @@ export const CreateReportSchema = z.object({
export type CreateReportInput = z.infer<typeof CreateReportSchema>;
// Schema voor verpleegkundige notities (1-500 karakters, met category)
export const CreateVerpleegkundigSchema = z.object({
patient_id: z.string().uuid('Patient ID moet een geldige UUID zijn'),
type: z.literal('verpleegkundig'),
content: z
.string()
.min(1, 'Notitie mag niet leeg zijn')
.max(500, 'Notitie mag maximaal 500 karakters bevatten'),
category: z.enum(VERPLEEGKUNDIG_CATEGORIES),
include_in_handover: z.boolean().default(false),
});
export type CreateVerpleegkundigInput = z.infer<typeof CreateVerpleegkundigSchema>;
// Update schema voor verpleegkundige notities
export const UpdateVerpleegkundigSchema = z.object({
content: z
.string()
.min(1, 'Notitie mag niet leeg zijn')
.max(500, 'Notitie mag maximaal 500 karakters bevatten')
.optional(),
category: z.enum(VERPLEEGKUNDIG_CATEGORIES).optional(),
include_in_handover: z.boolean().optional(),
});
export type UpdateVerpleegkundigInput = z.infer<typeof UpdateVerpleegkundigSchema>;
export interface ClassificationResult {
type: ReportType;
confidence: number;
@@ -43,3 +140,29 @@ export interface ReportListResponse {
reports: Report[];
total: number;
}
// Helper function to calculate shift_date from timestamp
// Night shift (before 7:00) belongs to previous day
export function calculateShiftDate(timestamp: Date | string): string {
const date = typeof timestamp === 'string' ? new Date(timestamp) : timestamp;
const hours = date.getHours();
// Night shift (before 7:00) belongs to previous day
if (hours < 7) {
date.setDate(date.getDate() - 1);
}
return date.toISOString().split('T')[0];
}
// Helper to extract category from structured_data
export function getVerpleegkundigCategory(
structuredData: Report['structured_data'] | undefined | null
): VerpleegkundigCategory | null {
if (!structuredData || typeof structuredData !== 'object') return null;
const data = structuredData as { category?: string };
if (data.category && VERPLEEGKUNDIG_CATEGORIES.includes(data.category as VerpleegkundigCategory)) {
return data.category as VerpleegkundigCategory;
}
return null;
}