Fixes 4 bugs preventing intake blocks from working: 1. Chat API validation: Add 'nudge' to message type enum, allow empty content for streaming messages 2. AI chat recognition: Add P3 intake intents (intake_status, risico_query, diagnose_query, intake_navigeer) to system prompt with triggers, entities, and JSON examples 3. Artifact rendering: Add intake block imports and switch cases to artifact-container.tsx 4. API 400 error: Convert null to undefined for optional Zod params (searchParams.get returns null, Zod .optional() expects undefined) Also adds: - Testplan for E2E testing (60+ test cases) - Session log with lessons learned and new intent checklist Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
132 lines
3.4 KiB
TypeScript
132 lines
3.4 KiB
TypeScript
'use client';
|
|
|
|
/**
|
|
* useBlockData Hook
|
|
*
|
|
* Generic data fetching hook for Cortex blocks.
|
|
* Handles loading, error states, and provides refetch capability.
|
|
*
|
|
* Epic: E2.S1 - Custom Hooks
|
|
*/
|
|
|
|
import { useState, useEffect, useCallback } from 'react';
|
|
import { useToast } from '@/hooks/use-toast';
|
|
import { safeFetch, getErrorInfo } from '@/lib/cortex/error-handler';
|
|
|
|
// ============================================================================
|
|
// Types
|
|
// ============================================================================
|
|
|
|
interface UseBlockDataOptions<T> {
|
|
/** API endpoint (relatief pad) */
|
|
endpoint: string;
|
|
/** Query parameters */
|
|
params?: Record<string, string | undefined>;
|
|
/** Of data moet worden opgehaald */
|
|
enabled?: boolean;
|
|
/** Callback bij error */
|
|
onError?: (error: Error) => void;
|
|
/** Operatie naam voor error messages */
|
|
operationName?: string;
|
|
}
|
|
|
|
interface UseBlockDataResult<T> {
|
|
/** Opgehaalde data */
|
|
data: T | null;
|
|
/** Loading state */
|
|
isLoading: boolean;
|
|
/** Error message */
|
|
error: string | null;
|
|
/** Refetch functie */
|
|
refetch: () => Promise<void>;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Hook
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Generic data fetching hook for Cortex blocks.
|
|
*
|
|
* @example
|
|
* const { data, isLoading, error, refetch } = useBlockData<RiskData>({
|
|
* endpoint: `/api/cortex/intake/${intakeId}/risks`,
|
|
* enabled: Boolean(intakeId),
|
|
* operationName: 'Risico\'s laden',
|
|
* });
|
|
*/
|
|
export function useBlockData<T>({
|
|
endpoint,
|
|
params,
|
|
enabled = true,
|
|
onError,
|
|
operationName = 'Data laden',
|
|
}: UseBlockDataOptions<T>): UseBlockDataResult<T> {
|
|
const [data, setData] = useState<T | null>(null);
|
|
const [isLoading, setIsLoading] = useState(enabled);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const { toast } = useToast();
|
|
|
|
// Stringify params for dependency comparison
|
|
const paramsKey = params ? JSON.stringify(params) : '';
|
|
|
|
const fetchData = useCallback(async () => {
|
|
if (!enabled) return;
|
|
|
|
setIsLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
// Build URL with params
|
|
const url = new URL(endpoint, window.location.origin);
|
|
if (params) {
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
if (value !== undefined) {
|
|
url.searchParams.set(key, value);
|
|
}
|
|
});
|
|
}
|
|
|
|
const response = await safeFetch(url.toString(), undefined, {
|
|
operation: operationName,
|
|
});
|
|
|
|
const result = await response.json();
|
|
setData(result);
|
|
} catch (err) {
|
|
const statusCode = (err as any)?.statusCode;
|
|
const details = (err as any)?.details;
|
|
console.error('[useBlockData] Error:', {
|
|
operation: operationName,
|
|
statusCode,
|
|
message: (err as Error)?.message,
|
|
details,
|
|
});
|
|
|
|
const errorInfo = getErrorInfo(err, {
|
|
operation: operationName,
|
|
statusCode,
|
|
});
|
|
|
|
setError(errorInfo.description);
|
|
onError?.(err as Error);
|
|
|
|
toast({
|
|
variant: 'destructive',
|
|
title: errorInfo.title,
|
|
description: errorInfo.description,
|
|
});
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [endpoint, paramsKey, enabled, onError, operationName, toast]);
|
|
|
|
useEffect(() => {
|
|
if (enabled) {
|
|
fetchData();
|
|
}
|
|
}, [fetchData, enabled]);
|
|
|
|
return { data, isLoading, error, refetch: fetchData };
|
|
}
|