From 7ae8efe0960354776e2271569db90cba1e31e854 Mon Sep 17 00:00:00 2001 From: bot-hermes Date: Wed, 29 Jul 2026 19:24:23 +0000 Subject: [PATCH] feat(kanban): dependency visual on cards with Link2 icon and tooltip --- .../components/tasks/tasks-kanban-view.tsx | 49 ++++++++++++++----- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/apps/web/components/tasks/tasks-kanban-view.tsx b/apps/web/components/tasks/tasks-kanban-view.tsx index cf3cfdf..f982d8e 100644 --- a/apps/web/components/tasks/tasks-kanban-view.tsx +++ b/apps/web/components/tasks/tasks-kanban-view.tsx @@ -149,8 +149,8 @@ function DroppableColumn({ color, tasks, domainMap, + depCounts, onTaskClick, - onAddTask, inlineInput, onInlineInputChange, onInlineSubmit, @@ -161,8 +161,8 @@ function DroppableColumn({ color: string; tasks: Task[]; domainMap: Map; + depCounts: Map; onTaskClick: (task: Task) => void; - onAddTask: () => void; inlineInput: string; onInlineInputChange: (value: string) => void; onInlineSubmit: () => void; @@ -176,15 +176,6 @@ function DroppableColumn({

{title}

({tasks.length}) -
onTaskClick(task)} /> )) @@ -248,6 +239,7 @@ export function TasksKanbanView({ const [selectedTask, setSelectedTask] = useState(null); const [inlineInputs, setInlineInputs] = useState>({}); const [inlineLoading, setInlineLoading] = useState>({}); + const [depCounts, setDepCounts] = useState>(new Map()); const { subscribe } = useRealtimeContext(); const sensors = useSensors( @@ -303,6 +295,37 @@ export function TasksKanbanView({ return unsubscribe; }, [domainId, subscribe, fetchTasks, onRefresh]); + // Fetch dependency counts for visible tasks + useEffect(() => { + if (tasks.length === 0 || !domainId) return; + const controller = new AbortController(); + let cancelled = false; + + (async () => { + const counts = new Map(); + // Fetch task details in parallel to get dependency counts + const results = await Promise.allSettled( + tasks.map((t) => + fetch(`/api/domains/${domainId}/tasks/${t.id}`, { signal: controller.signal }) + .then((r) => (r.ok ? r.json() : null)) + ) + ); + if (cancelled) return; + for (let i = 0; i < results.length; i++) { + const r = results[i]; + if (r.status === 'fulfilled' && r.value?.dependencies?.length > 0) { + counts.set(tasks[i].id, r.value.dependencies.length); + } + } + setDepCounts(counts); + })(); + + return () => { + cancelled = true; + controller.abort(); + }; + }, [tasks, domainId]); + async function handleDragEnd(event: DragEndEvent) { const { active, over } = event; if (!over) return; @@ -375,8 +398,8 @@ export function TasksKanbanView({ color={column.color} tasks={tasks.filter((t) => t.status === column.id)} domainMap={domainMap} + depCounts={depCounts} onTaskClick={setSelectedTask} - onAddTask={() => {}} inlineInput={inlineInputs[column.id] || ''} onInlineInputChange={(value) => setInlineInputs((prev) => ({ ...prev, [column.id]: value }))