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

46
lib/server/api-client.ts Normal file
View File

@@ -0,0 +1,46 @@
import { headers, cookies } from 'next/headers';
export function getBaseUrl(): string {
if (process.env.NEXT_PUBLIC_APP_URL) {
return process.env.NEXT_PUBLIC_APP_URL;
}
try {
const headersList = headers();
const host = headersList.get('host');
const protocol = headersList.get('x-forwarded-proto') || 'http';
if (host) {
return `${protocol}://${host}`;
}
} catch {
// Ignore header errors
}
return 'http://localhost:3000';
}
export async function getCookieHeader(): Promise<string> {
try {
const cookieStore = await cookies();
return cookieStore
.getAll()
.map((cookie) => `${cookie.name}=${cookie.value}`)
.join('; ');
} catch {
return '';
}
}
export async function authFetch(input: string | URL, init: RequestInit = {}) {
const cookieHeader = await getCookieHeader();
const headersObj = new Headers(init.headers);
if (cookieHeader && !headersObj.has('cookie')) {
headersObj.set('cookie', cookieHeader);
}
return fetch(input, {
...init,
headers: headersObj,
});
}

File diff suppressed because it is too large Load Diff

35
lib/types/report.ts Normal file
View File

@@ -0,0 +1,35 @@
import { z } from 'zod';
import type { Database } from '@/lib/supabase/database.types';
export type Report = Database['public']['Tables']['reports']['Row'];
export type ReportInsert = Database['public']['Tables']['reports']['Insert'];
export type ReportUpdate = Database['public']['Tables']['reports']['Update'];
export const REPORT_TYPES = ['behandeladvies', 'vrije_notitie'] as const;
export type ReportType = (typeof REPORT_TYPES)[number];
export const CreateReportSchema = z.object({
patient_id: z.string().uuid('Patient ID moet een geldige UUID zijn'),
type: z.enum(REPORT_TYPES, {
errorMap: () => ({ message: 'Type moet behandeladvies of vrije_notitie zijn' }),
}),
content: z
.string()
.min(20, 'Rapportage moet minimaal 20 karakters bevatten')
.max(5000, 'Rapportage mag maximaal 5000 karakters bevatten'),
ai_confidence: z.number().min(0).max(1).optional(),
ai_reasoning: z.string().optional(),
});
export type CreateReportInput = z.infer<typeof CreateReportSchema>;
export interface ClassificationResult {
type: ReportType;
confidence: number;
reasoning?: string;
}
export interface ReportListResponse {
reports: Report[];
total: number;
}