'use client'; import type { ReactNode, MouseEvent } from 'react'; import { memo, useEffect, useCallback, useState, useMemo } from 'react'; import { Badge } from '@kandev/ui/badge'; import { SessionPanel, SessionPanelContent } from '@kandev/ui/pannel-session'; import { Group, Panel } from 'react-resizable-panels'; import { TabsContent } from '@kandev/ui/tabs'; import { getLocalStorage, setLocalStorage, getSessionStorage, setSessionStorage } from '@/lib/local-storage'; import { ShellTerminal } from '@/components/task/shell-terminal'; import { PassthroughTerminal } from '@/components/task/passthrough-terminal'; import { useAppStore } from '@/components/state-provider'; import { useLayoutStore } from '@/lib/state/layout-store'; import { useDefaultLayout } from '@/lib/layout/use-default-layout'; import { SessionTabs, type SessionTab } from '@/components/session-tabs'; import { useRepositoryScripts } from '@/hooks/domains/workspace/use-repository-scripts'; import { useTerminals } from '@/hooks/domains/session/use-terminals'; import type { RepositoryScript } from '@/lib/types/http'; import type { Terminal } from '@/hooks/domains/session/use-terminals'; type TaskRightPanelProps = { topPanel: ReactNode; sessionId?: string | null; repositoryId?: string | null; initialScripts?: RepositoryScript[]; initialTerminals?: Terminal[]; }; const DEFAULT_RIGHT_LAYOUT: Record = { top: 55, bottom: 45 }; const TaskRightPanel = memo(function TaskRightPanel({ topPanel, sessionId = null, repositoryId = null, initialScripts = [], initialTerminals }: TaskRightPanelProps) { const rightPanelIds = ['top', 'bottom']; const rightLayoutKey = 'task-layout-right-v2'; const { defaultLayout: rightLayout, onLayoutChanged: onRightLayoutChange } = useDefaultLayout({ id: rightLayoutKey, panelIds: rightPanelIds, baseLayout: DEFAULT_RIGHT_LAYOUT, }); const [isBottomCollapsed, setIsBottomCollapsed] = useState(() => getLocalStorage('task-right-panel-collapsed', false) ); const setRightPanelActiveTab = useAppStore((state) => state.setRightPanelActiveTab); const closeLayoutPreview = useLayoutStore((state) => state.closePreview); // Use the terminals hook const { terminals, activeTab, terminalTabValue, addTerminal, handleCloseDevTab: baseHandleCloseDevTab, handleCloseTab, handleRunCommand, isStoppingDev, devProcessId, devOutput, } = useTerminals({ sessionId, initialTerminals }); // Wrap handleCloseDevTab to also close the layout preview const handleCloseDevTab = useCallback(async (event: MouseEvent) => { await baseHandleCloseDevTab(event); if (sessionId) { closeLayoutPreview(sessionId); } }, [baseHandleCloseDevTab, sessionId, closeLayoutPreview]); // Repository scripts - use initialScripts as fallback for SSR const { scripts: storeScripts, isLoaded: scriptsLoaded } = useRepositoryScripts(repositoryId); const scripts = scriptsLoaded ? storeScripts : (initialScripts.length > 0 ? initialScripts : storeScripts); const hasScripts = scripts.length > 0; // Restore 'commands' tab from sessionStorage (needs hasScripts check) useEffect(() => { if (!sessionId || !hasScripts) return; const savedTab = getSessionStorage(`rightPanel-tab-${sessionId}`, null); if (savedTab === 'commands') { setRightPanelActiveTab(sessionId, savedTab); } }, [sessionId, hasScripts, setRightPanelActiveTab]); // Persist active tab to sessionStorage when it changes useEffect(() => { if (!sessionId || !activeTab) return; setSessionStorage(`rightPanel-tab-${sessionId}`, activeTab); }, [sessionId, activeTab]); // Save collapse state to local storage useEffect(() => { setLocalStorage('task-right-panel-collapsed', isBottomCollapsed); }, [isBottomCollapsed]); const tabs: SessionTab[] = useMemo(() => { const allTabs: SessionTab[] = []; // Only show Commands tab if scripts exist (from SSR or loaded) if (hasScripts) { allTabs.push({ id: 'commands', label: 'Commands' }); } // Add terminal tabs (dev-server, shell, script) // Use closable from backend (source of truth) for shell terminals const terminalTabs: SessionTab[] = terminals.map((terminal) => { return { id: terminal.id, label: terminal.label, className: terminal.closable ? 'group flex items-center cursor-pointer' : 'cursor-pointer', closable: terminal.closable, onClose: terminal.type === 'dev-server' ? handleCloseDevTab : (e: MouseEvent) => handleCloseTab(e, terminal.id), }; }); return [...allTabs, ...terminalTabs]; }, [hasScripts, terminals, handleCloseDevTab, handleCloseTab]); if (isBottomCollapsed) { return (
{topPanel}
{ if (sessionId) { setRightPanelActiveTab(sessionId, value); } }} showAddButton onAddTab={addTerminal} collapsible isCollapsed={isBottomCollapsed} onToggleCollapse={() => setIsBottomCollapsed(false)} className="flex-1 min-h-0" />
); } return ( {topPanel} { if (sessionId) { setRightPanelActiveTab(sessionId, value); } }} showAddButton onAddTab={addTerminal} collapsible isCollapsed={isBottomCollapsed} onToggleCollapse={() => setIsBottomCollapsed(true)} className="flex-1 min-h-0" >
{scripts.map((script) => ( ))}
{terminals.map((terminal) => ( {terminal.type === 'dev-server' ? ( ) : ( )} ))}
); }); export { TaskRightPanel };