'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}
/>
);
})}
);
})}
)}
);
});