chore(strip): verwijder behandelplan-module

AI-generatie negeerde de geregistreerde diagnose en ernst — elk plan was
generiek (reviewbevinding). Module komt terug bij de rebuild, gekoppeld
aan het nieuwe datamodel.

- app/epd/patients/[id]/behandelplan/ en /api/behandelplan/ verwijderd
- components/behandelplan/ (view, list, forms) verwijderd
- lib/ai/behandelplan-prompt.ts, lib/ai/intervention-mapping.ts,
  lib/types/behandelplan.ts verwijderd
- behandelplan-sectie uit patientdashboard, dashboard-API en
  Cortex patient-dashboard-block
- 'Doorzetten naar behandelplan' uit behandeladvies-formulier

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
colinislit
2026-07-14 22:47:58 +02:00
parent a8b02c1ef3
commit 96848ecc81
27 changed files with 3 additions and 6407 deletions

View File

@@ -1,650 +0,0 @@
'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`);
}

View File

@@ -1,518 +0,0 @@
'use client';
import { useState, useCallback, useMemo } from 'react';
import { useRouter } from 'next/navigation';
import { BehandelplanView, BehandelplanList } from '@/components/behandelplan';
import { BehandelplanFlat } from '@/components/behandelplan/flat';
import {
createCarePlan,
updateCarePlanStatus,
createEmptyCarePlan,
updateBehandelstructuur,
addGoal,
updateGoal,
deleteGoal,
addIntervention,
updateIntervention,
deleteIntervention,
saveBehandeldoel,
deleteBehandeldoel,
} from './actions';
import type { GeneratedPlan, SmartGoal, Intervention, Sessie, Evaluatiemoment, Veiligheidsplan, Behandelstructuur, Behandeldoel } from '@/lib/types/behandelplan';
import type { LifeDomainScore } from '@/lib/types/leefgebieden';
import type { Json } from '@/lib/supabase/database.types';
import { Button } from '@/components/ui/button';
import { LayoutGrid, List } from 'lucide-react';
// Database row types (what we get from Supabase)
interface DbCarePlan {
id: string;
title: string;
status: string;
version: number | null;
goals: Json | null;
activities: Json | null;
behandelstructuur: Json | null;
sessie_planning: Json | null;
evaluatiemomenten: Json | null;
veiligheidsplan: Json | null;
created_at: string | null;
published_at: string | null;
period_start: string | null;
}
interface DbIntake {
id: string;
title: string;
status: string;
start_date: string;
life_domains: Json | null;
notes: string | null;
}
interface DbCondition {
id: string;
category: string;
code_display: string;
severity_code: string | null;
severity_display: string | null;
}
// Mapped types for the view component
interface ViewCarePlan {
id: string;
title: string;
status: 'draft' | 'active' | 'on-hold' | 'completed' | 'revoked' | 'entered-in-error' | 'unknown';
version: number | null;
goals: SmartGoal[] | null;
activities: Intervention[] | null;
behandelstructuur: GeneratedPlan['behandelstructuur'] | null;
sessie_planning: Sessie[] | null;
evaluatiemomenten: Evaluatiemoment[] | null;
veiligheidsplan: Veiligheidsplan | null;
created_at: string | null;
published_at: string | null;
period_start: string | null;
}
interface ViewIntake {
id: string;
title: string;
status: string;
start_date: string;
life_domains: LifeDomainScore[] | null;
notes: string | null;
}
interface ViewCondition {
id: string;
category: string;
code_display: string;
severity_code: string | null;
severity_display: string | null;
}
interface BehandelplanPageClientProps {
patientId: string;
allPlans: DbCarePlan[];
intakes: DbIntake[];
conditions: DbCondition[];
}
// Helper to map database types to view types
function mapCarePlan(dbPlan: DbCarePlan | null): ViewCarePlan | null {
if (!dbPlan) return null;
return {
...dbPlan,
status: dbPlan.status as ViewCarePlan['status'],
goals: dbPlan.goals as SmartGoal[] | null,
activities: dbPlan.activities as Intervention[] | null,
behandelstructuur: dbPlan.behandelstructuur as GeneratedPlan['behandelstructuur'] | null,
sessie_planning: dbPlan.sessie_planning as Sessie[] | null,
evaluatiemomenten: dbPlan.evaluatiemomenten as Evaluatiemoment[] | null,
veiligheidsplan: dbPlan.veiligheidsplan as Veiligheidsplan | null,
};
}
function mapIntakes(dbIntakes: DbIntake[]): ViewIntake[] {
return dbIntakes.map((intake) => ({
...intake,
life_domains: intake.life_domains as LifeDomainScore[] | null,
}));
}
export function BehandelplanPageClient({
patientId,
allPlans: initialPlans,
intakes,
conditions,
}: BehandelplanPageClientProps) {
const router = useRouter();
// View mode toggle: 'flat' = nieuwe platte UI, 'detailed' = oude gedetailleerde UI
const [viewMode, setViewMode] = useState<'flat' | 'detailed'>('flat');
// State voor alle plannen en selectie
const [plans, setPlans] = useState<DbCarePlan[]>(initialPlans);
const [selectedPlanId, setSelectedPlanId] = useState<string | null>(
// Selecteer standaard het nieuwste actieve/draft plan, of het eerste plan
initialPlans.find(p => p.status === 'active')?.id ||
initialPlans.find(p => p.status === 'draft')?.id ||
initialPlans[0]?.id ||
null
);
const [isCreatingNew, setIsCreatingNew] = useState(false);
const [showCreateView, setShowCreateView] = useState(false);
// Geselecteerd plan
const selectedPlan = useMemo(() => {
const plan = plans.find(p => p.id === selectedPlanId);
return plan ? mapCarePlan(plan) : null;
}, [plans, selectedPlanId]);
// Handler voor plan selectie
const handleSelectPlan = useCallback((planId: string) => {
setSelectedPlanId(planId);
setShowCreateView(false);
}, []);
// Handler voor nieuw plan aanmaken (toont create view)
const handleShowCreateView = useCallback(() => {
setSelectedPlanId(null);
setShowCreateView(true);
}, []);
const handleGenerate = useCallback(
async (intakeId: string) => {
// Get the first condition if available
const conditionId = conditions[0]?.id;
// Call the generate API
const response = await fetch('/api/behandelplan/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
patientId,
intakeId,
conditionId,
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Er ging iets mis bij het genereren');
}
const generatedPlan: GeneratedPlan = await response.json();
// Save to database via server action
const savedPlan = await createCarePlan(patientId, intakeId, generatedPlan);
// Update plans list en selecteer het nieuwe plan
const newPlan = savedPlan as DbCarePlan;
setPlans(prev => [newPlan, ...prev]);
setSelectedPlanId(newPlan.id);
setShowCreateView(false);
// Revalidate the page
router.refresh();
},
[patientId, conditions, router]
);
const handleStatusChange = useCallback(
async (status: ViewCarePlan['status']) => {
if (!selectedPlan) return;
// Only allow valid status transitions
if (!['draft', 'active', 'on-hold', 'completed', 'revoked'].includes(status)) {
throw new Error('Ongeldige status');
}
await updateCarePlanStatus(
selectedPlan.id,
patientId,
status as 'draft' | 'active' | 'on-hold' | 'completed' | 'revoked'
);
// Update plans list
setPlans(prev => prev.map(p =>
p.id === selectedPlan.id
? {
...p,
status,
published_at: status === 'active' ? new Date().toISOString() : p.published_at
}
: p
));
// Revalidate
router.refresh();
},
[selectedPlan, patientId, router]
);
const handleCreateManual = useCallback(
async (intakeId?: string) => {
setIsCreatingNew(true);
try {
// Create empty care plan via server action
const savedPlan = await createEmptyCarePlan(patientId, intakeId);
// Update plans list en selecteer het nieuwe plan
const newPlan = savedPlan as DbCarePlan;
setPlans(prev => [newPlan, ...prev]);
setSelectedPlanId(newPlan.id);
setShowCreateView(false);
// Revalidate the page
router.refresh();
} finally {
setIsCreatingNew(false);
}
},
[patientId, router]
);
// =============================================================================
// EDIT HANDLERS
// =============================================================================
const handleUpdateBehandelstructuur = useCallback(
async (data: Behandelstructuur) => {
if (!selectedPlan) return;
await updateBehandelstructuur(selectedPlan.id, patientId, data);
// Update local state
setPlans(prev => prev.map(p =>
p.id === selectedPlan.id
? { ...p, behandelstructuur: data as unknown as Json }
: p
));
router.refresh();
},
[selectedPlan, patientId, router]
);
const handleAddGoal = useCallback(
async (goal: SmartGoal) => {
if (!selectedPlan) return;
await addGoal(selectedPlan.id, patientId, goal);
// Update local state
setPlans(prev => prev.map(p => {
if (p.id !== selectedPlan.id) return p;
const currentGoals = (p.goals as unknown as SmartGoal[]) || [];
return { ...p, goals: [...currentGoals, goal] as unknown as Json };
}));
router.refresh();
},
[selectedPlan, patientId, router]
);
const handleUpdateGoal = useCallback(
async (goalId: string, updatedGoal: SmartGoal) => {
if (!selectedPlan) return;
await updateGoal(selectedPlan.id, patientId, goalId, updatedGoal);
// Update local state
setPlans(prev => prev.map(p => {
if (p.id !== selectedPlan.id) return p;
const currentGoals = (p.goals as unknown as SmartGoal[]) || [];
const newGoals = currentGoals.map(g => g.id === goalId ? updatedGoal : g);
return { ...p, goals: newGoals as unknown as Json };
}));
router.refresh();
},
[selectedPlan, patientId, router]
);
const handleDeleteGoal = useCallback(
async (goalId: string) => {
if (!selectedPlan) return;
await deleteGoal(selectedPlan.id, patientId, goalId);
// Update local state
setPlans(prev => prev.map(p => {
if (p.id !== selectedPlan.id) return p;
const currentGoals = (p.goals as unknown as SmartGoal[]) || [];
const newGoals = currentGoals.filter(g => g.id !== goalId);
return { ...p, goals: newGoals as unknown as Json };
}));
router.refresh();
},
[selectedPlan, patientId, router]
);
const handleAddIntervention = useCallback(
async (intervention: Intervention) => {
if (!selectedPlan) return;
await addIntervention(selectedPlan.id, patientId, intervention);
// Update local state
setPlans(prev => prev.map(p => {
if (p.id !== selectedPlan.id) return p;
const currentInterventions = (p.activities as unknown as Intervention[]) || [];
return { ...p, activities: [...currentInterventions, intervention] as unknown as Json };
}));
router.refresh();
},
[selectedPlan, patientId, router]
);
const handleUpdateIntervention = useCallback(
async (interventionId: string, updatedIntervention: Intervention) => {
if (!selectedPlan) return;
await updateIntervention(selectedPlan.id, patientId, interventionId, updatedIntervention);
// Update local state
setPlans(prev => prev.map(p => {
if (p.id !== selectedPlan.id) return p;
const currentInterventions = (p.activities as unknown as Intervention[]) || [];
const newInterventions = currentInterventions.map(i => i.id === interventionId ? updatedIntervention : i);
return { ...p, activities: newInterventions as unknown as Json };
}));
router.refresh();
},
[selectedPlan, patientId, router]
);
const handleDeleteIntervention = useCallback(
async (interventionId: string) => {
if (!selectedPlan) return;
await deleteIntervention(selectedPlan.id, patientId, interventionId);
// Update local state
setPlans(prev => prev.map(p => {
if (p.id !== selectedPlan.id) return p;
const currentInterventions = (p.activities as unknown as Intervention[]) || [];
const newInterventions = currentInterventions.filter(i => i.id !== interventionId);
return { ...p, activities: newInterventions as unknown as Json };
}));
router.refresh();
},
[selectedPlan, patientId, router]
);
// =============================================================================
// FLAT VIEW HANDLERS (Behandeldoel met embedded interventies)
// =============================================================================
const handleSaveBehandeldoel = useCallback(
async (doel: Behandeldoel) => {
if (!selectedPlan) return;
await saveBehandeldoel(selectedPlan.id, patientId, doel);
// Update local state - we need to update both goals and activities
// For now, just refresh the page to get fresh data
router.refresh();
},
[selectedPlan, patientId, router]
);
const handleDeleteBehandeldoel = useCallback(
async (doelId: string) => {
if (!selectedPlan) return;
await deleteBehandeldoel(selectedPlan.id, patientId, doelId);
router.refresh();
},
[selectedPlan, patientId, router]
);
// Get hulpvraag from first intake notes (first line/sentence)
const hulpvraag = useMemo(() => {
const firstIntake = intakes[0];
if (!firstIntake?.notes) return null;
// Get first sentence or first 150 chars
const notes = firstIntake.notes;
const firstSentence = notes.split(/[.!?]/)[0];
return firstSentence.length > 150 ? firstSentence.slice(0, 150) + '...' : firstSentence;
}, [intakes]);
// Get life domain scores from first intake
const lifeDomainScores = useMemo(() => {
const firstIntake = intakes[0];
return firstIntake?.life_domains as LifeDomainScore[] | null;
}, [intakes]);
return (
<div className="space-y-6">
{/* Header met view toggle */}
<div className="flex items-center justify-between">
<BehandelplanList
plans={plans.map(p => ({
id: p.id,
title: p.title,
status: p.status,
version: p.version,
created_at: p.created_at,
published_at: p.published_at,
}))}
selectedPlanId={showCreateView ? null : selectedPlanId}
onSelectPlan={handleSelectPlan}
onCreateNew={handleShowCreateView}
isCreating={isCreatingNew}
/>
{/* View mode toggle */}
<div className="flex items-center gap-1 border rounded-lg p-1 bg-slate-50">
<Button
variant={viewMode === 'flat' ? 'default' : 'ghost'}
size="sm"
onClick={() => setViewMode('flat')}
className="h-8 px-3"
>
<LayoutGrid className="h-4 w-4 mr-1.5" />
Compact
</Button>
<Button
variant={viewMode === 'detailed' ? 'default' : 'ghost'}
size="sm"
onClick={() => setViewMode('detailed')}
className="h-8 px-3"
>
<List className="h-4 w-4 mr-1.5" />
Uitgebreid
</Button>
</div>
</div>
{/* Geselecteerd plan - Flat view */}
{viewMode === 'flat' && (
<BehandelplanFlat
patientId={patientId}
carePlan={showCreateView ? null : selectedPlan}
condition={conditions[0] || null}
hulpvraag={hulpvraag}
lifeDomainScores={lifeDomainScores}
onGenerate={async () => {
const intakeId = intakes[0]?.id;
if (intakeId) await handleGenerate(intakeId);
}}
onCreateManual={async () => {
await handleCreateManual(intakes[0]?.id);
}}
onStatusChange={handleStatusChange}
onSaveBehandeldoel={handleSaveBehandeldoel}
onDeleteBehandeldoel={handleDeleteBehandeldoel}
/>
)}
{/* Geselecteerd plan - Detailed view (oude UI) */}
{viewMode === 'detailed' && (
<BehandelplanView
patientId={patientId}
carePlan={showCreateView ? null : selectedPlan}
intakes={mapIntakes(intakes)}
conditions={conditions}
onGenerate={handleGenerate}
onStatusChange={handleStatusChange}
onCreateManual={handleCreateManual}
onUpdateBehandelstructuur={handleUpdateBehandelstructuur}
onAddGoal={handleAddGoal}
onUpdateGoal={handleUpdateGoal}
onDeleteGoal={handleDeleteGoal}
onAddIntervention={handleAddIntervention}
onUpdateIntervention={handleUpdateIntervention}
onDeleteIntervention={handleDeleteIntervention}
/>
)}
</div>
);
}

