|
1 | 1 | "use client"; |
2 | 2 |
|
3 | | -import React, { useCallback, useEffect, useState, useMemo } from "react"; |
| 3 | +import React, { useCallback, useEffect, useState, useMemo, useRef } from "react"; |
4 | 4 | import { |
5 | 5 | DragDropContext, |
6 | 6 | Droppable, |
@@ -48,6 +48,7 @@ import { useValidateTransition, TransitionValidationResult } from "@/features/wo |
48 | 48 | import { useGetCustomColumns } from "../api/use-get-custom-columns"; |
49 | 49 | import { useDefaultColumns } from "../hooks/use-default-columns"; |
50 | 50 | import { CustomColumnHeader } from "./custom-column-header"; |
| 51 | +import { useKanbanAutoScroll } from "@/hooks/use-kanban-auto-scroll"; |
51 | 52 | import { CustomColumn } from "../types"; |
52 | 53 | import { useUpdateColumnOrder } from "@/features/default-column-settings/api/use-update-column-order"; |
53 | 54 |
|
@@ -117,6 +118,7 @@ export const EnhancedDataKanban = ({ |
117 | 118 |
|
118 | 119 |
|
119 | 120 | useCreateTaskModal(); |
| 121 | + const { scrollRef, handleDragStart, handleDragEnd } = useKanbanAutoScroll(); |
120 | 122 | const { getEnabledColumns } = useDefaultColumns(workspaceId, projectId); |
121 | 123 | const { mutate: updateColumnOrder } = useUpdateColumnOrder(); |
122 | 124 |
|
@@ -169,6 +171,9 @@ export const EnhancedDataKanban = ({ |
169 | 171 |
|
170 | 172 | const [tasks, setTasks] = useState<TasksState>({}); |
171 | 173 | const [orderedColumns, setOrderedColumns] = useState<ColumnData[]>([]); |
| 174 | + // Track current orderedColumns in a ref so the sync effect never goes stale |
| 175 | + const orderedColumnsRef = useRef(orderedColumns); |
| 176 | + orderedColumnsRef.current = orderedColumns; |
172 | 177 |
|
173 | 178 | const [selectedTasks, setSelectedTasks] = useState<Set<string>>(new Set()); |
174 | 179 | const [selectionMode, setSelectionMode] = useState(false); |
@@ -215,9 +220,33 @@ export const EnhancedDataKanban = ({ |
215 | 220 | }); |
216 | 221 | }; |
217 | 222 |
|
218 | | - // Update ordered columns when allColumns changes |
| 223 | + // Sync orderedColumns from allColumns ONLY when the set of column IDs changes |
| 224 | + // (initial load, or columns added/removed). Ignores position-only changes so |
| 225 | + // that a manual drag-reorder is never snapped back by a subsequent re-render. |
219 | 226 | useEffect(() => { |
220 | | - setOrderedColumns(allColumns); |
| 227 | + const current = orderedColumnsRef.current; |
| 228 | + const currentIds = new Set(current.map((c) => c.id)); |
| 229 | + const newIds = new Set(allColumns.map((c) => c.id)); |
| 230 | + |
| 231 | + const hasAdded = allColumns.some((c) => !currentIds.has(c.id)); |
| 232 | + const hasRemoved = current.some((c) => !newIds.has(c.id)); |
| 233 | + |
| 234 | + // Initial load — set directly |
| 235 | + if (current.length === 0) { |
| 236 | + setOrderedColumns(allColumns); |
| 237 | + return; |
| 238 | + } |
| 239 | + |
| 240 | + // No structural change — preserve the user's manual order |
| 241 | + if (!hasAdded && !hasRemoved) return; |
| 242 | + |
| 243 | + // Columns were added or removed — merge while preserving current order |
| 244 | + const allColumnsMap = new Map(allColumns.map((c) => [c.id, c])); |
| 245 | + const merged = current |
| 246 | + .filter((c) => newIds.has(c.id)) |
| 247 | + .map((c) => allColumnsMap.get(c.id)!) |
| 248 | + .concat(allColumns.filter((c) => !currentIds.has(c.id))); |
| 249 | + setOrderedColumns(merged); |
221 | 250 | }, [allColumns]); |
222 | 251 |
|
223 | 252 | // Update tasks when data changes or columns change |
@@ -601,12 +630,21 @@ export const EnhancedDataKanban = ({ |
601 | 630 | </div> |
602 | 631 | </div> |
603 | 632 |
|
604 | | - <DragDropContext onDragEnd={onDragEnd}> |
| 633 | + <DragDropContext |
| 634 | + onDragEnd={(result) => { |
| 635 | + handleDragEnd(); |
| 636 | + onDragEnd(result); |
| 637 | + }} |
| 638 | + onDragStart={handleDragStart} |
| 639 | + > |
605 | 640 | <Droppable droppableId="columns" direction="horizontal" type="column"> |
606 | 641 | {(provided) => ( |
607 | 642 | <div |
608 | 643 | {...provided.droppableProps} |
609 | | - ref={provided.innerRef} |
| 644 | + ref={(el: HTMLDivElement | null) => { |
| 645 | + scrollRef.current = el; |
| 646 | + provided.innerRef(el); |
| 647 | + }} |
610 | 648 | className="flex overflow-x-scroll gap-4 pb-4 kanban-scrollbar" |
611 | 649 | > |
612 | 650 | {orderedColumns.map((column, index) => { |
|
0 commit comments