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:
103
components/cortex/shared/block-footer.tsx
Normal file
103
components/cortex/shared/block-footer.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* BlockFooter Component
|
||||
*
|
||||
* Footer with action buttons for Cortex blocks.
|
||||
* Secondary action on left, primary action on right.
|
||||
*
|
||||
* Epic: E1.S3 - Block Layout
|
||||
*/
|
||||
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
// ============================================================================
|
||||
|
||||
interface ActionConfig {
|
||||
/** Knop label */
|
||||
label: string;
|
||||
/** Optioneel icoon */
|
||||
icon?: LucideIcon;
|
||||
/** Click handler */
|
||||
onClick: () => void;
|
||||
/** Loading state (alleen voor primary) */
|
||||
loading?: boolean;
|
||||
/** Disabled state */
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface BlockFooterProps {
|
||||
/** Linker actie (ghost button) */
|
||||
secondaryAction?: ActionConfig;
|
||||
/** Rechter actie (solid button) */
|
||||
primaryAction?: ActionConfig;
|
||||
/** Extra CSS classes */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Component
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Footer for Cortex blocks with action buttons.
|
||||
* Returns null if no actions provided.
|
||||
*/
|
||||
export function BlockFooter({
|
||||
secondaryAction,
|
||||
primaryAction,
|
||||
className,
|
||||
}: BlockFooterProps) {
|
||||
// Don't render if no actions
|
||||
if (!secondaryAction && !primaryAction) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-between',
|
||||
'pt-4 mt-4 border-t border-slate-200',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{/* Secondary Action (left) */}
|
||||
{secondaryAction ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={secondaryAction.onClick}
|
||||
disabled={secondaryAction.disabled}
|
||||
>
|
||||
{secondaryAction.icon && (
|
||||
<secondaryAction.icon className="h-4 w-4 mr-1.5" />
|
||||
)}
|
||||
{secondaryAction.label}
|
||||
</Button>
|
||||
) : (
|
||||
<div /> // Spacer
|
||||
)}
|
||||
|
||||
{/* Primary Action (right) */}
|
||||
{primaryAction && (
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={primaryAction.onClick}
|
||||
disabled={primaryAction.loading || primaryAction.disabled}
|
||||
>
|
||||
{primaryAction.loading ? (
|
||||
<Loader2 className="h-4 w-4 mr-1.5 animate-spin" />
|
||||
) : primaryAction.icon ? (
|
||||
<primaryAction.icon className="h-4 w-4 mr-1.5" />
|
||||
) : null}
|
||||
{primaryAction.label}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user