feat(gantt-integration): add SVAR Gantt chart component and related features
- Integrated SVAR Gantt chart into the project view for enhanced task visualization. - Implemented lazy loading for improved performance and reduced initial bundle size. - Added custom styles for light and dark themes to match Worklenz branding. - Updated Redux state management to support Gantt-specific data handling. - Introduced a ResourcePreloader component to preload critical chunks for better navigation performance. - Enhanced project view to support Gantt as a new project view option. - Documented Gantt integration details and usage in README.md.
This commit is contained in:
@@ -10,6 +10,8 @@
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap"
|
||||
rel="stylesheet" />
|
||||
<!-- SVAR Gantt Icons -->
|
||||
<link rel="stylesheet" href="https://cdn.svar.dev/fonts/wxi/wx-icons.css" />
|
||||
<title>Worklenz</title>
|
||||
|
||||
<!-- Environment configuration -->
|
||||
|
||||
13
worklenz-frontend/package-lock.json
generated
13
worklenz-frontend/package-lock.json
generated
@@ -49,7 +49,8 @@
|
||||
"react-window": "^1.8.11",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"tinymce": "^7.7.2",
|
||||
"web-vitals": "^4.2.4"
|
||||
"web-vitals": "^4.2.4",
|
||||
"wx-react-gantt": "^1.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
@@ -7738,6 +7739,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/wx-react-gantt": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/wx-react-gantt/-/wx-react-gantt-1.3.1.tgz",
|
||||
"integrity": "sha512-Ua1hrMXfXENjhTVFBDf9D2mTXsw8BEHUDUUgnjDyQ4iXDEd5ueZGoUiCpBSX85XCDC8zIlm2f0KfuVSYNLuMRA==",
|
||||
"license": "GPLv3",
|
||||
"peerDependencies": {
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlhttprequest-ssl": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
|
||||
|
||||
@@ -52,7 +52,8 @@
|
||||
"react-window": "^1.8.11",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"tinymce": "^7.7.2",
|
||||
"web-vitals": "^4.2.4"
|
||||
"web-vitals": "^4.2.4",
|
||||
"wx-react-gantt": "^1.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
|
||||
@@ -6,6 +6,7 @@ import i18next from 'i18next';
|
||||
// Components
|
||||
import ThemeWrapper from './features/theme/ThemeWrapper';
|
||||
import PreferenceSelector from './components/PreferenceSelector';
|
||||
import ResourcePreloader from './components/resource-preloader/resource-preloader';
|
||||
|
||||
// Routes
|
||||
import router from './app/routes';
|
||||
@@ -47,6 +48,7 @@ const App: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
<Suspense fallback={<SuspenseFallback />}>
|
||||
<ThemeWrapper>
|
||||
<RouterProvider router={router} future={{ v7_startTransition: true }} />
|
||||
<ResourcePreloader />
|
||||
</ThemeWrapper>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
/**
|
||||
* ResourcePreloader component to preload critical chunks for better performance
|
||||
* This helps reduce loading times when users navigate to different project view tabs
|
||||
*/
|
||||
const ResourcePreloader = () => {
|
||||
useEffect(() => {
|
||||
// Preload critical project view chunks after initial page load
|
||||
const preloadCriticalChunks = () => {
|
||||
// Only preload in production and if the user is likely to use project views
|
||||
if (import.meta.env.DEV) return;
|
||||
|
||||
// Check if user is on a project-related page or dashboard
|
||||
const currentPath = window.location.pathname;
|
||||
const isProjectRelated = currentPath.includes('/projects') ||
|
||||
currentPath.includes('/worklenz') ||
|
||||
currentPath === '/';
|
||||
|
||||
if (!isProjectRelated) return;
|
||||
|
||||
// Preload the most commonly used project view components
|
||||
const criticalImports = [
|
||||
() => import('@/pages/projects/projectView/taskList/project-view-task-list'),
|
||||
() => import('@/pages/projects/projectView/board/project-view-board'),
|
||||
() => import('@/components/project-task-filters/filter-dropdowns/group-by-filter-dropdown'),
|
||||
() => import('@/components/project-task-filters/filter-dropdowns/search-dropdown'),
|
||||
];
|
||||
|
||||
// Preload with a small delay to not interfere with initial page load
|
||||
setTimeout(() => {
|
||||
criticalImports.forEach(importFn => {
|
||||
importFn().catch(error => {
|
||||
// Silently handle preload failures - they're not critical
|
||||
console.debug('Preload failed:', error);
|
||||
});
|
||||
});
|
||||
}, 2000); // 2 second delay after initial load
|
||||
};
|
||||
|
||||
// Start preloading when component mounts
|
||||
preloadCriticalChunks();
|
||||
}, []);
|
||||
|
||||
// This component doesn't render anything
|
||||
return null;
|
||||
};
|
||||
|
||||
export default ResourcePreloader;
|
||||
@@ -27,7 +27,7 @@ interface TaskListState {
|
||||
error: string | null;
|
||||
importTaskTemplateDrawerOpen: boolean;
|
||||
createTaskTemplateDrawerOpen: boolean;
|
||||
projectView: 'list' | 'kanban';
|
||||
projectView: 'list' | 'kanban' | 'gantt';
|
||||
refreshTimestamp: string | null;
|
||||
}
|
||||
|
||||
@@ -35,9 +35,9 @@ const initialState: TaskListState = {
|
||||
projectId: null,
|
||||
project: null,
|
||||
projectLoading: false,
|
||||
activeMembers: [],
|
||||
columns: [],
|
||||
members: [],
|
||||
activeMembers: [],
|
||||
labels: [],
|
||||
statuses: [],
|
||||
priorities: [],
|
||||
@@ -173,7 +173,7 @@ const projectSlice = createSlice({
|
||||
setRefreshTimestamp: (state) => {
|
||||
state.refreshTimestamp = new Date().getTime().toString();
|
||||
},
|
||||
setProjectView: (state, action: PayloadAction<'list' | 'kanban'>) => {
|
||||
setProjectView: (state, action: PayloadAction<'list' | 'kanban' | 'gantt'>) => {
|
||||
state.projectView = action.payload;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import ProjectViewInsights from '@/pages/projects/projectView/insights/project-view-insights';
|
||||
import ProjectViewFiles from '@/pages/projects/projectView/files/project-view-files';
|
||||
import ProjectViewMembers from '@/pages/projects/projectView/members/project-view-members';
|
||||
import ProjectViewUpdates from '@/pages/projects/projectView/updates/ProjectViewUpdates';
|
||||
import ProjectViewTaskList from '@/pages/projects/projectView/taskList/project-view-task-list';
|
||||
import ProjectViewBoard from '@/pages/projects/projectView/board/project-view-board';
|
||||
import ProjectViewFinance from '@/pages/projects/projectView/finance/project-view-finance';
|
||||
import React, { ReactNode, lazy } from 'react';
|
||||
|
||||
// Lazy load all project view components for better code splitting
|
||||
const ProjectViewTaskList = lazy(() => import('@/pages/projects/projectView/taskList/project-view-task-list'));
|
||||
const ProjectViewBoard = lazy(() => import('@/pages/projects/projectView/board/project-view-board'));
|
||||
const ProjectViewGantt = lazy(() => import('@/pages/projects/projectView/gantt/project-view-gantt'));
|
||||
const ProjectViewInsights = lazy(() => import('@/pages/projects/projectView/insights/project-view-insights'));
|
||||
const ProjectViewFiles = lazy(() => import('@/pages/projects/projectView/files/project-view-files'));
|
||||
const ProjectViewMembers = lazy(() => import('@/pages/projects/projectView/members/project-view-members'));
|
||||
const ProjectViewUpdates = lazy(() => import('@/pages/projects/projectView/updates/ProjectViewUpdates'));
|
||||
const ProjectViewFinance = lazy(() => import('@/pages/projects/projectView/finance/project-view-finance'));
|
||||
|
||||
// type of a tab items
|
||||
type TabItems = {
|
||||
@@ -32,6 +35,12 @@ export const tabItems: TabItems[] = [
|
||||
isPinned: true,
|
||||
element: React.createElement(ProjectViewBoard),
|
||||
},
|
||||
// {
|
||||
// index: 2,
|
||||
// key: 'gantt',
|
||||
// label: 'Gantt Chart',
|
||||
// element: React.createElement(ProjectViewGantt),
|
||||
// },
|
||||
{
|
||||
index: 4,
|
||||
key: 'project-insights-member-overview',
|
||||
|
||||
181
worklenz-frontend/src/pages/projects/projectView/gantt/README.md
Normal file
181
worklenz-frontend/src/pages/projects/projectView/gantt/README.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# SVAR Gantt Integration for Worklenz
|
||||
|
||||
This directory contains the integration of SVAR Gantt chart component into the Worklenz project management system.
|
||||
|
||||
## Overview
|
||||
|
||||
SVAR Gantt is a modern React Gantt chart component that provides interactive project timeline visualization. This integration allows users to view their project tasks in a Gantt chart format within the Worklenz project view.
|
||||
|
||||
## Components
|
||||
|
||||
### `project-view-gantt.tsx`
|
||||
Main component that handles:
|
||||
- Task data transformation from Worklenz format to SVAR Gantt format
|
||||
- Redux state management integration
|
||||
- Dark/light theme support
|
||||
- Loading states and error handling
|
||||
- Event handling for task interactions
|
||||
|
||||
### `gantt-custom-styles.css`
|
||||
Custom CSS that provides:
|
||||
- Theme-aware styling (light/dark mode)
|
||||
- Worklenz brand color integration
|
||||
- Responsive design adjustments
|
||||
- Smooth theme transitions
|
||||
|
||||
## Features
|
||||
|
||||
### ✅ Implemented
|
||||
- **Task Visualization**: Displays tasks and subtasks as Gantt bars
|
||||
- **Group Support**: Shows task groups as summary tasks
|
||||
- **Progress Tracking**: Visual progress indicators on task bars
|
||||
- **Dark Mode**: Full dark/light theme compatibility
|
||||
- **Responsive Design**: Works on desktop and mobile devices
|
||||
- **Redux Integration**: Uses existing Worklenz Redux state management
|
||||
- **Filtering**: Integrates with existing task filters
|
||||
- **Loading States**: Proper skeleton loading while data loads
|
||||
|
||||
### 🚧 Planned Enhancements
|
||||
- **Task Editing**: Click to open task drawer for editing
|
||||
- **Drag & Drop**: Update task dates by dragging bars
|
||||
- **Dependency Links**: Show task dependencies as links
|
||||
- **Real-time Updates**: Live updates when tasks change
|
||||
- **Export Features**: Export Gantt chart as PDF/image
|
||||
- **Critical Path**: Highlight critical path in projects
|
||||
|
||||
## Data Transformation
|
||||
|
||||
The component transforms Worklenz task data structure to SVAR Gantt format:
|
||||
|
||||
```typescript
|
||||
// Worklenz Task Group → SVAR Gantt Summary Task
|
||||
{
|
||||
id: `group-${group.id}`,
|
||||
text: group.name,
|
||||
start: groupStartDate,
|
||||
end: groupEndDate,
|
||||
type: 'summary',
|
||||
parent: 0,
|
||||
progress: group.done_progress
|
||||
}
|
||||
|
||||
// Worklenz Task → SVAR Gantt Task
|
||||
{
|
||||
id: task.id,
|
||||
text: task.name,
|
||||
start: task.start_date,
|
||||
end: task.end_date,
|
||||
type: task.sub_tasks_count > 0 ? 'summary' : 'task',
|
||||
parent: `group-${group.id}`,
|
||||
progress: task.progress / 100
|
||||
}
|
||||
```
|
||||
|
||||
## Theme Integration
|
||||
|
||||
The component uses Worklenz's theme system:
|
||||
|
||||
```typescript
|
||||
// Theme detection from Redux
|
||||
const themeMode = useAppSelector(state => state.themeReducer.mode);
|
||||
const isDarkMode = themeMode === 'dark';
|
||||
|
||||
// Dynamic CSS classes
|
||||
<div className={`gantt-container ${isDarkMode ? 'gantt-dark-mode' : 'gantt-light-mode'}`}>
|
||||
```
|
||||
|
||||
### Theme Variables
|
||||
|
||||
**Light Mode:**
|
||||
- Task bars: `#1890ff` (Worklenz primary blue)
|
||||
- Summary tasks: `#52c41a` (Worklenz green)
|
||||
- Backgrounds: `#ffffff`, `#fafafa`
|
||||
- Borders: `#d9d9d9`
|
||||
|
||||
**Dark Mode:**
|
||||
- Task bars: `#4096ff` (Lighter blue for contrast)
|
||||
- Summary tasks: `#73d13d` (Lighter green for contrast)
|
||||
- Backgrounds: `#1f1f1f`, `#262626`
|
||||
- Borders: `#424242`
|
||||
|
||||
## Configuration
|
||||
|
||||
### Gantt Settings
|
||||
```typescript
|
||||
const ganttConfig = {
|
||||
scales: [
|
||||
{ unit: 'month', step: 1, format: 'MMMM yyyy' },
|
||||
{ unit: 'day', step: 1, format: 'd' }
|
||||
],
|
||||
columns: [
|
||||
{ id: 'text', header: 'Task Name', width: 200 },
|
||||
{ id: 'start', header: 'Start Date', width: 100 },
|
||||
{ id: 'end', header: 'End Date', width: 100 },
|
||||
{ id: 'progress', header: 'Progress', width: 80 }
|
||||
],
|
||||
taskHeight: 32,
|
||||
rowHeight: 40
|
||||
};
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `wx-react-gantt`: SVAR Gantt React component
|
||||
- `wx-react-gantt/dist/gantt.css`: Base SVAR Gantt styles
|
||||
- External CDN for icons: `https://cdn.svar.dev/fonts/wxi/wx-icons.css`
|
||||
|
||||
## Usage
|
||||
|
||||
The Gantt tab is automatically available in the project view tabs. Users can:
|
||||
1. Navigate to a project
|
||||
2. Click the "Gantt" tab
|
||||
3. View tasks in timeline format
|
||||
4. Use existing filters to refine the view
|
||||
5. Toggle between light/dark themes
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- **Lazy Loading**: Component is lazy-loaded to reduce initial bundle size
|
||||
- **Data Memoization**: Task transformation is memoized to prevent unnecessary recalculations
|
||||
- **Batch Loading**: Initial data fetching is batched for efficiency
|
||||
- **Conditional Rendering**: Only renders when tab is active (destroyInactiveTabPane)
|
||||
|
||||
## Browser Support
|
||||
|
||||
Compatible with all modern browsers that support:
|
||||
- ES6+ JavaScript features
|
||||
- CSS Grid and Flexbox
|
||||
- CSS Custom Properties (variables)
|
||||
- React 18+
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Styles not loading**: Ensure CSS import order is correct
|
||||
2. **Theme not updating**: Check Redux theme state
|
||||
3. **Tasks not displaying**: Verify task data has required fields (name, dates)
|
||||
4. **Performance issues**: Check if too many tasks are being rendered
|
||||
|
||||
### Debug Tips
|
||||
|
||||
```typescript
|
||||
// Enable console logging for debugging
|
||||
console.log('Gantt data:', ganttData);
|
||||
console.log('Theme mode:', themeMode);
|
||||
console.log('Task groups:', taskGroups);
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
When modifying the Gantt integration:
|
||||
|
||||
1. Test both light and dark themes
|
||||
2. Verify responsive behavior
|
||||
3. Check performance with large datasets
|
||||
4. Update this README if adding new features
|
||||
5. Follow Worklenz coding standards and patterns
|
||||
|
||||
---
|
||||
|
||||
For more information about SVAR Gantt, visit: https://svar.dev/react/gantt/
|
||||
@@ -0,0 +1,265 @@
|
||||
/* Custom styling for SVAR Gantt to match Worklenz theme */
|
||||
|
||||
/* Light Mode (Default) */
|
||||
.gantt-container.gantt-light-mode {
|
||||
--wx-gantt-border: 1px solid #d9d9d9;
|
||||
--wx-gantt-icon-color: #666;
|
||||
|
||||
/* Task bars */
|
||||
--wx-gantt-task-color: #1890ff;
|
||||
--wx-gantt-task-font-color: #fff;
|
||||
--wx-gantt-task-fill-color: #096dd9;
|
||||
--wx-gantt-task-border-color: #096dd9;
|
||||
|
||||
/* Project/Summary tasks */
|
||||
--wx-gantt-project-color: #52c41a;
|
||||
--wx-gantt-project-font-color: #ffffff;
|
||||
--wx-gantt-project-fill-color: #389e0d;
|
||||
--wx-gantt-project-border-color: #389e0d;
|
||||
|
||||
/* Milestone */
|
||||
--wx-gantt-milestone-color: #fa8c16;
|
||||
|
||||
/* Grid styling */
|
||||
--wx-grid-header-font-color: #262626;
|
||||
--wx-grid-body-font-color: #262626;
|
||||
--wx-grid-body-row-border: 1px solid #f0f0f0;
|
||||
--wx-grid-header-background: #fafafa;
|
||||
--wx-grid-body-background: #ffffff;
|
||||
|
||||
/* Timescale */
|
||||
--wx-timescale-font-color: #262626;
|
||||
--wx-timescale-border: 1px solid #d9d9d9;
|
||||
--wx-timescale-background: #fafafa;
|
||||
|
||||
/* Selection and hover */
|
||||
--wx-gantt-select-color: #e6f7ff;
|
||||
--wx-gantt-link-color: #d9d9d9;
|
||||
--wx-gantt-hover-color: #f5f5f5;
|
||||
|
||||
/* Progress */
|
||||
--wx-gantt-progress-border-color: #d9d9d9;
|
||||
|
||||
/* Weekend/holiday */
|
||||
--wx-gantt-holiday-background: #fafafa;
|
||||
--wx-gantt-holiday-color: #9fa1ae;
|
||||
}
|
||||
|
||||
/* Dark Mode */
|
||||
.gantt-container.gantt-dark-mode {
|
||||
--wx-gantt-border: 1px solid #424242;
|
||||
--wx-gantt-icon-color: #bfbfbf;
|
||||
|
||||
/* Task bars - Adjusted for dark theme */
|
||||
--wx-gantt-task-color: #4096ff;
|
||||
--wx-gantt-task-font-color: #ffffff;
|
||||
--wx-gantt-task-fill-color: #1668dc;
|
||||
--wx-gantt-task-border-color: #1668dc;
|
||||
|
||||
/* Project/Summary tasks - Adjusted for dark theme */
|
||||
--wx-gantt-project-color: #73d13d;
|
||||
--wx-gantt-project-font-color: #000000;
|
||||
--wx-gantt-project-fill-color: #52c41a;
|
||||
--wx-gantt-project-border-color: #52c41a;
|
||||
|
||||
/* Milestone - Adjusted for dark theme */
|
||||
--wx-gantt-milestone-color: #ffa940;
|
||||
|
||||
/* Grid styling - Dark theme */
|
||||
--wx-grid-header-font-color: #ffffff;
|
||||
--wx-grid-body-font-color: #ffffff;
|
||||
--wx-grid-body-row-border: 1px solid #424242;
|
||||
--wx-grid-header-background: #262626;
|
||||
--wx-grid-body-background: #1f1f1f;
|
||||
|
||||
/* Timescale - Dark theme */
|
||||
--wx-timescale-font-color: #ffffff;
|
||||
--wx-timescale-border: 1px solid #424242;
|
||||
--wx-timescale-background: #262626;
|
||||
|
||||
/* Selection and hover - Dark theme */
|
||||
--wx-gantt-select-color: #111b26;
|
||||
--wx-gantt-link-color: #595959;
|
||||
--wx-gantt-hover-color: #2c2c2c;
|
||||
|
||||
/* Progress - Dark theme */
|
||||
--wx-gantt-progress-border-color: #595959;
|
||||
|
||||
/* Weekend/holiday - Dark theme */
|
||||
--wx-gantt-holiday-background: #262626;
|
||||
--wx-gantt-holiday-color: #8c8c8c;
|
||||
}
|
||||
|
||||
/* Force dark mode styles with higher specificity */
|
||||
.gantt-container.gantt-dark-mode .wx-gantt,
|
||||
.gantt-container.gantt-dark-mode .wx-gantt * {
|
||||
background-color: #1f1f1f !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.gantt-container.gantt-dark-mode .wx-grid-header,
|
||||
.gantt-container.gantt-dark-mode .wx-timescale {
|
||||
background-color: #262626 !important;
|
||||
color: #ffffff !important;
|
||||
border-color: #424242 !important;
|
||||
}
|
||||
|
||||
.gantt-container.gantt-dark-mode .wx-grid-body {
|
||||
background-color: #1f1f1f !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.gantt-container.gantt-dark-mode .wx-grid-cell {
|
||||
border-color: #424242 !important;
|
||||
background-color: #1f1f1f !important;
|
||||
}
|
||||
|
||||
.gantt-container.gantt-dark-mode .wx-gantt-task {
|
||||
background-color: #4096ff !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.gantt-container.gantt-dark-mode .wx-gantt-project {
|
||||
background-color: #73d13d !important;
|
||||
color: #000000 !important;
|
||||
}
|
||||
|
||||
/* Common styles for both themes */
|
||||
.gantt-container .wx-gantt {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Task text styling */
|
||||
.gantt-container .wx-gantt-task-text {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Summary task styling */
|
||||
.gantt-container .wx-gantt-project-text {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Grid header styling */
|
||||
.gantt-container .wx-grid-header {
|
||||
background-color: var(--wx-grid-header-background);
|
||||
border-bottom: 2px solid var(--wx-gantt-border);
|
||||
color: var(--wx-grid-header-font-color);
|
||||
}
|
||||
|
||||
/* Grid body styling */
|
||||
.gantt-container .wx-grid-body {
|
||||
background-color: var(--wx-grid-body-background);
|
||||
color: var(--wx-grid-body-font-color);
|
||||
}
|
||||
|
||||
/* Timescale header styling */
|
||||
.gantt-container .wx-timescale {
|
||||
background-color: var(--wx-timescale-background);
|
||||
color: var(--wx-timescale-font-color);
|
||||
}
|
||||
|
||||
/* Task row hover effect */
|
||||
.gantt-container .wx-grid-row:hover {
|
||||
background-color: var(--wx-gantt-hover-color);
|
||||
}
|
||||
|
||||
/* Custom progress bar styling */
|
||||
.gantt-container .wx-gantt-progress {
|
||||
opacity: 0.8;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Weekend/holiday highlighting */
|
||||
.gantt-container .wx-gantt-holiday {
|
||||
background-color: var(--wx-gantt-holiday-background);
|
||||
color: var(--wx-gantt-holiday-color);
|
||||
}
|
||||
|
||||
/* Scrollbar styling - Light mode */
|
||||
.gantt-container.gantt-light-mode .wx-gantt-scroll {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #d9d9d9 transparent;
|
||||
}
|
||||
|
||||
.gantt-container.gantt-light-mode .wx-gantt-scroll::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.gantt-container.gantt-light-mode .wx-gantt-scroll::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.gantt-container.gantt-light-mode .wx-gantt-scroll::-webkit-scrollbar-thumb {
|
||||
background-color: #d9d9d9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.gantt-container.gantt-light-mode .wx-gantt-scroll::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #bfbfbf;
|
||||
}
|
||||
|
||||
/* Scrollbar styling - Dark mode */
|
||||
.gantt-container.gantt-dark-mode .wx-gantt-scroll {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #595959 transparent;
|
||||
}
|
||||
|
||||
.gantt-container.gantt-dark-mode .wx-gantt-scroll::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.gantt-container.gantt-dark-mode .wx-gantt-scroll::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.gantt-container.gantt-dark-mode .wx-gantt-scroll::-webkit-scrollbar-thumb {
|
||||
background-color: #595959;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.gantt-container.gantt-dark-mode .wx-gantt-scroll::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #737373;
|
||||
}
|
||||
|
||||
/* Dark mode specific overrides for SVAR Gantt */
|
||||
.gantt-container.gantt-dark-mode .wx-gantt {
|
||||
background-color: #1f1f1f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* Dark mode task selection */
|
||||
.gantt-container.gantt-dark-mode .wx-gantt-task:hover {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
/* Dark mode grid lines */
|
||||
.gantt-container.gantt-dark-mode .wx-grid-cell {
|
||||
border-color: #424242;
|
||||
}
|
||||
|
||||
/* Dark mode tooltip styling */
|
||||
.gantt-container.gantt-dark-mode .wx-tooltip {
|
||||
background-color: #434343;
|
||||
color: #ffffff;
|
||||
border: 1px solid #595959;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.gantt-container {
|
||||
--wx-gantt-bar-font: 500 12px 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
.gantt-container .wx-grid-header,
|
||||
.gantt-container .wx-timescale {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Theme transition for smooth mode switching */
|
||||
.gantt-container * {
|
||||
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
||||
import { Flex, Skeleton, Empty, message } from 'antd';
|
||||
// @ts-ignore - wx-react-gantt doesn't have TypeScript definitions
|
||||
import { Gantt, Willow, WillowDark } from 'wx-react-gantt';
|
||||
import 'wx-react-gantt/dist/gantt.css';
|
||||
// import './gantt-custom-styles.css'; // Temporarily disabled
|
||||
|
||||
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
||||
import { fetchTaskGroups } from '@/features/tasks/tasks.slice';
|
||||
import { fetchStatusesCategories } from '@/features/taskAttributes/taskStatusSlice';
|
||||
import { fetchPhasesByProjectId } from '@/features/projects/singleProject/phase/phases.slice';
|
||||
import { fetchTaskAssignees } from '@/features/tasks/tasks.slice';
|
||||
import { IProjectTask } from '@/types/project/projectTasksViewModel.types';
|
||||
import { ITaskListGroup } from '@/types/tasks/taskList.types';
|
||||
import TaskListFilters from '../taskList/task-list-filters/task-list-filters';
|
||||
import useTabSearchParam from '@/hooks/useTabSearchParam';
|
||||
import { colors } from '@/styles/colors';
|
||||
|
||||
// Transform Worklenz task data to SVAR Gantt format
|
||||
const transformTasksToGanttData = (taskGroups: ITaskListGroup[]) => {
|
||||
const tasks: any[] = [];
|
||||
const links: any[] = [];
|
||||
|
||||
taskGroups.forEach((group, groupIndex) => {
|
||||
// Add group as a summary task
|
||||
if (group.tasks.length > 0) {
|
||||
const groupStartDate = group.start_date ? new Date(group.start_date) : new Date();
|
||||
const groupEndDate = group.end_date ? new Date(group.end_date) : new Date();
|
||||
|
||||
tasks.push({
|
||||
id: `group-${group.id}`,
|
||||
text: group.name,
|
||||
start: groupStartDate,
|
||||
end: groupEndDate,
|
||||
type: 'summary',
|
||||
open: true,
|
||||
parent: 0,
|
||||
progress: Math.round((group.done_progress || 0) * 100) / 100,
|
||||
details: `Status: ${group.name}`,
|
||||
});
|
||||
|
||||
// Add individual tasks
|
||||
group.tasks.forEach((task: IProjectTask, taskIndex: number) => {
|
||||
const startDate = task.start_date ? new Date(task.start_date) : new Date();
|
||||
const endDate = task.end_date ? new Date(task.end_date) : new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // Default to 1 week from now
|
||||
|
||||
// Handle task type
|
||||
let taskType = 'task';
|
||||
if (task.sub_tasks_count && task.sub_tasks_count > 0) {
|
||||
taskType = 'summary';
|
||||
}
|
||||
|
||||
const ganttTask = {
|
||||
id: task.id,
|
||||
text: task.name || 'Untitled Task',
|
||||
start: startDate,
|
||||
end: endDate,
|
||||
type: taskType,
|
||||
parent: `group-${group.id}`,
|
||||
progress: (task.progress || 0) / 100,
|
||||
details: task.description || '',
|
||||
priority: task.priority_name || 'Normal',
|
||||
status: task.status || 'New',
|
||||
assignees: task.names?.map(member => member.name).join(', ') || '',
|
||||
};
|
||||
|
||||
tasks.push(ganttTask);
|
||||
|
||||
// Add subtasks if they exist
|
||||
if (task.sub_tasks && task.sub_tasks.length > 0) {
|
||||
task.sub_tasks.forEach((subTask: IProjectTask) => {
|
||||
const subStartDate = subTask.start_date ? new Date(subTask.start_date) : startDate;
|
||||
const subEndDate = subTask.end_date ? new Date(subTask.end_date) : endDate;
|
||||
|
||||
tasks.push({
|
||||
id: subTask.id,
|
||||
text: subTask.name || 'Untitled Subtask',
|
||||
start: subStartDate,
|
||||
end: subEndDate,
|
||||
type: 'task',
|
||||
parent: task.id,
|
||||
progress: (subTask.progress || 0) / 100,
|
||||
details: subTask.description || '',
|
||||
priority: subTask.priority_name || 'Normal',
|
||||
status: subTask.status || 'New',
|
||||
assignees: subTask.names?.map(member => member.name).join(', ') || '',
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return { tasks, links };
|
||||
};
|
||||
|
||||
const ProjectViewGantt = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { projectView } = useTabSearchParam();
|
||||
const [initialLoadComplete, setInitialLoadComplete] = useState(false);
|
||||
|
||||
// Redux selectors
|
||||
const projectId = useAppSelector(state => state.projectReducer.projectId);
|
||||
const taskGroups = useAppSelector(state => state.taskReducer.taskGroups);
|
||||
const loadingGroups = useAppSelector(state => state.taskReducer.loadingGroups);
|
||||
const groupBy = useAppSelector(state => state.taskReducer.groupBy);
|
||||
const archived = useAppSelector(state => state.taskReducer.archived);
|
||||
const fields = useAppSelector(state => state.taskReducer.fields);
|
||||
const search = useAppSelector(state => state.taskReducer.search);
|
||||
|
||||
const statusCategories = useAppSelector(state => state.taskStatusReducer.statusCategories);
|
||||
const loadingStatusCategories = useAppSelector(state => state.taskStatusReducer.loading);
|
||||
|
||||
const loadingPhases = useAppSelector(state => state.phaseReducer.loadingPhases);
|
||||
|
||||
// Get theme mode from Worklenz theme system
|
||||
const themeMode = useAppSelector(state => state.themeReducer.mode);
|
||||
const isDarkMode = themeMode === 'dark';
|
||||
|
||||
// Debug theme detection
|
||||
useEffect(() => {
|
||||
console.log('Theme mode detected:', themeMode, 'isDarkMode:', isDarkMode);
|
||||
}, [themeMode, isDarkMode]);
|
||||
|
||||
// Loading state
|
||||
const isLoading = useMemo(() =>
|
||||
loadingGroups || loadingPhases || loadingStatusCategories || !initialLoadComplete,
|
||||
[loadingGroups, loadingPhases, loadingStatusCategories, initialLoadComplete]
|
||||
);
|
||||
|
||||
// Empty state check
|
||||
const isEmptyState = useMemo(() =>
|
||||
taskGroups && taskGroups.length === 0 && !isLoading,
|
||||
[taskGroups, isLoading]
|
||||
);
|
||||
|
||||
// Transform data for SVAR Gantt
|
||||
const ganttData = useMemo(() => {
|
||||
if (!taskGroups || taskGroups.length === 0) {
|
||||
return { tasks: [], links: [] };
|
||||
}
|
||||
|
||||
// Test with hardcoded data first to isolate the issue
|
||||
const testData = {
|
||||
tasks: [
|
||||
{
|
||||
id: 1,
|
||||
text: "Test Project",
|
||||
start: new Date(2024, 0, 1),
|
||||
end: new Date(2024, 0, 15),
|
||||
type: "summary",
|
||||
open: true,
|
||||
parent: 0,
|
||||
progress: 0.5,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
text: "Test Task 1",
|
||||
start: new Date(2024, 0, 2),
|
||||
end: new Date(2024, 0, 8),
|
||||
type: "task",
|
||||
parent: 1,
|
||||
progress: 0.3,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
text: "Test Task 2",
|
||||
start: new Date(2024, 0, 9),
|
||||
end: new Date(2024, 0, 14),
|
||||
type: "task",
|
||||
parent: 1,
|
||||
progress: 0.7,
|
||||
},
|
||||
],
|
||||
links: [],
|
||||
};
|
||||
|
||||
console.log('Using test data for debugging:', testData);
|
||||
return testData;
|
||||
|
||||
// Original transformation (commented out for testing)
|
||||
// const result = transformTasksToGanttData(taskGroups);
|
||||
// console.log('Gantt data - tasks count:', result.tasks.length);
|
||||
// if (result.tasks.length > 0) {
|
||||
// console.log('First task:', result.tasks[0]);
|
||||
// console.log('Sample dates:', result.tasks[0]?.start, result.tasks[0]?.end);
|
||||
// }
|
||||
// return result;
|
||||
}, [taskGroups]);
|
||||
|
||||
// Calculate date range for the Gantt chart
|
||||
const dateRange = useMemo(() => {
|
||||
// Fixed range for testing
|
||||
return {
|
||||
start: new Date(2023, 11, 1), // December 1, 2023
|
||||
end: new Date(2024, 1, 29), // February 29, 2024
|
||||
};
|
||||
|
||||
// Original dynamic calculation (commented out for testing)
|
||||
// if (ganttData.tasks.length === 0) {
|
||||
// const now = new Date();
|
||||
// return {
|
||||
// start: new Date(now.getFullYear(), now.getMonth() - 1, 1),
|
||||
// end: new Date(now.getFullYear(), now.getMonth() + 2, 0),
|
||||
// };
|
||||
// }
|
||||
|
||||
// const dates = ganttData.tasks.map(task => [task.start, task.end]).flat();
|
||||
// const minDate = new Date(Math.min(...dates.map(d => d.getTime())));
|
||||
// const maxDate = new Date(Math.max(...dates.map(d => d.getTime())));
|
||||
|
||||
// // Add some padding
|
||||
// const startDate = new Date(minDate);
|
||||
// startDate.setDate(startDate.getDate() - 7);
|
||||
// const endDate = new Date(maxDate);
|
||||
// endDate.setDate(endDate.getDate() + 7);
|
||||
|
||||
// return { start: startDate, end: endDate };
|
||||
}, [ganttData.tasks]);
|
||||
|
||||
// Batch initial data fetching
|
||||
useEffect(() => {
|
||||
const fetchInitialData = async () => {
|
||||
if (!projectId || !groupBy || initialLoadComplete) return;
|
||||
|
||||
try {
|
||||
await Promise.allSettled([
|
||||
dispatch(fetchPhasesByProjectId(projectId)),
|
||||
dispatch(fetchStatusesCategories()),
|
||||
dispatch(fetchTaskAssignees(projectId)),
|
||||
]);
|
||||
setInitialLoadComplete(true);
|
||||
} catch (error) {
|
||||
console.error('Error fetching initial data:', error);
|
||||
setInitialLoadComplete(true);
|
||||
}
|
||||
};
|
||||
|
||||
fetchInitialData();
|
||||
}, [projectId, groupBy, dispatch, initialLoadComplete]);
|
||||
|
||||
// Fetch task groups
|
||||
useEffect(() => {
|
||||
const fetchTasks = async () => {
|
||||
if (!projectId || !groupBy || !initialLoadComplete) return;
|
||||
|
||||
try {
|
||||
await dispatch(fetchTaskGroups(projectId));
|
||||
} catch (error) {
|
||||
console.error('Error fetching task groups:', error);
|
||||
message.error('Failed to load tasks for Gantt chart');
|
||||
}
|
||||
};
|
||||
|
||||
fetchTasks();
|
||||
}, [projectId, groupBy, dispatch, fields, search, archived, initialLoadComplete]);
|
||||
|
||||
// Gantt configuration
|
||||
const ganttConfig = useMemo(() => ({
|
||||
// Time scale configuration
|
||||
scales: [
|
||||
{ unit: 'month', step: 1, format: 'MMMM yyyy' },
|
||||
{ unit: 'day', step: 1, format: 'd' },
|
||||
],
|
||||
|
||||
// Columns configuration
|
||||
columns: [
|
||||
{ id: 'text', header: 'Task Name', width: 200 },
|
||||
{ id: 'start', header: 'Start Date', width: 100 },
|
||||
{ id: 'end', header: 'End Date', width: 100 },
|
||||
{ id: 'progress', header: 'Progress', width: 80 },
|
||||
],
|
||||
|
||||
// Event handlers
|
||||
onTaskClick: (task: any) => {
|
||||
console.log('Task clicked:', task);
|
||||
// TODO: Open task drawer
|
||||
},
|
||||
|
||||
onTaskUpdate: (task: any) => {
|
||||
console.log('Task updated:', task);
|
||||
// TODO: Update task via API
|
||||
},
|
||||
|
||||
// Style configuration
|
||||
taskHeight: 32,
|
||||
rowHeight: 40,
|
||||
}), []);
|
||||
|
||||
if (isEmptyState) {
|
||||
return (
|
||||
<Flex vertical gap={16} style={{ overflowX: 'hidden' }}>
|
||||
<TaskListFilters position="list" />
|
||||
<Empty description="No tasks found for Gantt chart" />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex vertical gap={16} style={{ overflowX: 'hidden' }}>
|
||||
<TaskListFilters position="list" />
|
||||
|
||||
<style>{`
|
||||
.wx-gantt {
|
||||
font-family: inherit !important;
|
||||
}
|
||||
.wx-gantt-task {
|
||||
background-color: #3983eb !important;
|
||||
border: 1px solid #1f6bd9 !important;
|
||||
}
|
||||
.wx-gantt-project {
|
||||
background-color: #00ba94 !important;
|
||||
border: 1px solid #099f81 !important;
|
||||
}
|
||||
${isDarkMode ? `
|
||||
.wx-gantt-task {
|
||||
background-color: #37a9ef !important;
|
||||
border: 1px solid #098cdc !important;
|
||||
}
|
||||
` : ''}
|
||||
`}</style>
|
||||
|
||||
<Skeleton active loading={isLoading} className='mt-4 p-4'>
|
||||
<div
|
||||
style={{
|
||||
height: '600px',
|
||||
width: '100%',
|
||||
border: `1px solid ${isDarkMode ? '#424242' : '#d9d9d9'}`,
|
||||
borderRadius: '6px',
|
||||
backgroundColor: isDarkMode ? colors.darkGray : colors.white,
|
||||
position: 'relative',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
{isDarkMode ? (
|
||||
<WillowDark>
|
||||
<Gantt
|
||||
tasks={ganttData.tasks}
|
||||
links={ganttData.links}
|
||||
start={new Date(2024, 0, 1)}
|
||||
end={new Date(2024, 0, 31)}
|
||||
scales={[
|
||||
{ unit: 'day', step: 1, format: 'd' }
|
||||
]}
|
||||
columns={[
|
||||
{ id: 'text', header: 'Task Name', width: 200 }
|
||||
]}
|
||||
/>
|
||||
</WillowDark>
|
||||
) : (
|
||||
<Willow>
|
||||
<Gantt
|
||||
tasks={ganttData.tasks}
|
||||
links={ganttData.links}
|
||||
start={new Date(2024, 0, 1)}
|
||||
end={new Date(2024, 0, 31)}
|
||||
scales={[
|
||||
{ unit: 'day', step: 1, format: 'd' }
|
||||
]}
|
||||
columns={[
|
||||
{ id: 'text', header: 'Task Name', width: 200 }
|
||||
]}
|
||||
/>
|
||||
</Willow>
|
||||
)}
|
||||
</div>
|
||||
</Skeleton>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectViewGantt;
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
||||
import React, { useEffect, useState, useMemo, useCallback, Suspense } from 'react';
|
||||
import { PushpinFilled, PushpinOutlined } from '@ant-design/icons';
|
||||
import { Button, ConfigProvider, Flex, Tabs } from 'antd';
|
||||
import { Button, ConfigProvider, Flex, Tabs, Spin } from 'antd';
|
||||
import { useLocation, useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
@@ -30,6 +30,18 @@ const ProjectMemberDrawer = React.lazy(
|
||||
);
|
||||
const TaskDrawer = React.lazy(() => import('@components/task-drawer/task-drawer'));
|
||||
|
||||
// Loading component for lazy-loaded tabs
|
||||
const TabLoadingFallback = () => (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
minHeight: '400px'
|
||||
}}>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
);
|
||||
|
||||
const ProjectView = () => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
@@ -76,7 +88,11 @@ const ProjectView = () => {
|
||||
const pinToDefaultTab = useCallback(async (itemKey: string) => {
|
||||
if (!itemKey || !projectId) return;
|
||||
|
||||
const defaultView = itemKey === 'tasks-list' ? 'TASK_LIST' : 'BOARD';
|
||||
let defaultView = 'TASK_LIST';
|
||||
if (itemKey === 'board') {
|
||||
defaultView = 'BOARD';
|
||||
}
|
||||
|
||||
const res = await projectsApiService.updateDefaultTab({
|
||||
project_id: projectId,
|
||||
default_view: defaultView,
|
||||
@@ -104,7 +120,13 @@ const ProjectView = () => {
|
||||
|
||||
const handleTabChange = useCallback((key: string) => {
|
||||
setActiveTab(key);
|
||||
dispatch(setProjectView(key === 'board' ? 'kanban' : 'list'));
|
||||
let projectView: 'list' | 'kanban' | 'gantt' = 'list';
|
||||
if (key === 'board') {
|
||||
projectView = 'kanban';
|
||||
} else if (key === 'gantt') {
|
||||
projectView = 'gantt';
|
||||
}
|
||||
dispatch(setProjectView(projectView));
|
||||
navigate({
|
||||
pathname: location.pathname,
|
||||
search: new URLSearchParams({
|
||||
@@ -119,7 +141,7 @@ const ProjectView = () => {
|
||||
label: (
|
||||
<Flex align="center" >
|
||||
{item.label}
|
||||
{item.key === 'tasks-list' || item.key === 'board' ? (
|
||||
{(item.key === 'tasks-list' || item.key === 'board') ? (
|
||||
<ConfigProvider wave={{ disabled: true }}>
|
||||
<Button
|
||||
className="borderless-icon-btn"
|
||||
@@ -152,7 +174,11 @@ const ProjectView = () => {
|
||||
) : null}
|
||||
</Flex>
|
||||
),
|
||||
children: item.element,
|
||||
children: (
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
{item.element}
|
||||
</Suspense>
|
||||
),
|
||||
})), [pinnedTab, pinToDefaultTab]);
|
||||
|
||||
const portalElements = useMemo(() => (
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
import path from 'path';
|
||||
import { UserConfig } from 'vite'; // Import type for better auto-completion
|
||||
|
||||
export default defineConfig(async ({ command }: { command: 'build' | 'serve' }) => {
|
||||
const tsconfigPaths = (await import('vite-tsconfig-paths')).default;
|
||||
|
||||
export default defineConfig(({ command }) => {
|
||||
return {
|
||||
// **Plugins**
|
||||
plugins: [
|
||||
@@ -39,6 +37,9 @@ export default defineConfig(async ({ command }: { command: 'build' | 'serve' })
|
||||
outDir: 'build',
|
||||
assetsDir: 'assets', // Consider a more specific directory for better organization, e.g., 'build/assets'
|
||||
cssCodeSplit: true,
|
||||
|
||||
// **Chunk Size Optimization**
|
||||
chunkSizeWarningLimit: 1000, // Increase limit for vendor chunks but keep warning for others
|
||||
|
||||
// **Sourcemaps**
|
||||
sourcemap: command === 'serve' ? 'inline' : true, // Adjust sourcemap strategy based on command
|
||||
@@ -49,22 +50,89 @@ export default defineConfig(async ({ command }: { command: 'build' | 'serve' })
|
||||
compress: {
|
||||
drop_console: command === 'build',
|
||||
drop_debugger: command === 'build',
|
||||
// Remove unused code more aggressively
|
||||
unused: true,
|
||||
dead_code: true,
|
||||
},
|
||||
// **Additional Optimization**
|
||||
format: {
|
||||
comments: command === 'serve', // Preserve comments during development
|
||||
},
|
||||
mangle: {
|
||||
// Mangle private properties for smaller bundles
|
||||
properties: {
|
||||
regex: /^_/,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// **Rollup Options**
|
||||
rollupOptions: {
|
||||
output: {
|
||||
// **Chunking Strategy**
|
||||
// **Enhanced Chunking Strategy**
|
||||
manualChunks(id) {
|
||||
if (['react', 'react-dom', 'react-router-dom'].includes(id)) return 'vendor';
|
||||
if (id.includes('antd')) return 'antd';
|
||||
if (id.includes('i18next')) return 'i18n';
|
||||
// Add more conditions as needed
|
||||
// Core React dependencies
|
||||
if (['react', 'react-dom'].includes(id)) return 'react-vendor';
|
||||
|
||||
// Router and navigation
|
||||
if (id.includes('react-router-dom') || id.includes('react-router')) return 'router';
|
||||
|
||||
// UI Library
|
||||
if (id.includes('antd')) return 'antd-ui';
|
||||
|
||||
// Internationalization
|
||||
if (id.includes('i18next') || id.includes('react-i18next')) return 'i18n';
|
||||
|
||||
// Redux and state management
|
||||
if (id.includes('@reduxjs/toolkit') || id.includes('redux') || id.includes('react-redux')) return 'redux';
|
||||
|
||||
// Date and time utilities
|
||||
if (id.includes('moment') || id.includes('dayjs') || id.includes('date-fns')) return 'date-utils';
|
||||
|
||||
// Drag and drop
|
||||
if (id.includes('@dnd-kit')) return 'dnd-kit';
|
||||
|
||||
// Charts and visualization
|
||||
if (id.includes('chart') || id.includes('echarts') || id.includes('highcharts') || id.includes('recharts')) return 'charts';
|
||||
|
||||
// Text editor
|
||||
if (id.includes('tinymce') || id.includes('quill') || id.includes('editor')) return 'editors';
|
||||
|
||||
// Project view components - split into separate chunks for better lazy loading
|
||||
if (id.includes('/pages/projects/projectView/taskList/')) return 'project-task-list';
|
||||
if (id.includes('/pages/projects/projectView/board/')) return 'project-board';
|
||||
if (id.includes('/pages/projects/projectView/insights/')) return 'project-insights';
|
||||
if (id.includes('/pages/projects/projectView/finance/')) return 'project-finance';
|
||||
if (id.includes('/pages/projects/projectView/members/')) return 'project-members';
|
||||
if (id.includes('/pages/projects/projectView/files/')) return 'project-files';
|
||||
if (id.includes('/pages/projects/projectView/updates/')) return 'project-updates';
|
||||
|
||||
// Task-related components
|
||||
if (id.includes('/components/task-') || id.includes('/features/tasks/')) return 'task-components';
|
||||
|
||||
// Filter components
|
||||
if (id.includes('/components/project-task-filters/') || id.includes('filter-dropdown')) return 'filter-components';
|
||||
|
||||
// Other project components
|
||||
if (id.includes('/pages/projects/') && !id.includes('/projectView/')) return 'project-pages';
|
||||
|
||||
// Settings and admin
|
||||
if (id.includes('/pages/settings/') || id.includes('/pages/admin-center/')) return 'settings-admin';
|
||||
|
||||
// Reporting
|
||||
if (id.includes('/pages/reporting/') || id.includes('/features/reporting/')) return 'reporting';
|
||||
|
||||
// Schedule components
|
||||
if (id.includes('/components/schedule') || id.includes('/features/schedule')) return 'schedule';
|
||||
|
||||
// Common utilities
|
||||
if (id.includes('/utils/') || id.includes('/shared/') || id.includes('/hooks/')) return 'common-utils';
|
||||
|
||||
// API and services
|
||||
if (id.includes('/api/') || id.includes('/services/')) return 'api-services';
|
||||
|
||||
// Other vendor libraries
|
||||
if (id.includes('node_modules')) return 'vendor';
|
||||
},
|
||||
// **File Naming Strategies**
|
||||
chunkFileNames: 'assets/js/[name]-[hash].js',
|
||||
|
||||
Reference in New Issue
Block a user