import React, { useState, useCallback, useRef, useMemo, useEffect } from 'react'; import { Spin, message } from '@/shared/antd-imports'; import { useParams } from 'react-router-dom'; import GanttTimeline from './components/gantt-timeline/GanttTimeline'; import GanttTaskList from './components/gantt-task-list/GanttTaskList'; import GanttChart from './components/gantt-chart/GanttChart'; import GanttToolbar from './components/gantt-toolbar/GanttToolbar'; import ManagePhaseModal from '@components/task-management/ManagePhaseModal'; import { GanttProvider } from './context/gantt-context'; import { GanttViewMode } from './types/gantt-types'; import { useGetRoadmapTasksQuery, useGetProjectPhasesQuery, transformToGanttTasks, transformToGanttPhases, } from './services/gantt-api.service'; import { TimelineUtils } from './utils/timeline-calculator'; import { useAppDispatch } from '@/hooks/useAppDispatch'; import { setShowTaskDrawer, setSelectedTaskId, setTaskFormViewModel, fetchTask, } from '@features/task-drawer/task-drawer.slice'; import { fetchPriorities } from '@/features/taskAttributes/taskPrioritySlice'; import { DEFAULT_TASK_NAME } from '@/shared/constants'; import './gantt-styles.css'; const ProjectViewGantt: React.FC = React.memo(() => { const { projectId } = useParams<{ projectId: string }>(); const dispatch = useAppDispatch(); const [viewMode, setViewMode] = useState('month'); const [showPhaseModal, setShowPhaseModal] = useState(false); const [expandedTasks, setExpandedTasks] = useState>(new Set()); const timelineRef = useRef(null); const chartRef = useRef(null); const taskListRef = useRef(null); const containerRef = useRef(null); // RTK Query hooks const { data: tasksResponse, error: tasksError, isLoading: tasksLoading, refetch: refetchTasks, } = useGetRoadmapTasksQuery({ projectId: projectId || '' }, { skip: !projectId }); const { data: phasesResponse, error: phasesError, isLoading: phasesLoading, refetch: refetchPhases, } = useGetProjectPhasesQuery({ projectId: projectId || '' }, { skip: !projectId }); // Transform API data to component format const tasks = useMemo(() => { if (tasksResponse?.body && phasesResponse?.body) { const transformedTasks = transformToGanttTasks(tasksResponse.body, phasesResponse.body); const result: any[] = []; transformedTasks.forEach(task => { // Always show phase milestones if (task.type === 'milestone' || task.is_milestone) { result.push(task); // If this phase is expanded, show its children tasks const phaseId = task.id === 'phase-unmapped' ? 'unmapped' : task.phase_id || task.id.replace('phase-', ''); const isExpanded = expandedTasks.has(phaseId); if (isExpanded && task.children) { task.children.forEach((child: any) => { result.push({ ...child, phase_id: task.phase_id, // Ensure child has correct phase_id }); }); } } }); return result; } return []; }, [tasksResponse, phasesResponse, expandedTasks]); const phases = useMemo(() => { if (phasesResponse?.body) { return transformToGanttPhases(phasesResponse.body); } return []; }, [phasesResponse]); // Calculate date range based on tasks const dateRange = useMemo(() => { if (tasks.length > 0) { return TimelineUtils.getSmartDateRange(tasks, viewMode); } return { start: new Date(), end: new Date() }; }, [tasks, viewMode]); const loading = tasksLoading || phasesLoading; // Load priorities for task drawer functionality useEffect(() => { dispatch(fetchPriorities()); }, [dispatch]); const handleViewModeChange = useCallback((mode: GanttViewMode) => { setViewMode(mode); }, []); const handleChartScroll = useCallback((e: React.UIEvent) => { const target = e.target as HTMLDivElement; // Sync horizontal scroll with timeline if (timelineRef.current) { timelineRef.current.scrollLeft = target.scrollLeft; } // Sync vertical scroll with task list if (taskListRef.current) { taskListRef.current.scrollTop = target.scrollTop; } }, []); const handleTaskListScroll = useCallback((e: React.UIEvent) => { const target = e.target as HTMLDivElement; // Sync vertical scroll with chart if (chartRef.current) { chartRef.current.scrollTop = target.scrollTop; } }, []); const handleRefresh = useCallback(() => { refetchTasks(); refetchPhases(); }, [refetchTasks, refetchPhases]); const handleCreatePhase = useCallback(() => { setShowPhaseModal(true); }, []); const handleCreateTask = useCallback( (phaseId?: string) => { // Create a new task using the task drawer const newTaskViewModel = { id: null, name: DEFAULT_TASK_NAME, project_id: projectId, phase_id: phaseId || null, // Add other default properties as needed }; dispatch(setSelectedTaskId(null)); dispatch(setTaskFormViewModel(newTaskViewModel)); dispatch(setShowTaskDrawer(true)); }, [dispatch, projectId] ); const handleTaskClick = useCallback( (taskId: string) => { // Open existing task in the task drawer dispatch(setSelectedTaskId(taskId)); dispatch(setTaskFormViewModel(null)); // Clear form view model for existing task dispatch(setShowTaskDrawer(true)); // Fetch the complete task data including priorities if (projectId) { dispatch(fetchTask({ taskId, projectId })); } }, [dispatch, projectId] ); const handleClosePhaseModal = useCallback(() => { setShowPhaseModal(false); }, []); const handlePhaseReorder = useCallback((oldIndex: number, newIndex: number) => { // TODO: Implement phase reordering API call console.log('Reorder phases:', { oldIndex, newIndex }); message.info('Phase reordering will be implemented with the backend API'); }, []); const handleCreateQuickTask = useCallback( (taskName: string, phaseId?: string) => { // Refresh the Gantt data after task creation to show the new task refetchTasks(); }, [refetchTasks] ); // Handle errors if (tasksError || phasesError) { message.error('Failed to load Gantt chart data'); } if (loading) { return (
); } return (
{/* Fixed Task List - positioned absolutely to avoid scrollbar interference */}
{/* Scrollable Timeline and Chart - with left margin for task list */}
{/* Phase Management Modal */}
); }); ProjectViewGantt.displayName = 'ProjectViewGantt'; export default ProjectViewGantt;