'use client'; /** * Command Center (v3.0) * * Main container for the Cortex interface. * Split-screen layout: Chat Panel (40%) | Artifact Area (60%) * * Layout specs: * - Context Bar: 48px (h-12) - UNCHANGED * - Split container: flex-1 (fills remaining space) * - Chat Panel: 40% width (desktop), 100% (mobile) * - Artifact Area: 60% width (desktop), 100% (mobile) * * Epic: E1 (Foundation) * Stories: E1.S2 (Split-screen layout), E1.S3 (Placeholders), E1.S4 (Responsive) */ import { useEffect, useCallback, useRef } from 'react'; import { useRouter } from 'next/navigation'; import { AnimatePresence } from 'framer-motion'; import { useCortexStore } from '@/stores/cortex-store'; import { cn } from '@/lib/utils'; import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; import { useMediaQuery } from '@/hooks/use-media-query'; import { useToast } from '@/hooks/use-toast'; import { ContextBar } from './context-bar'; import { OfflineBanner } from './offline-banner'; import { NudgeToast } from './nudge-toast'; import { PatientSidebar } from '../patient-sidebar'; import { ChatPanel } from '../chat/chat-panel'; import { ArtifactArea } from '../artifacts/artifact-area'; import { getArtifactTitle } from '../artifacts/artifact-container'; import { routeIntentToArtifact } from '@/lib/cortex/action-parser'; import { isFeatureEnabled } from '@/lib/config/feature-flags'; import type { ExtractedEntities } from '@/lib/cortex/types'; // Intake tab mapping for navigation const INTAKE_TAB_PATHS: Record = { contacts: 'contacts', kindcheck: 'kindcheck', risk: 'risk', anamnese: 'anamnese', examination: 'examination', rom: 'rom', diagnosis: 'diagnosis', behandeladvies: 'behandeladvies', }; export function CommandCenter() { const router = useRouter(); const { toast } = useToast(); const { closeAllArtifacts, openArtifacts, openArtifact, pendingAction, setPendingAction, activePatient, // Nudge state (E4) suggestions, acceptSuggestion, dismissSuggestion, // Patient sidebar (E1) togglePatientSidebar, patientSidebarOpen, setPatientSidebarOpen, } = useCortexStore(); const inputRef = useRef(null); // Global keyboard shortcuts const handleKeyDown = useCallback( (e: KeyboardEvent) => { // Escape: close sidebar first, then artifacts if (e.key === 'Escape') { if (patientSidebarOpen) { e.preventDefault(); setPatientSidebarOpen(false); return; } if (openArtifacts.length > 0) { e.preventDefault(); closeAllArtifacts(); } } // Cmd/Ctrl + K: focus input (chat input in v3.0) if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); inputRef.current?.focus(); } // Cmd/Ctrl + P: toggle patient sidebar (E1.S3) if ((e.metaKey || e.ctrlKey) && e.key === 'p') { e.preventDefault(); togglePatientSidebar(); } }, [openArtifacts, closeAllArtifacts, patientSidebarOpen, setPatientSidebarOpen, togglePatientSidebar] ); useEffect(() => { window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [handleKeyDown]); // E3.S6 + E4.S2: Handle pending actions from chat (artifact opening) // E5.S2: Handle intake_navigeer navigation useEffect(() => { if (!pendingAction) return; console.log('[CommandCenter] Processing pending action:', pendingAction); // E5.S2: Handle intake_navigeer intent - navigate instead of opening artifact if (pendingAction.intent === 'intake_navigeer') { const entities = pendingAction.entities as ExtractedEntities; const navigationTarget = entities.navigationTarget; if (!activePatient) { toast({ variant: 'destructive', title: 'Geen patiënt geselecteerd', description: 'Selecteer eerst een patiënt om te navigeren.', }); setPendingAction(null); return; } // For now, we need an intake ID. Try to get it from entities or navigate to intakes list const tabPath = navigationTarget ? INTAKE_TAB_PATHS[navigationTarget] : null; if (tabPath) { // Navigate to the patient's intakes page with a toast // Note: Full navigation to specific intake tab requires intakeId // For MVP, navigate to patient intakes page const url = `/epd/patients/${activePatient.id}/intakes`; toast({ title: 'Navigeren...', description: `Naar ${navigationTarget} sectie`, }); console.log('[CommandCenter] Navigating to:', url, 'target:', navigationTarget); router.push(url); } else { toast({ variant: 'destructive', title: 'Navigatie mislukt', description: 'Onbekende sectie. Probeer: risico, diagnose, anamnese.', }); } setPendingAction(null); return; } // Check if action has artifact data if (pendingAction.artifact) { const { type, prefill } = pendingAction.artifact; // Generate title for the artifact const title = getArtifactTitle(type, prefill); console.log('[CommandCenter] Opening artifact:', type, title); // Open the artifact (E4.S2 - new artifact system) openArtifact({ type, prefill, title, }); setPendingAction(null); return; } const routedArtifact = routeIntentToArtifact( pendingAction.intent, pendingAction.entities, pendingAction.confidence ); if (routedArtifact) { console.log('[CommandCenter] Routing action to artifact:', routedArtifact.type); openArtifact({ type: routedArtifact.type, prefill: routedArtifact.prefill, title: routedArtifact.title, }); } else { console.log('[CommandCenter] Action has no artifact, skipping'); } setPendingAction(null); }, [pendingAction, openArtifact, setPendingAction, activePatient, router, toast]); return (
{/* Patient Sidebar (E1.S2) */} {/* Offline Banner */} {/* Context Bar - 48px (unchanged) */} {/* Split-screen container - flex-1 */}
{/* Mobile View / Desktop Resizable View Toggle */} {/* Chat Panel Panel */} {/* Resize Handle - Premium feel */} {/* Visual indicator (line) */}
{/* Interaction Area (Invisible width boost) */}
{/* Artifact Area Panel */} {/* Mobile-only Layout (Legacy overlay behavior) */}
0 ? "translate-x-0" : "translate-x-full" )} >
{/* Nudge Toast - E4 (fixed position, bottom-left above chat, feature flagged) */} {isFeatureEnabled('CORTEX_NUDGE') && ( {suggestions.length > 0 && (
{ const suggestion = suggestions.find((s) => s.id === id); if (suggestion) { // Route the suggested intent to an artifact const artifact = routeIntentToArtifact( suggestion.suggestion.intent, suggestion.suggestion.entities, 1.0 // High confidence since user explicitly accepted ); if (artifact) { console.log('[CommandCenter] Opening artifact from nudge:', artifact.type); openArtifact({ type: artifact.type, prefill: artifact.prefill, title: artifact.title, }); } } acceptSuggestion(id); }} onDismiss={(id) => { dismissSuggestion(id); console.log('[CommandCenter] Nudge dismissed:', id); }} />
)}
)}
); }