import React, { useEffect, useState, useMemo } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { DndContext, DragOverlay, DragStartEvent, DragEndEvent, DragOverEvent, closestCorners, KeyboardSensor, PointerSensor, useSensor, useSensors, } from '@dnd-kit/core'; import { horizontalListSortingStrategy, SortableContext, sortableKeyboardCoordinates, } from '@dnd-kit/sortable'; import { Card, Spin, Empty, Flex } from 'antd'; import { RootState } from '@/app/store'; import { IGroupBy, setGroup, fetchTaskGroups, reorderTasks, } from '@/features/tasks/tasks.slice'; import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; import { AppDispatch } from '@/app/store'; import BoardSectionCard from '@/pages/projects/projectView/board/board-section/board-section-card/board-section-card'; import BoardCreateSectionCard from '@/pages/projects/projectView/board/board-section/board-section-card/board-create-section-card'; import { useAuthService } from '@/hooks/useAuth'; import useIsProjectManager from '@/hooks/useIsProjectManager'; import BoardViewTaskCard from '@/pages/projects/projectView/board/board-section/board-task-card/board-view-task-card'; import TaskGroup from '../task-management/TaskGroup'; import TaskRow from '../task-management/TaskRow'; import KanbanGroup from './kanbanGroup'; import KanbanTaskCard from './kanbanTaskCard'; import SortableKanbanGroup from './SortableKanbanGroup'; // Import the TaskListFilters component const TaskListFilters = React.lazy(() => import('@/pages/projects/projectView/taskList/task-list-filters/task-list-filters')); interface TaskListBoardProps { projectId: string; className?: string; } interface DragState { activeTask: IProjectTask | null; activeGroupId: string | null; } const KanbanTaskListBoard: React.FC = ({ projectId, className = '' }) => { const dispatch = useDispatch(); const [dragState, setDragState] = useState({ activeTask: null, activeGroupId: null, }); // New state for active/over ids const [activeTaskId, setActiveTaskId] = useState(null); const [overId, setOverId] = useState(null); // Redux selectors const { taskGroups, groupBy, loadingGroups, error, search, archived } = useSelector((state: RootState) => state.boardReducer); // Selection state const [selectedTaskIds, setSelectedTaskIds] = useState([]); // Drag and Drop sensors const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 8, }, }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }) ); const isOwnerorAdmin = useAuthService().isOwnerOrAdmin(); const isProjectManager = useIsProjectManager(); // Fetch task groups when component mounts or dependencies change useEffect(() => { if (projectId) { dispatch(fetchTaskGroups(projectId)); } }, [dispatch, projectId, groupBy, search, archived]); // Memoized calculations const allTaskIds = useMemo(() => { return taskGroups.flatMap(group => group.tasks.map(task => task.id!)); }, [taskGroups]); const totalTasksCount = useMemo(() => { return taskGroups.reduce((sum, group) => sum + group.tasks.length, 0); }, [taskGroups]); const hasSelection = selectedTaskIds.length > 0; // // Handlers // const handleGroupingChange = (newGroupBy: IGroupBy) => { // dispatch(setGroup(newGroupBy)); // }; const handleDragStart = (event: DragStartEvent) => { const { active } = event; const taskId = active.id as string; setActiveTaskId(taskId); setOverId(null); // Find the task and its group let activeTask: IProjectTask | null = null; let activeGroupId: string | null = null; for (const group of taskGroups) { const task = group.tasks.find(t => t.id === taskId); if (task) { activeTask = task; activeGroupId = group.id; break; } } setDragState({ activeTask, activeGroupId, }); }; const handleDragOver = (event: DragOverEvent) => { setOverId(event.over?.id as string || null); }; const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event; setActiveTaskId(null); setOverId(null); setDragState({ activeTask: null, activeGroupId: null, }); if (!over || !dragState.activeTask || !dragState.activeGroupId) { return; } const activeTaskId = active.id as string; const overIdVal = over.id as string; // Find the group and index for drop let targetGroupId = overIdVal; let targetIndex = -1; let isOverTask = false; // Check if over is a group or a task const overGroup = taskGroups.find(g => g.id === overIdVal); if (!overGroup) { // Dropping on a task, find which group it belongs to for (const group of taskGroups) { const taskIndex = group.tasks.findIndex(t => t.id === overIdVal); if (taskIndex !== -1) { targetGroupId = group.id; targetIndex = taskIndex; isOverTask = true; break; } } } const sourceGroup = taskGroups.find(g => g.id === dragState.activeGroupId); const targetGroup = taskGroups.find(g => g.id === targetGroupId); if (!sourceGroup || !targetGroup) return; const sourceIndex = sourceGroup.tasks.findIndex(t => t.id === activeTaskId); if (sourceIndex === -1) return; // Calculate new positions let finalTargetIndex = targetIndex; if (!isOverTask || finalTargetIndex === -1) { finalTargetIndex = targetGroup.tasks.length; } // If moving within the same group and after itself, adjust index if (sourceGroup.id === targetGroup.id && sourceIndex < finalTargetIndex) { finalTargetIndex--; } // Create updated task arrays const updatedSourceTasks = [...sourceGroup.tasks]; const [movedTask] = updatedSourceTasks.splice(sourceIndex, 1); let updatedTargetTasks: IProjectTask[]; if (sourceGroup.id === targetGroup.id) { updatedTargetTasks = updatedSourceTasks; updatedTargetTasks.splice(finalTargetIndex, 0, movedTask); } else { updatedTargetTasks = [...targetGroup.tasks]; updatedTargetTasks.splice(finalTargetIndex, 0, movedTask); } // Dispatch the reorder action dispatch(reorderTasks({ activeGroupId: sourceGroup.id, overGroupId: targetGroup.id, fromIndex: sourceIndex, toIndex: finalTargetIndex, task: movedTask, updatedSourceTasks, updatedTargetTasks, })); }; const handleSelectTask = (taskId: string, selected: boolean) => { setSelectedTaskIds(prev => { if (selected) { return [...prev, taskId]; } else { return prev.filter(id => id !== taskId); } }); }; const handleToggleSubtasks = (taskId: string) => { // Implementation for toggling subtasks console.log('Toggle subtasks for task:', taskId); }; if (error) { return ( ); } return (
{/* Task Filters */} Loading filters...
}> {/* Task Groups Container */}
{loadingGroups ? (
) : taskGroups.length === 0 ? ( ) : ( g.id)} strategy={horizontalListSortingStrategy} >
{taskGroups.map((group) => ( ))}
{dragState.activeTask ? ( ) : null}
)}
); }; export default KanbanTaskListBoard;