feat: rapportage API, speech recorder refactor, docs reorganisatie

Multiple features and improvements:

Reports/Rapportage API:
- Created REST API endpoints: /api/reports (GET/POST), /api/reports/[id] (GET/PATCH/DELETE)
- Added /api/reports/classify endpoint for AI classification
- Supabase migrations: reports table + RLS policies
- Server utilities: api-client.ts for DRY fetch logic
- Type definitions: lib/types/report.ts with Zod schemas
- Removed old rapportage-modal component (replaced by split-view)

Speech Recorder Refactor:
- Moved speech-recorder from intake-specific to shared components/
- Updated treatment-advice-form to use new location
- Updated intake actions for speech functionality

UI Components (shadcn):
- Added dialog, dropdown-menu, toast, toaster components
- Added use-toast hook for toast notifications

Documentation:
- Reorganized docs/release/ → docs/reports/ for better structure
- Archived old specs to docs/specs/archive/
- Added screening-system.mdx documentation
- Added rapportage-split-view-design.md
- Added UI screenshots for troubleshooting

Dependencies:
- Updated package.json and pnpm-lock.yaml
- Regenerated database.types.ts from Supabase

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
colinislit
2025-11-24 14:14:06 +01:00
parent 0130e28f1e
commit a789bebe96
37 changed files with 5034 additions and 1977 deletions

120
app/api/reports/route.ts Normal file
View File

@@ -0,0 +1,120 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { createClient } from '@/lib/auth/server';
import { CreateReportSchema, type ReportListResponse } from '@/lib/types/report';
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const patientId = searchParams.get('patientId');
if (!patientId) {
return NextResponse.json(
{ error: 'patientId query parameter is verplicht' },
{ status: 400 }
);
}
if (!z.string().uuid().safeParse(patientId).success) {
return NextResponse.json(
{ error: 'patientId moet een geldige UUID zijn' },
{ status: 400 }
);
}
const supabase = await createClient();
const { data, error } = await supabase
.from('reports')
.select('*')
.eq('patient_id', patientId)
.is('deleted_at', null)
.order('created_at', { ascending: false });
if (error) {
console.error('Error fetching reports:', error);
return NextResponse.json(
{ error: 'Fout bij ophalen rapportages', details: error.message },
{ status: 500 }
);
}
const response: ReportListResponse = {
reports: data ?? [],
total: data?.length ?? 0,
};
return NextResponse.json(response);
} catch (error) {
console.error('Unexpected error in GET /api/reports:', error);
return NextResponse.json(
{ error: 'Onverwachte serverfout' },
{ status: 500 }
);
}
}
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const result = CreateReportSchema.safeParse(body);
if (!result.success) {
return NextResponse.json(
{
error: 'Validatiefout',
details: result.error.issues.map((issue) => ({
field: issue.path.join('.'),
message: issue.message,
})),
},
{ status: 400 }
);
}
const supabase = await createClient();
const { data: authData } = await supabase.auth.getUser();
if (!authData?.user) {
return NextResponse.json(
{ error: 'Niet geautoriseerd' },
{ status: 401 }
);
}
const { patient_id, type, content, ai_confidence, ai_reasoning } = result.data;
const { data, error } = await supabase
.from('reports')
.insert({
patient_id,
type,
content,
ai_confidence,
ai_reasoning,
created_by: authData.user.id,
})
.select('*')
.single();
if (error) {
console.error('Error creating report:', error);
return NextResponse.json(
{ error: 'Opslaan mislukt', details: error.message },
{ status: 500 }
);
}
return NextResponse.json(data, { status: 201 });
} catch (error) {
console.error('Unexpected error in POST /api/reports:', error);
if (error instanceof SyntaxError) {
return NextResponse.json(
{ error: 'Ongeldige JSON in request body' },
{ status: 400 }
);
}
return NextResponse.json(
{ error: 'Onverwachte serverfout' },
{ status: 500 }
);
}
}