feat(gantt): introduce advanced Gantt chart components and demo page

- Added new components for an advanced Gantt chart, including AdvancedGanttChart, GanttGrid, DraggableTaskBar, and TimelineMarkers.
- Implemented a demo page (GanttDemoPage) to showcase the functionality of the new Gantt chart components.
- Enhanced project roadmap features with ProjectRoadmapGantt and related components for better project management visualization.
- Introduced sample data for testing and demonstration purposes, improving the user experience in the Gantt chart interface.
- Updated main routes to include the new Gantt demo page for easy access.
This commit is contained in:
chamikaJ
2025-07-20 22:05:42 +05:30
parent a112d39321
commit 78d960bf01
19 changed files with 5011 additions and 0 deletions

View File

@@ -0,0 +1,406 @@
import React, { useState } from 'react';
import {
Modal,
Tabs,
Progress,
Tag,
List,
Avatar,
Badge,
Space,
Button,
Statistic,
Row,
Col,
Timeline,
Input,
Form,
DatePicker,
Select
} from 'antd';
import {
CalendarOutlined,
TeamOutlined,
CheckCircleOutlined,
ClockCircleOutlined,
FlagOutlined,
ExclamationCircleOutlined,
EditOutlined,
SaveOutlined,
CloseOutlined
} from '@ant-design/icons';
import { PhaseModalData, ProjectPhase, PhaseTask, PhaseMilestone } from '../../types/project-roadmap.types';
import { useAppSelector } from '../../hooks/useAppSelector';
import { themeWiseColor } from '../../utils/themeWiseColor';
import dayjs from 'dayjs';
const { TabPane } = Tabs;
const { TextArea } = Input;
interface PhaseModalProps {
visible: boolean;
phase: PhaseModalData | null;
onClose: () => void;
onUpdate?: (updates: Partial<ProjectPhase>) => void;
}
const PhaseModal: React.FC<PhaseModalProps> = ({
visible,
phase,
onClose,
onUpdate,
}) => {
const [isEditing, setIsEditing] = useState(false);
const [form] = Form.useForm();
// Theme support
const themeMode = useAppSelector(state => state.themeReducer.mode);
const isDarkMode = themeMode === 'dark';
if (!phase) return null;
const handleEdit = () => {
setIsEditing(true);
form.setFieldsValue({
name: phase.name,
description: phase.description,
startDate: dayjs(phase.startDate),
endDate: dayjs(phase.endDate),
status: phase.status,
});
};
const handleSave = async () => {
try {
const values = await form.validateFields();
const updates: Partial<ProjectPhase> = {
name: values.name,
description: values.description,
startDate: values.startDate.toDate(),
endDate: values.endDate.toDate(),
status: values.status,
};
onUpdate?.(updates);
setIsEditing(false);
} catch (error) {
console.error('Validation failed:', error);
}
};
const handleCancel = () => {
setIsEditing(false);
form.resetFields();
};
const getStatusColor = (status: string) => {
switch (status) {
case 'completed': return 'success';
case 'in-progress': return 'processing';
case 'on-hold': return 'warning';
default: return 'default';
}
};
const getPriorityColor = (priority: string) => {
switch (priority) {
case 'high': return 'red';
case 'medium': return 'orange';
case 'low': return 'green';
default: return 'default';
}
};
const getTaskStatusIcon = (status: string) => {
switch (status) {
case 'done': return <CheckCircleOutlined style={{ color: '#52c41a' }} />;
case 'in-progress': return <ClockCircleOutlined style={{ color: '#1890ff' }} />;
default: return <ExclamationCircleOutlined style={{ color: '#d9d9d9' }} />;
}
};
return (
<Modal
title={
<div className="flex justify-between items-center">
<Space>
<Badge status={getStatusColor(phase.status)} />
{isEditing ? (
<Form.Item name="name" className="mb-0">
<Input className="dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100" />
</Form.Item>
) : (
<h4 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-0">
{phase.name}
</h4>
)}
</Space>
<Space>
{isEditing ? (
<>
<Button
type="primary"
icon={<SaveOutlined />}
onClick={handleSave}
size="small"
>
Save
</Button>
<Button
icon={<CloseOutlined />}
onClick={handleCancel}
size="small"
className="dark:border-gray-600 dark:text-gray-300"
>
Cancel
</Button>
</>
) : (
<Button
type="text"
icon={<EditOutlined />}
onClick={handleEdit}
size="small"
className="dark:text-gray-300 dark:hover:bg-gray-700"
>
Edit
</Button>
)}
</Space>
</div>
}
open={visible}
onCancel={onClose}
width={800}
footer={null}
className="dark:bg-gray-800"
>
<Form form={form} layout="vertical">
<div className="mb-4">
{isEditing ? (
<Form.Item name="description" label={<span className="text-gray-700 dark:text-gray-300">Description</span>}>
<TextArea
rows={2}
className="dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100"
/>
</Form.Item>
) : (
<p className="text-gray-600 dark:text-gray-400">{phase.description}</p>
)}
</div>
{/* Phase Statistics */}
<Row gutter={16} className="mb-6">
<Col span={6}>
<div className="bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg p-4">
<Statistic
title={<span className="text-gray-600 dark:text-gray-400">Progress</span>}
value={phase.progress}
suffix="%"
valueStyle={{ color: themeWiseColor('#1890ff', '#40a9ff', themeMode) }}
/>
<Progress
percent={phase.progress}
showInfo={false}
size="small"
strokeColor={themeWiseColor('#1890ff', '#40a9ff', themeMode)}
trailColor={themeWiseColor('#f0f0f0', '#4b5563', themeMode)}
/>
</div>
</Col>
<Col span={6}>
<div className="bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg p-4">
<Statistic
title={<span className="text-gray-600 dark:text-gray-400">Tasks</span>}
value={phase.completedTaskCount}
suffix={`/ ${phase.taskCount}`}
valueStyle={{ color: themeWiseColor('#52c41a', '#34d399', themeMode) }}
/>
</div>
</Col>
<Col span={6}>
<div className="bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg p-4">
<Statistic
title={<span className="text-gray-600 dark:text-gray-400">Milestones</span>}
value={phase.completedMilestoneCount}
suffix={`/ ${phase.milestoneCount}`}
valueStyle={{ color: themeWiseColor('#722ed1', '#9f7aea', themeMode) }}
/>
</div>
</Col>
<Col span={6}>
<div className="bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg p-4">
<Statistic
title={<span className="text-gray-600 dark:text-gray-400">Team</span>}
value={phase.teamMembers.length}
suffix="members"
valueStyle={{ color: themeWiseColor('#fa8c16', '#fbbf24', themeMode) }}
/>
</div>
</Col>
</Row>
{/* Timeline */}
<Row gutter={16} className="mb-6">
<Col span={12}>
{isEditing ? (
<Form.Item name="startDate" label={<span className="text-gray-700 dark:text-gray-300">Start Date</span>}>
<DatePicker className="w-full dark:bg-gray-700 dark:border-gray-600" />
</Form.Item>
) : (
<div className="bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg p-4">
<div className="flex items-center gap-2">
<CalendarOutlined className="text-gray-600 dark:text-gray-400" />
<span className="font-medium text-gray-900 dark:text-gray-100">Start:</span>
<span className="text-gray-600 dark:text-gray-400">{phase.startDate.toLocaleDateString()}</span>
</div>
</div>
)}
</Col>
<Col span={12}>
{isEditing ? (
<Form.Item name="endDate" label={<span className="text-gray-700 dark:text-gray-300">End Date</span>}>
<DatePicker className="w-full dark:bg-gray-700 dark:border-gray-600" />
</Form.Item>
) : (
<div className="bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg p-4">
<div className="flex items-center gap-2">
<CalendarOutlined className="text-gray-600 dark:text-gray-400" />
<span className="font-medium text-gray-900 dark:text-gray-100">End:</span>
<span className="text-gray-600 dark:text-gray-400">{phase.endDate.toLocaleDateString()}</span>
</div>
</div>
)}
</Col>
</Row>
{isEditing && (
<Row gutter={16} className="mb-6">
<Col span={12}>
<Form.Item name="status" label={<span className="text-gray-700 dark:text-gray-300">Status</span>}>
<Select className="dark:bg-gray-700 dark:border-gray-600">
<Select.Option value="not-started">Not Started</Select.Option>
<Select.Option value="in-progress">In Progress</Select.Option>
<Select.Option value="completed">Completed</Select.Option>
<Select.Option value="on-hold">On Hold</Select.Option>
</Select>
</Form.Item>
</Col>
</Row>
)}
<Tabs
defaultActiveKey="tasks"
className="dark:bg-gray-800"
tabBarStyle={{
borderBottom: `1px solid ${themeWiseColor('#f0f0f0', '#4b5563', themeMode)}`
}}
>
<TabPane tab={`Tasks (${phase.taskCount})`} key="tasks">
<List
dataSource={phase.tasks}
renderItem={(task: PhaseTask) => (
<List.Item>
<List.Item.Meta
avatar={getTaskStatusIcon(task.status)}
title={
<div className="flex justify-between items-center">
<Text strong>{task.name}</Text>
<Space>
<Tag color={getPriorityColor(task.priority)}>
{task.priority}
</Tag>
<Progress
percent={task.progress}
size="small"
style={{ width: 100 }}
/>
</Space>
</div>
}
description={
<div>
<Text type="secondary">{task.description}</Text>
<div className="mt-2 flex justify-between items-center">
<Space>
<CalendarOutlined />
<Text type="secondary">
{task.startDate.toLocaleDateString()} - {task.endDate.toLocaleDateString()}
</Text>
</Space>
{task.assigneeName && (
<Space>
<TeamOutlined />
<Text type="secondary">{task.assigneeName}</Text>
</Space>
)}
</div>
</div>
}
/>
</List.Item>
)}
/>
</TabPane>
<TabPane tab={`Milestones (${phase.milestoneCount})`} key="milestones">
<Timeline>
{phase.milestones.map((milestone: PhaseMilestone) => (
<Timeline.Item
key={milestone.id}
color={milestone.isCompleted ? 'green' : milestone.criticalPath ? 'red' : 'blue'}
dot={milestone.isCompleted ? <CheckCircleOutlined /> : <FlagOutlined />}
>
<div className="flex justify-between items-start">
<div>
<Text strong>{milestone.name}</Text>
{milestone.criticalPath && (
<Tag color="red" className="ml-2">Critical Path</Tag>
)}
{milestone.description && (
<div className="mt-1">
<Text type="secondary">{milestone.description}</Text>
</div>
)}
</div>
<div className="text-right">
<div>
<CalendarOutlined />
<span className="ml-1">{milestone.dueDate.toLocaleDateString()}</span>
</div>
<Badge
status={milestone.isCompleted ? 'success' : 'processing'}
text={milestone.isCompleted ? 'Completed' : 'Pending'}
/>
</div>
</div>
</Timeline.Item>
))}
</Timeline>
</TabPane>
<TabPane tab={`Team (${phase.teamMembers.length})`} key="team">
<List
dataSource={phase.teamMembers}
renderItem={(member: string) => (
<List.Item>
<List.Item.Meta
avatar={<Avatar>{member.charAt(0).toUpperCase()}</Avatar>}
title={member}
description={
<Text type="secondary">
{phase.tasks.filter(task => task.assigneeName === member).length} tasks assigned
</Text>
}
/>
</List.Item>
)}
/>
</TabPane>
</Tabs>
</Form>
</Modal>
);
};
export default PhaseModal;

