Merge pull request #211 from shancds/fix/enhanced-board-sub-task-section
Fix/enhanced board sub task section
This commit is contained in:
@@ -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<EnhancedKanbanBoardProps> = ({ 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<any>(null);
|
||||
|
||||
@@ -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<boolean>(false);
|
||||
const [newSubtaskName, setNewSubtaskName] = useState<string>('');
|
||||
const [isEnterKeyPressed, setIsEnterKeyPressed] = useState<boolean>(false);
|
||||
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<InputRef>(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<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
setIsEnterKeyPressed(true);
|
||||
handleAddSubtask();
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputBlur = () => {
|
||||
if (!isEnterKeyPressed && newSubtaskName.length > 0) {
|
||||
handleAddSubtask();
|
||||
}
|
||||
setIsEnterKeyPressed(false);
|
||||
};
|
||||
|
||||
const handleCancelNewCard = (e: React.FocusEvent<HTMLDivElement>) => {
|
||||
if (cardRef.current && !cardRef.current.contains(e.relatedTarget)) {
|
||||
setNewSubtaskName('');
|
||||
setShowNewSubtaskCard(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
ref={cardRef}
|
||||
vertical
|
||||
gap={4}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: 2,
|
||||
backgroundColor: themeMode === 'dark' ? '#292929' : '#fafafa',
|
||||
borderRadius: 6,
|
||||
cursor: 'pointer',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
// className={`outline-1 ${themeWiseColor('outline-[#edeae9]', 'outline-[#6a696a]', themeMode)} hover:outline`}
|
||||
onBlur={handleCancelNewCard}
|
||||
>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={newSubtaskName}
|
||||
onClick={e => 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
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnhancedKanbanCreateSubtaskCard;
|
||||
@@ -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<EnhancedKanbanTaskCardProps> = React.memo
|
||||
const [dueDate, setDueDate] = useState<Dayjs | null>(
|
||||
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<EnhancedKanbanTaskCardProps> = 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<EnhancedKanbanTaskCardProps> = React.memo
|
||||
>
|
||||
<ForkOutlined rotate={90} />
|
||||
<span>{task.sub_tasks_count}</span>
|
||||
{isSubTaskShow ? <CaretDownFilled /> : <CaretRightFilled />}
|
||||
{task.show_sub_tasks ? <CaretDownFilled /> : <CaretRightFilled />}
|
||||
</Tag>
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex vertical gap={8}>
|
||||
{isSubTaskShow && (
|
||||
{task.show_sub_tasks && (
|
||||
<Flex vertical>
|
||||
<Divider style={{ marginBlock: 0 }} />
|
||||
<List>
|
||||
@@ -215,13 +220,21 @@ const EnhancedKanbanTaskCard: React.FC<EnhancedKanbanTaskCardProps> = React.memo
|
||||
</List.Item>
|
||||
)}
|
||||
|
||||
{!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) => (
|
||||
<BoardSubTaskCard key={subtask.id} subtask={subtask} sectionId={sectionId} />
|
||||
))}
|
||||
|
||||
{!task.sub_tasks_loading && (!task?.sub_tasks || task.sub_tasks.length === 0) && task.sub_tasks_count === 0 && (
|
||||
<List.Item>
|
||||
<div style={{ padding: '8px 0', color: '#999', fontSize: '12px' }}>
|
||||
{t('noSubtasks', 'No subtasks')}
|
||||
</div>
|
||||
</List.Item>
|
||||
)}
|
||||
|
||||
{showNewSubtaskCard && (
|
||||
<BoardCreateSubtaskCard
|
||||
<EnhancedKanbanCreateSubtaskCard
|
||||
sectionId={sectionId}
|
||||
parentTaskId={task.id || ''}
|
||||
setShowNewSubtaskCard={setShowNewSubtaskCard}
|
||||
|
||||
@@ -14,6 +14,8 @@ import { ITaskViewModel } from '@/types/tasks/task.types';
|
||||
import { IProjectTask } from '@/types/project/projectTasksViewModel.types';
|
||||
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
||||
import { setStartDate, setTaskEndDate } from '@/features/task-drawer/task-drawer.slice';
|
||||
import { updateEnhancedKanbanTaskStartDate, updateEnhancedKanbanTaskEndDate } from '@/features/enhanced-kanban/enhanced-kanban.slice';
|
||||
import useTabSearchParam from '@/hooks/useTabSearchParam';
|
||||
interface TaskDrawerDueDateProps {
|
||||
task: ITaskViewModel;
|
||||
t: TFunction;
|
||||
@@ -24,6 +26,7 @@ const TaskDrawerDueDate = ({ task, t, form }: TaskDrawerDueDateProps) => {
|
||||
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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<string>) => {
|
||||
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<IProjectTask[]>) => {
|
||||
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;
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user