refactor(dnd): remove drag-and-drop reorder for stories and tasks

- Remove reorderStoriesAction, reorderTasksAction, reorderSprintStoriesAction
- Delete REST route app/api/stories/[id]/tasks/reorder/route.ts
- Remove DnD from backlog story-panel and task-panel (flat list)
- Remove reorder-within-sprint branch from sprint-board-client handleDragEnd
- Switch SortableSprintRow to plain SprintRow using useDraggable (membership drag kept)
- Remove all DnD from task-list (status toggle + edit kept)
- Remove story-order/task-order/sprint-story-order/sprint-task-order mutation types and store handlers
- Remove related tests for deleted reorder route; fix sprint store tests
This commit is contained in:
Scrum4Me Agent 2026-05-14 16:29:56 +02:00
parent b816cbe710
commit f68d985c2c
16 changed files with 52 additions and 816 deletions

View file

@ -1,27 +1,6 @@
'use client'
import { useState, useTransition } from 'react'
import { useRouter } from 'next/navigation'
import {
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
closestCenter,
} from '@dnd-kit/core'
import {
SortableContext,
useSortable,
rectSortingStrategy,
arrayMove,
sortableKeyboardCoordinates,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { toast } from 'sonner'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { PanelNavBar } from '@/components/shared/panel-nav-bar'
@ -33,7 +12,6 @@ import type {
BacklogTask,
TaskDetail,
} from '@/stores/product-workspace/types'
import { reorderTasksAction } from '@/actions/tasks'
import { BacklogCard } from './backlog-card'
import { debugProps } from '@/lib/debug'
import { EmptyPanel } from './empty-panel'
@ -52,32 +30,17 @@ const STATUS_LABELS: Record<string, string> = {
DONE: 'Klaar',
}
function SortableTaskCard({
function TaskCard({
task,
isDemo,
onClick,
}: {
task: BacklogTask | TaskDetail
isDemo: boolean
onClick: () => void
}) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } =
useSortable({ id: task.id })
const style = {
transform: CSS.Transform.toString(transform),
transition,
}
return (
<BacklogCard
ref={setNodeRef}
style={style}
{...attributes}
{...(isDemo ? {} : listeners)}
title={task.title}
priority={task.priority}
isDragging={isDragging}
onClick={onClick}
badge={
<Badge
@ -99,64 +62,17 @@ interface TaskPanelProps {
closePath: string
}
// PBI-74 / T-851: leest tasks voor active story via selectTasksForActiveStory
// (useShallow). DnD via applyOptimisticMutation('task-order'). Detail-view
// (ensureTaskLoaded + isDetail()) zit in de task-dialog, niet in deze lijst.
// PBI-74 / T-851: leest tasks voor active story via selectTasksForActiveStory.
export function TaskPanel({ isDemo, closePath }: TaskPanelProps) {
const router = useRouter()
const [, startTransition] = useTransition()
const selectedStoryId = useProductWorkspaceStore((s) => s.context.activeStoryId)
const rawTasks = useProductWorkspaceStore(useShallow(selectTasksForActiveStory)) as
| (BacklogTask | TaskDetail)[]
const [activeDragId, setActiveDragId] = useState<string | null>(null)
const tasks: (BacklogTask | TaskDetail)[] | null = selectedStoryId
? rawTasks
: null
const sensors = useSensors(
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
)
function handleDragStart(event: DragStartEvent) {
setActiveDragId(event.active.id as string)
}
function handleDragEnd(event: DragEndEvent) {
setActiveDragId(null)
if (!selectedStoryId || !tasks) return
const { active, over } = event
if (!over || active.id === over.id) return
const store = useProductWorkspaceStore.getState()
const prevOrder = [...(store.relations.taskIdsByStory[selectedStoryId] ?? [])]
const oldIndex = prevOrder.indexOf(active.id as string)
const newIndex = prevOrder.indexOf(over.id as string)
if (oldIndex === -1 || newIndex === -1) return
const newOrder = arrayMove([...prevOrder], oldIndex, newIndex)
const orderMutationId = store.applyOptimisticMutation({
kind: 'task-order',
storyId: selectedStoryId,
prevTaskIds: prevOrder,
})
useProductWorkspaceStore.setState((s) => {
s.relations.taskIdsByStory[selectedStoryId] = newOrder
})
startTransition(async () => {
const result = await reorderTasksAction(selectedStoryId, newOrder)
const st = useProductWorkspaceStore.getState()
if (result?.error) {
st.rollbackMutation(orderMutationId)
toast.error(result.error)
} else {
st.settleMutation(orderMutationId)
}
})
}
const navActions = (
<DemoTooltip show={isDemo}>
<Button
@ -201,42 +117,19 @@ export function TaskPanel({ isDemo, closePath }: TaskPanelProps) {
)
}
const activeTask = activeDragId ? tasks.find((t) => t.id === activeDragId) : null
return (
<div className="flex flex-col h-full" {...dp}>
<PanelNavBar title="Taken" actions={navActions} />
<div className="flex-1 overflow-y-auto p-3">
<DndContext
id="task-panel"
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
<SortableContext items={tasks.map((t) => t.id)} strategy={rectSortingStrategy}>
<div className="grid grid-cols-2 gap-2">
{tasks.map((task) => (
<SortableTaskCard
key={task.id}
task={task}
isDemo={isDemo}
onClick={() => router.push(`${closePath}?editTask=${task.id}`)}
/>
))}
</div>
</SortableContext>
<DragOverlay>
{activeTask && (
<BacklogCard
title={activeTask.title}
priority={activeTask.priority}
className="border-primary shadow-xl opacity-90"
/>
)}
</DragOverlay>
</DndContext>
<div className="grid grid-cols-2 gap-2">
{tasks.map((task) => (
<TaskCard
key={task.id}
task={task}
onClick={() => router.push(`${closePath}?editTask=${task.id}`)}
/>
))}
</div>
</div>
</div>
)