Files
triqura-ecd/components/cortex/shared/block-states.tsx
colinislit b095b1e492 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>
2026-02-03 23:21:18 +01:00

137 lines
3.3 KiB
TypeScript

'use client';
/**
* Block State Components
*
* Reusable state components for Cortex blocks:
* - BlockLoading: Loading spinner with message
* - BlockError: Error state with retry option
* - BlockEmpty: Empty state with action option
*
* Epic: E0 - Block States
*/
import { Loader2, AlertCircle, RefreshCw } from 'lucide-react';
import type { LucideIcon } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
// ============================================================================
// BlockLoading (E0.S1)
// ============================================================================
interface BlockLoadingProps {
/** Tekst onder de spinner */
message?: string;
/** Extra CSS classes */
className?: string;
}
/**
* Loading state for Cortex blocks.
* Shows centered spinner with optional message.
*/
export function BlockLoading({
message = 'Laden...',
className,
}: BlockLoadingProps) {
return (
<div
className={cn(
'flex flex-col items-center justify-center py-12',
className
)}
>
<Loader2 className="h-6 w-6 animate-spin text-slate-400 mb-2" />
<span className="text-sm text-slate-500">{message}</span>
</div>
);
}
// ============================================================================
// BlockError (E0.S2)
// ============================================================================
interface BlockErrorProps {
/** Foutmelding tekst */
message: string;
/** Callback voor retry knop (toont knop indien aanwezig) */
onRetry?: () => void;
/** Extra CSS classes */
className?: string;
}
/**
* Error state for Cortex blocks.
* Shows error icon, message, and optional retry button.
*/
export function BlockError({
message,
onRetry,
className,
}: BlockErrorProps) {
return (
<div
className={cn(
'flex flex-col items-center justify-center py-8 text-center',
className
)}
>
<AlertCircle className="h-8 w-8 text-red-500 mb-2" />
<p className="text-sm text-red-700 mb-3 max-w-xs">{message}</p>
{onRetry && (
<Button variant="outline" size="sm" onClick={onRetry}>
<RefreshCw className="h-4 w-4 mr-1.5" />
Opnieuw proberen
</Button>
)}
</div>
);
}
// ============================================================================
// BlockEmpty (E0.S3)
// ============================================================================
interface BlockEmptyProps {
/** Icoon component */
icon: LucideIcon;
/** Hoofdboodschap */
message: string;
/** Optionele actie knop */
action?: {
label: string;
onClick: () => void;
};
/** Extra CSS classes */
className?: string;
}
/**
* Empty state for Cortex blocks.
* Shows icon, message, and optional action button.
*/
export function BlockEmpty({
icon: Icon,
message,
action,
className,
}: BlockEmptyProps) {
return (
<div
className={cn(
'flex flex-col items-center justify-center py-8 text-center',
className
)}
>
<Icon className="h-8 w-8 text-slate-300 mb-2" />
<p className="text-sm text-slate-500 mb-3">{message}</p>
{action && (
<Button variant="outline" size="sm" onClick={action.onClick}>
{action.label}
</Button>
)}
</div>
);
}