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:
parent
b816cbe710
commit
f68d985c2c
16 changed files with 52 additions and 816 deletions
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue