From 72269322476ace1a7ac0b445209c62eae1c63165 Mon Sep 17 00:00:00 2001 From: shancds Date: Thu, 17 Jul 2025 10:56:06 +0530 Subject: [PATCH 1/5] Enhance EnhancedKanbanBoardNativeDnD to support phase reordering - Integrated phase reordering functionality within the EnhancedKanbanBoardNativeDnD component. - Added logic to fetch phases by project ID and update phase order through API calls. - Updated drag-and-drop handling to accommodate reordering of phases alongside existing status-based reordering. --- .../EnhancedKanbanBoardNativeDnD.tsx | 27 +- .../projectView/board/project-view-board.tsx | 583 ++++++++++++++++++ 2 files changed, 608 insertions(+), 2 deletions(-) create mode 100644 worklenz-frontend/src/pages/projects/projectView/board/project-view-board.tsx diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx index 8431073f..48219464 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx @@ -21,6 +21,9 @@ import alertService from '@/services/alerts/alertService'; import logger from '@/utils/errorLogger'; import { checkTaskDependencyStatus } from '@/utils/check-task-dependency-status'; import { useTaskSocketHandlers } from '@/hooks/useTaskSocketHandlers'; +import { phasesApiService } from '@/api/taskAttributes/phases/phases.api.service'; +import { ITaskListGroup } from '@/types/tasks/taskList.types'; +import { fetchPhasesByProjectId, updatePhaseListOrder } from '@/features/projects/singleProject/phase/phases.slice'; const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ projectId }) => { const dispatch = useDispatch(); @@ -34,6 +37,7 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project loadingGroups, error, } = useSelector((state: RootState) => state.enhancedKanbanReducer); + const { phaseList, loadingPhases } = useAppSelector(state => state.phaseReducer); const [draggedGroupId, setDraggedGroupId] = useState(null); const [draggedTaskId, setDraggedTaskId] = useState(null); const [draggedTaskGroupId, setDraggedTaskGroupId] = useState(null); @@ -56,6 +60,9 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project if (!statusCategories.length) { dispatch(fetchStatusesCategories() as any); } + if ( groupBy === 'phase' && !phaseList.length) { + dispatch(fetchPhasesByProjectId(projectId) as any); + } }, [dispatch, projectId]); // Reset drag state if taskGroups changes (e.g., real-time update) useEffect(() => { @@ -90,9 +97,9 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project reorderedGroups.splice(toIdx, 0, moved); dispatch(reorderGroups({ fromIndex: fromIdx, toIndex: toIdx, reorderedGroups })); dispatch(reorderEnhancedKanbanGroups({ fromIndex: fromIdx, toIndex: toIdx, reorderedGroups }) as any); - // API call for group order try { + if (groupBy === 'status') { const columnOrder = reorderedGroups.map(group => group.id); const requestBody = { status_order: columnOrder }; const response = await statusApiService.updateStatusOrder(requestBody, projectId); @@ -104,6 +111,22 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project dispatch(reorderGroups({ fromIndex: toIdx, toIndex: fromIdx, reorderedGroups: revertedGroups })); alertService.error('Failed to update column order', 'Please try again'); } + } else if (groupBy === 'phase') { + const newPhaseList = [...phaseList]; + const [movedItem] = newPhaseList.splice(fromIdx, 1); + newPhaseList.splice(toIdx, 0, movedItem); + dispatch(updatePhaseListOrder(newPhaseList)); + const requestBody = { + from_index: fromIdx, + to_index: toIdx, + phases: newPhaseList, + project_id: projectId, + }; + const response = await phasesApiService.updatePhaseOrder(projectId, requestBody); + if (!response.done) { + alertService.error('Failed to update phase order', 'Please try again'); + } + } } catch (error) { // Revert the change if API call fails const revertedGroups = [...reorderedGroups]; @@ -119,7 +142,7 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project }; // Utility to recalculate all task orders for all groups - function getAllTaskUpdates(allGroups, groupBy) { + function getAllTaskUpdates(allGroups: ITaskListGroup[], groupBy: string) { const taskUpdates = []; let currentSortOrder = 0; for (const group of allGroups) { diff --git a/worklenz-frontend/src/pages/projects/projectView/board/project-view-board.tsx b/worklenz-frontend/src/pages/projects/projectView/board/project-view-board.tsx new file mode 100644 index 00000000..4bea53db --- /dev/null +++ b/worklenz-frontend/src/pages/projects/projectView/board/project-view-board.tsx @@ -0,0 +1,583 @@ +import { useEffect, useState, useRef, useMemo, useCallback } from 'react'; +import { useAppSelector } from '@/hooks/useAppSelector'; + +import TaskListFilters from '../taskList/task-list-filters/task-list-filters'; +import { Flex, Skeleton } from 'antd'; +import BoardSectionCardContainer from './board-section/board-section-container'; +import { + fetchBoardTaskGroups, + reorderTaskGroups, + moveTaskBetweenGroups, + IGroupBy, + updateTaskProgress, +} from '@features/board/board-slice'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { + DndContext, + DragEndEvent, + DragOverEvent, + DragStartEvent, + closestCenter, + DragOverlay, + MouseSensor, + TouchSensor, + useSensor, + useSensors, + getFirstCollision, + pointerWithin, + rectIntersection, + UniqueIdentifier, +} from '@dnd-kit/core'; +import BoardViewTaskCard from './board-section/board-task-card/board-view-task-card'; +import { fetchStatusesCategories } from '@/features/taskAttributes/taskStatusSlice'; +import useTabSearchParam from '@/hooks/useTabSearchParam'; +import { useSocket } from '@/socket/socketContext'; +import { useAuthService } from '@/hooks/useAuth'; +import { SocketEvents } from '@/shared/socket-events'; +import alertService from '@/services/alerts/alertService'; +import { useMixpanelTracking } from '@/hooks/useMixpanelTracking'; +import { evt_project_board_visit, evt_project_task_list_drag_and_move } from '@/shared/worklenz-analytics-events'; +import { ITaskStatusCreateRequest } from '@/types/tasks/task-status-create-request'; +import { statusApiService } from '@/api/taskAttributes/status/status.api.service'; +import logger from '@/utils/errorLogger'; +import { checkTaskDependencyStatus } from '@/utils/check-task-dependency-status'; +import { debounce } from 'lodash'; +import { ITaskListPriorityChangeResponse } from '@/types/tasks/task-list-priority.types'; +import { updateTaskPriority as updateBoardTaskPriority } from '@/features/board/board-slice'; + +interface DroppableContainer { + id: UniqueIdentifier; + data: { + current?: { + type?: string; + }; + }; +} + +const ProjectViewBoard = () => { + const dispatch = useAppDispatch(); + const { projectView } = useTabSearchParam(); + const { socket } = useSocket(); + const authService = useAuthService(); + const currentSession = authService.getCurrentSession(); + const { trackMixpanelEvent } = useMixpanelTracking(); + const [currentTaskIndex, setCurrentTaskIndex] = useState(-1); + // Add local loading state to immediately show skeleton + const [isLoading, setIsLoading] = useState(true); + + const { projectId } = useAppSelector(state => state.projectReducer); + const { taskGroups, groupBy, loadingGroups, search, archived } = useAppSelector(state => state.boardReducer); + const { statusCategories, loading: loadingStatusCategories } = useAppSelector( + state => state.taskStatusReducer + ); + const [activeItem, setActiveItem] = useState(null); + + // Store the original source group ID when drag starts + const originalSourceGroupIdRef = useRef(null); + const lastOverId = useRef(null); + const recentlyMovedToNewContainer = useRef(false); + const [clonedItems, setClonedItems] = useState(null); + const isDraggingRef = useRef(false); + + // Update loading state based on all loading conditions + useEffect(() => { + setIsLoading(loadingGroups || loadingStatusCategories); + }, [loadingGroups, loadingStatusCategories]); + + // Load data efficiently with async/await and Promise.all + useEffect(() => { + const loadData = async () => { + if (projectId && groupBy && projectView === 'kanban') { + const promises = []; + + if (!loadingGroups) { + promises.push(dispatch(fetchBoardTaskGroups(projectId))); + } + + if (!statusCategories.length) { + promises.push(dispatch(fetchStatusesCategories())); + } + + // Wait for all data to load + await Promise.all(promises); + } + }; + + loadData(); + }, [dispatch, projectId, groupBy, projectView, search, archived]); + + // Create sensors with memoization to prevent unnecessary re-renders + const sensors = useSensors( + useSensor(MouseSensor, { + activationConstraint: { + distance: 10, + delay: 100, + tolerance: 5, + }, + }), + useSensor(TouchSensor, { + activationConstraint: { + delay: 250, + tolerance: 5, + }, + }) + ); + + const collisionDetectionStrategy = useCallback( + (args: { + active: { id: UniqueIdentifier; data: { current?: { type?: string } } }; + droppableContainers: DroppableContainer[]; + }) => { + if (activeItem?.type === 'section') { + return closestCenter({ + ...args, + droppableContainers: args.droppableContainers.filter( + (container: DroppableContainer) => container.data.current?.type === 'section' + ), + }); + } + + // Start by finding any intersecting droppable + const pointerIntersections = pointerWithin(args); + const intersections = + pointerIntersections.length > 0 + ? pointerIntersections + : rectIntersection(args); + let overId = getFirstCollision(intersections, 'id'); + + if (overId !== null) { + const overContainer = args.droppableContainers.find( + (container: DroppableContainer) => container.id === overId + ); + + if (overContainer?.data.current?.type === 'section') { + const containerItems = taskGroups.find( + (group) => group.id === overId + )?.tasks || []; + + if (containerItems.length > 0) { + overId = closestCenter({ + ...args, + droppableContainers: args.droppableContainers.filter( + (container: DroppableContainer) => + container.id !== overId && + container.data.current?.type === 'task' + ), + })[0]?.id; + } + } + + lastOverId.current = overId; + return [{ id: overId }]; + } + + if (recentlyMovedToNewContainer.current) { + lastOverId.current = activeItem?.id; + } + + return lastOverId.current ? [{ id: lastOverId.current }] : []; + }, + [activeItem, taskGroups] + ); + + const handleTaskProgress = (data: { + id: string; + status: string; + complete_ratio: number; + completed_count: number; + total_tasks_count: number; + parent_task: string; + }) => { + dispatch(updateTaskProgress(data)); + }; + + // Debounced move task function to prevent rapid updates + const debouncedMoveTask = useCallback( + debounce((taskId: string, sourceGroupId: string, targetGroupId: string, targetIndex: number) => { + dispatch( + moveTaskBetweenGroups({ + taskId, + sourceGroupId, + targetGroupId, + targetIndex, + }) + ); + }, 100), + [dispatch] + ); + + const handleDragStart = (event: DragStartEvent) => { + const { active } = event; + isDraggingRef.current = true; + setActiveItem(active.data.current); + setCurrentTaskIndex(active.data.current?.sortable.index); + if (active.data.current?.type === 'task') { + originalSourceGroupIdRef.current = active.data.current.sectionId; + } + setClonedItems(taskGroups); + }; + + const findGroupForId = (id: string) => { + // If id is a sectionId + if (taskGroups.some(group => group.id === id)) return id; + // If id is a taskId, find the group containing it + const group = taskGroups.find(g => g.tasks.some(t => t.id === id)); + return group?.id; + }; + + const handleDragOver = (event: DragOverEvent) => { + try { + if (!isDraggingRef.current) return; + + const { active, over } = event; + if (!over) return; + + // Get the ids + const activeId = active.id; + const overId = over.id; + + // Find the group (section) for each + const activeGroupId = findGroupForId(activeId as string); + const overGroupId = findGroupForId(overId as string); + + // Only move if both groups exist and are different, and the active is a task + if ( + activeGroupId && + overGroupId && + active.data.current?.type === 'task' + ) { + // Find the target index in the over group + const targetGroup = taskGroups.find(g => g.id === overGroupId); + let targetIndex = 0; + if (targetGroup) { + // If over is a task, insert before it; if over is a section, append to end + if (over.data.current?.type === 'task') { + targetIndex = targetGroup.tasks.findIndex(t => t.id === overId); + if (targetIndex === -1) targetIndex = targetGroup.tasks.length; + } else { + targetIndex = targetGroup.tasks.length; + } + } + // Use debounced move task to prevent rapid updates + debouncedMoveTask( + activeId as string, + activeGroupId, + overGroupId, + targetIndex + ); + } + } catch (error) { + console.error('handleDragOver error:', error); + } + }; + + const handlePriorityChange = (taskId: string, priorityId: string) => { + if (!taskId || !priorityId || !socket) return; + + const payload = { + task_id: taskId, + priority_id: priorityId, + team_id: currentSession?.team_id, + }; + + socket.emit(SocketEvents.TASK_PRIORITY_CHANGE.toString(), JSON.stringify(payload)); + socket.once(SocketEvents.TASK_PRIORITY_CHANGE.toString(), (data: ITaskListPriorityChangeResponse) => { + dispatch(updateBoardTaskPriority(data)); + }); + }; + + const handleDragEnd = async (event: DragEndEvent) => { + isDraggingRef.current = false; + const { active, over } = event; + + if (!over || !projectId) { + setActiveItem(null); + originalSourceGroupIdRef.current = null; + setClonedItems(null); + return; + } + + const isActiveTask = active.data.current?.type === 'task'; + const isActiveSection = active.data.current?.type === 'section'; + + // Handle task dragging between columns + if (isActiveTask) { + const task = active.data.current?.task; + + // Use the original source group ID from ref instead of the potentially modified one + const sourceGroupId = originalSourceGroupIdRef.current || active.data.current?.sectionId; + + // Fix: Ensure we correctly identify the target group ID + let targetGroupId; + if (over.data.current?.type === 'task') { + // If dropping on a task, get its section ID + targetGroupId = over.data.current?.sectionId; + } else if (over.data.current?.type === 'section') { + // If dropping directly on a section + targetGroupId = over.id; + } else { + // Fallback to the over ID if type is not specified + targetGroupId = over.id; + } + + // Find source and target groups + const sourceGroup = taskGroups.find(group => group.id === sourceGroupId); + const targetGroup = taskGroups.find(group => group.id === targetGroupId); + + if (!sourceGroup || !targetGroup || !task) { + logger.error('Could not find source or target group, or task is undefined'); + setActiveItem(null); + originalSourceGroupIdRef.current = null; // Reset the ref + return; + } + + if (targetGroupId !== sourceGroupId) { + const canContinue = await checkTaskDependencyStatus(task.id, targetGroupId); + if (!canContinue) { + alertService.error( + 'Task is not completed', + 'Please complete the task dependencies before proceeding' + ); + dispatch( + moveTaskBetweenGroups({ + taskId: task.id, + sourceGroupId: targetGroupId, // Current group (where it was moved optimistically) + targetGroupId: sourceGroupId, // Move it back to the original source group + targetIndex: currentTaskIndex !== -1 ? currentTaskIndex : 0, // Original position or append to end + }) + ); + + setActiveItem(null); + originalSourceGroupIdRef.current = null; + return; + } + } + + // Find indices + let fromIndex = sourceGroup.tasks.findIndex(t => t.id === task.id); + // Handle case where task is not found in source group (might have been moved already in UI) + if (fromIndex === -1) { + logger.info('Task not found in source group. Using task sort_order from task object.'); + + // Use the sort_order from the task object itself + const fromSortOrder = task.sort_order; + + // Calculate target index and position + let toIndex = -1; + if (over.data.current?.type === 'task') { + const overTaskId = over.data.current?.task.id; + toIndex = targetGroup.tasks.findIndex(t => t.id === overTaskId); + } else { + // If dropping on a section, append to the end + toIndex = targetGroup.tasks.length; + } + + // Calculate toPos similar to Angular implementation + const toPos = targetGroup.tasks[toIndex]?.sort_order || + targetGroup.tasks[targetGroup.tasks.length - 1]?.sort_order || + -1; + + // Prepare socket event payload + const body = { + project_id: projectId, + from_index: fromSortOrder, + to_index: toPos, + to_last_index: !toPos, + from_group: sourceGroupId, + to_group: targetGroupId, + group_by: groupBy || 'status', + task, + team_id: currentSession?.team_id + }; + + // Emit socket event + if (socket) { + socket.emit(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), body); + + // Set up listener for task progress update + socket.once(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), () => { + if (task.is_sub_task) { + socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), task.parent_task_id); + } else { + socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), task.id); + } + }); + + // Handle priority change if groupBy is priority + if (groupBy === IGroupBy.PRIORITY) { + handlePriorityChange(task.id, targetGroupId); + } + } + + // Track analytics event + trackMixpanelEvent(evt_project_task_list_drag_and_move); + + setActiveItem(null); + originalSourceGroupIdRef.current = null; // Reset the ref + return; + } + + // Calculate target index and position + let toIndex = -1; + if (over.data.current?.type === 'task') { + const overTaskId = over.data.current?.task.id; + toIndex = targetGroup.tasks.findIndex(t => t.id === overTaskId); + } else { + // If dropping on a section, append to the end + toIndex = targetGroup.tasks.length; + } + + // Calculate toPos similar to Angular implementation + const toPos = targetGroup.tasks[toIndex]?.sort_order || + targetGroup.tasks[targetGroup.tasks.length - 1]?.sort_order || + -1; + // Prepare socket event payload + const body = { + project_id: projectId, + from_index: sourceGroup.tasks[fromIndex].sort_order, + to_index: toPos, + to_last_index: !toPos, + from_group: sourceGroupId, // Use the direct IDs instead of group objects + to_group: targetGroupId, // Use the direct IDs instead of group objects + group_by: groupBy || 'status', // Use the current groupBy value + task, + team_id: currentSession?.team_id + }; + // Emit socket event + if (socket) { + socket.emit(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), body); + + // Set up listener for task progress update + socket.once(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), () => { + if (task.is_sub_task) { + socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), task.parent_task_id); + } else { + socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), task.id); + } + }); + } + // Track analytics event + trackMixpanelEvent(evt_project_task_list_drag_and_move); + } + // Handle column reordering + else if (isActiveSection) { + // Don't allow reordering if groupBy is phases + if (groupBy === IGroupBy.PHASE) { + setActiveItem(null); + originalSourceGroupIdRef.current = null; + return; + } + + const sectionId = active.id; + const fromIndex = taskGroups.findIndex(group => group.id === sectionId); + const toIndex = taskGroups.findIndex(group => group.id === over.id); + + if (fromIndex !== -1 && toIndex !== -1) { + // Create a new array with the reordered groups + const reorderedGroups = [...taskGroups]; + const [movedGroup] = reorderedGroups.splice(fromIndex, 1); + reorderedGroups.splice(toIndex, 0, movedGroup); + + // Dispatch action to reorder columns with the new array + dispatch(reorderTaskGroups(reorderedGroups)); + + // Prepare column order for API + const columnOrder = reorderedGroups.map(group => group.id); + + // Call API to update status order + try { + // Use the correct API endpoint based on the Angular code + const requestBody: ITaskStatusCreateRequest = { + status_order: columnOrder + }; + + const response = await statusApiService.updateStatusOrder(requestBody, projectId); + if (!response.done) { + const revertedGroups = [...reorderedGroups]; + const [movedBackGroup] = revertedGroups.splice(toIndex, 1); + revertedGroups.splice(fromIndex, 0, movedBackGroup); + dispatch(reorderTaskGroups(revertedGroups)); + alertService.error('Failed to update column order', 'Please try again'); + } + } catch (error) { + // Revert the change if API call fails + const revertedGroups = [...reorderedGroups]; + const [movedBackGroup] = revertedGroups.splice(toIndex, 1); + revertedGroups.splice(fromIndex, 0, movedBackGroup); + dispatch(reorderTaskGroups(revertedGroups)); + alertService.error('Failed to update column order', 'Please try again'); + } + } + } + + setActiveItem(null); + originalSourceGroupIdRef.current = null; // Reset the ref + }; + + const handleDragCancel = () => { + isDraggingRef.current = false; + if (clonedItems) { + dispatch(reorderTaskGroups(clonedItems)); + } + setActiveItem(null); + setClonedItems(null); + originalSourceGroupIdRef.current = null; + }; + + // Reset the recently moved flag after animation frame + useEffect(() => { + requestAnimationFrame(() => { + recentlyMovedToNewContainer.current = false; + }); + }, [taskGroups]); + + useEffect(() => { + if (socket) { + socket.on(SocketEvents.GET_TASK_PROGRESS.toString(), handleTaskProgress); + } + + return () => { + socket?.off(SocketEvents.GET_TASK_PROGRESS.toString(), handleTaskProgress); + }; + }, [socket]); + + // Track analytics event on component mount + useEffect(() => { + trackMixpanelEvent(evt_project_board_visit); + }, []); + + // Cleanup debounced function on unmount + useEffect(() => { + return () => { + debouncedMoveTask.cancel(); + }; + }, [debouncedMoveTask]); + + return ( + + + + + + + {activeItem?.type === 'task' && ( + + )} + + + + + ); +}; + +export default ProjectViewBoard; \ No newline at end of file From fa08463e65efaa2483b71a9844c87953cf6c32d7 Mon Sep 17 00:00:00 2001 From: shancds Date: Thu, 17 Jul 2025 11:10:23 +0530 Subject: [PATCH 2/5] Enhance localization support in Kanban board - Added new localized messages for error handling, task management, and filter loading across multiple languages (Albanian, German, English, Spanish, Portuguese, Chinese). - Updated the EnhancedKanbanBoardNativeDnD component to utilize these localized messages for improved user experience during task operations and error notifications. --- .../public/locales/alb/kanban-board.json | 14 +++++- .../public/locales/de/kanban-board.json | 14 +++++- .../public/locales/en/kanban-board.json | 11 +++- .../public/locales/es/kanban-board.json | 14 +++++- .../public/locales/pt/kanban-board.json | 14 +++++- .../public/locales/zh/kanban-board.json | 21 +++++++- .../EnhancedKanbanBoardNativeDnD.tsx | 50 ++++++++++++------- 7 files changed, 114 insertions(+), 24 deletions(-) diff --git a/worklenz-frontend/public/locales/alb/kanban-board.json b/worklenz-frontend/public/locales/alb/kanban-board.json index 48987f10..50835235 100644 --- a/worklenz-frontend/public/locales/alb/kanban-board.json +++ b/worklenz-frontend/public/locales/alb/kanban-board.json @@ -37,5 +37,17 @@ "noDueDate": "Pa datë përfundimi", "save": "Ruaj", "clear": "Pastro", - "nextWeek": "Javën e ardhshme" + "nextWeek": "Javën e ardhshme", + "noSubtasks": "Pa nëndetyra", + "showSubtasks": "Shfaq nëndetyrat", + "hideSubtasks": "Fshih nëndetyrat", + + "errorLoadingTasks": "Gabim gjatë ngarkimit të detyrave", + "noTasksFound": "Nuk u gjetën detyra", + "loadingFilters": "Duke ngarkuar filtra...", + "failedToUpdateColumnOrder": "Dështoi përditësimi i rendit të kolonave", + "failedToUpdatePhaseOrder": "Dështoi përditësimi i rendit të fazave", + "pleaseTryAgain": "Ju lutemi provoni përsëri", + "taskNotCompleted": "Detyra nuk është përfunduar", + "completeTaskDependencies": "Ju lutemi përfundoni varësitë e detyrës para se të vazhdoni" } diff --git a/worklenz-frontend/public/locales/de/kanban-board.json b/worklenz-frontend/public/locales/de/kanban-board.json index 9d748e30..10b58b95 100644 --- a/worklenz-frontend/public/locales/de/kanban-board.json +++ b/worklenz-frontend/public/locales/de/kanban-board.json @@ -37,5 +37,17 @@ "noDueDate": "Kein Fälligkeitsdatum", "save": "Speichern", "clear": "Löschen", - "nextWeek": "Nächste Woche" + "nextWeek": "Nächste Woche", + "noSubtasks": "Keine Unteraufgaben", + "showSubtasks": "Unteraufgaben anzeigen", + "hideSubtasks": "Unteraufgaben ausblenden", + + "errorLoadingTasks": "Fehler beim Laden der Aufgaben", + "noTasksFound": "Keine Aufgaben gefunden", + "loadingFilters": "Filter werden geladen...", + "failedToUpdateColumnOrder": "Fehler beim Aktualisieren der Spaltenreihenfolge", + "failedToUpdatePhaseOrder": "Fehler beim Aktualisieren der Phasenreihenfolge", + "pleaseTryAgain": "Bitte versuchen Sie es erneut", + "taskNotCompleted": "Aufgabe ist nicht abgeschlossen", + "completeTaskDependencies": "Bitte schließen Sie die Aufgabenabhängigkeiten ab, bevor Sie fortfahren" } diff --git a/worklenz-frontend/public/locales/en/kanban-board.json b/worklenz-frontend/public/locales/en/kanban-board.json index 018d52a9..77659152 100644 --- a/worklenz-frontend/public/locales/en/kanban-board.json +++ b/worklenz-frontend/public/locales/en/kanban-board.json @@ -40,5 +40,14 @@ "nextWeek": "Next week", "noSubtasks": "No subtasks", "showSubtasks": "Show subtasks", - "hideSubtasks": "Hide subtasks" + "hideSubtasks": "Hide subtasks", + + "errorLoadingTasks": "Error loading tasks", + "noTasksFound": "No tasks found", + "loadingFilters": "Loading filters...", + "failedToUpdateColumnOrder": "Failed to update column order", + "failedToUpdatePhaseOrder": "Failed to update phase order", + "pleaseTryAgain": "Please try again", + "taskNotCompleted": "Task is not completed", + "completeTaskDependencies": "Please complete the task dependencies before proceeding" } diff --git a/worklenz-frontend/public/locales/es/kanban-board.json b/worklenz-frontend/public/locales/es/kanban-board.json index c49a02ca..df4f2b1e 100644 --- a/worklenz-frontend/public/locales/es/kanban-board.json +++ b/worklenz-frontend/public/locales/es/kanban-board.json @@ -37,5 +37,17 @@ "noDueDate": "Sin fecha de vencimiento", "save": "Guardar", "clear": "Limpiar", - "nextWeek": "Próxima semana" + "nextWeek": "Próxima semana", + "noSubtasks": "Sin subtareas", + "showSubtasks": "Mostrar subtareas", + "hideSubtasks": "Ocultar subtareas", + + "errorLoadingTasks": "Error al cargar tareas", + "noTasksFound": "No se encontraron tareas", + "loadingFilters": "Cargando filtros...", + "failedToUpdateColumnOrder": "Error al actualizar el orden de las columnas", + "failedToUpdatePhaseOrder": "Error al actualizar el orden de las fases", + "pleaseTryAgain": "Por favor, inténtalo de nuevo", + "taskNotCompleted": "La tarea no está completada", + "completeTaskDependencies": "Por favor, completa las dependencias de la tarea antes de continuar" } diff --git a/worklenz-frontend/public/locales/pt/kanban-board.json b/worklenz-frontend/public/locales/pt/kanban-board.json index f290258e..5bac3adb 100644 --- a/worklenz-frontend/public/locales/pt/kanban-board.json +++ b/worklenz-frontend/public/locales/pt/kanban-board.json @@ -37,5 +37,17 @@ "noDueDate": "Sem data de vencimento", "save": "Salvar", "clear": "Limpar", - "nextWeek": "Próxima semana" + "nextWeek": "Próxima semana", + "noSubtasks": "Sem subtarefas", + "showSubtasks": "Mostrar subtarefas", + "hideSubtasks": "Ocultar subtarefas", + + "errorLoadingTasks": "Erro ao carregar tarefas", + "noTasksFound": "Nenhuma tarefa encontrada", + "loadingFilters": "Carregando filtros...", + "failedToUpdateColumnOrder": "Falha ao atualizar a ordem das colunas", + "failedToUpdatePhaseOrder": "Falha ao atualizar a ordem das fases", + "pleaseTryAgain": "Por favor, tente novamente", + "taskNotCompleted": "Tarefa não está concluída", + "completeTaskDependencies": "Por favor, complete as dependências da tarefa antes de prosseguir" } diff --git a/worklenz-frontend/public/locales/zh/kanban-board.json b/worklenz-frontend/public/locales/zh/kanban-board.json index 4c51dfa8..20c7cb08 100644 --- a/worklenz-frontend/public/locales/zh/kanban-board.json +++ b/worklenz-frontend/public/locales/zh/kanban-board.json @@ -23,5 +23,24 @@ "deleteStatusTitle": "删除状态", "deleteStatusContent": "您确定要删除此状态吗?此操作无法撤销。", "deletePhaseTitle": "删除阶段", - "deletePhaseContent": "您确定要删除此阶段吗?此操作无法撤销。" + "deletePhaseContent": "您确定要删除此阶段吗?此操作无法撤销。", + "untitledSection": "未命名部分", + "unmapped": "未映射", + "clickToChangeDate": "点击更改日期", + "noDueDate": "无截止日期", + "save": "保存", + "clear": "清除", + "nextWeek": "下周", + "noSubtasks": "无子任务", + "showSubtasks": "显示子任务", + "hideSubtasks": "隐藏子任务", + + "errorLoadingTasks": "加载任务时出错", + "noTasksFound": "未找到任务", + "loadingFilters": "正在加载过滤器...", + "failedToUpdateColumnOrder": "更新列顺序失败", + "failedToUpdatePhaseOrder": "更新阶段顺序失败", + "pleaseTryAgain": "请重试", + "taskNotCompleted": "任务未完成", + "completeTaskDependencies": "请先完成任务依赖项,然后再继续" } \ No newline at end of file diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx index 48219464..2b542eeb 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx @@ -24,8 +24,10 @@ import { useTaskSocketHandlers } from '@/hooks/useTaskSocketHandlers'; import { phasesApiService } from '@/api/taskAttributes/phases/phases.api.service'; import { ITaskListGroup } from '@/types/tasks/taskList.types'; import { fetchPhasesByProjectId, updatePhaseListOrder } from '@/features/projects/singleProject/phase/phases.slice'; +import { useTranslation } from 'react-i18next'; const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ projectId }) => { + const { t } = useTranslation('kanban-board'); const dispatch = useDispatch(); const authService = useAuthService(); const { socket } = useSocket(); @@ -103,14 +105,14 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project const columnOrder = reorderedGroups.map(group => group.id); const requestBody = { status_order: columnOrder }; const response = await statusApiService.updateStatusOrder(requestBody, projectId); - if (!response.done) { - // Revert the change if API call fails - const revertedGroups = [...reorderedGroups]; - const [movedBackGroup] = revertedGroups.splice(toIdx, 1); - revertedGroups.splice(fromIdx, 0, movedBackGroup); - dispatch(reorderGroups({ fromIndex: toIdx, toIndex: fromIdx, reorderedGroups: revertedGroups })); - alertService.error('Failed to update column order', 'Please try again'); - } + if (!response.done) { + // Revert the change if API call fails + const revertedGroups = [...reorderedGroups]; + const [movedBackGroup] = revertedGroups.splice(toIdx, 1); + revertedGroups.splice(fromIdx, 0, movedBackGroup); + dispatch(reorderGroups({ fromIndex: toIdx, toIndex: fromIdx, reorderedGroups: revertedGroups })); + alertService.error(t('failedToUpdateColumnOrder'), t('pleaseTryAgain')); + } } else if (groupBy === 'phase') { const newPhaseList = [...phaseList]; const [movedItem] = newPhaseList.splice(fromIdx, 1); @@ -124,7 +126,7 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project }; const response = await phasesApiService.updatePhaseOrder(projectId, requestBody); if (!response.done) { - alertService.error('Failed to update phase order', 'Please try again'); + alertService.error(t('failedToUpdatePhaseOrder'), t('pleaseTryAgain')); } } } catch (error) { @@ -133,7 +135,7 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project const [movedBackGroup] = revertedGroups.splice(toIdx, 1); revertedGroups.splice(fromIdx, 0, movedBackGroup); dispatch(reorderGroups({ fromIndex: toIdx, toIndex: fromIdx, reorderedGroups: revertedGroups })); - alertService.error('Failed to update column order', 'Please try again'); + alertService.error(t('failedToUpdateColumnOrder'), t('pleaseTryAgain')); logger.error('Failed to update column order', error); } @@ -143,11 +145,23 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project // Utility to recalculate all task orders for all groups function getAllTaskUpdates(allGroups: ITaskListGroup[], groupBy: string) { - const taskUpdates = []; + const taskUpdates: Array<{ + task_id: string | undefined; + sort_order: number; + status_id?: string; + priority_id?: string; + phase_id?: string; + }> = []; let currentSortOrder = 0; for (const group of allGroups) { for (const task of group.tasks) { - const update = { + const update: { + task_id: string | undefined; + sort_order: number; + status_id?: string; + priority_id?: string; + phase_id?: string; + } = { task_id: task.id, sort_order: currentSortOrder, }; @@ -200,8 +214,8 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project const canContinue = await checkTaskDependencyStatus(movedTask.id, targetGroupId); if (!canContinue) { alertService.error( - 'Task is not completed', - 'Please complete the task dependencies before proceeding' + t('taskNotCompleted'), + t('completeTaskDependencies') ); return; } @@ -295,7 +309,7 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project team_id: teamId, from_index: taskIdx, to_index: insertIdx, - to_last_index: insertIdx === (targetGroup.id === sourceGroup.id ? newTaskGroups.find(g => g.id === targetGroup.id)?.tasks.length - 1 : targetGroup.tasks.length), + to_last_index: insertIdx === (targetGroup.id === sourceGroup.id ? (newTaskGroups.find(g => g.id === targetGroup.id)?.tasks.length || 0) - 1 : targetGroup.tasks.length), task: { id: movedTask.id, project_id: movedTask.project_id || projectId, @@ -336,7 +350,7 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project if (error) { return ( - + ); } @@ -344,7 +358,7 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project return ( <>
- Loading filters...
}> + {t('loadingFilters')}}> @@ -358,7 +372,7 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project ) : taskGroups.length === 0 ? ( - + ) : (
From 22d2023e2a3ee3f3bfa51fb798839422d0b617b2 Mon Sep 17 00:00:00 2001 From: shancds Date: Thu, 17 Jul 2025 11:54:33 +0530 Subject: [PATCH 3/5] Update phase handling in EnhancedKanbanBoardNativeDnD component - Modified phase update logic to prevent setting phase_id for 'Unmapped' phases, ensuring only valid phases are processed. - Cleaned up unnecessary whitespace in the task reordering section for improved code clarity. --- .../EnhancedKanbanBoardNativeDnD.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx index 2b542eeb..52703297 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx @@ -167,7 +167,7 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project }; if (groupBy === 'status') update.status_id = group.id; else if (groupBy === 'priority') update.priority_id = group.id; - else if (groupBy === 'phase') update.phase_id = group.id; + else if (groupBy === 'phase' && group.name !== 'Unmapped') update.phase_id = group.id; taskUpdates.push(update); currentSortOrder++; } @@ -240,7 +240,6 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project if (insertIdx > updatedTasks.length) insertIdx = updatedTasks.length; updatedTasks.splice(insertIdx, 0, movedTask); // Insert at new position - dispatch(reorderTasks({ activeGroupId: sourceGroup.id, overGroupId: targetGroup.id, From 7f71e8952b6aed40d0b78e2e000b466e73c9e499 Mon Sep 17 00:00:00 2001 From: shancds Date: Thu, 17 Jul 2025 12:52:40 +0530 Subject: [PATCH 4/5] Enhance EnhancedKanbanBoardNativeDnD with task priority updates and socket integration - Added functionality to handle task priority changes, emitting updates via socket for real-time synchronization. - Updated the EnhancedKanbanBoardNativeDnD component to include new logic for managing task priorities within the drag-and-drop interface. - Cleaned up console log statements in the useTaskSocketHandlers hook for improved performance monitoring. --- .../EnhancedKanbanBoardNativeDnD.tsx | 41 +++++++++++++------ .../enhanced-kanban/enhanced-kanban.slice.ts | 2 - .../src/hooks/useTaskSocketHandlers.ts | 35 ---------------- .../utils/enhanced-performance-monitoring.ts | 6 +-- 4 files changed, 32 insertions(+), 52 deletions(-) diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx index 52703297..6f8bfe4c 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx @@ -8,7 +8,7 @@ import ImprovedTaskFilters from '../../task-management/improved-task-filters'; import Card from 'antd/es/card'; import Spin from 'antd/es/spin'; import Empty from 'antd/es/empty'; -import { reorderGroups, reorderEnhancedKanbanGroups, reorderTasks, reorderEnhancedKanbanTasks, fetchEnhancedKanbanLabels, fetchEnhancedKanbanGroups, fetchEnhancedKanbanTaskAssignees } from '@/features/enhanced-kanban/enhanced-kanban.slice'; +import { reorderGroups, reorderEnhancedKanbanGroups, reorderTasks, reorderEnhancedKanbanTasks, fetchEnhancedKanbanLabels, fetchEnhancedKanbanGroups, fetchEnhancedKanbanTaskAssignees, updateEnhancedKanbanTaskPriority } from '@/features/enhanced-kanban/enhanced-kanban.slice'; import { fetchStatusesCategories } from '@/features/taskAttributes/taskStatusSlice'; import { useAppSelector } from '@/hooks/useAppSelector'; import KanbanGroup from './KanbanGroup'; @@ -25,6 +25,7 @@ import { phasesApiService } from '@/api/taskAttributes/phases/phases.api.service import { ITaskListGroup } from '@/types/tasks/taskList.types'; import { fetchPhasesByProjectId, updatePhaseListOrder } from '@/features/projects/singleProject/phase/phases.slice'; import { useTranslation } from 'react-i18next'; +import { ITaskListPriorityChangeResponse } from '@/types/tasks/task-list-priority.types'; const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ projectId }) => { const { t } = useTranslation('kanban-board'); @@ -62,7 +63,7 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project if (!statusCategories.length) { dispatch(fetchStatusesCategories() as any); } - if ( groupBy === 'phase' && !phaseList.length) { + if (groupBy === 'phase' && !phaseList.length) { dispatch(fetchPhasesByProjectId(projectId) as any); } }, [dispatch, projectId]); @@ -102,10 +103,10 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project // API call for group order try { if (groupBy === 'status') { - const columnOrder = reorderedGroups.map(group => group.id); - const requestBody = { status_order: columnOrder }; - const response = await statusApiService.updateStatusOrder(requestBody, projectId); - if (!response.done) { + const columnOrder = reorderedGroups.map(group => group.id); + const requestBody = { status_order: columnOrder }; + const response = await statusApiService.updateStatusOrder(requestBody, projectId); + if (!response.done) { // Revert the change if API call fails const revertedGroups = [...reorderedGroups]; const [movedBackGroup] = revertedGroups.splice(toIdx, 1); @@ -329,6 +330,22 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project }) ); } + if (groupBy === 'priority' && movedTask.id) { + socket?.emit( + SocketEvents.TASK_PRIORITY_CHANGE.toString(), + JSON.stringify({ + task_id: movedTask.id, + priority_id: targetGroupId, + team_id: teamId, + }) + ); + socket?.once( + SocketEvents.TASK_PRIORITY_CHANGE.toString(), + (data: ITaskListPriorityChangeResponse) => { + dispatch(updateEnhancedKanbanTaskPriority(data)); + } + ); + } } setDraggedTaskId(null); @@ -363,12 +380,12 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project
{loadingGroups ? ( -
-
-
-
-
-
+
+
+
+
+
+
) : taskGroups.length === 0 ? ( 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 3ccac5d2..9bbf5694 100644 --- a/worklenz-frontend/src/features/enhanced-kanban/enhanced-kanban.slice.ts +++ b/worklenz-frontend/src/features/enhanced-kanban/enhanced-kanban.slice.ts @@ -575,7 +575,6 @@ const enhancedKanbanSlice = createSlice({ action: PayloadAction ) => { const { id, priority_id, color_code, color_code_dark } = action.payload; - // Find the task in any group const taskInfo = findTaskInAllGroups(state.taskGroups, id); if (!taskInfo || !priority_id) return; @@ -603,7 +602,6 @@ const enhancedKanbanSlice = createSlice({ // Update cache state.taskCache[id] = task; }, - // Enhanced Kanban assignee update (for use in task drawer dropdown) updateEnhancedKanbanTaskAssignees: ( state, diff --git a/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts b/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts index 2cb419a9..598b7040 100644 --- a/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts +++ b/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts @@ -285,23 +285,7 @@ export const useTaskSocketHandlers = () => { ); } - console.log('🔄 Status change group movement debug:', { - taskId: response.id, - newStatusValue, - currentGroupId: currentGroup?.id, - currentGroupValue: currentGroup?.groupValue, - currentGroupTitle: currentGroup?.title, - targetGroupId: targetGroup?.id, - targetGroupValue: targetGroup?.groupValue, - targetGroupTitle: targetGroup?.title, - allGroups: groups.map(g => ({ id: g.id, title: g.title, groupValue: g.groupValue })) - }); - if (currentGroup && targetGroup && currentGroup.id !== targetGroup.id) { - console.log('✅ Moving task between groups:', { - from: currentGroup.title, - to: targetGroup.title - }); // Use the action to move task between groups dispatch( moveTaskBetweenGroups({ @@ -448,12 +432,6 @@ export const useTaskSocketHandlers = () => { } if (currentGroup && targetGroup && currentGroup.id !== targetGroup.id) { - console.log('🔄 Moving task between priority groups:', { - taskId: response.id, - from: currentGroup.title, - to: targetGroup.title, - newPriorityValue - }); dispatch( moveTaskBetweenGroups({ taskId: response.id, @@ -603,12 +581,6 @@ export const useTaskSocketHandlers = () => { } if (currentGroup && targetGroup && currentGroup.id !== targetGroup.id) { - console.log('🔄 Moving task between phase groups:', { - taskId, - from: currentGroup.title, - to: targetGroup.title, - newPhaseValue - }); dispatch( moveTaskBetweenGroups({ taskId: taskId, @@ -925,10 +897,6 @@ export const useTaskSocketHandlers = () => { // Handler for TASK_ASSIGNEES_CHANGE (fallback event with limited data) const handleTaskAssigneesChange = useCallback((data: { assigneeIds: string[] }) => { if (!data || !data.assigneeIds) return; - - // This event only provides assignee IDs, so we update what we can - // The full assignee data will come from QUICK_ASSIGNEES_UPDATE - // console.log('🔄 Task assignees change (limited data):', data); }, []); // Handler for timer start events @@ -994,9 +962,6 @@ export const useTaskSocketHandlers = () => { try { if (!Array.isArray(data) || data.length === 0) return; - // DEBUG: Log the data received from the backend - console.log('[TASK_SORT_ORDER_CHANGE] Received data:', data); - // Get canonical lists from Redux const state = store.getState(); const priorityList = state.priorityReducer?.priorities || []; diff --git a/worklenz-frontend/src/utils/enhanced-performance-monitoring.ts b/worklenz-frontend/src/utils/enhanced-performance-monitoring.ts index 9e50424e..c30d189f 100644 --- a/worklenz-frontend/src/utils/enhanced-performance-monitoring.ts +++ b/worklenz-frontend/src/utils/enhanced-performance-monitoring.ts @@ -86,7 +86,7 @@ export class EnhancedPerformanceMonitor { this.collectInitialMetrics(); this.startPeriodicCollection(); - console.log('🚀 Enhanced performance monitoring started'); + // console.log('🚀 Enhanced performance monitoring started'); } // Stop monitoring and cleanup @@ -97,7 +97,7 @@ export class EnhancedPerformanceMonitor { this.cleanupObservers(); this.clearIntervals(); - console.log('🛑 Enhanced performance monitoring stopped'); + // console.log('🛑 Enhanced performance monitoring stopped'); } // Setup performance observers @@ -357,7 +357,7 @@ export class EnhancedPerformanceMonitor { const recent = this.metrics.slice(-10); // Last 10 metrics const report = this.analyzeMetrics(recent); - console.log('📊 Performance Report:', report); + // console.log('📊 Performance Report:', report); // Check for performance issues this.checkPerformanceIssues(report); From 1709fad733b14a707f4057e9f93079073ffa4cf2 Mon Sep 17 00:00:00 2001 From: shancds Date: Thu, 17 Jul 2025 15:51:23 +0530 Subject: [PATCH 5/5] Add drag-and-drop data transfer support in EnhancedKanbanBoardNativeDnD --- .../EnhancedKanbanBoardNativeDnD.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx index 6f8bfe4c..ef587923 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx @@ -82,6 +82,9 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project setDraggedGroupId(groupId); setDragType('group'); e.dataTransfer.effectAllowed = 'move'; + try { + e.dataTransfer.setData('text/plain', groupId); + } catch {} }; const handleGroupDragOver = (e: React.DragEvent) => { if (dragType !== 'group') return; @@ -182,6 +185,9 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project setDraggedTaskGroupId(groupId); setDragType('task'); e.dataTransfer.effectAllowed = 'move'; + try { + e.dataTransfer.setData('text/plain', taskId); + } catch {} }; const handleTaskDragOver = (e: React.DragEvent, groupId: string, taskIdx: number | null) => { if (dragType !== 'task') return;