// ======================================== // TaskDrawer Component // ======================================== // Right-side task detail drawer with Overview/Flowchart/Files tabs import * as React from 'react'; import { useIntl } from 'react-intl'; import { X, FileText, GitBranch, Folder, CheckCircle, Circle, Loader2, XCircle } from 'lucide-react'; import { Flowchart } from './Flowchart'; import { Badge } from '../ui/Badge'; import { Button } from '../ui/Button'; import { Tabs, TabsList, TabsTrigger, TabsContent } from '../ui/Tabs'; import type { LiteTask, FlowControl } from '@/lib/api'; import type { TaskData } from '@/types/store'; // ========== Types ========== export interface TaskDrawerProps { task: LiteTask | TaskData | null; isOpen: boolean; onClose: () => void; } type TabValue = 'overview' | 'flowchart' | 'files'; // ========== Helper: Unified Task Access ========== /** * Normalize task data to common interface */ function getTaskId(task: LiteTask | TaskData): string { if ('task_id' in task && task.task_id) return task.task_id; if ('id' in task) return task.id; return 'N/A'; } function getTaskTitle(task: LiteTask | TaskData): string { return task.title || 'Untitled Task'; } function getTaskDescription(task: LiteTask | TaskData): string | undefined { return task.description; } function getTaskStatus(task: LiteTask | TaskData): string { return task.status; } function getFlowControl(task: LiteTask | TaskData): FlowControl | undefined { if ('flow_control' in task) return task.flow_control; return undefined; } // Status configuration const taskStatusConfig: Record }> = { pending: { label: 'sessionDetail.taskDrawer.status.pending', variant: 'secondary', icon: Circle, }, in_progress: { label: 'sessionDetail.taskDrawer.status.inProgress', variant: 'warning', icon: Loader2, }, completed: { label: 'sessionDetail.taskDrawer.status.completed', variant: 'success', icon: CheckCircle, }, blocked: { label: 'sessionDetail.taskDrawer.status.blocked', variant: 'destructive', icon: XCircle, }, skipped: { label: 'sessionDetail.taskDrawer.status.skipped', variant: 'default', icon: Circle, }, failed: { label: 'sessionDetail.taskDrawer.status.failed', variant: 'destructive', icon: XCircle, }, }; // ========== Component ========== export function TaskDrawer({ task, isOpen, onClose }: TaskDrawerProps) { const { formatMessage } = useIntl(); const [activeTab, setActiveTab] = React.useState('overview'); // Reset to overview when task changes React.useEffect(() => { if (task) { setActiveTab('overview'); } }, [task]); // ESC key to close React.useEffect(() => { const handleEsc = (e: KeyboardEvent) => { if (e.key === 'Escape' && isOpen) { onClose(); } }; window.addEventListener('keydown', handleEsc); return () => window.removeEventListener('keydown', handleEsc); }, [isOpen, onClose]); if (!task || !isOpen) { return null; } const taskId = getTaskId(task); const taskTitle = getTaskTitle(task); const taskDescription = getTaskDescription(task); const taskStatus = getTaskStatus(task); const flowControl = getFlowControl(task); const statusConfig = taskStatusConfig[taskStatus] || taskStatusConfig.pending; const StatusIcon = statusConfig.icon; const hasFlowchart = !!flowControl?.implementation_approach && flowControl.implementation_approach.length > 0; const hasFiles = !!flowControl?.target_files && flowControl.target_files.length > 0; return ( <> {/* Overlay */}