diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoard.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoard.tsx index dfee2303..1226cbad 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoard.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoard.tsx @@ -49,6 +49,7 @@ import EnhancedKanbanCreateSection from './EnhancedKanbanCreateSection'; import ImprovedTaskFilters from '../task-management/improved-task-filters'; import { fetchStatusesCategories } from '@/features/taskAttributes/taskStatusSlice'; import { useFilterDataLoader } from '@/hooks/useFilterDataLoader'; +import { useTaskSocketHandlers } from '@/hooks/useTaskSocketHandlers'; // Import the TaskListFilters component const TaskListFilters = React.lazy(() => import('@/pages/projects/projectView/taskList/task-list-filters/task-list-filters')); @@ -75,6 +76,9 @@ const EnhancedKanbanBoard: React.FC = ({ projectId, cl // Load filter data useFilterDataLoader(); + + // Set up socket event handlers for real-time updates + useTaskSocketHandlers(); // Local state for drag overlay const [activeTask, setActiveTask] = useState(null); diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanCreateSubtaskCard.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanCreateSubtaskCard.tsx new file mode 100644 index 00000000..5cd0dabc --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanCreateSubtaskCard.tsx @@ -0,0 +1,173 @@ +import { Flex, Input, InputRef } from 'antd'; +import React, { useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { updateEnhancedKanbanTaskProgress } from '@/features/enhanced-kanban/enhanced-kanban.slice'; +import { themeWiseColor } from '@/utils/themeWiseColor'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import { getCurrentGroup } from '@/features/enhanced-kanban/enhanced-kanban.slice'; +import { useAuthService } from '@/hooks/useAuth'; +import { ITaskCreateRequest } from '@/types/tasks/task-create-request.types'; +import { useParams } from 'react-router-dom'; +import { useSocket } from '@/socket/socketContext'; +import { SocketEvents } from '@/shared/socket-events'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; +import logger from '@/utils/errorLogger'; + +type EnhancedKanbanCreateSubtaskCardProps = { + sectionId: string; + parentTaskId: string; + setShowNewSubtaskCard: (x: boolean) => void; +}; + +const EnhancedKanbanCreateSubtaskCard = ({ + sectionId, + parentTaskId, + setShowNewSubtaskCard, +}: EnhancedKanbanCreateSubtaskCardProps) => { + const { socket, connected } = useSocket(); + const dispatch = useAppDispatch(); + + const [creatingTask, setCreatingTask] = useState(false); + const [newSubtaskName, setNewSubtaskName] = useState(''); + const [isEnterKeyPressed, setIsEnterKeyPressed] = useState(false); + + const cardRef = useRef(null); + const inputRef = useRef(null); + + const { t } = useTranslation('kanban-board'); + + const themeMode = useAppSelector(state => state.themeReducer.mode); + const { projectId } = useParams(); + const currentSession = useAuthService().getCurrentSession(); + + const createRequestBody = (): ITaskCreateRequest | null => { + if (!projectId || !currentSession) return null; + const body: ITaskCreateRequest = { + project_id: projectId, + name: newSubtaskName, + reporter_id: currentSession.id, + team_id: currentSession.team_id, + }; + + const groupBy = getCurrentGroup(); + if (groupBy === 'status') { + body.status_id = sectionId || undefined; + } else if (groupBy === 'priority') { + body.priority_id = sectionId || undefined; + } else if (groupBy === 'phase') { + body.phase_id = sectionId || undefined; + } + + if (parentTaskId) { + body.parent_task_id = parentTaskId; + } + return body; + }; + + const handleAddSubtask = () => { + if (creatingTask || !projectId || !currentSession || newSubtaskName.trim() === '' || !connected) + return; + + try { + setCreatingTask(true); + const body = createRequestBody(); + if (!body) return; + + socket?.emit(SocketEvents.QUICK_TASK.toString(), JSON.stringify(body)); + socket?.once(SocketEvents.QUICK_TASK.toString(), (task: IProjectTask) => { + if (!task) return; + setCreatingTask(false); + setNewSubtaskName(''); + setTimeout(() => { + inputRef.current?.focus(); + }, 0); + if (task.parent_task_id) { + socket?.emit(SocketEvents.GET_TASK_PROGRESS.toString(), task.parent_task_id); + socket?.once(SocketEvents.GET_TASK_PROGRESS.toString(), (data: { + id: string; + complete_ratio: number; + completed_count: number; + total_tasks_count: number; + parent_task: string; + }) => { + if (!data.parent_task) data.parent_task = task.parent_task_id || ''; + dispatch(updateEnhancedKanbanTaskProgress({ + id: task.id || '', + complete_ratio: data.complete_ratio, + completed_count: data.completed_count, + total_tasks_count: data.total_tasks_count, + parent_task: data.parent_task, + })); + }); + } + }); + } catch (error) { + logger.error('Error adding task:', error); + setCreatingTask(false); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + setIsEnterKeyPressed(true); + handleAddSubtask(); + } + }; + + const handleInputBlur = () => { + if (!isEnterKeyPressed && newSubtaskName.length > 0) { + handleAddSubtask(); + } + setIsEnterKeyPressed(false); + }; + + const handleCancelNewCard = (e: React.FocusEvent) => { + if (cardRef.current && !cardRef.current.contains(e.relatedTarget)) { + setNewSubtaskName(''); + setShowNewSubtaskCard(false); + } + }; + + return ( + + e.stopPropagation()} + onChange={e => setNewSubtaskName(e.target.value)} + onKeyDown={e => { + e.stopPropagation(); + if (e.key === 'Enter') { + setIsEnterKeyPressed(true); + handleAddSubtask(); + } + }} + onKeyUp={e => e.stopPropagation()} + onKeyPress={e => e.stopPropagation()} + onBlur={handleInputBlur} + placeholder={t('kanbanBoard.addSubTaskPlaceholder')} + className={`enhanced-kanban-create-subtask-input ${themeMode === 'dark' ? 'dark' : ''}`} + disabled={creatingTask} + autoFocus + /> + + ); +}; + +export default EnhancedKanbanCreateSubtaskCard; \ No newline at end of file diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.tsx index 37b9b535..e4f84142 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.tsx @@ -20,7 +20,7 @@ import { ForkOutlined } from '@ant-design/icons'; import { Dayjs } from 'dayjs'; import dayjs from 'dayjs'; import { CaretDownFilled, CaretRightFilled } from '@ant-design/icons'; -import { fetchBoardSubTasks } from '@/features/enhanced-kanban/enhanced-kanban.slice'; +import { fetchBoardSubTasks, toggleTaskExpansion } from '@/features/enhanced-kanban/enhanced-kanban.slice'; import { Divider } from 'antd'; import { List } from 'antd'; import { Skeleton } from 'antd'; @@ -28,6 +28,7 @@ import { PlusOutlined } from '@ant-design/icons'; import BoardSubTaskCard from '@/pages/projects/projectView/board/board-section/board-sub-task-card/board-sub-task-card'; import BoardCreateSubtaskCard from '@/pages/projects/projectView/board/board-section/board-sub-task-card/board-create-sub-task-card'; import { useTranslation } from 'react-i18next'; +import EnhancedKanbanCreateSubtaskCard from './EnhancedKanbanCreateSubtaskCard'; interface EnhancedKanbanTaskCardProps { task: IProjectTask; @@ -58,7 +59,7 @@ const EnhancedKanbanTaskCard: React.FC = React.memo const [dueDate, setDueDate] = useState( task?.end_date ? dayjs(task?.end_date) : null ); - const [isSubTaskShow, setIsSubTaskShow] = useState(false); + const projectId = useAppSelector(state => state.projectReducer.projectId); const { attributes, @@ -115,13 +116,17 @@ const EnhancedKanbanTaskCard: React.FC = React.memo const handleSubTaskExpand = useCallback(() => { if (task && task.id && projectId) { - if (task.show_sub_tasks) { + // Check if subtasks are already loaded and we have subtask data + if (task.sub_tasks && task.sub_tasks.length > 0 && task.sub_tasks_count > 0) { // If subtasks are already loaded, just toggle visibility - setIsSubTaskShow(prev => !prev); - } else { - // If subtasks need to be fetched, show the section first with loading state - setIsSubTaskShow(true); + dispatch(toggleTaskExpansion(task.id)); + } else if (task.sub_tasks_count > 0) { + // If we have a subtask count but no loaded subtasks, fetch them + dispatch(toggleTaskExpansion(task.id)); dispatch(fetchBoardSubTasks({ taskId: task.id, projectId })); + } else { + // If no subtasks exist, just toggle visibility (will show empty state) + dispatch(toggleTaskExpansion(task.id)); } } }, [task, projectId, dispatch]); @@ -199,13 +204,13 @@ const EnhancedKanbanTaskCard: React.FC = React.memo > {task.sub_tasks_count} - {isSubTaskShow ? : } + {task.show_sub_tasks ? : } - {isSubTaskShow && ( + {task.show_sub_tasks && ( @@ -215,13 +220,21 @@ const EnhancedKanbanTaskCard: React.FC = React.memo )} - {!task.sub_tasks_loading && task?.sub_tasks && - task?.sub_tasks.map((subtask: any) => ( + {!task.sub_tasks_loading && task?.sub_tasks && task.sub_tasks.length > 0 && + task.sub_tasks.map((subtask: any) => ( ))} + {!task.sub_tasks_loading && (!task?.sub_tasks || task.sub_tasks.length === 0) && task.sub_tasks_count === 0 && ( + +
+ {t('noSubtasks', 'No subtasks')} +
+
+ )} + {showNewSubtaskCard && ( - { const { socket } = useSocket(); const [isShowStartDate, setIsShowStartDate] = useState(false); const dispatch = useAppDispatch(); + const { tab } = useTabSearchParam(); // Date handling const startDayjs = task?.start_date ? dayjs(task.start_date) : null; const dueDayjs = task?.end_date ? dayjs(task.end_date) : null; @@ -63,6 +66,10 @@ const TaskDrawerDueDate = ({ task, t, form }: TaskDrawerDueDateProps) => { (data: IProjectTask) => { dispatch(setStartDate(data)); + // Also update enhanced kanban if on board tab + if (tab === 'board') { + dispatch(updateEnhancedKanbanTaskStartDate({ task: data })); + } } ); } catch (error) { @@ -88,6 +95,10 @@ const TaskDrawerDueDate = ({ task, t, form }: TaskDrawerDueDateProps) => { (data: IProjectTask) => { dispatch(setTaskEndDate(data)); + // Also update enhanced kanban if on board tab + if (tab === 'board') { + dispatch(updateEnhancedKanbanTaskEndDate({ task: data })); + } } ); } catch (error) { diff --git a/worklenz-frontend/src/components/task-drawer/shared/info-tab/subtask-table.tsx b/worklenz-frontend/src/components/task-drawer/shared/info-tab/subtask-table.tsx index 5d63c177..64913fc3 100644 --- a/worklenz-frontend/src/components/task-drawer/shared/info-tab/subtask-table.tsx +++ b/worklenz-frontend/src/components/task-drawer/shared/info-tab/subtask-table.tsx @@ -92,6 +92,9 @@ const SubTaskTable = ({ subTasks, loadingSubTasks, refreshSubTasks, t }: SubTask if (task.parent_task_id) { refreshSubTasks(); dispatch(updateSubtask({ sectionId: '', subtask: task, mode: 'add' })); + + // Note: Enhanced kanban updates are now handled by the global socket handler + // No need to dispatch here as it will be handled by useTaskSocketHandlers } }); } catch (error) { @@ -109,6 +112,10 @@ const SubTaskTable = ({ subTasks, loadingSubTasks, refreshSubTasks, t }: SubTask try { await tasksApiService.deleteTask(taskId); dispatch(updateSubtask({ sectionId: '', subtask: { id: taskId, parent_task_id: selectedTaskId || '' }, mode: 'delete' })); + + // Note: Enhanced kanban updates are now handled by the global socket handler + // No need to dispatch here as it will be handled by useTaskSocketHandlers + refreshSubTasks(); } catch (error) { logger.error('Error deleting subtask:', error); diff --git a/worklenz-frontend/src/features/enhanced-kanban/enhanced-kanban.slice.ts b/worklenz-frontend/src/features/enhanced-kanban/enhanced-kanban.slice.ts index f4974f0f..c1ee0bb4 100644 --- a/worklenz-frontend/src/features/enhanced-kanban/enhanced-kanban.slice.ts +++ b/worklenz-frontend/src/features/enhanced-kanban/enhanced-kanban.slice.ts @@ -6,6 +6,7 @@ import { ITaskListSortableColumn, } from '@/types/tasks/taskList.types'; import { tasksApiService } from '@/api/tasks/tasks.api.service'; +import { subTasksApiService } from '@/api/tasks/subtasks.api.service'; import logger from '@/utils/errorLogger'; import { ITaskListMemberFilter } from '@/types/tasks/taskListFilters.types'; import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; @@ -280,43 +281,9 @@ export const fetchBoardSubTasks = createAsyncThunk( { rejectWithValue, getState } ) => { try { - const state = getState() as { enhancedKanbanReducer: EnhancedKanbanState }; - const { enhancedKanbanReducer } = state; - - // Check if the task is already expanded (optional, can be enhanced later) - // const task = enhancedKanbanReducer.taskGroups.flatMap(group => group.tasks).find(t => t.id === taskId); - // if (task?.show_sub_tasks) { - // return []; - // } - - const selectedMembers = enhancedKanbanReducer.taskAssignees - .filter(member => member.selected) - .map(member => member.id) - .join(' '); - - const selectedLabels = enhancedKanbanReducer.labels - .filter(label => label.selected) - .map(label => label.id) - .join(' '); - - const config: ITaskListConfigV2 = { - id: projectId, - archived: enhancedKanbanReducer.archived, - group: enhancedKanbanReducer.groupBy, - field: enhancedKanbanReducer.fields.map(field => `${field.key} ${field.sort_order}`).join(','), - order: '', - search: enhancedKanbanReducer.search || '', - statuses: '', - members: selectedMembers, - projects: '', - isSubtasksInclude: false, - labels: selectedLabels, - priorities: enhancedKanbanReducer.priorities.join(' '), - parent_task: taskId, - }; - - const response = await tasksApiService.getTaskList(config); - return response.body; + // Use the dedicated subtasks API endpoint + const response = await subTasksApiService.getSubTasks(taskId); + return response.body || []; } catch (error) { logger.error('Fetch Enhanced Board Sub Tasks', error); if (error instanceof Error) { @@ -794,6 +761,116 @@ const enhancedKanbanSlice = createSlice({ state.groupCache[sectionId] = group; } }, + + // Enhanced Kanban end date update (for use in task drawer and socket events) + updateEnhancedKanbanTaskEndDate: ( + state, + action: PayloadAction<{ + task: IProjectTask; + }> + ) => { + const { task } = action.payload; + + // Find the task and update it + const result = findTaskInAllGroups(state.taskGroups, task.id || ''); + if (result) { + result.task.end_date = task.end_date; + // Update cache + state.taskCache[task.id!] = result.task; + } + }, + + // Enhanced Kanban start date update (for use in task drawer and socket events) + updateEnhancedKanbanTaskStartDate: ( + state, + action: PayloadAction<{ + task: IProjectTask; + }> + ) => { + const { task } = action.payload; + + // Find the task and update it + const result = findTaskInAllGroups(state.taskGroups, task.id || ''); + if (result) { + result.task.start_date = task.start_date; + // Update cache + state.taskCache[task.id!] = result.task; + } + }, + + // Enhanced Kanban task expansion toggle (for subtask expand/collapse) + toggleTaskExpansion: (state, action: PayloadAction) => { + const taskId = action.payload; + const result = findTaskInAllGroups(state.taskGroups, taskId); + + if (result) { + result.task.show_sub_tasks = !result.task.show_sub_tasks; + // Update cache + state.taskCache[taskId] = result.task; + } + }, + + // Enhanced Kanban subtask update (for use in task drawer and socket events) + updateEnhancedKanbanSubtask: ( + state, + action: PayloadAction<{ + sectionId: string; + subtask: IProjectTask; + mode: 'add' | 'delete'; + }> + ) => { + const { sectionId, subtask, mode } = action.payload; + const parentTaskId = subtask?.parent_task_id || null; + + if (!parentTaskId) return; + + // Function to update a task with a new subtask + const updateTaskWithSubtask = (task: IProjectTask): boolean => { + if (!task) return false; + + // Initialize sub_tasks array if it doesn't exist + if (!task.sub_tasks) { + task.sub_tasks = []; + } + + if (mode === 'add') { + // Increment subtask count + task.sub_tasks_count = (task.sub_tasks_count || 0) + 1; + + // Add the subtask + task.sub_tasks.push({ ...subtask }); + } else { + // Remove the subtask + task.sub_tasks = task.sub_tasks.filter(t => t.id !== subtask.id); + task.sub_tasks_count = Math.max(0, (task.sub_tasks_count || 1) - 1); + } + + // Update cache + state.taskCache[task.id!] = task; + return true; + }; + + // First try to find the task in the specified section + if (sectionId) { + const section = state.taskGroups.find(sec => sec.id === sectionId); + if (section) { + const task = section.tasks.find(task => task.id === parentTaskId); + if (task && updateTaskWithSubtask(task)) { + // Update group cache + state.groupCache[sectionId] = section; + return; + } + } + } + + // If not found in the specified section, try all groups + const result = findTaskInAllGroups(state.taskGroups, parentTaskId); + if (result) { + updateTaskWithSubtask(result.task); + // Update group cache + state.groupCache[result.groupId] = result.group; + } + }, }, extraReducers: (builder) => { builder @@ -808,10 +885,24 @@ const enhancedKanbanSlice = createSlice({ // Update performance metrics state.performanceMetrics = calculatePerformanceMetrics(action.payload); - // Update caches + // Update caches and initialize subtask properties action.payload.forEach(group => { state.groupCache[group.id] = group; group.tasks.forEach(task => { + // Initialize subtask-related properties if they don't exist + if (task.sub_tasks === undefined) { + task.sub_tasks = []; + } + if (task.sub_tasks_loading === undefined) { + task.sub_tasks_loading = false; + } + if (task.show_sub_tasks === undefined) { + task.show_sub_tasks = false; + } + if (task.sub_tasks_count === undefined) { + task.sub_tasks_count = 0; + } + state.taskCache[task.id!] = task; }); }); @@ -886,6 +977,46 @@ const enhancedKanbanSlice = createSlice({ .addCase(fetchEnhancedKanbanLabels.rejected, (state, action) => { state.loadingLabels = false; state.error = action.payload as string; + }) + // Fetch Board Sub Tasks + .addCase(fetchBoardSubTasks.pending, (state, action) => { + state.error = null; + // Find the task and set sub_tasks_loading to true + const taskId = action.meta.arg.taskId; + const result = findTaskInAllGroups(state.taskGroups, taskId); + if (result) { + result.task.sub_tasks_loading = true; + } + }) + .addCase(fetchBoardSubTasks.fulfilled, (state, action: PayloadAction) => { + const taskId = (action as any).meta?.arg?.taskId; + const result = findTaskInAllGroups(state.taskGroups, taskId); + + if (result) { + result.task.sub_tasks_loading = false; + result.task.sub_tasks = action.payload; + result.task.show_sub_tasks = true; + + // Only update the count if we don't have a count yet or if the API returned a different count + // This preserves the original count from the initial data load + if (!result.task.sub_tasks_count || result.task.sub_tasks_count === 0) { + result.task.sub_tasks_count = action.payload.length; + } + + // Update cache + state.taskCache[taskId] = result.task; + } + }) + .addCase(fetchBoardSubTasks.rejected, (state, action) => { + state.error = action.error.message || 'Failed to fetch sub tasks'; + // Set loading to false on rejection + const taskId = action.meta.arg.taskId; + const result = findTaskInAllGroups(state.taskGroups, taskId); + if (result) { + result.task.sub_tasks_loading = false; + // Update cache + state.taskCache[taskId] = result.task; + } }); }, }); @@ -926,6 +1057,10 @@ export const { updateEnhancedKanbanTaskLabels, updateEnhancedKanbanTaskProgress, updateEnhancedKanbanTaskName, + updateEnhancedKanbanTaskEndDate, + updateEnhancedKanbanTaskStartDate, + updateEnhancedKanbanSubtask, + toggleTaskExpansion, } = enhancedKanbanSlice.actions; export default enhancedKanbanSlice.reducer; \ No newline at end of file diff --git a/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts b/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts index 3a0c0807..bdf9480b 100644 --- a/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts +++ b/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts @@ -42,6 +42,18 @@ import { selectCurrentGroupingV3, fetchTasksV3 } from '@/features/task-management/task-management.slice'; +import { + updateEnhancedKanbanSubtask, + addTaskToGroup as addEnhancedKanbanTaskToGroup, + updateEnhancedKanbanTaskStatus, + updateEnhancedKanbanTaskPriority, + updateEnhancedKanbanTaskAssignees, + updateEnhancedKanbanTaskLabels, + updateEnhancedKanbanTaskProgress, + updateEnhancedKanbanTaskName, + updateEnhancedKanbanTaskEndDate, + updateEnhancedKanbanTaskStartDate +} from '@/features/enhanced-kanban/enhanced-kanban.slice'; import { selectCurrentGrouping } from '@/features/task-management/grouping.slice'; import { fetchLabels } from '@/features/taskAttributes/taskLabelSlice'; import { @@ -110,6 +122,9 @@ export const useTaskSocketHandlers = () => { manual_progress: false, } as IProjectTask) ); + + // Update enhanced kanban slice + dispatch(updateEnhancedKanbanTaskAssignees(data)); // Remove unnecessary refetch - real-time updates handle this // if (currentSession?.team_id && !loadingAssignees) { @@ -150,6 +165,9 @@ export const useTaskSocketHandlers = () => { // dispatch(fetchLabels()), // projectId && dispatch(fetchLabelsByProject(projectId)), ]); + + // Update enhanced kanban slice + dispatch(updateEnhancedKanbanTaskLabels(labels)); }, [dispatch, projectId] ); @@ -171,6 +189,9 @@ export const useTaskSocketHandlers = () => { // Update the old task slice (for backward compatibility) dispatch(updateTaskStatus(response)); dispatch(deselectAll()); + + // Update enhanced kanban slice + dispatch(updateEnhancedKanbanTaskStatus(response)); // For the task management slice, move task between groups without resetting const state = store.getState(); @@ -267,6 +288,15 @@ export const useTaskSocketHandlers = () => { } })); } + + // Update enhanced kanban slice + dispatch(updateEnhancedKanbanTaskProgress({ + id: data.id, + complete_ratio: data.complete_ratio, + completed_count: data.completed_count, + total_tasks_count: data.total_tasks_count, + parent_task: data.parent_task, + })); }, [dispatch] ); @@ -281,6 +311,9 @@ export const useTaskSocketHandlers = () => { dispatch(updateTaskPriority(response)); dispatch(setTaskPriority(response)); dispatch(deselectAll()); + + // Update enhanced kanban slice + dispatch(updateEnhancedKanbanTaskPriority(response)); // For the task management slice, always update the task entity first const state = store.getState(); @@ -371,6 +404,9 @@ export const useTaskSocketHandlers = () => { dispatch(updateTaskEndDate({ task: taskWithProgress })); dispatch(setTaskEndDate(taskWithProgress)); + + // Update enhanced kanban slice + dispatch(updateEnhancedKanbanTaskEndDate({ task: taskWithProgress })); }, [dispatch] ); @@ -392,6 +428,9 @@ export const useTaskSocketHandlers = () => { } })); } + + // Update enhanced kanban slice + dispatch(updateEnhancedKanbanTaskName({ task: data })); }, [dispatch] ); @@ -558,6 +597,13 @@ export const useTaskSocketHandlers = () => { if (data.parent_task_id) { // Handle subtask creation dispatch(updateSubTasks(data)); + + // Also update enhanced kanban slice for subtask creation + dispatch(updateEnhancedKanbanSubtask({ + sectionId: '', + subtask: data, + mode: 'add' + })); } else { // Handle regular task creation - transform to Task format and add const task = { @@ -613,6 +659,12 @@ export const useTaskSocketHandlers = () => { // Use addTaskToGroup with the actual group UUID dispatch(addTaskToGroup({ task, groupId })); + + // Also update enhanced kanban slice for regular task creation + dispatch(addEnhancedKanbanTaskToGroup({ + sectionId: groupId || '', + task: data + })); } }, [dispatch]