diff --git a/app/api/behandelplan/generate/route.ts b/app/api/behandelplan/generate/route.ts deleted file mode 100644 index 9387b9a..0000000 --- a/app/api/behandelplan/generate/route.ts +++ /dev/null @@ -1,327 +0,0 @@ -/** - * Behandelplan Generate API - * - * POST /api/behandelplan/generate - * Genereert een behandelplan met Claude AI - */ - -import { createClient } from '@/lib/auth/server'; -import { NextRequest, NextResponse } from 'next/server'; -import { z } from 'zod'; -import { - BEHANDELPLAN_SYSTEM_PROMPT, - buildUserPrompt, - validatePlanContext, - type PlanContext, -} from '@/lib/ai/behandelplan-prompt'; -import { type Severity } from '@/lib/ai/intervention-mapping'; -import { - GeneratedPlanSchema, - type GeneratedPlan, -} from '@/lib/types/behandelplan'; -import { createDefaultLifeDomainScores, type LifeDomainScore } from '@/lib/types/leefgebieden'; - -// Input validation schema -const GenerateInputSchema = z.object({ - patientId: z.string().uuid('Patient ID moet een geldige UUID zijn'), - intakeId: z.string().uuid('Intake ID moet een geldige UUID zijn'), - conditionId: z.string().uuid().optional(), - extraInstructions: z.string().max(500).optional(), -}); - -/** - * Map severity_code to Severity type - */ -function mapSeverity(severityCode: string | null | undefined): Severity { - if (!severityCode) return 'middel'; - - const code = severityCode.toLowerCase(); - if (code.includes('mild') || code.includes('laag') || code.includes('light')) { - return 'laag'; - } - if (code.includes('severe') || code.includes('hoog') || code.includes('ernstig')) { - return 'hoog'; - } - return 'middel'; -} - -/** - * Load context from database - */ -async function loadPlanContext( - supabase: Awaited>, - patientId: string, - intakeId: string, - conditionId?: string -): Promise { - // 1. Load intake - const { data: intake, error: intakeError } = await supabase - .from('intakes') - .select('*') - .eq('id', intakeId) - .eq('patient_id', patientId) - .single(); - - if (intakeError || !intake) { - throw new Error(`Intake niet gevonden: ${intakeError?.message || 'niet gevonden'}`); - } - - // 2. Load condition (latest for patient, or specific one) - let conditionQuery = supabase - .from('conditions') - .select('*') - .eq('patient_id', patientId); - - if (conditionId) { - conditionQuery = conditionQuery.eq('id', conditionId); - } else { - conditionQuery = conditionQuery.order('recorded_date', { ascending: false }).limit(1); - } - - const { data: conditions } = await conditionQuery; - const condition = conditions?.[0]; - - // 3. Build intake notes from available data - const intakeNotes = buildIntakeNotes(intake); - - // 4. Get life domains (from intake or default) - const lifeDomains: LifeDomainScore[] = - (intake.life_domains as LifeDomainScore[] | null) || - createDefaultLifeDomainScores(); - - // 5. Build context - return { - patientId, - intakeNotes, - dsmCategory: condition?.category || condition?.code_display || 'overig', - severity: mapSeverity(condition?.severity_code), - lifeDomains, - extraInstructions: undefined, - }; -} - -/** - * Build intake notes from intake data - */ -function buildIntakeNotes(intake: Record): string { - const parts: string[] = []; - - if (intake.notes) { - parts.push(String(intake.notes)); - } - - if (intake.treatment_advice) { - const advice = intake.treatment_advice as Record; - if (advice.content) { - parts.push(`Behandeladvies: ${String(advice.content)}`); - } - } - - if (intake.kindcheck_data) { - const kindcheck = intake.kindcheck_data as Record; - if (kindcheck.observations) { - parts.push(`Observaties: ${String(kindcheck.observations)}`); - } - } - - return parts.join('\n\n') || 'Geen intake notities beschikbaar.'; -} - -/** - * Call Claude API - */ -async function callClaudeAPI(context: PlanContext): Promise { - const apiKey = process.env.ANTHROPIC_API_KEY; - if (!apiKey) { - throw new Error('ANTHROPIC_API_KEY ontbreekt in environment'); - } - - const userPrompt = buildUserPrompt(context); - - const response = await fetch('https://api.anthropic.com/v1/messages', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'x-api-key': apiKey, - 'anthropic-version': '2023-06-01', - }, - body: JSON.stringify({ - model: 'claude-sonnet-4-20250514', - max_tokens: 4096, - temperature: 0.3, - system: BEHANDELPLAN_SYSTEM_PROMPT, - messages: [{ role: 'user', content: userPrompt }], - }), - }); - - if (!response.ok) { - const errorBody = await response.text(); - console.error('Claude API error:', errorBody); - throw new Error(`Claude API fout: ${response.status}`); - } - - const data = await response.json(); - const rawText = data?.content?.[0]?.text; - - if (!rawText) { - throw new Error('Geen response van Claude API'); - } - - // Parse JSON from response (handle potential markdown code blocks) - let jsonText = rawText.trim(); - if (jsonText.startsWith('```json')) { - jsonText = jsonText.slice(7); - } - if (jsonText.startsWith('```')) { - jsonText = jsonText.slice(3); - } - if (jsonText.endsWith('```')) { - jsonText = jsonText.slice(0, -3); - } - - const parsed = JSON.parse(jsonText.trim()); - - // Validate with Zod schema - const validated = GeneratedPlanSchema.parse(parsed); - - return validated; -} - -/** - * Log AI event to database - */ -async function logAIEvent( - supabase: Awaited>, - kind: string, - patientId: string, - input: Record, - output: Record, - durationMs: number -) { - try { - await supabase.from('ai_events').insert({ - kind, - patient_id: patientId, - input_data: input, - output_data: output, - duration_ms: durationMs, - }); - } catch (error) { - // Log but don't fail the request - console.error('Failed to log AI event:', error); - } -} - -/** - * POST /api/behandelplan/generate - */ -export async function POST(request: NextRequest) { - const startTime = Date.now(); - - try { - const body = await request.json(); - - // Validate input - const result = GenerateInputSchema.safeParse(body); - if (!result.success) { - return NextResponse.json( - { - error: 'Validatiefout', - details: result.error.issues.map((e) => ({ - field: e.path.join('.'), - message: e.message, - })), - }, - { status: 400 } - ); - } - - const { patientId, intakeId, conditionId, extraInstructions } = result.data; - const supabase = await createClient(); - - // Load context from database - const context = await loadPlanContext(supabase, patientId, intakeId, conditionId); - - // Add extra instructions if provided - if (extraInstructions) { - context.extraInstructions = extraInstructions; - } - - // Validate context - const validation = validatePlanContext(context); - if (!validation.valid) { - return NextResponse.json( - { - error: 'Onvoldoende context voor behandelplan generatie', - details: validation.errors, - }, - { status: 400 } - ); - } - - // Call Claude API - const plan = await callClaudeAPI(context); - - const durationMs = Date.now() - startTime; - - // Log AI event - await logAIEvent( - supabase, - 'behandelplan_generate', - patientId, - { intakeId, conditionId, extraInstructions }, - { goalCount: plan.doelen.length, interventionCount: plan.interventies.length }, - durationMs - ); - - return NextResponse.json({ - success: true, - plan, - meta: { - generatedAt: new Date().toISOString(), - durationMs, - context: { - dsmCategory: context.dsmCategory, - severity: context.severity, - highPriorityDomains: context.lifeDomains - .filter((d) => d.priority === 'hoog') - .map((d) => d.domain), - }, - }, - }); - } catch (error) { - console.error('Error generating behandelplan:', error); - - const errorMessage = error instanceof Error ? error.message : 'Onbekende fout'; - - // Check for specific error types - if (errorMessage.includes('Intake niet gevonden')) { - return NextResponse.json( - { error: errorMessage }, - { status: 404 } - ); - } - - if (errorMessage.includes('ANTHROPIC_API_KEY')) { - return NextResponse.json( - { error: 'AI service niet geconfigureerd' }, - { status: 503 } - ); - } - - if (errorMessage.includes('Claude API')) { - return NextResponse.json( - { error: 'AI service tijdelijk niet beschikbaar', details: errorMessage }, - { status: 503 } - ); - } - - return NextResponse.json( - { - error: 'Fout bij genereren behandelplan', - details: process.env.NODE_ENV === 'development' ? errorMessage : undefined, - }, - { status: 500 } - ); - } -} diff --git a/app/api/patients/[patientId]/dashboard/route.ts b/app/api/patients/[patientId]/dashboard/route.ts index ceea07b..ec94ed8 100644 --- a/app/api/patients/[patientId]/dashboard/route.ts +++ b/app/api/patients/[patientId]/dashboard/route.ts @@ -4,18 +4,11 @@ import { createClient } from '@/lib/auth/server'; import { getPatient } from '@/app/epd/patients/actions'; import { getIntakesByPatientId } from '@/app/epd/patients/[id]/intakes/actions'; import { getPatientEncounters } from '@/app/epd/agenda/actions'; -import { getActiveCarePlan } from '@/app/epd/patients/[id]/behandelplan/actions'; interface RouteParams { params: Promise<{ patientId: string }>; } -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 async function GET(_request: NextRequest, { params }: RouteParams) { try { const { patientId } = await params; @@ -38,26 +31,15 @@ export async function GET(_request: NextRequest, { params }: RouteParams) { return NextResponse.json({ error: 'Patiënt niet gevonden' }, { status: 404 }); } - const [intakes, encounters, carePlan] = await Promise.all([ + const [intakes, encounters] = await Promise.all([ getIntakesByPatientId(patientId).catch(() => []), getPatientEncounters(patientId).catch(() => []), - getActiveCarePlan(patientId).catch(() => null), ]); - let hulpvraag: string | null = null; - if (carePlan?.based_on_intake_id) { - const linkedIntake = intakes.find((intake) => intake.id === carePlan.based_on_intake_id); - if (linkedIntake?.notes) { - hulpvraag = extractHulpvraag(linkedIntake.notes); - } - } - return NextResponse.json({ patient, intakes, encounters, - carePlan, - hulpvraag, }); } catch (error) { console.error('Unexpected error in GET /api/patients/[patientId]/dashboard:', error); diff --git a/app/epd/patients/[id]/behandelplan/actions.ts b/app/epd/patients/[id]/behandelplan/actions.ts deleted file mode 100644 index 27da569..0000000 --- a/app/epd/patients/[id]/behandelplan/actions.ts +++ /dev/null @@ -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 = { 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`); -} diff --git a/app/epd/patients/[id]/behandelplan/page-client.tsx b/app/epd/patients/[id]/behandelplan/page-client.tsx deleted file mode 100644 index 80d7749..0000000 --- a/app/epd/patients/[id]/behandelplan/page-client.tsx +++ /dev/null @@ -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(initialPlans); - const [selectedPlanId, setSelectedPlanId] = useState( - // 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 ( -
- {/* Header met view toggle */} -
- ({ - 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 */} -
- - -
-
- - {/* Geselecteerd plan - Flat view */} - {viewMode === 'flat' && ( - { - 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' && ( - - )} -
- ); -} diff --git a/app/epd/patients/[id]/behandelplan/page.tsx b/app/epd/patients/[id]/behandelplan/page.tsx deleted file mode 100644 index 0938ead..0000000 --- a/app/epd/patients/[id]/behandelplan/page.tsx +++ /dev/null @@ -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 ( -
- -
- ); -} diff --git a/app/epd/patients/[id]/intakes/[intakeId]/behandeladvies/components/treatment-advice-form.tsx b/app/epd/patients/[id]/intakes/[intakeId]/behandeladvies/components/treatment-advice-form.tsx index 154ce1d..46141f0 100644 --- a/app/epd/patients/[id]/intakes/[intakeId]/behandeladvies/components/treatment-advice-form.tsx +++ b/app/epd/patients/[id]/intakes/[intakeId]/behandeladvies/components/treatment-advice-form.tsx @@ -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 /> -
-
- Doorzetten naar behandelplan -
-

- Gebruik dit advies als basis voor het behandelplan of koppel direct door. -

- - Open behandelplan - -
diff --git a/app/epd/patients/[id]/page.tsx b/app/epd/patients/[id]/page.tsx index 0e872ba..97cbf46 100644 --- a/app/epd/patients/[id]/page.tsx +++ b/app/epd/patients/[id]/page.tsx @@ -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 (
{/* Page Header */} @@ -266,154 +247,6 @@ export default async function PatientDashboardPage({
)} - {/* Behandelplan Section */} - {activeCarePlan && ( -
-
-

Actief Behandelplan

- - Bekijk volledig plan → - -
- - {/* Hulpvraag */} - {hulpvraag && ( -
-

Hulpvraag

-

“{hulpvraag}”

-
- )} - - {/* Behandelstructuur */} - {activeCarePlan.behandelstructuur && ( -
-

Behandelstructuur

-
- {(() => { - const bs = activeCarePlan.behandelstructuur as unknown as Behandelstructuur; - return ( - <> -
- Duur: {bs.duur} -
-
- Frequentie: {bs.frequentie} -
-
- Aantal sessies: {bs.aantalSessies} -
-
- Vorm: {bs.vorm} -
- - ); - })()} -
-
- )} - - {/* Doelen Overzicht */} - {activeCarePlan.goals && Array.isArray(activeCarePlan.goals) && activeCarePlan.goals.length > 0 && ( -
-

Doelen ({activeCarePlan.goals.length})

-
- {(activeCarePlan.goals as unknown as SmartGoal[]).slice(0, 3).map((goal) => ( -
-
-

{goal.title}

- - {goal.status === 'bezig' ? 'Bezig' : - goal.status === 'gehaald' ? 'Gehaald' : - goal.status === 'niet_gestart' ? 'Niet gestart' : goal.status} - -
- {goal.progress > 0 && ( -
-
-
-
-

{goal.progress}% voltooid

-
- )} -
- ))} - {activeCarePlan.goals.length > 3 && ( -

- +{activeCarePlan.goals.length - 3} meer doelen -

- )} -
-
- )} - - {/* Interventies Overzicht */} - {activeCarePlan.activities && Array.isArray(activeCarePlan.activities) && activeCarePlan.activities.length > 0 && ( -
-

Interventies ({activeCarePlan.activities.length})

-
- {(activeCarePlan.activities as unknown as Intervention[]).slice(0, 5).map((intervention) => ( - - {intervention.name} - - ))} - {activeCarePlan.activities.length > 5 && ( - - +{activeCarePlan.activities.length - 5} meer - - )} -
-
- )} - - {/* Aankomende Evaluatiemomenten */} - {activeCarePlan.evaluatiemomenten && - Array.isArray(activeCarePlan.evaluatiemomenten) && - activeCarePlan.evaluatiemomenten.length > 0 && ( -
-

Aankomende evaluatiemomenten

-
- {(activeCarePlan.evaluatiemomenten as unknown as Evaluatiemoment[]) - .filter((evaluatie) => evaluatie.status === 'gepland') - .slice(0, 2) - .map((evaluatie) => ( -
-
-
-

- {evaluatie.type === 'tussentijds' ? 'Tussentijdse evaluatie' : - evaluatie.type === 'eind' ? 'Eindevaluatie' : 'Crisis evaluatie'} -

- {evaluatie.plannedDate && ( -

- Week {evaluatie.weekNumber} • {format(new Date(evaluatie.plannedDate), 'd MMM yyyy', { locale: nl })} -

- )} -
- - Gepland - -
-
- ))} -
-
- )} -
- )} - {/* Next Steps Section */}
diff --git a/components/behandelplan/behandelplan-list.tsx b/components/behandelplan/behandelplan-list.tsx deleted file mode 100644 index 53a7fa9..0000000 --- a/components/behandelplan/behandelplan-list.tsx +++ /dev/null @@ -1,125 +0,0 @@ -'use client'; - -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { Badge } from '@/components/ui/badge'; -import { Plus, FileText, CheckCircle2 } from 'lucide-react'; -import { FHIR_STATUS_LABELS, type FhirCarePlanStatus } from '@/lib/types/behandelplan'; - -interface CarePlanSummary { - id: string; - title: string; - status: string; - version: number | null; - created_at: string | null; - published_at: string | null; -} - -interface BehandelplanListProps { - plans: CarePlanSummary[]; - selectedPlanId: string | null; - onSelectPlan: (planId: string) => void; - onCreateNew: () => void; - isCreating?: boolean; -} - -export function BehandelplanList({ - plans, - selectedPlanId, - onSelectPlan, - onCreateNew, - isCreating = false, -}: BehandelplanListProps) { - return ( - - -
- - - Behandelplannen ({plans.length}) - - -
-
- - {plans.length === 0 ? ( -

- Nog geen behandelplannen aangemaakt -

- ) : ( -
- {plans.map((plan) => { - const isSelected = plan.id === selectedPlanId; - const statusInfo = FHIR_STATUS_LABELS[plan.status as FhirCarePlanStatus] || { - label: plan.status, - color: '#6b7280', - }; - const isActive = plan.status === 'active'; - - return ( - - ); - })} -
- )} -
-
- ); -} diff --git a/components/behandelplan/behandelplan-view.tsx b/components/behandelplan/behandelplan-view.tsx deleted file mode 100644 index 7047cb8..0000000 --- a/components/behandelplan/behandelplan-view.tsx +++ /dev/null @@ -1,1023 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { Badge } from '@/components/ui/badge'; -import { - Sparkles, - FileText, - Clock, - CheckCircle2, - AlertCircle, - ChevronRight, - Calendar, - Target, - Stethoscope, - CalendarDays, - ClipboardCheck, - Shield, - Phone, - AlertTriangle, -} from 'lucide-react'; -import type { GeneratedPlan, SmartGoal, Intervention, Sessie, Evaluatiemoment, Veiligheidsplan } from '@/lib/types/behandelplan'; -import type { LifeDomainScore } from '@/lib/types/leefgebieden'; -import { LeefgebiedenScoresCard } from './leefgebieden-scores'; -import { LeefgebiedenBadge } from './leefgebieden-badge'; -import { FHIR_STATUS_LABELS, GOAL_STATUS_LABELS, type FhirCarePlanStatus, type Behandelstructuur } from '@/lib/types/behandelplan'; -import { EditableSection, ItemActions } from './editable-section'; -import { BehandelstructuurForm } from './sections/behandelstructuur-form'; -import { GoalForm } from './sections/goal-form'; -import { InterventionForm } from './sections/intervention-form'; -import { Plus } from 'lucide-react'; - -interface CarePlan { - 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 Intake { - id: string; - title: string; - status: string; - start_date: string; - life_domains: LifeDomainScore[] | null; - notes: string | null; -} - -interface Condition { - id: string; - category: string; - code_display: string; - severity_code: string | null; - severity_display: string | null; -} - -interface BehandelplanViewProps { - patientId: string; - carePlan: CarePlan | null; - intakes: Intake[]; - conditions: Condition[]; - onGenerate: (intakeId: string) => Promise; - onStatusChange: (status: CarePlan['status']) => Promise; - onCreateManual: (intakeId?: string) => Promise; - // Edit callbacks - onUpdateBehandelstructuur?: (data: Behandelstructuur) => Promise; - onAddGoal?: (goal: SmartGoal) => Promise; - onUpdateGoal?: (goalId: string, goal: SmartGoal) => Promise; - onDeleteGoal?: (goalId: string) => Promise; - onAddIntervention?: (intervention: Intervention) => Promise; - onUpdateIntervention?: (interventionId: string, intervention: Intervention) => Promise; - onDeleteIntervention?: (interventionId: string) => Promise; -} - -export function BehandelplanView({ - carePlan, - intakes, - conditions, - onGenerate, - onStatusChange, - onCreateManual, - onUpdateBehandelstructuur, - onAddGoal, - onUpdateGoal, - onDeleteGoal, - onAddIntervention, - onUpdateIntervention, - onDeleteIntervention, -}: BehandelplanViewProps) { - const [isGenerating, setIsGenerating] = useState(false); - const [isCreatingManual, setIsCreatingManual] = useState(false); - const [selectedIntakeId, setSelectedIntakeId] = useState( - intakes[0]?.id || null - ); - const [error, setError] = useState(null); - - // Edit state - const [editingBehandelstructuur, setEditingBehandelstructuur] = useState(false); - const [editingGoalId, setEditingGoalId] = useState(null); - const [addingGoal, setAddingGoal] = useState(false); - const [editingInterventionId, setEditingInterventionId] = useState(null); - const [addingIntervention, setAddingIntervention] = useState(false); - - // Temp data for forms - const [tempBehandelstructuur, setTempBehandelstructuur] = useState(null); - const [tempGoal, setTempGoal] = useState(null); - const [tempIntervention, setTempIntervention] = useState(null); - - const selectedIntake = intakes.find((i) => i.id === selectedIntakeId); - const hasLifeDomains = selectedIntake?.life_domains && selectedIntake.life_domains.length > 0; - const latestCondition = conditions[0]; - - const handleGenerate = async () => { - if (!selectedIntakeId) { - setError('Selecteer eerst een intake'); - return; - } - - // Leefgebieden zijn aanbevolen maar niet verplicht - if (!hasLifeDomains) { - // Toon waarschuwing maar blokkeer niet - console.warn('Leefgebieden niet ingevuld - AI generatie mogelijk minder nauwkeurig'); - } - - setIsGenerating(true); - setError(null); - - try { - await onGenerate(selectedIntakeId); - } catch (err) { - setError(err instanceof Error ? err.message : 'Er ging iets mis'); - } finally { - setIsGenerating(false); - } - }; - - const handleCreateManual = async () => { - setIsCreatingManual(true); - setError(null); - - try { - await onCreateManual(selectedIntakeId || undefined); - } catch (err) { - setError(err instanceof Error ? err.message : 'Er ging iets mis'); - } finally { - setIsCreatingManual(false); - } - }; - - // No care plan exists - show generation UI - if (!carePlan) { - return ( -
- {/* Header */} -
-

Behandelplan

-

- Genereer een behandelplan op basis van de intake en diagnose -

-
- - {/* Context Cards */} -
- {/* Intake Selection */} - - - - - Intake selecteren - - - - {intakes.length === 0 ? ( -

- Geen intakes gevonden. Maak eerst een intake aan. -

- ) : ( -
- {intakes.map((intake) => ( - - ))} -
- )} -
-
- - {/* Diagnosis Info */} - - - - - Diagnose - - - - {latestCondition ? ( -
-

{latestCondition.code_display}

-
- {latestCondition.category} - {latestCondition.severity_display && ( - - {latestCondition.severity_display} - - )} -
-
- ) : ( -

- Geen diagnose gevonden. Voeg eerst een diagnose toe. -

- )} -
-
-
- - {/* Leefgebieden Preview */} - {selectedIntake && hasLifeDomains && ( - - )} - - {/* Create Options */} -
- {/* AI Generate Option */} - - -
-
-
- -
-
-

- AI Genereren -

-

- Automatisch SMART doelen en interventies genereren -

-
-
- - {!hasLifeDomains && ( -

- Tip: vul leefgebieden in voor betere AI resultaten -

- )} -
-
-
- - {/* Manual Create Option */} - - -
-
-
- -
-
-

- Handmatig Aanmaken -

-

- Zelf doelen en interventies invoeren -

-
-
- -
-
-
-
- - {/* Error Display */} - {error && ( -
- - {error} -
- )} -
- ); - } - - // Care plan exists - show plan view - const goals = (carePlan.goals as SmartGoal[]) || []; - const interventions = (carePlan.activities as Intervention[]) || []; - const behandelstructuur = carePlan.behandelstructuur as GeneratedPlan['behandelstructuur'] | null; - const sessiePlanning = (carePlan.sessie_planning as Sessie[]) || []; - const evaluatiemomenten = (carePlan.evaluatiemomenten as Evaluatiemoment[]) || []; - const veiligheidsplan = carePlan.veiligheidsplan as Veiligheidsplan | null; - const statusInfo = FHIR_STATUS_LABELS[carePlan.status as FhirCarePlanStatus] || { - label: carePlan.status, - color: '#6b7280', - }; - - return ( -
- {/* Header with Status */} -
-
-
-

{carePlan.title}

- - {statusInfo.label} - -
-

- {carePlan.created_at && ( - <>Aangemaakt op {new Date(carePlan.created_at).toLocaleDateString('nl-NL')} - )} - {carePlan.published_at && ( - <> • Gepubliceerd op {new Date(carePlan.published_at).toLocaleDateString('nl-NL')} - )} -

-
- - {/* Status Actions */} -
- {carePlan.status === 'draft' && ( - - )} - {carePlan.status === 'active' && ( - - )} -
-
- - {/* Behandelstructuur */} - } - isEditing={editingBehandelstructuur} - onEditChange={(editing) => { - setEditingBehandelstructuur(editing); - if (editing) { - setTempBehandelstructuur(behandelstructuur || { - duur: '8 weken', - frequentie: 'Wekelijks', - aantalSessies: 8, - vorm: 'Individueel', - }); - } - }} - canEdit={!!onUpdateBehandelstructuur} - onSave={async () => { - if (tempBehandelstructuur && onUpdateBehandelstructuur) { - await onUpdateBehandelstructuur(tempBehandelstructuur); - setEditingBehandelstructuur(false); - } - }} - onCancel={() => { - setEditingBehandelstructuur(false); - setTempBehandelstructuur(null); - }} - editForm={ - - } - > - {behandelstructuur ? ( -
-
-

Duur

-

{behandelstructuur.duur}

-
-
-

Frequentie

-

{behandelstructuur.frequentie}

-
-
-

Sessies

-

{behandelstructuur.aantalSessies}

-
-
-

Vorm

-

{behandelstructuur.vorm}

-
-
- ) : ( -

Nog geen behandelstructuur ingesteld

- )} -
- - {/* Goals Section */} - - -
-
- - - SMART Doelen ({goals.length}) - - - Behandeldoelen gekoppeld aan leefgebieden - -
- {onAddGoal && ( - - )} -
-
- -
- {/* Add Goal Form */} - {addingGoal && ( -
-

Nieuw doel toevoegen

- -
- - -
-
- )} - - {goals.map((goal) => { - const goalStatus = GOAL_STATUS_LABELS[goal.status] || { - label: goal.status, - color: '#6b7280', - }; - const isEditing = editingGoalId === goal.id; - - if (isEditing) { - return ( -
- -
- - -
-
- ); - } - - return ( -
-
-
-
- - - {goalStatus.label} - -
-

{goal.title}

-

{goal.description}

- - {/* Client version */} - {goal.clientVersion && ( -
- Voor cliënt: - {goal.clientVersion} -
- )} - - {/* Progress bar */} -
-
- Voortgang - {goal.progress}% -
-
-
-
-
-
- {(onUpdateGoal || onDeleteGoal) && ( - { - setEditingGoalId(goal.id); - setTempGoal(goal); - } : undefined} - onDelete={onDeleteGoal ? () => onDeleteGoal(goal.id) : undefined} - /> - )} -
-
- ); - })} - - {goals.length === 0 && !addingGoal && ( -

- Geen doelen gevonden -

- )} -
- - - - {/* Interventions Section */} - - -
-
- - - Interventies ({interventions.length}) - - - Evidence-based behandelmethoden - -
- {onAddIntervention && ( - - )} -
-
- -
- {/* Add Intervention Form */} - {addingIntervention && ( -
-

Nieuwe interventie toevoegen

- -
- - -
-
- )} - - {interventions.map((intervention) => { - const isEditing = editingInterventionId === intervention.id; - - if (isEditing) { - return ( -
- -
- - -
-
- ); - } - - return ( -
-
-
-

{intervention.name}

-

{intervention.description}

- {intervention.rationale && ( -
- Rationale: - {intervention.rationale} -
- )} - {intervention.linkedGoalIds.length > 0 && ( -
- Gekoppeld aan: - {intervention.linkedGoalIds.map((goalId) => { - const goal = goals.find((g) => g.id === goalId); - return goal ? ( - - ) : null; - })} -
- )} -
- {(onUpdateIntervention || onDeleteIntervention) && ( - { - setEditingInterventionId(intervention.id); - setTempIntervention(intervention); - } : undefined} - onDelete={onDeleteIntervention ? () => onDeleteIntervention(intervention.id) : undefined} - /> - )} -
-
- ); - })} - - {interventions.length === 0 && !addingIntervention && ( -

- Geen interventies gevonden -

- )} -
-
-
- - {/* Sessie Planning Section */} - {sessiePlanning.length > 0 && ( - - - - - Sessie Planning ({sessiePlanning.length}) - - - Overzicht van geplande en afgeronde sessies - - - -
- {sessiePlanning.map((sessie) => { - const statusColors: Record = { - gepland: 'bg-blue-100 text-blue-800', - afgerond: 'bg-green-100 text-green-800', - no_show: 'bg-red-100 text-red-800', - verzet: 'bg-amber-100 text-amber-800', - geannuleerd: 'bg-slate-100 text-slate-800', - }; - return ( -
-
-
- {sessie.nummer} -
-
-

{sessie.focus}

- {sessie.datum && ( -

- {new Date(sessie.datum).toLocaleDateString('nl-NL', { - weekday: 'short', - day: 'numeric', - month: 'short', - })} -

- )} -
-
- - {sessie.status === 'no_show' ? 'No-show' : sessie.status} - -
- ); - })} -
-
-
- )} - - {/* Evaluatiemomenten Section */} - {evaluatiemomenten.length > 0 && ( - - - - - Evaluatiemomenten ({evaluatiemomenten.length}) - - - Geplande evaluaties en voortgangsmomenten - - - -
- {evaluatiemomenten.map((evaluatie) => { - const typeLabels: Record = { - tussentijds: { label: 'Tussentijds', color: 'bg-blue-100 text-blue-800' }, - eind: { label: 'Eindevaluatie', color: 'bg-green-100 text-green-800' }, - crisis: { label: 'Crisis', color: 'bg-red-100 text-red-800' }, - }; - const statusLabels: Record = { - gepland: 'Gepland', - afgerond: 'Afgerond', - overgeslagen: 'Overgeslagen', - }; - const typeInfo = typeLabels[evaluatie.type] || { label: evaluatie.type, color: 'bg-slate-100' }; - return ( -
-
-
- W{evaluatie.weekNumber} -
-
-
- {typeInfo.label} - - {statusLabels[evaluatie.status] || evaluatie.status} - -
-

- {new Date(evaluatie.plannedDate).toLocaleDateString('nl-NL', { - day: 'numeric', - month: 'long', - year: 'numeric', - })} -

-
-
- {evaluatie.status === 'afgerond' && ( - - )} -
- ); - })} -
-
-
- )} - - {/* Veiligheidsplan Section */} - {veiligheidsplan && ( - - - - - Veiligheidsplan - - - Crisisplan en veiligheidsafspraken - - - - {/* Waarschuwingssignalen */} -
-

- - Waarschuwingssignalen -

-
    - {veiligheidsplan.waarschuwingssignalen.map((signaal, i) => ( -
  • - - {signaal} -
  • - ))} -
-
- - {/* Coping Strategieën */} -
-

- - Coping Strategieën -

-
    - {veiligheidsplan.copingStrategieen.map((strategie, i) => ( -
  • - - {strategie} -
  • - ))} -
-
- - {/* Contactpersonen */} -
-

- - Noodcontacten -

-
- {veiligheidsplan.contacten.map((contact, i) => ( -
-

{contact.naam}

-

{contact.rol}

-

{contact.telefoon}

-
- ))} -
-
- - {/* Restricties */} - {veiligheidsplan.restricties && veiligheidsplan.restricties.length > 0 && ( -
-

Afspraken/Restricties

-
    - {veiligheidsplan.restricties.map((restrictie, i) => ( -
  • - - {restrictie} -
  • - ))} -
-
- )} -
-
- )} -
- ); -} diff --git a/components/behandelplan/editable-section.tsx b/components/behandelplan/editable-section.tsx deleted file mode 100644 index 24d6e57..0000000 --- a/components/behandelplan/editable-section.tsx +++ /dev/null @@ -1,162 +0,0 @@ -'use client'; - -import { useState, ReactNode } from 'react'; -import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { Pencil, X, Check, Loader2 } from 'lucide-react'; - -interface EditableSectionProps { - title: string; - description?: string; - icon?: ReactNode; - children: ReactNode; - editForm?: ReactNode; - onSave?: () => Promise; - onCancel?: () => void; - isEditing?: boolean; - onEditChange?: (isEditing: boolean) => void; - canEdit?: boolean; - className?: string; -} - -export function EditableSection({ - title, - description, - icon, - children, - editForm, - onSave, - onCancel, - isEditing: externalIsEditing, - onEditChange, - canEdit = true, - className = '', -}: EditableSectionProps) { - const [internalIsEditing, setInternalIsEditing] = useState(false); - const [isSaving, setIsSaving] = useState(false); - - // Use external or internal state - const isEditing = externalIsEditing ?? internalIsEditing; - const setIsEditing = onEditChange ?? setInternalIsEditing; - - const handleSave = async () => { - if (!onSave) return; - setIsSaving(true); - try { - await onSave(); - setIsEditing(false); - } catch (error) { - console.error('Error saving:', error); - } finally { - setIsSaving(false); - } - }; - - const handleCancel = () => { - onCancel?.(); - setIsEditing(false); - }; - - return ( - - -
-
- - {icon} - {title} - - {description && {description}} -
- {canEdit && !isEditing && ( - - )} -
-
- - {isEditing && editForm ? ( -
- {editForm} -
- - -
-
- ) : ( - children - )} -
-
- ); -} - -// Simple inline edit buttons for list items -interface ItemActionsProps { - onEdit?: () => void; - onDelete?: () => void; - isDeleting?: boolean; -} - -export function ItemActions({ onEdit, onDelete, isDeleting }: ItemActionsProps) { - return ( -
- {onEdit && ( - - )} - {onDelete && ( - - )} -
- ); -} diff --git a/components/behandelplan/flat/behandeldoel-card.tsx b/components/behandelplan/flat/behandeldoel-card.tsx deleted file mode 100644 index 40dca82..0000000 --- a/components/behandelplan/flat/behandeldoel-card.tsx +++ /dev/null @@ -1,160 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { Card, CardContent, CardHeader } from '@/components/ui/card'; -import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; -import { Progress } from '@/components/ui/progress'; -import { cn } from '@/lib/utils'; -import { type Behandeldoel, GOAL_STATUS_LABELS } from '@/lib/types/behandelplan'; -import { LIFE_DOMAIN_META } from '@/lib/types/leefgebieden'; -import { Target, Pencil, ChevronDown, ChevronUp } from 'lucide-react'; -import { BehandeldoelForm } from './behandeldoel-form'; - -interface BehandeldoelCardProps { - doel: Behandeldoel; - isEditing: boolean; - onEdit: () => void; - onSave: (doel: Behandeldoel) => Promise; - onCancel: () => void; - onDelete?: () => Promise; - className?: string; -} - -/** - * Behandeldoel Card - * View mode: Compact card met doel + interventies - * Edit mode: Inline form met alle velden - */ -export function BehandeldoelCard({ - doel, - isEditing, - onEdit, - onSave, - onCancel, - onDelete, - className, -}: BehandeldoelCardProps) { - const [isExpanded, setIsExpanded] = useState(false); - - if (isEditing) { - return ( - - ); - } - - const meta = LIFE_DOMAIN_META[doel.lifeDomain]; - const statusInfo = GOAL_STATUS_LABELS[doel.status]; - - return ( - - -
-
- -

{doel.title}

-
-
- - {meta.shortLabel} - - - {statusInfo.label} - -
-
-
- - - {/* Client version (B1 tekst) - altijd zichtbaar */} -
-

- “{doel.clientVersion}” -

-
- - {/* Interventies */} - {doel.interventies.length > 0 && ( -
- - Aanpak - -
    - {doel.interventies.map((int) => ( -
  • - - - {int.name} - {int.description && ( - - {int.description} - )} - -
  • - ))} -
-
- )} - - {/* Progress & timeline */} -
-
-
- Week {doel.startWeek}-{doel.endWeek} - {doel.progress}% -
- -
-
- - -
-
- - {/* Expanded details (optional) */} - {isExpanded && ( -
-

Leefgebied: {meta.label}

-

Status: {statusInfo.label}

-
- )} -
-
- ); -} diff --git a/components/behandelplan/flat/behandeldoel-form.tsx b/components/behandelplan/flat/behandeldoel-form.tsx deleted file mode 100644 index bfea115..0000000 --- a/components/behandelplan/flat/behandeldoel-form.tsx +++ /dev/null @@ -1,357 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Textarea } from '@/components/ui/textarea'; -import { Label } from '@/components/ui/label'; -import { Slider } from '@/components/ui/slider'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { cn } from '@/lib/utils'; -import { - type Behandeldoel, - type EmbeddedInterventie, - type GoalStatus, - GOAL_STATUSES, - GOAL_STATUS_LABELS, - createEmptyEmbeddedInterventie, -} from '@/lib/types/behandelplan'; -import { type LifeDomain, LIFE_DOMAINS, LIFE_DOMAIN_META } from '@/lib/types/leefgebieden'; -import { Plus, X, Sparkles, Trash2, Save } from 'lucide-react'; - -interface BehandeldoelFormProps { - doel: Behandeldoel; - onSave: (doel: Behandeldoel) => Promise; - onCancel: () => void; - onDelete?: () => Promise; - className?: string; -} - -/** - * Inline edit form voor Behandeldoel - * Alle velden in één uitklapbare card - */ -export function BehandeldoelForm({ - doel, - onSave, - onCancel, - onDelete, - className, -}: BehandeldoelFormProps) { - const [formData, setFormData] = useState(doel); - const [isSaving, setIsSaving] = useState(false); - const [isDeleting, setIsDeleting] = useState(false); - - const handleSave = async () => { - setIsSaving(true); - try { - await onSave(formData); - } finally { - setIsSaving(false); - } - }; - - const handleDelete = async () => { - if (!onDelete) return; - if (!confirm('Weet je zeker dat je dit behandeldoel wilt verwijderen?')) return; - setIsDeleting(true); - try { - await onDelete(); - } finally { - setIsDeleting(false); - } - }; - - const updateField = ( - field: K, - value: Behandeldoel[K] - ) => { - setFormData((prev) => ({ ...prev, [field]: value })); - }; - - const addInterventie = () => { - setFormData((prev) => ({ - ...prev, - interventies: [...prev.interventies, createEmptyEmbeddedInterventie()], - })); - }; - - const updateInterventie = ( - index: number, - field: keyof EmbeddedInterventie, - value: string - ) => { - setFormData((prev) => ({ - ...prev, - interventies: prev.interventies.map((int, i) => - i === index ? { ...int, [field]: value } : int - ), - })); - }; - - const removeInterventie = (index: number) => { - setFormData((prev) => ({ - ...prev, - interventies: prev.interventies.filter((_, i) => i !== index), - })); - }; - - const isValid = - formData.title.trim().length >= 5 && - formData.clientVersion.trim().length >= 5; - - return ( - - - - Behandeldoel bewerken - - - - - {/* Doel titel */} -
- - updateField('title', e.target.value)} - placeholder="Bijv. Weer 4 dagen per week stabiel kunnen werken" - className="text-sm" - /> -
- - {/* Client versie (B1) */} -
-
- - -
-