| 53 | function DraggableTaskCard({ |
| 54 | task, |
| 55 | status, |
| 56 | isSelected, |
| 57 | project, |
| 58 | onSelectTask, |
| 59 | onTaskStatusChange, |
| 60 | onDeleteTask, |
| 61 | disableDrag, |
| 62 | }: { |
| 63 | task: UITask |
| 64 | status: UITaskStatus |
| 65 | isSelected: boolean |
| 66 | project: UIProject | undefined |
| 67 | onSelectTask: (id: string) => void |
| 68 | onTaskStatusChange?: (taskId: string, newStatus: UITaskStatus) => void |
| 69 | onDeleteTask?: (taskId: string) => void |
| 70 | disableDrag?: boolean |
| 71 | }) { |
| 72 | const { t } = useI18n() |
| 73 | const flip = useContext(FlipContext) |
| 74 | const cardRef = useRef<HTMLButtonElement | null>(null) |
| 75 | const isTaskReadOnly = Boolean(task.projectArchivedAt) |
| 76 | const dragDisabled = isTaskReadOnly || !!disableDrag |
| 77 | const { attributes, listeners, setNodeRef: setDragRef, isDragging } = useDraggable({ |
| 78 | id: task.id, |
| 79 | data: { task, fromStatus: status }, |
| 80 | disabled: dragDisabled, |
| 81 | }) |
| 82 | |
| 83 | const mergedRef = useCallback((node: HTMLButtonElement | null) => { |
| 84 | cardRef.current = node |
| 85 | setDragRef(node) |
| 86 | if (flip) { |
| 87 | if (node) flip.registry.set(task.id, node) |
| 88 | else flip.registry.delete(task.id) |
| 89 | } |
| 90 | }, [setDragRef, flip, task.id]) |
| 91 | |
| 92 | useEffect(() => { |
| 93 | return () => { flip?.registry.delete(task.id) } |
| 94 | }, [task.id, flip]) |
| 95 | |
| 96 | const [contextMenu, setContextMenu] = useState<{ x: number; y: number } | null>(null) |
| 97 | const [isDeleteConfirmOpen, setIsDeleteConfirmOpen] = useState(false) |
| 98 | const menuRef = useRef<HTMLDivElement>(null) |
| 99 | const longPressTimer = useRef<ReturnType<typeof setTimeout> | null>(null) |
| 100 | |
| 101 | const handleContextMenu = useCallback((e: React.MouseEvent) => { |
| 102 | if (isTaskReadOnly) return |
| 103 | if (!onTaskStatusChange && !onDeleteTask) return |
| 104 | e.preventDefault() |
| 105 | setContextMenu({ x: e.clientX, y: e.clientY }) |
| 106 | }, [isTaskReadOnly, onTaskStatusChange, onDeleteTask]) |
| 107 | |
| 108 | const clearLongPress = useCallback(() => { |
| 109 | if (longPressTimer.current) { |
| 110 | clearTimeout(longPressTimer.current) |
| 111 | longPressTimer.current = null |
| 112 | } |