'use client' import { useState, useEffect } from 'react' import { useMissionControl } from '@/store' import { useNavigateToPanel } from '@/lib/navigation' interface NavItem { id: string label: string icon: React.ReactNode priority: boolean // Show in mobile bottom bar requiresGateway?: boolean } interface NavGroup { id: string label?: string // undefined = no header (core group) items: NavItem[] } const navGroups: NavGroup[] = [ { id: 'core', items: [ { id: 'overview', label: 'Overview', icon: , priority: true }, { id: 'agents', label: 'Agents', icon: , priority: true, requiresGateway: true }, { id: 'tasks', label: 'Tasks', icon: , priority: true }, { id: 'sessions', label: 'Sessions', icon: , priority: false }, { id: 'office', label: 'Office', icon: , priority: false }, ], }, { id: 'observe', label: 'OBSERVE', items: [ { id: 'activity', label: 'Activity', icon: , priority: true }, { id: 'logs', label: 'Logs', icon: , priority: false }, { id: 'tokens', label: 'Tokens', icon: , priority: false }, { id: 'agent-costs', label: 'Agent Costs', icon: , priority: false }, { id: 'memory', label: 'Memory', icon: , priority: false }, ], }, { id: 'automate', label: 'AUTOMATE', items: [ { id: 'cron', label: 'Cron', icon: , priority: false }, { id: 'spawn', label: 'Spawn', icon: , priority: false, requiresGateway: true }, { id: 'webhooks', label: 'Webhooks', icon: , priority: false }, { id: 'alerts', label: 'Alerts', icon: , priority: false }, { id: 'github', label: 'GitHub', icon: , priority: false }, ], }, { id: 'admin', label: 'ADMIN', items: [ { id: 'users', label: 'Users', icon: , priority: false }, { id: 'audit', label: 'Audit', icon: , priority: false }, { id: 'history', label: 'History', icon: , priority: false }, { id: 'gateways', label: 'Gateways', icon: , priority: false }, { id: 'gateway-config', label: 'Config', icon: , priority: false, requiresGateway: true }, { id: 'integrations', label: 'Integrations', icon: , priority: false }, { id: 'super-admin', label: 'Super Admin', icon: , priority: false }, { id: 'settings', label: 'Settings', icon: , priority: false }, ], }, ] // Flat list for mobile bar const allNavItems = navGroups.flatMap(g => g.items) export function NavRail() { const { activeTab, connection, dashboardMode, sidebarExpanded, collapsedGroups, toggleSidebar, toggleGroup } = useMissionControl() const navigateToPanel = useNavigateToPanel() const isLocal = dashboardMode === 'local' // Keyboard shortcut: [ to toggle sidebar useEffect(() => { function handleKey(e: KeyboardEvent) { if (e.key === '[' && !(e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement || (e.target as HTMLElement)?.isContentEditable)) { e.preventDefault() toggleSidebar() } } window.addEventListener('keydown', handleKey) return () => window.removeEventListener('keydown', handleKey) }, [toggleSidebar]) return ( <> {/* Desktop: Grouped sidebar */} {/* Mobile: Bottom tab bar */} ) } function NavButton({ item, active, expanded, disabled, onClick }: { item: NavItem active: boolean expanded: boolean disabled?: boolean onClick: () => void }) { const disabledClass = disabled ? 'opacity-40 pointer-events-none' : '' const tooltipLabel = disabled ? `${item.label} (Requires gateway)` : item.label if (expanded) { return ( ) } return ( ) } function MobileBottomBar({ activeTab, navigateToPanel }: { activeTab: string navigateToPanel: (tab: string) => void }) { const [sheetOpen, setSheetOpen] = useState(false) const priorityItems = allNavItems.filter(i => i.priority) const nonPriorityIds = new Set(allNavItems.filter(i => !i.priority).map(i => i.id)) const moreIsActive = nonPriorityIds.has(activeTab) return ( <> {/* Bottom sheet */} setSheetOpen(false)} activeTab={activeTab} navigateToPanel={navigateToPanel} /> ) } function MobileBottomSheet({ open, onClose, activeTab, navigateToPanel }: { open: boolean onClose: () => void activeTab: string navigateToPanel: (tab: string) => void }) { // Track mount state for animation const [visible, setVisible] = useState(false) useEffect(() => { if (open) { // Mount first, then animate in on next frame requestAnimationFrame(() => { requestAnimationFrame(() => setVisible(true)) }) } else { setVisible(false) } }, [open]) // Handle close with animation function handleClose() { setVisible(false) setTimeout(onClose, 200) // match transition duration } if (!open) return null return (
{/* Backdrop */}
{/* Sheet */}
{/* Drag handle */}
{/* Grouped navigation */}
{navGroups.map((group, groupIndex) => (
{groupIndex > 0 &&
} {/* Group header */}
{group.label || 'CORE'}
{/* 2-column grid */}
{group.items.map((item) => ( ))}
))}
) } // SVG Icons (16x16 viewbox, stroke-based) function OverviewIcon() { return ( ) } function AgentsIcon() { return ( ) } function TasksIcon() { return ( ) } function SessionsIcon() { return ( ) } function ActivityIcon() { return ( ) } function LogsIcon() { return ( ) } function SpawnIcon() { return ( ) } function CronIcon() { return ( ) } function MemoryIcon() { return ( ) } function TokensIcon() { return ( ) } function UsersIcon() { return ( ) } function HistoryIcon() { return ( ) } function AuditIcon() { return ( ) } function WebhookIcon() { return ( ) } function GatewayConfigIcon() { return ( ) } function GatewaysIcon() { return ( ) } function AlertIcon() { return ( ) } function SuperAdminIcon() { return ( ) } function IntegrationsIcon() { return ( ) } function AgentCostsIcon() { return ( ) } function GitHubIcon() { return ( ) } function SettingsIcon() { return ( ) } function OfficeIcon() { return ( ) }