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:
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
),
|
||||
})),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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>
|
||||
),
|
||||
})),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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>
|
||||
),
|
||||
})),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ import {
|
||||
fetchTasksV3,
|
||||
selectTaskGroupsV3,
|
||||
selectCurrentGroupingV3,
|
||||
fetchSubTasks,
|
||||
toggleTaskExpansion,
|
||||
} from '@/features/task-management/task-management.slice';
|
||||
import {
|
||||
selectTaskGroups,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -266,6 +266,7 @@ const TaskListTableWrapper = ({
|
||||
tableId={tableId}
|
||||
activeId={activeId}
|
||||
groupBy={groupBy}
|
||||
isOver={isOver}
|
||||
/>
|
||||
</Collapsible>
|
||||
</Flex>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user