From b96a2858037242ad52ae52c062b78eaf6dfc9ef5 Mon Sep 17 00:00:00 2001 From: Madhura68 Date: Thu, 30 Apr 2026 17:20:43 +0200 Subject: [PATCH] feat(backlog): add TaskPanel with sortable rows and TaskDialog wiring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reads selectedStoryId + tasksByStory from stores. DnD reorder via reorderTasksAction. Row click → ?editTask, + button → ?newTask&storyId. DemoTooltip on drag handles and + button. Co-Authored-By: Claude Sonnet 4.6 --- components/backlog/task-panel.tsx | 196 ++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 components/backlog/task-panel.tsx diff --git a/components/backlog/task-panel.tsx b/components/backlog/task-panel.tsx new file mode 100644 index 0000000..1453ea4 --- /dev/null +++ b/components/backlog/task-panel.tsx @@ -0,0 +1,196 @@ +'use client' + +import { useRouter } from 'next/navigation' +import { useTransition } from 'react' +import { + DndContext, DragEndEvent, + KeyboardSensor, PointerSensor, useSensor, useSensors, closestCenter, +} from '@dnd-kit/core' +import { + SortableContext, sortableKeyboardCoordinates, + useSortable, verticalListSortingStrategy, +} from '@dnd-kit/sortable' +import { CSS } from '@dnd-kit/utilities' +import { GripVertical, Plus } from 'lucide-react' +import { toast } from 'sonner' +import { useSelectionStore } from '@/stores/selection-store' +import { useBacklogStore, type BacklogTask } from '@/stores/backlog-store' +import { reorderTasksAction } from '@/actions/tasks' +import { Button } from '@/components/ui/button' +import { DemoTooltip } from '@/components/shared/demo-tooltip' +import { EmptyPanel } from './empty-panel' +import { PRIORITY_BORDER } from './backlog-card' +import { cn } from '@/lib/utils' + +const STATUS_COLORS: Record = { + TO_DO: 'bg-status-todo/15 text-status-todo border-status-todo/30', + IN_PROGRESS: 'bg-status-in-progress/15 text-status-in-progress border-status-in-progress/30', + REVIEW: 'bg-status-review/15 text-status-review border-status-review/30', + DONE: 'bg-status-done/15 text-status-done border-status-done/30', +} +const STATUS_LABELS: Record = { + TO_DO: 'To Do', + IN_PROGRESS: 'Bezig', + REVIEW: 'Review', + DONE: 'Klaar', +} + +function SortableTaskRow({ + task, + isDemo, + onClick, +}: { + task: BacklogTask + isDemo: boolean + onClick: () => void +}) { + const { attributes, listeners, setNodeRef, transform, transition, isDragging } = + useSortable({ id: task.id }) + + return ( +
+ + + + + {task.title} + + + {STATUS_LABELS[task.status] ?? task.status} + +
+ ) +} + +interface TaskPanelProps { + productId: string + isDemo: boolean + closePath: string +} + +export function TaskPanel({ productId, isDemo, closePath }: TaskPanelProps) { + const router = useRouter() + const [, startTransition] = useTransition() + const selectedStoryId = useSelectionStore((s) => s.selectedStoryId) + const tasksByStory = useBacklogStore((s) => s.tasksByStory) + + const tasks = selectedStoryId ? (tasksByStory[selectedStoryId] ?? []) : null + + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }), + ) + + function handleDragEnd(event: DragEndEvent) { + if (!selectedStoryId) return + const { active, over } = event + if (!over || active.id === over.id) return + + const ids = tasks!.map((t) => t.id) + const from = ids.indexOf(active.id as string) + const to = ids.indexOf(over.id as string) + if (from === -1 || to === -1) return + + const reordered = [...ids] + reordered.splice(from, 1) + reordered.splice(to, 0, active.id as string) + + startTransition(async () => { + const result = await reorderTasksAction(selectedStoryId, reordered) + if (result?.error) toast.error(result.error) + }) + } + + const header = ( +
+

Taken

+ + + +
+ ) + + if (tasks === null) { + return ( +
+ {header} + +
+ ) + } + + if (tasks.length === 0) { + return ( +
+ {header} + router.push(`${closePath}?newTask=1&storyId=${selectedStoryId}`), + disabled: isDemo, + }} + /> +
+ ) + } + + return ( +
+ {header} +
+ + t.id)} strategy={verticalListSortingStrategy}> + {tasks.map((task) => ( + router.push(`${closePath}?editTask=${task.id}`)} + /> + ))} + + +
+
+ ) +}