Diagnose Module: - Diagnose overzicht pagina met alle patiënt diagnoses - Diagnosis manager met ICD-10 combobox zoekfunctie - Diagnose kaarten met hoofddiagnose markering - Modal voor nieuwe/bewerkte diagnoses - ICD-10 GGZ codes dataset (lib/data/) - Zod schemas voor diagnose validatie - TypeScript types voor ICD-10 (lib/types/icd10.ts) - Complete documentatie (PRD, FO, TO, Bouwplan) Agenda Uitbreidingen: - Patient context card in afspraak modal - Rapportage composer direct in afspraak modal - Rapportage bewerken vanuit gekoppelde rapportages - Verbeterde focus styling voor inputs Behandelplan: - Flat componenten structuur (behandeldoel-card, form, planning) - Context header component - Uitgebreide types (lib/types/behandelplan.ts) - Actions voor behandelplan beheer UI Componenten: - Command component (shadcn/ui) voor combobox - Popover component (shadcn/ui) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
651 lines
17 KiB
TypeScript
651 lines
17 KiB
TypeScript
'use server';
|
|
|
|
import { createClient } from '@/lib/auth/server';
|
|
import { revalidatePath } from 'next/cache';
|
|
import type { GeneratedPlan, SmartGoal, Intervention, Behandelstructuur, Behandeldoel } from '@/lib/types/behandelplan';
|
|
import { transformFromFlat } from '@/lib/types/behandelplan';
|
|
import type { LifeDomainScore } from '@/lib/types/leefgebieden';
|
|
import type { Json } from '@/lib/supabase/database.types';
|
|
|
|
/**
|
|
* Get care plans for a patient
|
|
*/
|
|
export async function getCarePlans(patientId: string) {
|
|
const supabase = await createClient();
|
|
|
|
const { data, error } = await supabase
|
|
.from('care_plans')
|
|
.select('*')
|
|
.eq('patient_id', patientId)
|
|
.order('created_at', { ascending: false });
|
|
|
|
if (error) {
|
|
console.error('Error fetching care plans:', error);
|
|
return [];
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Get the latest active care plan for a patient
|
|
*/
|
|
export async function getActiveCarePlan(patientId: string) {
|
|
const supabase = await createClient();
|
|
|
|
const { data, error } = await supabase
|
|
.from('care_plans')
|
|
.select('*')
|
|
.eq('patient_id', patientId)
|
|
.in('status', ['draft', 'active'])
|
|
.order('version', { ascending: false })
|
|
.limit(1)
|
|
.single();
|
|
|
|
if (error && error.code !== 'PGRST116') {
|
|
console.error('Error fetching active care plan:', error);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Get intakes for a patient (to select for plan generation)
|
|
*/
|
|
export async function getPatientIntakes(patientId: string) {
|
|
const supabase = await createClient();
|
|
|
|
const { data, error } = await supabase
|
|
.from('intakes')
|
|
.select('id, title, status, start_date, life_domains, notes')
|
|
.eq('patient_id', patientId)
|
|
.order('start_date', { ascending: false });
|
|
|
|
if (error) {
|
|
console.error('Error fetching intakes:', error);
|
|
return [];
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Get conditions for a patient
|
|
*/
|
|
export async function getPatientConditions(patientId: string) {
|
|
const supabase = await createClient();
|
|
|
|
const { data, error } = await supabase
|
|
.from('conditions')
|
|
.select('id, category, code_display, severity_code, severity_display, recorded_date')
|
|
.eq('patient_id', patientId)
|
|
.order('recorded_date', { ascending: false });
|
|
|
|
if (error) {
|
|
console.error('Error fetching conditions:', error);
|
|
return [];
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Create a new care plan from generated plan
|
|
*/
|
|
export async function createCarePlan(
|
|
patientId: string,
|
|
intakeId: string,
|
|
generatedPlan: GeneratedPlan,
|
|
title: string = 'Behandelplan'
|
|
) {
|
|
const supabase = await createClient();
|
|
|
|
// Get current version
|
|
const { data: existing } = await supabase
|
|
.from('care_plans')
|
|
.select('version')
|
|
.eq('patient_id', patientId)
|
|
.order('version', { ascending: false })
|
|
.limit(1)
|
|
.single();
|
|
|
|
const nextVersion = (existing?.version || 0) + 1;
|
|
|
|
const { data, error } = await supabase
|
|
.from('care_plans')
|
|
.insert({
|
|
patient_id: patientId,
|
|
based_on_intake_id: intakeId,
|
|
title: `${title} v${nextVersion}`,
|
|
status: 'draft',
|
|
intent: 'plan',
|
|
version: nextVersion,
|
|
goals: generatedPlan.doelen as unknown as Json,
|
|
activities: generatedPlan.interventies as unknown as Json,
|
|
behandelstructuur: generatedPlan.behandelstructuur as unknown as Json,
|
|
evaluatiemomenten: generatedPlan.evaluatiemomenten as unknown as Json,
|
|
sessie_planning: generatedPlan.sessiePlanning as unknown as Json,
|
|
veiligheidsplan: (generatedPlan.veiligheidsplan || null) as unknown as Json,
|
|
period_start: new Date().toISOString(),
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (error) {
|
|
console.error('Error creating care plan:', error);
|
|
throw new Error('Kon behandelplan niet opslaan');
|
|
}
|
|
|
|
revalidatePath(`/epd/patients/${patientId}/behandelplan`);
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Update care plan status
|
|
*/
|
|
export async function updateCarePlanStatus(
|
|
carePlanId: string,
|
|
patientId: string,
|
|
status: 'draft' | 'active' | 'on-hold' | 'completed' | 'revoked'
|
|
) {
|
|
const supabase = await createClient();
|
|
|
|
const updateData: Record<string, unknown> = { status };
|
|
|
|
// Set published_at when activating
|
|
if (status === 'active') {
|
|
updateData.published_at = new Date().toISOString();
|
|
}
|
|
|
|
const { error } = await supabase
|
|
.from('care_plans')
|
|
.update(updateData)
|
|
.eq('id', carePlanId);
|
|
|
|
if (error) {
|
|
console.error('Error updating care plan status:', error);
|
|
throw new Error('Kon status niet bijwerken');
|
|
}
|
|
|
|
revalidatePath(`/epd/patients/${patientId}/behandelplan`);
|
|
}
|
|
|
|
/**
|
|
* Update care plan goals
|
|
*/
|
|
export async function updateCarePlanGoals(
|
|
carePlanId: string,
|
|
patientId: string,
|
|
goals: GeneratedPlan['doelen']
|
|
) {
|
|
const supabase = await createClient();
|
|
|
|
const { error } = await supabase
|
|
.from('care_plans')
|
|
.update({ goals: goals as unknown as Json })
|
|
.eq('id', carePlanId);
|
|
|
|
if (error) {
|
|
console.error('Error updating goals:', error);
|
|
throw new Error('Kon doelen niet bijwerken');
|
|
}
|
|
|
|
revalidatePath(`/epd/patients/${patientId}/behandelplan`);
|
|
}
|
|
|
|
/**
|
|
* Save life domains to intake
|
|
*/
|
|
export async function saveLifeDomains(
|
|
intakeId: string,
|
|
patientId: string,
|
|
lifeDomains: LifeDomainScore[]
|
|
) {
|
|
const supabase = await createClient();
|
|
|
|
const { error } = await supabase
|
|
.from('intakes')
|
|
.update({ life_domains: lifeDomains as unknown as Json })
|
|
.eq('id', intakeId);
|
|
|
|
if (error) {
|
|
console.error('Error saving life domains:', error);
|
|
throw new Error('Kon leefgebieden niet opslaan');
|
|
}
|
|
|
|
revalidatePath(`/epd/patients/${patientId}/behandelplan`);
|
|
}
|
|
|
|
/**
|
|
* Delete a care plan
|
|
*/
|
|
export async function deleteCarePlan(carePlanId: string, patientId: string) {
|
|
const supabase = await createClient();
|
|
|
|
const { error } = await supabase
|
|
.from('care_plans')
|
|
.delete()
|
|
.eq('id', carePlanId);
|
|
|
|
if (error) {
|
|
console.error('Error deleting care plan:', error);
|
|
throw new Error('Kon behandelplan niet verwijderen');
|
|
}
|
|
|
|
revalidatePath(`/epd/patients/${patientId}/behandelplan`);
|
|
}
|
|
|
|
/**
|
|
* Create a new empty/manual care plan
|
|
*/
|
|
export async function createEmptyCarePlan(
|
|
patientId: string,
|
|
intakeId?: string,
|
|
title: string = 'Behandelplan'
|
|
) {
|
|
const supabase = await createClient();
|
|
|
|
// Get current version
|
|
const { data: existing } = await supabase
|
|
.from('care_plans')
|
|
.select('version')
|
|
.eq('patient_id', patientId)
|
|
.order('version', { ascending: false })
|
|
.limit(1)
|
|
.single();
|
|
|
|
const nextVersion = (existing?.version || 0) + 1;
|
|
|
|
const { data, error } = await supabase
|
|
.from('care_plans')
|
|
.insert({
|
|
patient_id: patientId,
|
|
based_on_intake_id: intakeId || null,
|
|
title: `${title} v${nextVersion}`,
|
|
status: 'draft',
|
|
intent: 'plan',
|
|
version: nextVersion,
|
|
goals: [],
|
|
activities: [],
|
|
period_start: new Date().toISOString(),
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (error) {
|
|
console.error('Error creating empty care plan:', error);
|
|
throw new Error('Kon behandelplan niet aanmaken');
|
|
}
|
|
|
|
revalidatePath(`/epd/patients/${patientId}/behandelplan`);
|
|
return data;
|
|
}
|
|
|
|
// =============================================================================
|
|
// BEHANDELSTRUCTUUR
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Update behandelstructuur
|
|
*/
|
|
export async function updateBehandelstructuur(
|
|
carePlanId: string,
|
|
patientId: string,
|
|
behandelstructuur: Behandelstructuur
|
|
) {
|
|
const supabase = await createClient();
|
|
|
|
const { error } = await supabase
|
|
.from('care_plans')
|
|
.update({ behandelstructuur: behandelstructuur as unknown as Json })
|
|
.eq('id', carePlanId);
|
|
|
|
if (error) {
|
|
console.error('Error updating behandelstructuur:', error);
|
|
throw new Error('Kon behandelstructuur niet bijwerken');
|
|
}
|
|
|
|
revalidatePath(`/epd/patients/${patientId}/behandelplan`);
|
|
}
|
|
|
|
// =============================================================================
|
|
// GOALS (DOELEN)
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Add a goal to a care plan
|
|
*/
|
|
export async function addGoal(
|
|
carePlanId: string,
|
|
patientId: string,
|
|
goal: SmartGoal
|
|
) {
|
|
const supabase = await createClient();
|
|
|
|
// Get current goals
|
|
const { data: plan } = await supabase
|
|
.from('care_plans')
|
|
.select('goals')
|
|
.eq('id', carePlanId)
|
|
.single();
|
|
|
|
const currentGoals = (plan?.goals as unknown as SmartGoal[]) || [];
|
|
const updatedGoals = [...currentGoals, goal];
|
|
|
|
const { error } = await supabase
|
|
.from('care_plans')
|
|
.update({ goals: updatedGoals as unknown as Json })
|
|
.eq('id', carePlanId);
|
|
|
|
if (error) {
|
|
console.error('Error adding goal:', error);
|
|
throw new Error('Kon doel niet toevoegen');
|
|
}
|
|
|
|
revalidatePath(`/epd/patients/${patientId}/behandelplan`);
|
|
}
|
|
|
|
/**
|
|
* Update a single goal in a care plan
|
|
*/
|
|
export async function updateGoal(
|
|
carePlanId: string,
|
|
patientId: string,
|
|
goalId: string,
|
|
updatedGoal: SmartGoal
|
|
) {
|
|
const supabase = await createClient();
|
|
|
|
// Get current goals
|
|
const { data: plan } = await supabase
|
|
.from('care_plans')
|
|
.select('goals')
|
|
.eq('id', carePlanId)
|
|
.single();
|
|
|
|
const currentGoals = (plan?.goals as unknown as SmartGoal[]) || [];
|
|
const updatedGoals = currentGoals.map((g) =>
|
|
g.id === goalId ? updatedGoal : g
|
|
);
|
|
|
|
const { error } = await supabase
|
|
.from('care_plans')
|
|
.update({ goals: updatedGoals as unknown as Json })
|
|
.eq('id', carePlanId);
|
|
|
|
if (error) {
|
|
console.error('Error updating goal:', error);
|
|
throw new Error('Kon doel niet bijwerken');
|
|
}
|
|
|
|
revalidatePath(`/epd/patients/${patientId}/behandelplan`);
|
|
}
|
|
|
|
/**
|
|
* Delete a goal from a care plan
|
|
*/
|
|
export async function deleteGoal(
|
|
carePlanId: string,
|
|
patientId: string,
|
|
goalId: string
|
|
) {
|
|
const supabase = await createClient();
|
|
|
|
// Get current goals
|
|
const { data: plan } = await supabase
|
|
.from('care_plans')
|
|
.select('goals')
|
|
.eq('id', carePlanId)
|
|
.single();
|
|
|
|
const currentGoals = (plan?.goals as unknown as SmartGoal[]) || [];
|
|
const updatedGoals = currentGoals.filter((g) => g.id !== goalId);
|
|
|
|
const { error } = await supabase
|
|
.from('care_plans')
|
|
.update({ goals: updatedGoals as unknown as Json })
|
|
.eq('id', carePlanId);
|
|
|
|
if (error) {
|
|
console.error('Error deleting goal:', error);
|
|
throw new Error('Kon doel niet verwijderen');
|
|
}
|
|
|
|
revalidatePath(`/epd/patients/${patientId}/behandelplan`);
|
|
}
|
|
|
|
// =============================================================================
|
|
// INTERVENTIONS (INTERVENTIES)
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Add an intervention to a care plan
|
|
*/
|
|
export async function addIntervention(
|
|
carePlanId: string,
|
|
patientId: string,
|
|
intervention: Intervention
|
|
) {
|
|
const supabase = await createClient();
|
|
|
|
// Get current interventions
|
|
const { data: plan } = await supabase
|
|
.from('care_plans')
|
|
.select('activities')
|
|
.eq('id', carePlanId)
|
|
.single();
|
|
|
|
const currentInterventions = (plan?.activities as unknown as Intervention[]) || [];
|
|
const updatedInterventions = [...currentInterventions, intervention];
|
|
|
|
const { error } = await supabase
|
|
.from('care_plans')
|
|
.update({ activities: updatedInterventions as unknown as Json })
|
|
.eq('id', carePlanId);
|
|
|
|
if (error) {
|
|
console.error('Error adding intervention:', error);
|
|
throw new Error('Kon interventie niet toevoegen');
|
|
}
|
|
|
|
revalidatePath(`/epd/patients/${patientId}/behandelplan`);
|
|
}
|
|
|
|
/**
|
|
* Update a single intervention in a care plan
|
|
*/
|
|
export async function updateIntervention(
|
|
carePlanId: string,
|
|
patientId: string,
|
|
interventionId: string,
|
|
updatedIntervention: Intervention
|
|
) {
|
|
const supabase = await createClient();
|
|
|
|
// Get current interventions
|
|
const { data: plan } = await supabase
|
|
.from('care_plans')
|
|
.select('activities')
|
|
.eq('id', carePlanId)
|
|
.single();
|
|
|
|
const currentInterventions = (plan?.activities as unknown as Intervention[]) || [];
|
|
const updatedInterventions = currentInterventions.map((i) =>
|
|
i.id === interventionId ? updatedIntervention : i
|
|
);
|
|
|
|
const { error } = await supabase
|
|
.from('care_plans')
|
|
.update({ activities: updatedInterventions as unknown as Json })
|
|
.eq('id', carePlanId);
|
|
|
|
if (error) {
|
|
console.error('Error updating intervention:', error);
|
|
throw new Error('Kon interventie niet bijwerken');
|
|
}
|
|
|
|
revalidatePath(`/epd/patients/${patientId}/behandelplan`);
|
|
}
|
|
|
|
/**
|
|
* Delete an intervention from a care plan
|
|
*/
|
|
export async function deleteIntervention(
|
|
carePlanId: string,
|
|
patientId: string,
|
|
interventionId: string
|
|
) {
|
|
const supabase = await createClient();
|
|
|
|
// Get current interventions
|
|
const { data: plan } = await supabase
|
|
.from('care_plans')
|
|
.select('activities')
|
|
.eq('id', carePlanId)
|
|
.single();
|
|
|
|
const currentInterventions = (plan?.activities as unknown as Intervention[]) || [];
|
|
const updatedInterventions = currentInterventions.filter((i) => i.id !== interventionId);
|
|
|
|
const { error } = await supabase
|
|
.from('care_plans')
|
|
.update({ activities: updatedInterventions as unknown as Json })
|
|
.eq('id', carePlanId);
|
|
|
|
if (error) {
|
|
console.error('Error deleting intervention:', error);
|
|
throw new Error('Kon interventie niet verwijderen');
|
|
}
|
|
|
|
revalidatePath(`/epd/patients/${patientId}/behandelplan`);
|
|
}
|
|
|
|
// =============================================================================
|
|
// BEHANDELDOEL (FLAT STRUCTURE - doel met embedded interventies)
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Save a behandeldoel (creates or updates)
|
|
* Transforms flat structure to goals + activities for backwards compatibility
|
|
*/
|
|
export async function saveBehandeldoel(
|
|
carePlanId: string,
|
|
patientId: string,
|
|
behandeldoel: Behandeldoel
|
|
) {
|
|
const supabase = await createClient();
|
|
|
|
// Get current plan
|
|
const { data: plan } = await supabase
|
|
.from('care_plans')
|
|
.select('goals, activities')
|
|
.eq('id', carePlanId)
|
|
.single();
|
|
|
|
const currentGoals = (plan?.goals as unknown as SmartGoal[]) || [];
|
|
const currentActivities = (plan?.activities as unknown as Intervention[]) || [];
|
|
|
|
// Check if this is an update or a new goal
|
|
const existingGoalIndex = currentGoals.findIndex((g) => g.id === behandeldoel.id);
|
|
|
|
// Convert behandeldoel to SmartGoal
|
|
const smartGoal: SmartGoal = {
|
|
id: behandeldoel.id,
|
|
title: behandeldoel.title,
|
|
description: '', // Not used in flat structure
|
|
clientVersion: behandeldoel.clientVersion,
|
|
lifeDomain: behandeldoel.lifeDomain,
|
|
priority: 'middel', // Default
|
|
measurability: '', // Not used in flat structure
|
|
timelineWeeks: behandeldoel.endWeek,
|
|
status: behandeldoel.status,
|
|
progress: behandeldoel.progress,
|
|
};
|
|
|
|
// Update goals array
|
|
let updatedGoals: SmartGoal[];
|
|
if (existingGoalIndex >= 0) {
|
|
updatedGoals = currentGoals.map((g, i) =>
|
|
i === existingGoalIndex ? smartGoal : g
|
|
);
|
|
} else {
|
|
updatedGoals = [...currentGoals, smartGoal];
|
|
}
|
|
|
|
// Handle interventions: remove old ones for this goal and add new ones
|
|
const otherActivities = currentActivities.filter(
|
|
(a) => !a.linkedGoalIds.includes(behandeldoel.id)
|
|
);
|
|
|
|
const newActivities: Intervention[] = behandeldoel.interventies.map((int) => ({
|
|
id: int.id,
|
|
name: int.name,
|
|
description: int.description,
|
|
rationale: '', // Not used in flat structure
|
|
linkedGoalIds: [behandeldoel.id],
|
|
}));
|
|
|
|
const updatedActivities = [...otherActivities, ...newActivities];
|
|
|
|
// Save to database
|
|
const { error } = await supabase
|
|
.from('care_plans')
|
|
.update({
|
|
goals: updatedGoals as unknown as Json,
|
|
activities: updatedActivities as unknown as Json,
|
|
})
|
|
.eq('id', carePlanId);
|
|
|
|
if (error) {
|
|
console.error('Error saving behandeldoel:', error);
|
|
throw new Error('Kon behandeldoel niet opslaan');
|
|
}
|
|
|
|
revalidatePath(`/epd/patients/${patientId}/behandelplan`);
|
|
}
|
|
|
|
/**
|
|
* Delete a behandeldoel and its linked interventions
|
|
*/
|
|
export async function deleteBehandeldoel(
|
|
carePlanId: string,
|
|
patientId: string,
|
|
doelId: string
|
|
) {
|
|
const supabase = await createClient();
|
|
|
|
// Get current plan
|
|
const { data: plan } = await supabase
|
|
.from('care_plans')
|
|
.select('goals, activities')
|
|
.eq('id', carePlanId)
|
|
.single();
|
|
|
|
const currentGoals = (plan?.goals as unknown as SmartGoal[]) || [];
|
|
const currentActivities = (plan?.activities as unknown as Intervention[]) || [];
|
|
|
|
// Remove the goal
|
|
const updatedGoals = currentGoals.filter((g) => g.id !== doelId);
|
|
|
|
// Remove interventions linked to this goal
|
|
const updatedActivities = currentActivities.filter(
|
|
(a) => !a.linkedGoalIds.includes(doelId)
|
|
);
|
|
|
|
// Save to database
|
|
const { error } = await supabase
|
|
.from('care_plans')
|
|
.update({
|
|
goals: updatedGoals as unknown as Json,
|
|
activities: updatedActivities as unknown as Json,
|
|
})
|
|
.eq('id', carePlanId);
|
|
|
|
if (error) {
|
|
console.error('Error deleting behandeldoel:', error);
|
|
throw new Error('Kon behandeldoel niet verwijderen');
|
|
}
|
|
|
|
revalidatePath(`/epd/patients/${patientId}/behandelplan`);
|
|
}
|