Files
kandev/apps/web/components/kanban-column.tsx
T
2026-02-16 13:48:58 +00:00

95 lines
3.2 KiB
TypeScript

'use client';
import { useMemo } from 'react';
import { useDroppable } from '@dnd-kit/core';
import { KanbanCard, Task } from './kanban-card';
import { Badge } from '@kandev/ui/badge';
import { cn, getRepositoryDisplayName } from '@/lib/utils';
import { useAppStore } from '@/components/state-provider';
import type { Repository } from '@/lib/types/http';
export interface WorkflowStep {
id: string;
title: string;
color: string;
events?: { on_enter?: Array<{ type: string; config?: Record<string, unknown> }>; on_turn_complete?: Array<{ type: string; config?: Record<string, unknown> }> };
}
interface KanbanColumnProps {
step: WorkflowStep;
tasks: Task[];
onPreviewTask: (task: Task) => void;
onOpenTask: (task: Task) => void;
onEditTask: (task: Task) => void;
onDeleteTask: (task: Task) => void;
onMoveTask?: (task: Task, targetStepId: string) => void;
steps?: WorkflowStep[];
showMaximizeButton?: boolean;
deletingTaskId?: string | null;
hideHeader?: boolean;
}
export function KanbanColumn({ step, tasks, onPreviewTask, onOpenTask, onEditTask, onDeleteTask, onMoveTask, steps, showMaximizeButton, deletingTaskId, hideHeader = false }: KanbanColumnProps) {
const { setNodeRef, isOver } = useDroppable({
id: step.id,
});
// Access repositories from store to pass repository names to cards
const repositoriesByWorkspace = useAppStore((state) => state.repositories.itemsByWorkspaceId);
const repositories = useMemo(
() => Object.values(repositoriesByWorkspace).flat() as Repository[],
[repositoriesByWorkspace]
);
// Helper function to get repository name for a task
const getRepositoryName = (repositoryId?: string): string | null => {
if (!repositoryId) return null;
const repository = repositories.find((repo) => repo.id === repositoryId);
return repository ? getRepositoryDisplayName(repository.local_path) : null;
};
return (
<div
ref={setNodeRef}
className={cn(
'flex flex-col flex-1 h-full min-w-0 px-3 py-2 sm:min-h-[200px]',
'border-r border-dashed border-border/50 last:border-r-0',
isOver && 'bg-primary/5'
)}
>
{/* Column Header */}
{!hideHeader && (
<div className="flex items-center justify-between pb-2 mb-3 px-1">
<div className="flex items-center gap-2">
<div className={cn('w-2 h-2 rounded-full', step.color)} />
<h2 className="font-semibold text-sm">{step.title}</h2>
<Badge variant="secondary" className="text-xs">
{tasks.length}
</Badge>
</div>
</div>
)}
{/* Tasks */}
<div className="flex-1 min-h-0 overflow-y-auto overflow-x-hidden space-y-2 px-1">
{tasks.map((task) => (
<KanbanCard
key={task.id}
task={task}
repositoryName={getRepositoryName(task.repositoryId)}
onClick={onPreviewTask}
onOpenFullPage={onOpenTask}
onEdit={onEditTask}
onDelete={onDeleteTask}
onMove={onMoveTask}
steps={steps}
showMaximizeButton={showMaximizeButton}
isDeleting={deletingTaskId === task.id}
/>
))
}
</div>
</div>
);
}