View File

@@ -1,33 +0,0 @@
/**
* Behandelplan Page
* E3.S1: Server component met data loading
*/
import { getCarePlans, getPatientIntakes, getPatientConditions } from './actions';
import { BehandelplanPageClient } from './page-client';
export default async function BehandelplanPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
// Parallel data loading - haal ALLE plannen op
const [allPlans, intakes, conditions] = await Promise.all([
getCarePlans(id),
getPatientIntakes(id),
getPatientConditions(id),
]);
return (
<div className="p-6">
<BehandelplanPageClient
patientId={id}
allPlans={allPlans}
intakes={intakes}
conditions={conditions}
/>
</div>
);
}

View File

@@ -2,8 +2,7 @@
import { useState, useTransition, useCallback } from 'react';
import { saveTreatmentAdvice } from '../../actions';
import { Loader2, Calendar, UserCircle, ClipboardList, Share2, CheckCircle2 } from 'lucide-react';
import Link from 'next/link';
import { Loader2, Calendar, UserCircle, ClipboardList, CheckCircle2 } from 'lucide-react';
import dynamic from 'next/dynamic';
const RichTextEditor = dynamic(
@@ -238,20 +237,6 @@ export function TreatmentAdviceForm({ patientId, intakeId, initialData, initialD
/>
</div>
<div className="rounded-lg border border-slate-200 p-4 bg-slate-50 space-y-3">
<div className="flex items-center gap-2 text-sm font-semibold text-slate-700">
<Share2 className="h-4 w-4" /> Doorzetten naar behandelplan
</div>
<p className="text-xs text-slate-500">
Gebruik dit advies als basis voor het behandelplan of koppel direct door.
</p>
<Link
href={`/epd/patients/${patientId}/behandelplan`}
className="inline-flex items-center justify-center rounded-md border border-slate-300 px-3 py-2 text-xs font-medium text-slate-700 hover:bg-white"
>
Open behandelplan
</Link>
</div>
</div>
<div className="space-y-3">

View File

@@ -18,14 +18,6 @@ import type { Intake } from '@/lib/types/intake';
import { format } from 'date-fns';
import { nl } from 'date-fns/locale';
import { getPatientEncounters } from '@/app/epd/agenda/actions';
import { getActiveCarePlan } from './behandelplan/actions';
import type { SmartGoal, Intervention, Behandelstructuur, Evaluatiemoment } from '@/lib/types/behandelplan';
function extractHulpvraag(notes: string | null): string | null {
if (!notes) return null;
const firstSentence = notes.split(/[.!?]/)[0];
return firstSentence.length > 150 ? firstSentence.slice(0, 150) + '...' : firstSentence;
}
export default async function PatientDashboardPage({
params,
@@ -35,10 +27,9 @@ export default async function PatientDashboardPage({
const { id } = await params;
// Fetch all data in parallel for better performance
const [intakesResult, encountersResult, carePlanResult] = await Promise.all([
const [intakesResult, encountersResult] = await Promise.all([
getIntakesByPatientId(id).catch(() => [] as Intake[]),
getPatientEncounters(id).catch(() => []),
getActiveCarePlan(id).catch(() => null),
]);
// Process intakes
@@ -58,16 +49,6 @@ export default async function PatientDashboardPage({
recentEncounters = recent.slice(0, 5 - upcomingEncounters.length);
}
// Process care plan and get hulpvraag from already-fetched intakes
const activeCarePlan = carePlanResult;
let hulpvraag: string | null = null;
if (activeCarePlan?.based_on_intake_id) {
const linkedIntake = intakesResult.find((i: Intake) => i.id === activeCarePlan.based_on_intake_id);
if (linkedIntake?.notes) {
hulpvraag = extractHulpvraag(linkedIntake.notes);
}
}
return (
<div className="p-6">
{/* Page Header */}
@@ -266,154 +247,6 @@ export default async function PatientDashboardPage({
</div>
)}
{/* Behandelplan Section */}
{activeCarePlan && (
<div className="bg-white rounded-lg border border-slate-200 p-6 mb-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-slate-900">Actief Behandelplan</h3>
<Link
href={`/epd/patients/${id}/behandelplan`}
className="text-sm text-teal-600 hover:text-teal-700 font-medium"
>
Bekijk volledig plan
</Link>
</div>
{/* Hulpvraag */}
{hulpvraag && (
<div className="mb-4 p-3 bg-slate-50 rounded-lg">
<p className="text-xs font-medium text-slate-600 mb-1">Hulpvraag</p>
<p className="text-sm text-slate-700 italic">&ldquo;{hulpvraag}&rdquo;</p>
</div>
)}
{/* Behandelstructuur */}
{activeCarePlan.behandelstructuur && (
<div className="mb-4 p-3 bg-teal-50 rounded-lg">
<p className="text-xs font-medium text-teal-700 mb-2">Behandelstructuur</p>
<div className="grid grid-cols-2 gap-2 text-sm text-teal-900">
{(() => {
const bs = activeCarePlan.behandelstructuur as unknown as Behandelstructuur;
return (
<>
<div>
<span className="font-medium">Duur:</span> {bs.duur}
</div>
<div>
<span className="font-medium">Frequentie:</span> {bs.frequentie}
</div>
<div>
<span className="font-medium">Aantal sessies:</span> {bs.aantalSessies}
</div>
<div>
<span className="font-medium">Vorm:</span> {bs.vorm}
</div>
</>
);
})()}
</div>
</div>
)}
{/* Doelen Overzicht */}
{activeCarePlan.goals && Array.isArray(activeCarePlan.goals) && activeCarePlan.goals.length > 0 && (
<div className="mb-4">
<p className="text-xs font-medium text-slate-600 mb-2">Doelen ({activeCarePlan.goals.length})</p>
<div className="space-y-2">
{(activeCarePlan.goals as unknown as SmartGoal[]).slice(0, 3).map((goal) => (
<div key={goal.id} className="p-2 bg-slate-50 rounded border border-slate-200">
<div className="flex items-start justify-between mb-1">
<p className="text-sm font-medium text-slate-900">{goal.title}</p>
<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${
goal.status === 'bezig' ? 'bg-blue-50 text-blue-700' :
goal.status === 'gehaald' ? 'bg-green-50 text-green-700' :
'bg-slate-50 text-slate-700'
}`}>
{goal.status === 'bezig' ? 'Bezig' :
goal.status === 'gehaald' ? 'Gehaald' :
goal.status === 'niet_gestart' ? 'Niet gestart' : goal.status}
</span>
</div>
{goal.progress > 0 && (
<div className="mt-1">
<div className="h-1.5 bg-slate-200 rounded-full overflow-hidden">
<div
className="h-full bg-teal-500 transition-all"
style={{ width: `${goal.progress}%` }}
/>
</div>
<p className="text-xs text-slate-500 mt-0.5">{goal.progress}% voltooid</p>
</div>
)}
</div>
))}
{activeCarePlan.goals.length > 3 && (
<p className="text-xs text-slate-500 text-center">
+{activeCarePlan.goals.length - 3} meer doelen
</p>
)}
</div>
</div>
)}
{/* Interventies Overzicht */}
{activeCarePlan.activities && Array.isArray(activeCarePlan.activities) && activeCarePlan.activities.length > 0 && (
<div className="mb-4">
<p className="text-xs font-medium text-slate-600 mb-2">Interventies ({activeCarePlan.activities.length})</p>
<div className="flex flex-wrap gap-2">
{(activeCarePlan.activities as unknown as Intervention[]).slice(0, 5).map((intervention) => (
<span
key={intervention.id}
className="px-2 py-1 bg-purple-50 text-purple-700 rounded text-xs font-medium"
>
{intervention.name}
</span>
))}
{activeCarePlan.activities.length > 5 && (
<span className="px-2 py-1 bg-slate-100 text-slate-600 rounded text-xs">
+{activeCarePlan.activities.length - 5} meer
</span>
)}
</div>
</div>
)}
{/* Aankomende Evaluatiemomenten */}
{activeCarePlan.evaluatiemomenten &&
Array.isArray(activeCarePlan.evaluatiemomenten) &&
activeCarePlan.evaluatiemomenten.length > 0 && (
<div>
<p className="text-xs font-medium text-slate-600 mb-2">Aankomende evaluatiemomenten</p>
<div className="space-y-2">
{(activeCarePlan.evaluatiemomenten as unknown as Evaluatiemoment[])
.filter((evaluatie) => evaluatie.status === 'gepland')
.slice(0, 2)
.map((evaluatie) => (
<div key={evaluatie.id} className="p-2 bg-amber-50 rounded border border-amber-200">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-amber-900">
{evaluatie.type === 'tussentijds' ? 'Tussentijdse evaluatie' :
evaluatie.type === 'eind' ? 'Eindevaluatie' : 'Crisis evaluatie'}
</p>
{evaluatie.plannedDate && (
<p className="text-xs text-amber-700 mt-0.5">
Week {evaluatie.weekNumber} {format(new Date(evaluatie.plannedDate), 'd MMM yyyy', { locale: nl })}
</p>
)}
</div>
<span className="px-2 py-0.5 bg-amber-100 text-amber-800 rounded-full text-xs font-medium">
Gepland
</span>
</div>
</div>
))}
</div>
</div>
)}
</div>
)}
{/* Next Steps Section */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-6">
<div className="flex items-start gap-3">