feat(diagnose+agenda): Diagnose module met ICD-10 en agenda uitbreidingen
Diagnose Module: - Diagnose overzicht pagina met alle patiënt diagnoses - Diagnosis manager met ICD-10 combobox zoekfunctie - Diagnose kaarten met hoofddiagnose markering - Modal voor nieuwe/bewerkte diagnoses - ICD-10 GGZ codes dataset (lib/data/) - Zod schemas voor diagnose validatie - TypeScript types voor ICD-10 (lib/types/icd10.ts) - Complete documentatie (PRD, FO, TO, Bouwplan) Agenda Uitbreidingen: - Patient context card in afspraak modal - Rapportage composer direct in afspraak modal - Rapportage bewerken vanuit gekoppelde rapportages - Verbeterde focus styling voor inputs Behandelplan: - Flat componenten structuur (behandeldoel-card, form, planning) - Context header component - Uitgebreide types (lib/types/behandelplan.ts) - Actions voor behandelplan beheer UI Componenten: - Command component (shadcn/ui) voor combobox - Popover component (shadcn/ui) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
|
||||
import { createClient } from '@/lib/auth/server';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import type { GeneratedPlan, SmartGoal, Intervention, Behandelstructuur } from '@/lib/types/behandelplan';
|
||||
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';
|
||||
|
||||
@@ -518,3 +519,132 @@ export async function deleteIntervention(
|
||||
|
||||
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`);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
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,
|
||||
@@ -14,10 +15,14 @@ import {
|
||||
addIntervention,
|
||||
updateIntervention,
|
||||
deleteIntervention,
|
||||
saveBehandeldoel,
|
||||
deleteBehandeldoel,
|
||||
} from './actions';
|
||||
import type { GeneratedPlan, SmartGoal, Intervention, Sessie, Evaluatiemoment, Veiligheidsplan, Behandelstructuur } from '@/lib/types/behandelplan';
|
||||
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 {
|
||||
@@ -124,6 +129,9 @@ export function BehandelplanPageClient({
|
||||
}: 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>(
|
||||
@@ -380,41 +388,131 @@ export function BehandelplanPageClient({
|
||||
[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">
|
||||
{/* Plannen overzicht */}
|
||||
<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}
|
||||
/>
|
||||
{/* 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}
|
||||
/>
|
||||
|
||||
{/* Geselecteerd plan of create view */}
|
||||
<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}
|
||||
/>
|
||||
{/* 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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user