"use client"; import { memo, useMemo } from "react"; import type { ComponentType } from "react"; import { IconCircleCheck, IconCircleDashed, IconProgress } from "@tabler/icons-react"; import type { TaskState, TaskSessionState } from "@/lib/types/http"; import { truncateRepoPath } from "@/lib/utils"; import { TaskItem } from "./task-item"; const SECTION_ICONS: Record< string, { Icon: ComponentType<{ className?: string }>; className: string } > = { Review: { Icon: IconCircleCheck, className: "text-green-500" }, "In Progress": { Icon: IconProgress, className: "text-yellow-500" }, Backlog: { Icon: IconCircleDashed, className: "text-muted-foreground" }, }; type DiffStats = { additions: number; deletions: number; }; type TaskSwitcherItem = { id: string; title: string; state?: TaskState; sessionState?: TaskSessionState; description?: string; workflowStepId?: string; repositoryPath?: string; diffStats?: DiffStats; isRemoteExecutor?: boolean; remoteExecutorType?: string; remoteExecutorName?: string; updatedAt?: string; isArchived?: boolean; primarySessionId?: string | null; }; type TaskSwitcherProps = { tasks: TaskSwitcherItem[]; steps: Array<{ id: string; title: string; color?: string }>; activeTaskId: string | null; selectedTaskId: string | null; onSelectTask: (taskId: string) => void; onRenameTask?: (taskId: string, currentTitle: string) => void; onArchiveTask?: (taskId: string) => void; onDeleteTask?: (taskId: string) => void; deletingTaskId?: string | null; isLoading?: boolean; }; type Section = { label: string; tasks: TaskSwitcherItem[]; }; const REVIEW_STATES = new Set([ "WAITING_FOR_INPUT", "COMPLETED", "FAILED", "CANCELLED", ]); const IN_PROGRESS_STATES = new Set(["RUNNING"]); const BACKLOG_STATES = new Set(["CREATED", "STARTING"]); function classifyTask( sessionState: TaskSessionState | undefined, ): "review" | "in_progress" | "backlog" { if (!sessionState) return "backlog"; if (REVIEW_STATES.has(sessionState)) return "review"; if (IN_PROGRESS_STATES.has(sessionState)) return "in_progress"; if (BACKLOG_STATES.has(sessionState)) return "backlog"; return "backlog"; } function TaskSwitcherSkeleton() { return (
); } function SectionHeader({ label, count }: { label: string; count: number }) { const icon = SECTION_ICONS[label]; return (
{icon && } {label} {count}
); } function TaskSwitcherSection({ section, stepNameById, activeTaskId, selectedTaskId, onSelectTask, onRenameTask, onArchiveTask, onDeleteTask, deletingTaskId, }: { section: Section; stepNameById: Map; activeTaskId: string | null; selectedTaskId: string | null; onSelectTask: (taskId: string) => void; onRenameTask?: (taskId: string, currentTitle: string) => void; onArchiveTask?: (taskId: string) => void; onDeleteTask?: (taskId: string) => void; deletingTaskId?: string | null; }) { if (section.tasks.length === 0) return null; return (
{section.tasks.map((task) => { const isActive = task.id === activeTaskId; const isSelected = task.id === selectedTaskId || isActive; const repoLabel = task.repositoryPath ? truncateRepoPath(task.repositoryPath) : undefined; const stepName = task.workflowStepId ? stepNameById.get(task.workflowStepId) : undefined; return ( onSelectTask(task.id)} onRename={onRenameTask ? () => onRenameTask(task.id, task.title) : undefined} onArchive={onArchiveTask ? () => onArchiveTask(task.id) : undefined} onDelete={onDeleteTask ? () => onDeleteTask(task.id) : undefined} isDeleting={deletingTaskId === task.id} /> ); })}
); } export const TaskSwitcher = memo(function TaskSwitcher({ tasks, steps, activeTaskId, selectedTaskId, onSelectTask, onRenameTask, onArchiveTask, onDeleteTask, deletingTaskId, isLoading = false, }: TaskSwitcherProps) { const stepNameById = useMemo(() => { const map = new Map(); for (const col of steps) { map.set(col.id, col.title); } return map; }, [steps]); const sections = useMemo(() => { const review: TaskSwitcherItem[] = []; const inProgress: TaskSwitcherItem[] = []; const backlog: TaskSwitcherItem[] = []; for (const task of tasks) { const bucket = classifyTask(task.sessionState); if (bucket === "review") review.push(task); else if (bucket === "in_progress") inProgress.push(task); else backlog.push(task); } return [ { label: "Review", tasks: review }, { label: "In Progress", tasks: inProgress }, { label: "Backlog", tasks: backlog }, ] satisfies Section[]; }, [tasks]); if (isLoading) { return ; } if (tasks.length === 0) { return
No tasks yet.
; } return (
{sections.map((section) => ( ))}
); });