'use client'; import { memo, useMemo, useState } from 'react'; import type { TaskState } from '@/lib/types/http'; import { truncateRepoPath } from '@/lib/utils'; import { SidebarButton } from './sidebar-button'; import { TaskItem } from './task-item'; type DiffStats = { additions: number; deletions: number; }; type TaskSwitcherItem = { id: string; title: string; state?: TaskState; description?: string; workflowStepId?: string; repositoryPath?: string; diffStats?: DiffStats; updatedAt?: string; }; type TaskSwitcherProps = { tasks: TaskSwitcherItem[]; columns: Array<{ id: string; title: string; color?: string }>; activeTaskId: string | null; selectedTaskId: string | null; onSelectTask: (taskId: string) => void; onDeleteTask?: (taskId: string) => void; deletingTaskId?: string | null; isLoading?: boolean; }; function TaskSwitcherSkeleton() { return (
{/* Column skeleton */}
{/* Another column skeleton */}
); } export const TaskSwitcher = memo(function TaskSwitcher({ tasks, columns, activeTaskId, selectedTaskId, onSelectTask, onDeleteTask, deletingTaskId, isLoading = false, }: TaskSwitcherProps) { const [collapsedColumnIds, setCollapsedColumnIds] = useState>(() => new Set()); const sortedTasks = useMemo(() => { return [...tasks].sort((a, b) => a.title.localeCompare(b.title)); }, [tasks]); const tasksByColumn = useMemo(() => { const grouped: Record = {}; for (const task of sortedTasks) { const key = task.workflowStepId ?? 'unknown'; if (!grouped[key]) grouped[key] = []; grouped[key].push(task); } return grouped; }, [sortedTasks]); const columnsWithFallback = useMemo(() => { const seen = new Set(columns.map((column) => column.id)); const extra: Array<{ id: string; title: string; color?: string }> = []; if (tasksByColumn.unknown?.length) { extra.push({ id: 'unknown', title: 'Other', color: 'bg-neutral-400' }); } return [...columns, ...extra].filter((column) => { if (seen.has(column.id)) return true; seen.add(column.id); return true; }); }, [columns, tasksByColumn]); const handleToggleColumn = (columnId: string) => { setCollapsedColumnIds((prev) => { const next = new Set(prev); if (next.has(columnId)) next.delete(columnId); else next.add(columnId); return next; }); }; if (isLoading) { return ; } return (
{sortedTasks.length === 0 ? (
No tasks on this board.
) : (
{columnsWithFallback.map((column) => { const columnTasks = tasksByColumn[column.id] ?? []; if (columnTasks.length === 0) return null; const isColumnOpen = !collapsedColumnIds.has(column.id); return (
handleToggleColumn(column.id)} /> {/* Expanded content with CSS transition */}
{columnTasks.map((task) => { const isActive = task.id === activeTaskId; const isSelected = task.id === selectedTaskId || isActive; const repoLabel = task.repositoryPath ? truncateRepoPath(task.repositoryPath) : undefined; return ( onSelectTask(task.id)} onDelete={onDeleteTask ? () => onDeleteTask(task.id) : undefined} isDeleting={deletingTaskId === task.id} /> ); })}
); })}
)}
); });