View File

@@ -0,0 +1,333 @@
import React, { useState, useMemo } from 'react';
import { Gantt, Task, ViewMode } from 'gantt-task-react';
import { Button, Space, Badge } from 'antd';
import { CalendarOutlined, TeamOutlined, CheckCircleOutlined } from '@ant-design/icons';
import { ProjectPhase, ProjectRoadmap, GanttViewOptions, PhaseModalData } from '../../types/project-roadmap.types';
import PhaseModal from './PhaseModal';
import { useAppSelector } from '../../hooks/useAppSelector';
import { themeWiseColor } from '../../utils/themeWiseColor';
import 'gantt-task-react/dist/index.css';
import './gantt-theme.css';
interface ProjectRoadmapGanttProps {
roadmap: ProjectRoadmap;
viewOptions?: Partial<GanttViewOptions>;
onPhaseUpdate?: (phaseId: string, updates: Partial<ProjectPhase>) => void;
onTaskUpdate?: (phaseId: string, taskId: string, updates: any) => void;
}
const ProjectRoadmapGantt: React.FC<ProjectRoadmapGanttProps> = ({
roadmap,
viewOptions = {},
onPhaseUpdate,
onTaskUpdate,
}) => {
const [selectedPhase, setSelectedPhase] = useState<PhaseModalData | null>(null);
const [isModalVisible, setIsModalVisible] = useState(false);
const [viewMode, setViewMode] = useState<ViewMode>(ViewMode.Month);
// Theme support
const themeMode = useAppSelector(state => state.themeReducer.mode);
const isDarkMode = themeMode === 'dark';
const defaultViewOptions: GanttViewOptions = {
viewMode: 'month',
showTasks: true,
showMilestones: true,
groupByPhase: true,
...viewOptions,
};
// Theme-aware colors
const ganttColors = useMemo(() => {
return {
background: themeWiseColor('#ffffff', '#1f2937', themeMode),
surface: themeWiseColor('#f8f9fa', '#374151', themeMode),
border: themeWiseColor('#e5e7eb', '#4b5563', themeMode),
taskBar: themeWiseColor('#3b82f6', '#60a5fa', themeMode),
taskBarHover: themeWiseColor('#2563eb', '#93c5fd', themeMode),
progressBar: themeWiseColor('#10b981', '#34d399', themeMode),
milestone: themeWiseColor('#f59e0b', '#fbbf24', themeMode),
criticalPath: themeWiseColor('#ef4444', '#f87171', themeMode),
text: {
primary: themeWiseColor('#111827', '#f9fafb', themeMode),
secondary: themeWiseColor('#6b7280', '#d1d5db', themeMode),
},
grid: themeWiseColor('#f3f4f6', '#4b5563', themeMode),
today: themeWiseColor('rgba(59, 130, 246, 0.1)', 'rgba(96, 165, 250, 0.2)', themeMode),
};
}, [themeMode]);
// Convert phases to Gantt tasks
const ganttTasks = useMemo(() => {
const tasks: Task[] = [];
roadmap.phases.forEach((phase, phaseIndex) => {
// Add phase as main task with theme-aware colors
const phaseTask: Task = {
id: phase.id,
name: phase.name,
start: phase.startDate,
end: phase.endDate,
progress: phase.progress,
type: 'project',
styles: {
progressColor: themeWiseColor(phase.color, phase.color, themeMode),
progressSelectedColor: themeWiseColor(phase.color, phase.color, themeMode),
backgroundColor: themeWiseColor(`${phase.color}20`, `${phase.color}30`, themeMode),
},
};
tasks.push(phaseTask);
// Add phase tasks if enabled
if (defaultViewOptions.showTasks) {
phase.tasks.forEach((task) => {
const ganttTask: Task = {
id: task.id,
name: task.name,
start: task.startDate,
end: task.endDate,
progress: task.progress,
type: 'task',
project: phase.id,
dependencies: task.dependencies,
styles: {
progressColor: ganttColors.taskBar,
progressSelectedColor: ganttColors.taskBarHover,
backgroundColor: themeWiseColor('rgba(59, 130, 246, 0.1)', 'rgba(96, 165, 250, 0.2)', themeMode),
},
};
tasks.push(ganttTask);
});
}
// Add milestones if enabled
if (defaultViewOptions.showMilestones) {
phase.milestones.forEach((milestone) => {
const milestoneTask: Task = {
id: milestone.id,
name: milestone.name,
start: milestone.dueDate,
end: milestone.dueDate,
progress: milestone.isCompleted ? 100 : 0,
type: 'milestone',
project: phase.id,
styles: {
progressColor: milestone.criticalPath ? ganttColors.criticalPath : ganttColors.progressBar,
progressSelectedColor: milestone.criticalPath ? ganttColors.criticalPath : ganttColors.progressBar,
backgroundColor: milestone.criticalPath ?
themeWiseColor('rgba(239, 68, 68, 0.1)', 'rgba(248, 113, 113, 0.2)', themeMode) :
themeWiseColor('rgba(16, 185, 129, 0.1)', 'rgba(52, 211, 153, 0.2)', themeMode),
},
};
tasks.push(milestoneTask);
});
}
});
return tasks;
}, [roadmap.phases, defaultViewOptions, ganttColors, themeMode]);
const handlePhaseClick = (phase: ProjectPhase) => {
const taskCount = phase.tasks.length;
const completedTaskCount = phase.tasks.filter(task => task.status === 'done').length;
const milestoneCount = phase.milestones.length;
const completedMilestoneCount = phase.milestones.filter(m => m.isCompleted).length;
const teamMembers = [...new Set(phase.tasks.map(task => task.assigneeName).filter(Boolean))];
const phaseModalData: PhaseModalData = {
...phase,
taskCount,
completedTaskCount,
milestoneCount,
completedMilestoneCount,
teamMembers,
};
setSelectedPhase(phaseModalData);
setIsModalVisible(true);
};
const handleTaskClick = (task: Task) => {
// Find the phase this task belongs to
const phase = roadmap.phases.find(p =>
p.tasks.some(t => t.id === task.id) || p.milestones.some(m => m.id === task.id)
);
if (phase) {
handlePhaseClick(phase);
}
};
const handleDateChange = (task: Task) => {
const phase = roadmap.phases.find(p => p.id === task.id);
if (phase && onPhaseUpdate) {
onPhaseUpdate(phase.id, {
startDate: task.start,
endDate: task.end,
});
} else if (onTaskUpdate) {
const parentPhase = roadmap.phases.find(p =>
p.tasks.some(t => t.id === task.id)
);
if (parentPhase) {
onTaskUpdate(parentPhase.id, task.id, {
startDate: task.start,
endDate: task.end,
});
}
}
};
const handleProgressChange = (task: Task) => {
const phase = roadmap.phases.find(p => p.id === task.id);
if (phase && onPhaseUpdate) {
onPhaseUpdate(phase.id, { progress: task.progress });
} else if (onTaskUpdate) {
const parentPhase = roadmap.phases.find(p =>
p.tasks.some(t => t.id === task.id)
);
if (parentPhase) {
onTaskUpdate(parentPhase.id, task.id, { progress: task.progress });
}
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'completed': return '#52c41a';
case 'in-progress': return '#1890ff';
case 'on-hold': return '#faad14';
default: return '#d9d9d9';
}
};
const columnWidth = viewMode === ViewMode.Year ? 350 :
viewMode === ViewMode.Month ? 300 :
viewMode === ViewMode.Week ? 250 : 60;
return (
<div className="project-roadmap-gantt w-full">
{/* Header */}
<div className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm mb-4">
<div className="p-6">
<div className="flex justify-between items-center mb-4">
<div>
<h3 className="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-2">
{roadmap.name}
</h3>
{roadmap.description && (
<p className="text-gray-600 dark:text-gray-400 mb-0">
{roadmap.description}
</p>
)}
</div>
<Space>
<Button
type={viewMode === ViewMode.Week ? 'primary' : 'default'}
onClick={() => setViewMode(ViewMode.Week)}
className="dark:border-gray-600 dark:text-gray-300"
>
Week
</Button>
<Button
type={viewMode === ViewMode.Month ? 'primary' : 'default'}
onClick={() => setViewMode(ViewMode.Month)}
className="dark:border-gray-600 dark:text-gray-300"
>
Month
</Button>
<Button
type={viewMode === ViewMode.Year ? 'primary' : 'default'}
onClick={() => setViewMode(ViewMode.Year)}
className="dark:border-gray-600 dark:text-gray-300"
>
Year
</Button>
</Space>
</div>
{/* Phase Overview */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
{roadmap.phases.map((phase) => (
<div
key={phase.id}
className="bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg p-4 cursor-pointer hover:shadow-md hover:bg-gray-100 dark:hover:bg-gray-600 transition-all duration-200"
onClick={() => handlePhaseClick(phase)}
>
<div className="flex items-center justify-between mb-3">
<Badge
color={getStatusColor(phase.status)}
text={
<span className="font-medium text-gray-900 dark:text-gray-100">
{phase.name}
</span>
}
/>
</div>
<div className="space-y-2 text-sm">
<div className="flex items-center gap-2 text-gray-600 dark:text-gray-400">
<CalendarOutlined className="w-4 h-4" />
<span>{phase.startDate.toLocaleDateString()} - {phase.endDate.toLocaleDateString()}</span>
</div>
<div className="flex items-center gap-2 text-gray-600 dark:text-gray-400">
<TeamOutlined className="w-4 h-4" />
<span>{phase.tasks.length} tasks</span>
</div>
<div className="flex items-center gap-2 text-gray-600 dark:text-gray-400">
<CheckCircleOutlined className="w-4 h-4" />
<span>{phase.progress}% complete</span>
</div>
</div>
</div>
))}
</div>
</div>
</div>
{/* Gantt Chart */}
<div className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm">
<div className="p-4">
<div className="w-full overflow-x-auto">
<div
className="gantt-container"
style={{
'--gantt-background': ganttColors.background,
'--gantt-grid': ganttColors.grid,
'--gantt-text': ganttColors.text.primary,
'--gantt-border': ganttColors.border,
} as React.CSSProperties}
>
<Gantt
tasks={ganttTasks}
viewMode={viewMode}
onDateChange={handleDateChange}
onProgressChange={handleProgressChange}
onDoubleClick={handleTaskClick}
listCellWidth=""
columnWidth={columnWidth}
todayColor={ganttColors.today}
projectProgressColor={ganttColors.progressBar}
projectBackgroundColor={themeWiseColor('rgba(82, 196, 26, 0.1)', 'rgba(52, 211, 153, 0.2)', themeMode)}
/>
</div>
</div>
</div>
</div>
{/* Phase Modal */}
<PhaseModal
visible={isModalVisible}
phase={selectedPhase}
onClose={() => setIsModalVisible(false)}
onUpdate={(updates) => {
if (selectedPhase && onPhaseUpdate) {
onPhaseUpdate(selectedPhase.id, updates);
}
}}
/>
</div>
);
};
export default ProjectRoadmapGantt;

View File

@@ -0,0 +1,112 @@
import React, { useState } from 'react';
import { Button, Space, message } from 'antd';
import ProjectRoadmapGantt from './ProjectRoadmapGantt';
import { sampleProjectRoadmap } from './sample-data';
import { ProjectPhase, ProjectRoadmap } from '../../types/project-roadmap.types';
import { useAppSelector } from '../../hooks/useAppSelector';
const RoadmapDemo: React.FC = () => {
const [roadmap, setRoadmap] = useState<ProjectRoadmap>(sampleProjectRoadmap);
const themeMode = useAppSelector(state => state.themeReducer.mode);
const handlePhaseUpdate = (phaseId: string, updates: Partial<ProjectPhase>) => {
setRoadmap(prevRoadmap => ({
...prevRoadmap,
phases: prevRoadmap.phases.map(phase =>
phase.id === phaseId
? { ...phase, ...updates }
: phase
)
}));
message.success('Phase updated successfully!');
};
const handleTaskUpdate = (phaseId: string, taskId: string, updates: any) => {
setRoadmap(prevRoadmap => ({
...prevRoadmap,
phases: prevRoadmap.phases.map(phase =>
phase.id === phaseId
? {
...phase,
tasks: phase.tasks.map(task =>
task.id === taskId
? { ...task, ...updates }
: task
)
}
: phase
)
}));
message.success('Task updated successfully!');
};
const resetToSampleData = () => {
setRoadmap(sampleProjectRoadmap);
message.info('Roadmap reset to sample data');
};
return (
<div className="roadmap-demo p-6 bg-gray-50 dark:bg-gray-900 min-h-screen">
<div className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm mb-4">
<div className="p-6">
<div className="flex justify-between items-center">
<div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-2">
Project Roadmap Gantt Chart Demo
</h2>
<p className="text-gray-600 dark:text-gray-400 mb-0">
Interactive Gantt chart showing project phases as milestones/epics.
Click on any phase card or Gantt bar to view detailed information in a modal.
</p>
</div>
<Space>
<Button
onClick={resetToSampleData}
className="dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-700"
>
Reset to Sample Data
</Button>
</Space>
</div>
</div>
</div>
<ProjectRoadmapGantt
roadmap={roadmap}
viewOptions={{
viewMode: 'month',
showTasks: true,
showMilestones: true,
groupByPhase: true,
}}
onPhaseUpdate={handlePhaseUpdate}
onTaskUpdate={handleTaskUpdate}
/>
<div className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm mt-4">
<div className="p-6">
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-3">
Features Demonstrated:
</h3>
<ul className="space-y-2 text-gray-700 dark:text-gray-300">
<li> <strong className="text-gray-900 dark:text-gray-100">Phase-based Grouping:</strong> Projects organized by phases (Planning, Development, Testing, Deployment)</li>
<li> <strong className="text-gray-900 dark:text-gray-100">Interactive Phase Cards:</strong> Click on phase cards for detailed view</li>
<li> <strong className="text-gray-900 dark:text-gray-100">Gantt Chart Visualization:</strong> Timeline view with tasks, milestones, and dependencies</li>
<li> <strong className="text-gray-900 dark:text-gray-100">Modal Details:</strong> Comprehensive phase information with tasks, milestones, and team members</li>
<li> <strong className="text-gray-900 dark:text-gray-100">Progress Tracking:</strong> Visual progress indicators and completion statistics</li>
<li> <strong className="text-gray-900 dark:text-gray-100">Multiple View Modes:</strong> Week, Month, and Year timeline views</li>
<li> <strong className="text-gray-900 dark:text-gray-100">Task Management:</strong> Task assignments, priorities, and status tracking</li>
<li> <strong className="text-gray-900 dark:text-gray-100">Milestone Tracking:</strong> Critical path milestones and completion status</li>
<li> <strong className="text-gray-900 dark:text-gray-100">Team Overview:</strong> Team member assignments and workload distribution</li>
<li> <strong className="text-gray-900 dark:text-gray-100">Editable Fields:</strong> In-modal editing for phase attributes (name, description, dates, status)</li>
<li> <strong className="text-gray-900 dark:text-gray-100">Theme Support:</strong> Automatic light/dark theme adaptation with consistent styling</li>
</ul>
</div>
</div>
</div>
);
};
export default RoadmapDemo;

View File

@@ -0,0 +1,221 @@
/* Custom Gantt Chart Theme Overrides */
/* Light theme styles */
.gantt-container {
font-family: inherit;
}
.gantt-container .gantt-header {
background-color: var(--gantt-background, #ffffff);
border-bottom: 1px solid var(--gantt-border, #e5e7eb);
color: var(--gantt-text, #111827);
}
.gantt-container .gantt-task-list-wrapper {
background-color: var(--gantt-background, #ffffff);
border-right: 1px solid var(--gantt-border, #e5e7eb);
}
.gantt-container .gantt-task-list-table {
background-color: var(--gantt-background, #ffffff);
}
.gantt-container .gantt-task-list-table-row {
color: var(--gantt-text, #111827);
border-bottom: 1px solid var(--gantt-grid, #f3f4f6);
}
.gantt-container .gantt-task-list-table-row:hover {
background-color: var(--gantt-grid, #f3f4f6);
}
.gantt-container .gantt-gantt {
background-color: var(--gantt-background, #ffffff);
}
.gantt-container .gantt-vertical-container {
background-color: var(--gantt-background, #ffffff);
}
.gantt-container .gantt-horizontal-container {
background-color: var(--gantt-background, #ffffff);
}
.gantt-container .gantt-calendar {
background-color: var(--gantt-background, #ffffff);
border-bottom: 1px solid var(--gantt-border, #e5e7eb);
}
.gantt-container .gantt-calendar-row {
color: var(--gantt-text, #111827);
background-color: var(--gantt-background, #ffffff);
border-bottom: 1px solid var(--gantt-grid, #f3f4f6);
}
.gantt-container .gantt-grid-body {
background-color: var(--gantt-background, #ffffff);
}
.gantt-container .gantt-grid-row {
border-bottom: 1px solid var(--gantt-grid, #f3f4f6);
}
.gantt-container .gantt-grid-row-line {
border-left: 1px solid var(--gantt-grid, #f3f4f6);
}
/* Dark theme overrides */
html.dark .gantt-container .gantt-header {
background-color: #1f2937;
border-bottom-color: #4b5563;
color: #f9fafb;
}
html.dark .gantt-container .gantt-task-list-wrapper {
background-color: #1f2937;
border-right-color: #4b5563;
}
html.dark .gantt-container .gantt-task-list-table {
background-color: #1f2937;
}
html.dark .gantt-container .gantt-task-list-table-row {
color: #f9fafb;
border-bottom-color: #4b5563;
}
html.dark .gantt-container .gantt-task-list-table-row:hover {
background-color: #374151;
}
html.dark .gantt-container .gantt-gantt {
background-color: #1f2937;
}
html.dark .gantt-container .gantt-vertical-container {
background-color: #1f2937;
}
html.dark .gantt-container .gantt-horizontal-container {
background-color: #1f2937;
}
html.dark .gantt-container .gantt-calendar {
background-color: #1f2937;
border-bottom-color: #4b5563;
}
html.dark .gantt-container .gantt-calendar-row {
color: #f9fafb;
background-color: #1f2937;
border-bottom-color: #4b5563;
}
html.dark .gantt-container .gantt-grid-body {
background-color: #1f2937;
}
html.dark .gantt-container .gantt-grid-row {
border-bottom-color: #4b5563;
}
html.dark .gantt-container .gantt-grid-row-line {
border-left-color: #4b5563;
}
/* Tooltip theming */
.gantt-container .gantt-tooltip {
background-color: var(--gantt-background, #ffffff);
border: 1px solid var(--gantt-border, #e5e7eb);
color: var(--gantt-text, #111827);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
html.dark .gantt-container .gantt-tooltip {
background-color: #374151;
border-color: #4b5563;
color: #f9fafb;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
}
/* Task bar styling improvements */
.gantt-container .gantt-task-progress {
border-radius: 2px;
}
.gantt-container .gantt-task-background {
border-radius: 4px;
border: 1px solid var(--gantt-border, #e5e7eb);
}
html.dark .gantt-container .gantt-task-background {
border-color: #4b5563;
}
/* Milestone styling */
.gantt-container .gantt-milestone {
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1));
}
html.dark .gantt-container .gantt-milestone {
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3));
}
/* Selection and hover states */
.gantt-container .gantt-task:hover .gantt-task-background {
opacity: 0.9;
transform: translateY(-1px);
transition: all 0.2s ease;
}
.gantt-container .gantt-task-selected .gantt-task-background {
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3);
}
html.dark .gantt-container .gantt-task-selected .gantt-task-background {
box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.3);
}
/* Responsive improvements */
@media (max-width: 768px) {
.gantt-container {
font-size: 14px;
}
.gantt-container .gantt-task-list-wrapper {
min-width: 200px;
}
}
/* Scrollbar theming */
.gantt-container ::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.gantt-container ::-webkit-scrollbar-track {
background: var(--gantt-grid, #f3f4f6);
border-radius: 4px;
}
.gantt-container ::-webkit-scrollbar-thumb {
background: var(--gantt-border, #e5e7eb);
border-radius: 4px;
}
.gantt-container ::-webkit-scrollbar-thumb:hover {
background: #9ca3af;
}
html.dark .gantt-container ::-webkit-scrollbar-track {
background: #4b5563;
}
html.dark .gantt-container ::-webkit-scrollbar-thumb {
background: #6b7280;
}
html.dark .gantt-container ::-webkit-scrollbar-thumb:hover {
background: #9ca3af;
}

View File

@@ -0,0 +1,5 @@
export { default as ProjectRoadmapGantt } from './ProjectRoadmapGantt';
export { default as PhaseModal } from './PhaseModal';
export { default as RoadmapDemo } from './RoadmapDemo';
export { sampleProjectRoadmap } from './sample-data';
export * from '../../types/project-roadmap.types';

View File

@@ -0,0 +1,317 @@
import { ProjectRoadmap, ProjectPhase, PhaseTask, PhaseMilestone } from '../../types/project-roadmap.types';
// Sample tasks for Planning Phase
const planningTasks: PhaseTask[] = [
{
id: 'task-planning-1',
name: 'Project Requirements Analysis',
description: 'Gather and analyze project requirements from stakeholders',
startDate: new Date(2024, 11, 1), // December 1, 2024
endDate: new Date(2024, 11, 5),
progress: 100,
assigneeId: 'user-1',
assigneeName: 'Alice Johnson',
priority: 'high',
status: 'done',
},
{
id: 'task-planning-2',
name: 'Technical Architecture Design',
description: 'Design the technical architecture and system components',
startDate: new Date(2024, 11, 6),
endDate: new Date(2024, 11, 12),
progress: 75,
assigneeId: 'user-2',
assigneeName: 'Bob Smith',
priority: 'high',
status: 'in-progress',
},
{
id: 'task-planning-3',
name: 'Resource Allocation Planning',
description: 'Plan and allocate team resources for the project',
startDate: new Date(2024, 11, 8),
endDate: new Date(2024, 11, 15),
progress: 50,
assigneeId: 'user-3',
assigneeName: 'Carol Davis',
priority: 'medium',
status: 'in-progress',
dependencies: ['task-planning-1'],
}
];
// Sample milestones for Planning Phase
const planningMilestones: PhaseMilestone[] = [
{
id: 'milestone-planning-1',
name: 'Requirements Approved',
description: 'All project requirements have been reviewed and approved by stakeholders',
dueDate: new Date(2024, 11, 5),
isCompleted: true,
criticalPath: true,
},
{
id: 'milestone-planning-2',
name: 'Architecture Review Complete',
description: 'Technical architecture has been reviewed and approved',
dueDate: new Date(2024, 11, 15),
isCompleted: false,
criticalPath: true,
}
];
// Sample tasks for Development Phase
const developmentTasks: PhaseTask[] = [
{
id: 'task-dev-1',
name: 'Frontend Component Development',
description: 'Develop core frontend components using React',
startDate: new Date(2024, 11, 16),
endDate: new Date(2025, 0, 31), // January 31, 2025
progress: 30,
assigneeId: 'user-4',
assigneeName: 'David Wilson',
priority: 'high',
status: 'in-progress',
dependencies: ['task-planning-2'],
},
{
id: 'task-dev-2',
name: 'Backend API Development',
description: 'Develop REST APIs and database models',
startDate: new Date(2024, 11, 16),
endDate: new Date(2025, 0, 25),
progress: 45,
assigneeId: 'user-5',
assigneeName: 'Eva Brown',
priority: 'high',
status: 'in-progress',
dependencies: ['task-planning-2'],
},
{
id: 'task-dev-3',
name: 'Database Setup and Migration',
description: 'Set up production database and create migration scripts',
startDate: new Date(2024, 11, 20),
endDate: new Date(2025, 0, 15),
progress: 80,
assigneeId: 'user-6',
assigneeName: 'Frank Miller',
priority: 'medium',
status: 'in-progress',
}
];
// Sample milestones for Development Phase
const developmentMilestones: PhaseMilestone[] = [
{
id: 'milestone-dev-1',
name: 'Core Components Complete',
description: 'All core frontend components have been developed and tested',
dueDate: new Date(2025, 0, 20),
isCompleted: false,
criticalPath: false,
},
{
id: 'milestone-dev-2',
name: 'API Development Complete',
description: 'All backend APIs are developed and documented',
dueDate: new Date(2025, 0, 25),
isCompleted: false,
criticalPath: true,
}
];
// Sample tasks for Testing Phase
const testingTasks: PhaseTask[] = [
{
id: 'task-test-1',
name: 'Unit Testing Implementation',
description: 'Write and execute comprehensive unit tests',
startDate: new Date(2025, 1, 1), // February 1, 2025
endDate: new Date(2025, 1, 15),
progress: 0,
assigneeId: 'user-7',
assigneeName: 'Grace Lee',
priority: 'high',
status: 'todo',
dependencies: ['task-dev-1', 'task-dev-2'],
},
{
id: 'task-test-2',
name: 'Integration Testing',
description: 'Perform integration testing between frontend and backend',
startDate: new Date(2025, 1, 10),
endDate: new Date(2025, 1, 25),
progress: 0,
assigneeId: 'user-8',
assigneeName: 'Henry Clark',
priority: 'high',
status: 'todo',
dependencies: ['task-test-1'],
},
{
id: 'task-test-3',
name: 'User Acceptance Testing',
description: 'Conduct user acceptance testing with stakeholders',
startDate: new Date(2025, 1, 20),
endDate: new Date(2025, 2, 5), // March 5, 2025
progress: 0,
assigneeId: 'user-9',
assigneeName: 'Ivy Taylor',
priority: 'medium',
status: 'todo',
dependencies: ['task-test-2'],
}
];
// Sample milestones for Testing Phase
const testingMilestones: PhaseMilestone[] = [
{
id: 'milestone-test-1',
name: 'All Tests Passing',
description: 'All unit and integration tests are passing',
dueDate: new Date(2025, 1, 25),
isCompleted: false,
criticalPath: true,
},
{
id: 'milestone-test-2',
name: 'UAT Sign-off',
description: 'User acceptance testing completed and signed off',
dueDate: new Date(2025, 2, 5),
isCompleted: false,
criticalPath: true,
}
];
// Sample tasks for Deployment Phase
const deploymentTasks: PhaseTask[] = [
{
id: 'task-deploy-1',
name: 'Production Environment Setup',
description: 'Configure and set up production environment',
startDate: new Date(2025, 2, 6), // March 6, 2025
endDate: new Date(2025, 2, 12),
progress: 0,
assigneeId: 'user-10',
assigneeName: 'Jack Anderson',
priority: 'high',
status: 'todo',
dependencies: ['task-test-3'],
},
{
id: 'task-deploy-2',
name: 'Application Deployment',
description: 'Deploy application to production environment',
startDate: new Date(2025, 2, 13),
endDate: new Date(2025, 2, 15),
progress: 0,
assigneeId: 'user-11',
assigneeName: 'Kelly White',
priority: 'high',
status: 'todo',
dependencies: ['task-deploy-1'],
},
{
id: 'task-deploy-3',
name: 'Post-Deployment Monitoring',
description: 'Monitor application performance post-deployment',
startDate: new Date(2025, 2, 16),
endDate: new Date(2025, 2, 20),
progress: 0,
assigneeId: 'user-12',
assigneeName: 'Liam Garcia',
priority: 'medium',
status: 'todo',
dependencies: ['task-deploy-2'],
}
];
// Sample milestones for Deployment Phase
const deploymentMilestones: PhaseMilestone[] = [
{
id: 'milestone-deploy-1',
name: 'Production Go-Live',
description: 'Application is live in production environment',
dueDate: new Date(2025, 2, 15),
isCompleted: false,
criticalPath: true,
},
{
id: 'milestone-deploy-2',
name: 'Project Handover',
description: 'Project handed over to maintenance team',
dueDate: new Date(2025, 2, 20),
isCompleted: false,
criticalPath: false,
}
];
// Sample project phases
const samplePhases: ProjectPhase[] = [
{
id: 'phase-planning',
name: 'Planning & Analysis',
description: 'Initial project planning, requirements gathering, and technical analysis',
startDate: new Date(2024, 11, 1),
endDate: new Date(2024, 11, 15),
progress: 75,
color: '#1890ff',
status: 'in-progress',
tasks: planningTasks,
milestones: planningMilestones,
},
{
id: 'phase-development',
name: 'Development',
description: 'Core development of frontend, backend, and database components',
startDate: new Date(2024, 11, 16),
endDate: new Date(2025, 0, 31),
progress: 40,
color: '#52c41a',
status: 'in-progress',
tasks: developmentTasks,
milestones: developmentMilestones,
},
{
id: 'phase-testing',
name: 'Testing & QA',
description: 'Comprehensive testing including unit, integration, and user acceptance testing',
startDate: new Date(2025, 1, 1),
endDate: new Date(2025, 2, 5),
progress: 0,
color: '#faad14',
status: 'not-started',
tasks: testingTasks,
milestones: testingMilestones,
},
{
id: 'phase-deployment',
name: 'Deployment & Launch',
description: 'Production deployment and project launch activities',
startDate: new Date(2025, 2, 6),
endDate: new Date(2025, 2, 20),
progress: 0,
color: '#722ed1',
status: 'not-started',
tasks: deploymentTasks,
milestones: deploymentMilestones,
}
];
// Sample project roadmap
export const sampleProjectRoadmap: ProjectRoadmap = {
id: 'roadmap-sample-project',
projectId: 'project-web-platform',
name: 'Web Platform Development Roadmap',
description: 'Comprehensive roadmap for developing a new web-based platform with modern technologies and agile methodologies',
startDate: new Date(2024, 11, 1),
endDate: new Date(2025, 2, 20),
phases: samplePhases,
createdAt: new Date(2024, 10, 15),
updatedAt: new Date(2024, 11, 1),
};
export default sampleProjectRoadmap;