feat(cortex): add Intake Blocks MVP for voice/text commands
Adds 4 new intents for intake workflow: - intake_status: shows completion percentage and section checklist - intake_navigeer: navigation to specific intake tabs - risico_query: displays risk assessments with severity indicators - diagnose_query: shows primary/secondary diagnoses New components: - IntakeStatusBlock, RisicoBlock, DiagnoseBlock - Shared block components (BlockLoading, BlockError, BlockEmpty, BlockSection, BlockItem, BlockFooter) - useBlockData and useIntakeContext hooks New API routes: - GET /api/cortex/intake/status - GET /api/cortex/intake/risico - GET /api/cortex/intake/diagnose Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
123
lib/cortex/hooks/use-block-data.ts
Normal file
123
lib/cortex/hooks/use-block-data.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
'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 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 };
|
||||
}
|
||||
Reference in New Issue
Block a user