feat(routes): implement lazy loading and suspense fallback for route components

- Refactored account setup, admin center, reporting, and settings routes to utilize React's lazy loading for improved performance.
- Wrapped route components in Suspense with a fallback UI to enhance user experience during loading states.
- Removed unused drag-and-drop CSS file to clean up the codebase.
This commit is contained in:
chamikaJ
2025-07-03 10:34:06 +05:30
parent c19c1c2f34
commit e26f16bbc2
13 changed files with 104 additions and 79 deletions

View File

@@ -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: <AccountSetup />,
element: (
<Suspense fallback={<SuspenseFallback />}>
<AccountSetup />
</Suspense>
),
};
export default accountSetupRoute;

View File

@@ -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: (
<Suspense fallback={<SuspenseFallback />}>
{item.element}
</Suspense>
),
})),
},
];

View File

@@ -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: <ReportingLayout />,
children: flattenedItems.map(item => ({
path: item.endpoint,
element: item.element,
element: (
<Suspense fallback={<SuspenseFallback />}>
{item.element}
</Suspense>
),
})),
},
];

View File

@@ -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: <SettingsLayout />,
children: settingsItems.map(item => ({
path: item.endpoint,
element: <SettingsGuard adminRequired={!!item.adminOnly}>{item.element}</SettingsGuard>,
element: (
<Suspense fallback={<SuspenseFallback />}>
<SettingsGuard adminRequired={!!item.adminOnly}>{item.element}</SettingsGuard>
</Suspense>
),
})),
},
];

View File

@@ -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;
}

View File

@@ -27,6 +27,8 @@ import {
fetchTasksV3,
selectTaskGroupsV3,
selectCurrentGroupingV3,
fetchSubTasks,
toggleTaskExpansion,
} from '@/features/task-management/task-management.slice';
import {
selectTaskGroups,

View File

@@ -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<string>) => {
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;
}
});
},
});

View File

@@ -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 = {

View File

@@ -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 = {

View File

@@ -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 = {

View File

@@ -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;

View File

@@ -266,6 +266,7 @@ const TaskListTableWrapper = ({
tableId={tableId}
activeId={activeId}
groupBy={groupBy}
isOver={isOver}
/>
</Collapsible>
</Flex>

View File

@@ -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<TaskListTableProps> = ({ taskList, tableId, active
// Add drag state
const [dragActiveId, setDragActiveId] = useState<string | null>(null);
const [placeholderIndex, setPlaceholderIndex] = useState<number | null>(null);
// Configure sensors for drag and drop
const sensors = useSensors(
@@ -1640,6 +1642,7 @@ const TaskListTable: React.FC<TaskListTableProps> = ({ 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<TaskListTableProps> = ({ taskList, tableId, active
sensors={sensors}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver} // Add this line
autoScroll={false} // Disable auto-scroll animations
>
<SortableContext
@@ -1858,12 +1862,22 @@ const TaskListTable: React.FC<TaskListTableProps> = ({ 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 (
<React.Fragment key={updatedTask.id}>
{renderTaskRow(updatedTask)}
{placeholderIndex === index && (
<tr className="placeholder-row">
<td colSpan={visibleColumns.length + 2}>
<div className="h-10 border-2 border-dashed border-blue-400 rounded-md flex items-center justify-center text-blue-500">
Drop task here
</div>
</td>
</tr>
)}
{!isDraggingCurrent && renderTaskRow(updatedTask)}
{updatedTask.show_sub_tasks && (
<>
{updatedTask?.sub_tasks?.map(subtask =>
@@ -1910,6 +1924,15 @@ const TaskListTable: React.FC<TaskListTableProps> = ({ taskList, tableId, active
</td>
</tr>
)}
{placeholderIndex === displayTasks.length && (
<tr className="placeholder-row">
<td colSpan={visibleColumns.length + 2}>
<div className="h-10 border-2 border-dashed border-blue-400 rounded-md flex items-center justify-center text-blue-500">
Drop task here
</div>
</td>
</tr>
)}
</tbody>
</table>
</div>