diff --git a/worklenz-frontend/src/app/routes/account-setup-routes.tsx b/worklenz-frontend/src/app/routes/account-setup-routes.tsx
index b7f2fcaf..6df71a4d 100644
--- a/worklenz-frontend/src/app/routes/account-setup-routes.tsx
+++ b/worklenz-frontend/src/app/routes/account-setup-routes.tsx
@@ -1,9 +1,15 @@
import { RouteObject } from 'react-router-dom';
-import AccountSetup from '@/pages/account-setup/account-setup';
+import { lazy, Suspense } from 'react';
+import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback';
+const AccountSetup = lazy(() => import('@/pages/account-setup/account-setup'));
const accountSetupRoute: RouteObject = {
path: '/worklenz/setup',
- element: ,
+ element: (
+ }>
+
+
+ ),
};
export default accountSetupRoute;
diff --git a/worklenz-frontend/src/app/routes/admin-center-routes.tsx b/worklenz-frontend/src/app/routes/admin-center-routes.tsx
index 2e670a7f..67ad5b26 100644
--- a/worklenz-frontend/src/app/routes/admin-center-routes.tsx
+++ b/worklenz-frontend/src/app/routes/admin-center-routes.tsx
@@ -1,8 +1,10 @@
import { RouteObject } from 'react-router-dom';
+import { Suspense } from 'react';
import AdminCenterLayout from '@/layouts/admin-center-layout';
import { adminCenterItems } from '@/pages/admin-center/admin-center-constants';
import { Navigate } from 'react-router-dom';
import { useAuthService } from '@/hooks/useAuth';
+import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback';
const AdminCenterGuard = ({ children }: { children: React.ReactNode }) => {
const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin();
@@ -24,7 +26,11 @@ const adminCenterRoutes: RouteObject[] = [
),
children: adminCenterItems.map(item => ({
path: item.endpoint,
- element: item.element,
+ element: (
+ }>
+ {item.element}
+
+ ),
})),
},
];
diff --git a/worklenz-frontend/src/app/routes/reporting-routes.tsx b/worklenz-frontend/src/app/routes/reporting-routes.tsx
index 01f83e9b..2e62de18 100644
--- a/worklenz-frontend/src/app/routes/reporting-routes.tsx
+++ b/worklenz-frontend/src/app/routes/reporting-routes.tsx
@@ -1,6 +1,8 @@
import { RouteObject } from 'react-router-dom';
+import { Suspense } from 'react';
import ReportingLayout from '@/layouts/ReportingLayout';
import { ReportingMenuItems, reportingsItems } from '@/lib/reporting/reporting-constants';
+import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback';
// function to flatten nested menu items
const flattenItems = (items: ReportingMenuItems[]): ReportingMenuItems[] => {
@@ -20,7 +22,11 @@ const reportingRoutes: RouteObject[] = [
element: ,
children: flattenedItems.map(item => ({
path: item.endpoint,
- element: item.element,
+ element: (
+ }>
+ {item.element}
+
+ ),
})),
},
];
diff --git a/worklenz-frontend/src/app/routes/settings-routes.tsx b/worklenz-frontend/src/app/routes/settings-routes.tsx
index 9999841b..78f59f88 100644
--- a/worklenz-frontend/src/app/routes/settings-routes.tsx
+++ b/worklenz-frontend/src/app/routes/settings-routes.tsx
@@ -1,8 +1,10 @@
import { RouteObject } from 'react-router-dom';
import { Navigate } from 'react-router-dom';
+import { Suspense } from 'react';
import SettingsLayout from '@/layouts/SettingsLayout';
import { settingsItems } from '@/lib/settings/settings-constants';
import { useAuthService } from '@/hooks/useAuth';
+import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback';
const SettingsGuard = ({
children,
@@ -26,7 +28,11 @@ const settingsRoutes: RouteObject[] = [
element: ,
children: settingsItems.map(item => ({
path: item.endpoint,
- element: {item.element},
+ element: (
+ }>
+ {item.element}
+
+ ),
})),
},
];
diff --git a/worklenz-frontend/src/components/task-management/drag-drop-optimized.css b/worklenz-frontend/src/components/task-management/drag-drop-optimized.css
index 6f21e39c..e69de29b 100644
--- a/worklenz-frontend/src/components/task-management/drag-drop-optimized.css
+++ b/worklenz-frontend/src/components/task-management/drag-drop-optimized.css
@@ -1,40 +0,0 @@
-/* MINIMAL DRAG AND DROP CSS - SHOW ONLY TASK NAME */
-
-/* Basic drag handle styling */
-.drag-handle-optimized {
- cursor: grab;
- opacity: 0.6;
- transition: opacity 0.2s ease;
-}
-
-.drag-handle-optimized:hover {
- opacity: 1;
-}
-
-.drag-handle-optimized:active {
- cursor: grabbing;
-}
-
-/* Simple drag overlay - just show task name */
-[data-dnd-overlay] {
- background: white;
- border: 1px solid #d9d9d9;
- border-radius: 4px;
- padding: 8px 12px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
- pointer-events: none;
- z-index: 9999;
-}
-
-/* Dark mode support for drag overlay */
-.dark [data-dnd-overlay],
-[data-theme="dark"] [data-dnd-overlay] {
- background: #1f1f1f;
- border-color: #404040;
- color: white;
-}
-
-/* Hide drag handle during drag */
-[data-dnd-dragging="true"] .drag-handle-optimized {
- opacity: 0;
-}
diff --git a/worklenz-frontend/src/components/task-management/task-list-board.tsx b/worklenz-frontend/src/components/task-management/task-list-board.tsx
index ca89af5e..8c82609f 100644
--- a/worklenz-frontend/src/components/task-management/task-list-board.tsx
+++ b/worklenz-frontend/src/components/task-management/task-list-board.tsx
@@ -27,6 +27,8 @@ import {
fetchTasksV3,
selectTaskGroupsV3,
selectCurrentGroupingV3,
+ fetchSubTasks,
+ toggleTaskExpansion,
} from '@/features/task-management/task-management.slice';
import {
selectTaskGroups,
diff --git a/worklenz-frontend/src/features/task-management/task-management.slice.ts b/worklenz-frontend/src/features/task-management/task-management.slice.ts
index 20493847..29199214 100644
--- a/worklenz-frontend/src/features/task-management/task-management.slice.ts
+++ b/worklenz-frontend/src/features/task-management/task-management.slice.ts
@@ -343,6 +343,14 @@ export const moveTaskToGroupWithAPI = createAsyncThunk(
}
);
+// Add action to update task with subtasks
+export const updateTaskWithSubtasks = createAsyncThunk(
+ 'taskManagement/updateTaskWithSubtasks',
+ async ({ taskId, subtasks }: { taskId: string; subtasks: any[] }, { getState }) => {
+ return { taskId, subtasks };
+ }
+);
+
const taskManagementSlice = createSlice({
name: 'taskManagement',
initialState: tasksAdapter.getInitialState(initialState),
@@ -627,8 +635,7 @@ const taskManagementSlice = createSlice({
return tasksAdapter.getInitialState(initialState);
},
toggleTaskExpansion: (state, action: PayloadAction) => {
- const taskId = action.payload;
- const task = state.entities[taskId];
+ const task = state.entities[action.payload];
if (task) {
task.show_sub_tasks = !task.show_sub_tasks;
}
@@ -700,6 +707,14 @@ const taskManagementSlice = createSlice({
})
.addCase(refreshTaskProgress.rejected, (state, action) => {
state.error = (action.payload as string) || 'Failed to refresh task progress';
+ })
+ .addCase(updateTaskWithSubtasks.fulfilled, (state, action) => {
+ const { taskId, subtasks } = action.payload;
+ const task = state.entities[taskId];
+ if (task) {
+ task.sub_tasks = subtasks;
+ task.show_sub_tasks = true;
+ }
});
},
});
diff --git a/worklenz-frontend/src/lib/reporting/reporting-constants.ts b/worklenz-frontend/src/lib/reporting/reporting-constants.ts
index 25d23cbd..dfb975f9 100644
--- a/worklenz-frontend/src/lib/reporting/reporting-constants.ts
+++ b/worklenz-frontend/src/lib/reporting/reporting-constants.ts
@@ -1,11 +1,11 @@
-import React, { ReactNode } from 'react';
-import OverviewReports from '@/pages/reporting/overview-reports/overview-reports';
-import ProjectsReports from '@/pages/reporting/projects-reports/projects-reports';
-import MembersReports from '@/pages/reporting/members-reports/members-reports';
-import OverviewTimeReports from '@/pages/reporting/timeReports/overview-time-reports';
-import ProjectsTimeReports from '@/pages/reporting/timeReports/projects-time-reports';
-import MembersTimeReports from '@/pages/reporting/timeReports/members-time-reports';
-import EstimatedVsActualTimeReports from '@/pages/reporting/timeReports/estimated-vs-actual-time-reports';
+import React, { ReactNode, lazy } from 'react';
+const OverviewReports = lazy(() => import('@/pages/reporting/overview-reports/overview-reports'));
+const ProjectsReports = lazy(() => import('@/pages/reporting/projects-reports/projects-reports'));
+const MembersReports = lazy(() => import('@/pages/reporting/members-reports/members-reports'));
+const OverviewTimeReports = lazy(() => import('@/pages/reporting/timeReports/overview-time-reports'));
+const ProjectsTimeReports = lazy(() => import('@/pages/reporting/timeReports/projects-time-reports'));
+const MembersTimeReports = lazy(() => import('@/pages/reporting/timeReports/members-time-reports'));
+const EstimatedVsActualTimeReports = lazy(() => import('@/pages/reporting/timeReports/estimated-vs-actual-time-reports'));
// Type definition for a menu item
export type ReportingMenuItems = {
diff --git a/worklenz-frontend/src/lib/settings/settings-constants.ts b/worklenz-frontend/src/lib/settings/settings-constants.ts
index 7f9beb2a..8823bd7e 100644
--- a/worklenz-frontend/src/lib/settings/settings-constants.ts
+++ b/worklenz-frontend/src/lib/settings/settings-constants.ts
@@ -13,20 +13,20 @@ import {
UserSwitchOutlined,
BulbOutlined,
} from '@ant-design/icons';
-import React, { ReactNode } from 'react';
-import ProfileSettings from '../../pages/settings/profile/profile-settings';
-import NotificationsSettings from '../../pages/settings/notifications/notifications-settings';
-import ClientsSettings from '../../pages/settings/clients/clients-settings';
-import JobTitlesSettings from '@/pages/settings/job-titles/job-titles-settings';
-import LabelsSettings from '../../pages/settings/labels/labels-settings';
-import CategoriesSettings from '../../pages/settings/categories/categories-settings';
-import ProjectTemplatesSettings from '@/pages/settings/project-templates/project-templates-settings';
-import TaskTemplatesSettings from '@/pages/settings/task-templates/task-templates-settings';
-import TeamMembersSettings from '@/pages/settings/team-members/team-members-settings';
-import TeamsSettings from '../../pages/settings/teams/teams-settings';
-import ChangePassword from '@/pages/settings/change-password/change-password';
-import LanguageAndRegionSettings from '@/pages/settings/language-and-region/language-and-region-settings';
-import AppearanceSettings from '@/pages/settings/appearance/appearance-settings';
+import React, { ReactNode, lazy } from 'react';
+const ProfileSettings = lazy(() => import('../../pages/settings/profile/profile-settings'));
+const NotificationsSettings = lazy(() => import('../../pages/settings/notifications/notifications-settings'));
+const ClientsSettings = lazy(() => import('../../pages/settings/clients/clients-settings'));
+const JobTitlesSettings = lazy(() => import('@/pages/settings/job-titles/job-titles-settings'));
+const LabelsSettings = lazy(() => import('../../pages/settings/labels/labels-settings'));
+const CategoriesSettings = lazy(() => import('../../pages/settings/categories/categories-settings'));
+const ProjectTemplatesSettings = lazy(() => import('@/pages/settings/project-templates/project-templates-settings'));
+const TaskTemplatesSettings = lazy(() => import('@/pages/settings/task-templates/task-templates-settings'));
+const TeamMembersSettings = lazy(() => import('@/pages/settings/team-members/team-members-settings'));
+const TeamsSettings = lazy(() => import('../../pages/settings/teams/teams-settings'));
+const ChangePassword = lazy(() => import('@/pages/settings/change-password/change-password'));
+const LanguageAndRegionSettings = lazy(() => import('@/pages/settings/language-and-region/language-and-region-settings'));
+const AppearanceSettings = lazy(() => import('@/pages/settings/appearance/appearance-settings'));
// type of menu item in settings sidebar
type SettingMenuItems = {
diff --git a/worklenz-frontend/src/pages/admin-center/admin-center-constants.ts b/worklenz-frontend/src/pages/admin-center/admin-center-constants.ts
index 77161db2..1cee4eb8 100644
--- a/worklenz-frontend/src/pages/admin-center/admin-center-constants.ts
+++ b/worklenz-frontend/src/pages/admin-center/admin-center-constants.ts
@@ -5,12 +5,12 @@ import {
TeamOutlined,
UserOutlined,
} from '@ant-design/icons';
-import React, { ReactNode } from 'react';
-import Overview from './overview/overview';
-import Users from './users/users';
-import Teams from './teams/teams';
-import Billing from './billing/billing';
-import Projects from './projects/projects';
+import React, { ReactNode, lazy } from 'react';
+const Overview = lazy(() => import('./overview/overview'));
+const Users = lazy(() => import('./users/users'));
+const Teams = lazy(() => import('./teams/teams'));
+const Billing = lazy(() => import('./billing/billing'));
+const Projects = lazy(() => import('./projects/projects'));
// type of a menu item in admin center sidebar
type AdminCenterMenuItems = {
diff --git a/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-task-cell/task-list-task-cell.tsx b/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-task-cell/task-list-task-cell.tsx
index 553843bf..293efc05 100644
--- a/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-task-cell/task-list-task-cell.tsx
+++ b/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-task-cell/task-list-task-cell.tsx
@@ -20,7 +20,7 @@ import { setSelectedTaskId, setShowTaskDrawer } from '@/features/task-drawer/tas
import { useState, useRef, useEffect } from 'react';
import { useSocket } from '@/socket/socketContext';
import { SocketEvents } from '@/shared/socket-events';
-import { fetchSubTasks } from '@/features/tasks/tasks.slice';
+import { fetchSubTasks } from '@/features/task-management/task-management.slice';
type TaskListTaskCellProps = {
task: IProjectTask;
diff --git a/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-wrapper/task-list-table-wrapper.tsx b/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-wrapper/task-list-table-wrapper.tsx
index 784b4b76..bee5c22a 100644
--- a/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-wrapper/task-list-table-wrapper.tsx
+++ b/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-wrapper/task-list-table-wrapper.tsx
@@ -266,6 +266,7 @@ const TaskListTableWrapper = ({
tableId={tableId}
activeId={activeId}
groupBy={groupBy}
+ isOver={isOver}
/>
diff --git a/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table.tsx b/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table.tsx
index a9cec576..5f250577 100644
--- a/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table.tsx
+++ b/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table.tsx
@@ -32,7 +32,7 @@ import {
sortableKeyboardCoordinates,
} from '@dnd-kit/sortable';
import { createPortal } from 'react-dom';
-import { DragEndEvent } from '@dnd-kit/core';
+import { DragOverEvent } from '@dnd-kit/core';
import { List, Card, Avatar, Dropdown, Empty, Divider, Button } from 'antd';
import dayjs from 'dayjs';
@@ -90,6 +90,7 @@ interface TaskListTableProps {
tableId: string;
activeId?: string | null;
groupBy?: string;
+ isOver?: boolean; // Add this line
}
interface DraggableRowProps {
@@ -1291,6 +1292,7 @@ const TaskListTable: React.FC = ({ taskList, tableId, active
// Add drag state
const [dragActiveId, setDragActiveId] = useState(null);
+ const [placeholderIndex, setPlaceholderIndex] = useState(null);
// Configure sensors for drag and drop
const sensors = useSensors(
@@ -1640,6 +1642,7 @@ const TaskListTable: React.FC = ({ taskList, tableId, active
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
setDragActiveId(null);
+ setPlaceholderIndex(null); // Reset placeholder index
if (!over || !active || active.id === over.id) {
return;
@@ -1794,6 +1797,7 @@ const TaskListTable: React.FC = ({ taskList, tableId, active
sensors={sensors}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
+ onDragOver={handleDragOver} // Add this line
autoScroll={false} // Disable auto-scroll animations
>
= ({ taskList, tableId, active
{displayTasks && displayTasks.length > 0 ? (
displayTasks
.filter(task => task?.id) // Filter out tasks without valid IDs
- .map(task => {
+ .map((task, index) => {
const updatedTask = findTaskInGroups(task.id || '') || task;
+ const isDraggingCurrent = dragActiveId === updatedTask.id;
return (
- {renderTaskRow(updatedTask)}
+ {placeholderIndex === index && (
+
+ |
+
+ Drop task here
+
+ |
+
+ )}
+ {!isDraggingCurrent && renderTaskRow(updatedTask)}
{updatedTask.show_sub_tasks && (
<>
{updatedTask?.sub_tasks?.map(subtask =>
@@ -1910,6 +1924,15 @@ const TaskListTable: React.FC = ({ taskList, tableId, active
)}
+ {placeholderIndex === displayTasks.length && (
+
+ |
+
+ Drop task here
+
+ |
+
+ )}