/** * 底部 panel:左侧垂直 tab 切换四个内容区。 * * - tab 1 「工单」:现有 IssueDetailPanel(基于 PropertyGrid) * - tab 2 「Profile」:工作区 KV 表 ProfileGrid * - tab 3 「会话」:ManagedSessionsPanel * - tab 4 「改动」:PrFilesPanel * * 四个内容始终挂载,非当前用 hidden 隐藏,避免切 tab 卸载导致阅读态(选中文件、 * 树滚动/折叠、diff scroll)丢失。tab 状态用 useState,无持久化;激活态加左侧 2px 高亮条。 */ import type { ClaudeProfile } from '../hooks/useIssues' import type { ProfilesData, SessionHandoffPendingItem } from '../lib/messages' import type { Issue, ManagedSessionsData } from '../types' import { ClipboardList, FileDiff, Layers, MessagesSquare } from 'lucide-react' import { useState } from 'react' import { IssueDetailPanel } from './IssueDetailPanel' import { ManagedSessionsPanel } from './ManagedSessionsPanel' import { PrFilesPanel } from './PrFilesPanel' import { ProfileGrid } from './ProfileGrid' type TabKey = 'issue' | 'profile' | 'sessions' | 'changes' interface BottomTabsProps { // Issue tab props (pass-through to IssueDetailPanel) issue: Issue | null allIssues: Issue[] globalAutoReview: boolean onOpenInBrowser: (url: string) => void onResumeSession: (sessionId: string, kind: 'implement' | 'brainstorm', profilePath?: string, cwd?: string, issueNumber?: number) => void onResumeReviewSession: (sessionId: string, issueNumber: number, cwd?: string) => void onStartTestSession: (issueNumber: number) => void onResumeTestSession: (sessionId: string, issueNumber: number, cwd?: string) => void onOpenFile: (path: string) => void onImplement: (issueNumber: number, planFile: string, profilePath?: string, sessionId?: string) => void onOpenPr: (pr: string) => void onGeneratePrDiffSummary: (issueNumber: number) => void isPrDiffSummaryRunning: (issueNumber: number) => boolean onOpenWorktree: (path: string) => void onDeleteWorktree: (issueNumber: number, path: string) => void onMergeBranch: (issueNumber: number, branch: string) => void onDeleteIssue?: (issueNumber: number) => void onCloseIssue: (issueNumber: number) => void onResetToTodo: (issueNumber: number) => void onCloseSessionTab: (issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test') => void onContinueBrainstorm: (issueNumber: number) => void onUpdateAutoReview: (issueNumber: number, value: boolean) => void onUpdateProfilePath: (issueNumber: number, profilePath: string) => void onUpdateBrainstormProfilePath: (issueNumber: number, brainstormProfilePath: string) => void onUpdateTestProfilePath: (issueNumber: number, testProfilePath: string) => void onOpenLogs: () => void me?: string /** Gitea login → 头像 data URI。 */ avatars?: Record onStartHandoff: (issueNumber: number) => void onAcceptHandoff: (issueNumber: number) => void isHandoffRunning: (issueNumber: number) => boolean // Profile tab props profileData: ProfilesData onProfileSave: (next: ProfilesData) => void onProfileOpen: (value: string) => void // 会话管理 tab props managedSessions: ManagedSessionsData profiles: ClaudeProfile[] onManagedSessionCreate: (profilePath: string, name?: string, prompt?: string) => void onManagedSessionRename: (id: string, name: string) => void onManagedSessionResume: (id: string, profilePath?: string) => void onManagedSessionDelete: (id: string) => void onManagedSessionCloseTab: (id: string) => void importableSessions: Array<{ id: string, name: string, mtimeMs: number }> importableLoading: boolean onListImportableSessions: () => void onImportNamedSession: (sessionId: string, name: string) => void pendingHandoffs: SessionHandoffPendingItem[] onSessionHandoff: (sessionId: string) => void onAcceptSessionHandoff: (attachmentId: number) => void } export function BottomTabs(props: BottomTabsProps) { const [tab, setTab] = useState('issue') return (
{/* 左侧垂直 tab 栏 */}
setTab('issue')} icon={} title="工单" /> setTab('profile')} icon={} title="Profile" /> setTab('sessions')} icon={} title="会话" badge={props.pendingHandoffs.length} /> setTab('changes')} icon={} title="改动" />
{/* 右侧内容:始终挂载,非当前 hidden,保留阅读态 */}
) } interface TabButtonProps { active: boolean onClick: () => void icon: React.ReactNode title: string badge?: number } function TabButton({ active, onClick, icon, title, badge }: TabButtonProps) { return ( ) }