diff --git a/packages/web/src/components/InteractiveGraphVisualization.tsx b/packages/web/src/components/InteractiveGraphVisualization.tsx index e3a186b4..f9048c65 100644 --- a/packages/web/src/components/InteractiveGraphVisualization.tsx +++ b/packages/web/src/components/InteractiveGraphVisualization.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState, useCallback } from 'react'; import * as d3 from 'd3'; -import { Link2, Edit3, Trash2, Eye, Clock, User, Tag } from 'lucide-react'; +import { Link2, Edit3, Trash2, Eye, Clock, User, Tag, Plus, Minus } from 'lucide-react'; import { useGraph } from '../contexts/GraphContext'; import { mockProjectNodes, mockProjectEdges, relationshipTypeInfo, MockNode, MockEdge, RelationshipType } from '../types/projectData'; @@ -19,6 +19,8 @@ interface EdgeMenuState { export function InteractiveGraphVisualization() { const svgRef = useRef(null); const containerRef = useRef(null); + const graphSwitcherRef = useRef(null); + const zoomBehaviorRef = useRef | null>(null); const { currentGraph, availableGraphs, selectGraph } = useGraph(); const [nodeMenu, setNodeMenu] = useState({ node: null, position: { x: 0, y: 0 }, visible: false }); @@ -252,6 +254,7 @@ export function InteractiveGraphVisualization() { const zoom = d3.zoom() .scaleExtent([0.1, 4]); + zoomBehaviorRef.current = zoom; svg.call(zoom); const g = svg.append('g'); @@ -729,57 +732,7 @@ export function InteractiveGraphVisualization() { updateHtmlLabels(); }); - // Add zoom controls - const zoomControls = svg.append('g') - .attr('class', 'zoom-controls') - .attr('transform', 'translate(20, 20)'); - - const zoomIn = zoomControls.append('g') - .attr('class', 'zoom-button') - .style('cursor', 'pointer') - .on('click', () => { - svg.transition().duration(300).call(zoom.scaleBy, 1.5); - }); - - zoomIn.append('rect') - .attr('width', 30) - .attr('height', 30) - .attr('fill', '#374151') - .attr('stroke', '#6b7280') - .attr('rx', 4); - - zoomIn.append('text') - .attr('x', 15) - .attr('y', 20) - .attr('text-anchor', 'middle') - .attr('font-size', '16px') - .attr('font-weight', 'bold') - .attr('fill', 'white') - .text('+'); - - const zoomOut = zoomControls.append('g') - .attr('class', 'zoom-button') - .attr('transform', 'translate(0, 35)') - .style('cursor', 'pointer') - .on('click', () => { - svg.transition().duration(300).call(zoom.scaleBy, 0.67); - }); - - zoomOut.append('rect') - .attr('width', 30) - .attr('height', 30) - .attr('fill', '#374151') - .attr('stroke', '#6b7280') - .attr('rx', 4); - - zoomOut.append('text') - .attr('x', 15) - .attr('y', 20) - .attr('text-anchor', 'middle') - .attr('font-size', '16px') - .attr('font-weight', 'bold') - .attr('fill', 'white') - .text('−'); + // Zoom controls are now handled by React components }, [handleNodeClick, handleEdgeClick]); @@ -794,17 +747,36 @@ export function InteractiveGraphVisualization() { return () => window.removeEventListener('resize', handleResize); }, [initializeVisualization]); + // Close graph switcher when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (graphSwitcherRef.current && !graphSwitcherRef.current.contains(event.target as Node)) { + setShowGraphSwitcher(false); + } + }; + + if (showGraphSwitcher) { + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + } + }, [showGraphSwitcher]); + return (
- + setShowGraphSwitcher(false)} + /> {/* Graph Switcher Trigger */}
setShowGraphSwitcher(true)} - onMouseLeave={() => setShowGraphSwitcher(false)} > + +
+ {/* Graph Controls */}
+
+ + ); + + // Pie Chart Component + const PieChart = ({ data, title }: { data: Array<{label: string, value: number, color: string}>, title: string }) => { + const filteredData = data.filter(item => item.value > 0); + const total = filteredData.reduce((sum, item) => sum + item.value, 0); + let cumulativePercentage = 0; + + const createPath = (percentage: number, startPercentage: number) => { + const startAngle = startPercentage * 3.6 - 90; + const endAngle = (startPercentage + percentage) * 3.6 - 90; + const largeArcFlag = percentage > 50 ? 1 : 0; + + const startX = 50 + 40 * Math.cos(startAngle * Math.PI / 180); + const startY = 50 + 40 * Math.sin(startAngle * Math.PI / 180); + const endX = 50 + 40 * Math.cos(endAngle * Math.PI / 180); + const endY = 50 + 40 * Math.sin(endAngle * Math.PI / 180); + + return `M 50 50 L ${startX} ${startY} A 40 40 0 ${largeArcFlag} 1 ${endX} ${endY} Z`; + }; + + if (total === 0) { + return ( +
+

{title}

+
+
+
No data to display
+
Try adjusting your filters
+
+
+
+ ); + } + + return ( +
+

{title}

+
+ + {filteredData.map((item, index) => { + const percentage = (item.value / total) * 100; + const path = createPath(percentage, cumulativePercentage); + const currentCumulative = cumulativePercentage; + cumulativePercentage += percentage; + + return ( + + ); + })} + +
+ {filteredData.map((item, index) => ( +
+
+ {item.label} + {item.value} +
+ ))} +
+
+
+ ); + }; + + // Bar Chart Component + const BarChart = ({ data, title }: { data: Array<{label: string, value: number, color: string}>, title: string }) => { + const filteredData = data.filter(item => item.value > 0); + const maxValue = filteredData.length > 0 ? Math.max(...filteredData.map(item => item.value)) : 0; + const total = filteredData.reduce((sum, item) => sum + item.value, 0); + + if (filteredData.length === 0) { + return ( +
+

{title}

+
+
+
No data to display
+
Try adjusting your filters
+
+
+
+ ); + } + + return ( +
+

{title}

+
+ {filteredData.map((item, index) => ( +
+
+ {item.label} + {item.value} +
+
+
+
+
+ ))} +
+
+ ); + }; + + // Dashboard View + const renderDashboardView = () => ( +
+ {/* Stats Cards - First Row */} +
+
+
+
+
+ {stats.total} +
+
+
+
Total Tasks
+
{stats.total}
+
+
+
+ +
+
+
+
+ {stats.completed} +
+
+
+
Completed
+
{stats.completed}
+
+
+
+ +
+
+
+
+ {stats.inProgress} +
+
+
+
In Progress
+
{stats.inProgress}
+
+
+
+
+ + {/* Stats Cards - Second Row */} +
+
+
+
+
+ {stats.blocked} +
+
+
+
Blocked
+
{stats.blocked}
+
+
+
+ +
+
+
+
+ {stats.planned} +
+
+
+
Planned
+
{stats.planned}
+
+
+
+ +
+
+
+
+ {stats.proposed} +
+
+
+
Proposed
+
{stats.proposed}
+
+
+
+
+ + {/* Charts Section */} +
+ {/* Status Distribution Pie Chart */} + + + {/* Task Types Bar Chart */} + ({ + label: type, + value: count, + color: type === 'EPIC' ? '#3b82f6' : + type === 'FEATURE' ? '#3b82f6' : + type === 'TASK' ? '#10b981' : + type === 'BUG' ? '#ef4444' : + type === 'MILESTONE' ? '#f59e0b' : '#6b7280' + }))} + /> +
+ + {/* Recent Activity */} +
+

Recent Tasks

+
+ {filteredNodes.slice(0, 5).map((node) => ( +
+ + {node.type} + +
+
{node.title}
+
{node.status}
+
+
+
+ ))} +
+
+
+ ); + + + const currentViewOption = viewOptions.find(option => option.id === currentView)!; + + if (!currentGraph) { + return ( +
+
+

No Graph Selected

+

Select a graph to view its data.

+
+
+ ); + } + + return ( +
+ {/* Main Content */} +
+ {/* Header */} +
+
+
+

+ {currentGraph.name} +

+ + {/* View Selector */} +
+ + + {isViewDropdownOpen && ( +
+
+ {viewOptions.map((option) => ( + + ))} +
+
+ )} +
+
+
+ + {/* Search and Filters */} +
+ {/* Primary Search Row */} +
+
+ + setSearchTerm(e.target.value)} + className="w-full pl-10 pr-4 py-2 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent text-white placeholder-gray-400" + /> +
+ + {/* Quick Clear Button */} + {(searchTerm || typeFilter !== 'All Types' || statusFilter !== 'All Statuses' || assigneeFilter !== 'All Assignees' || priorityFilter !== 'All Priorities' || tagFilter) && ( + + )} +
+ + {/* Advanced Filters Row */} +
+ + + + + + + + +
+ + setTagFilter(e.target.value)} + className="pl-10 pr-4 py-2 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent text-white placeholder-gray-400 text-sm w-40" + /> +
+
+ + {/* Active Filters Summary */} + {(searchTerm || typeFilter !== 'All Types' || statusFilter !== 'All Statuses' || assigneeFilter !== 'All Assignees' || priorityFilter !== 'All Priorities' || tagFilter) && ( +
+ Active filters: + {searchTerm && ( + + Search: "{searchTerm}" + + )} + {typeFilter !== 'All Types' && ( + + Type: {typeFilter} + + )} + {statusFilter !== 'All Statuses' && ( + + Status: {statusFilter} + + )} + {assigneeFilter !== 'All Assignees' && ( + + Assignee: {assigneeFilter} + + )} + {priorityFilter !== 'All Priorities' && ( + + Priority: {priorityFilter} + + )} + {tagFilter && ( + + Tag: "{tagFilter}" + + )} + ({filteredNodes.length} results) +
+ )} +
+
+ + {/* Content Area */} +
+ {currentView === 'dashboard' && renderDashboardView()} + {currentView === 'table' && renderTableView()} + {currentView === 'cards' && renderCardView()} + {currentView === 'kanban' && renderKanbanView()} +
+
+ + {/* Right Sidebar */} +
+
+ {/* Project Overview */} +
+

Project Overview

+ + {/* Progress Bar */} +
+
+ Overall Progress + + {stats.total > 0 ? Math.round((stats.completed / stats.total) * 100) : 0}% + +
+
+
0 ? (stats.completed / stats.total) * 100 : 0}%` }} + >
+
+
+ + {/* Total Count */} +
+
{stats.total}
+
Total Tasks
+
+
+ + {/* Status Breakdown */} +
+

Task Status

+ +
+
+
+ ✅ Completed +
+
+
{stats.completed}
+
{stats.total > 0 ? Math.round((stats.completed / stats.total) * 100) : 0}%
+
+
+ +
+
+ ⚡ In Progress +
+
+
{stats.inProgress}
+
{stats.total > 0 ? Math.round((stats.inProgress / stats.total) * 100) : 0}%
+
+
+ +
+
+ 🚫 Blocked +
+
+
{stats.blocked}
+
{stats.total > 0 ? Math.round((stats.blocked / stats.total) * 100) : 0}%
+
+
+ +
+
+ 📋 Planned +
+
+
{stats.planned}
+
{stats.total > 0 ? Math.round((stats.planned / stats.total) * 100) : 0}%
+
+
+ +
+
+ 💡 Proposed +
+
+
{stats.proposed}
+
{stats.total > 0 ? Math.round((stats.proposed / stats.total) * 100) : 0}%
+
+
+
+
+ + {/* Node Types */} +
+

Node Types

+
+ {Object.entries(stats.typeStats).length > 0 ? ( + Object.entries(stats.typeStats).map(([type, count]) => ( +
+
+ + {type} + +
+
+ {count} +
+
0 ? (count / stats.total) * 100 : 0}%` }} + >
+
+
+
+ )) + ) : ( +
+
No items match current filters
+ +
+ )} +
+
+ + {/* Priority Distribution */} +
+

Priority Distribution

+
+
+
+ 🔴 Critical Priority +
+
+ {stats.priorityStats.critical} +
+
0 ? (stats.priorityStats.critical / stats.total) * 100 : 0}%` }} + >
+
+
+
+ +
+
+ 🟠 High Priority +
+
+ {stats.priorityStats.high} +
+
0 ? (stats.priorityStats.high / stats.total) * 100 : 0}%` }} + >
+
+
+
+ +
+
+ 🟡 Moderate Priority +
+
+ {stats.priorityStats.moderate} +
+
0 ? (stats.priorityStats.moderate / stats.total) * 100 : 0}%` }} + >
+
+
+
+ +
+
+ 🔵 Low Priority +
+
+ {stats.priorityStats.low} +
+
0 ? (stats.priorityStats.low / stats.total) * 100 : 0}%` }} + >
+
+
+
+ +
+
+ 🟢 Minimal Priority +
+
+ {stats.priorityStats.minimal} +
+
0 ? (stats.priorityStats.minimal / stats.total) * 100 : 0}%` }} + >
+
+
+
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/packages/web/src/components/TimelineView.tsx b/packages/web/src/components/TimelineView.tsx new file mode 100644 index 00000000..be6f4653 --- /dev/null +++ b/packages/web/src/components/TimelineView.tsx @@ -0,0 +1,1599 @@ +import React, { useState, useMemo, useRef, useEffect } from 'react'; +import { + Search, + ChevronDown, + Calendar as CalendarIcon, + Activity, + Clock, + User, + Tag, + CheckCircle, + Circle, + AlertCircle, + MessageSquare, + Edit3, + Plus, + GitBranch, + Milestone, + ZoomIn, + ZoomOut, + Maximize2 +} from 'lucide-react'; +import { useGraph } from '../contexts/GraphContext'; +import { mockProjectNodes, MockNode, mockProjectEdges } from '../types/projectData'; + +type TimelineViewType = 'gantt' | 'calendar' | 'activity'; + +interface TimelineViewOption { + id: TimelineViewType; + name: string; + description: string; + icon: React.ReactNode; +} + +const timelineViewOptions: TimelineViewOption[] = [ + { + id: 'gantt', + name: 'Gantt Chart', + description: 'Interactive timeline with dependencies and milestones', + icon: + }, + { + id: 'calendar', + name: 'Calendar View', + description: 'Monthly calendar with due dates and milestones', + icon: + }, + { + id: 'activity', + name: 'Activity Feed', + description: 'Recent updates, changes, and collaboration', + icon: + } +]; + +export function TimelineView() { + const { currentGraph } = useGraph(); + const [currentView, setCurrentView] = useState('gantt'); + const [isViewDropdownOpen, setIsViewDropdownOpen] = useState(false); + const [searchTerm, setSearchTerm] = useState(''); + const [activityFilter, setActivityFilter] = useState('all'); + const [activityTimeRange, setActivityTimeRange] = useState('week'); + const [isTimeDropdownOpen, setIsTimeDropdownOpen] = useState(false); + const [isTypeDropdownOpen, setIsTypeDropdownOpen] = useState(false); + const [zoomLevel, setZoomLevel] = useState(1); + const dropdownRef = useRef(null); + const timeDropdownRef = useRef(null); + const typeDropdownRef = useRef(null); + + // Close dropdown when clicking outside + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setIsViewDropdownOpen(false); + } + if (timeDropdownRef.current && !timeDropdownRef.current.contains(event.target as Node)) { + setIsTimeDropdownOpen(false); + } + if (typeDropdownRef.current && !typeDropdownRef.current.contains(event.target as Node)) { + setIsTypeDropdownOpen(false); + } + } + + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + // Filter nodes based on search + const filteredNodes = useMemo(() => { + if (!searchTerm) return mockProjectNodes; + return mockProjectNodes.filter(node => + node.title.toLowerCase().includes(searchTerm.toLowerCase()) || + node.description?.toLowerCase().includes(searchTerm.toLowerCase()) || + node.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase())) + ); + }, [searchTerm]); + + // Helper functions + const getNodeTypeColor = (type: string) => { + switch (type) { + case 'EPIC': return 'bg-purple-500 text-white'; + case 'FEATURE': return 'bg-blue-500 text-white'; + case 'TASK': return 'bg-green-500 text-white'; + case 'BUG': return 'bg-red-500 text-white'; + case 'MILESTONE': return 'bg-yellow-500 text-black'; + default: return 'bg-gray-500 text-white'; + } + }; + + const getStatusColor = (status: string) => { + switch (status) { + case 'COMPLETED': return 'text-green-400'; + case 'IN_PROGRESS': return 'text-blue-400'; + case 'BLOCKED': return 'text-red-400'; + case 'PLANNED': return 'text-yellow-400'; + case 'PROPOSED': return 'text-purple-400'; + default: return 'text-gray-400'; + } + }; + + const getStatusIcon = (status: string) => { + switch (status) { + case 'COMPLETED': return ; + case 'IN_PROGRESS': return ; + case 'BLOCKED': return ; + case 'PLANNED': return ; + default: return ; + } + }; + + const getPriorityIndicator = (priority: number) => { + if (priority > 0.7) return 'bg-red-500'; + if (priority > 0.4) return 'bg-yellow-500'; + return 'bg-green-500'; + }; + + // Gantt Timeline View + const renderGanttView = () => { + // Calculate timeline bounds + const today = new Date(); + const startDate = new Date(2025, 7, 1); // August 1, 2025 + const endDate = new Date(2026, 2, 31); // March 31, 2026 + const totalDays = Math.ceil((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)); + + // Enhanced month calculation with weeks + const getWeeksInMonth = (year: number, month: number) => { + const firstDay = new Date(year, month, 1); + const lastDay = new Date(year, month + 1, 0); + const totalDaysInMonth = lastDay.getDate(); + return Math.ceil(totalDaysInMonth / 7); + }; + + // Generate month headers with enhanced info + const months = []; + for (let d = new Date(startDate); d <= endDate; d.setMonth(d.getMonth() + 1)) { + const monthDays = new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate(); + months.push({ + name: d.toLocaleDateString('en-US', { month: 'short' }), + year: d.getFullYear(), + days: monthDays, + weeks: getWeeksInMonth(d.getFullYear(), d.getMonth()), + percentage: (monthDays / totalDays) * 100 + }); + } + + // Calculate node positions + const getNodePosition = (node: MockNode) => { + const nodeStart = node.createdAt ? new Date(node.createdAt) : startDate; + const nodeEnd = node.dueDate ? new Date(node.dueDate) : new Date(nodeStart.getTime() + 14 * 24 * 60 * 60 * 1000); + + const startOffset = Math.max(0, Math.ceil((nodeStart.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24))); + const duration = Math.max(1, Math.ceil((nodeEnd.getTime() - nodeStart.getTime()) / (1000 * 60 * 60 * 24))); + + return { + left: (startOffset / totalDays) * 100, + width: (duration / totalDays) * 100, + startDate: nodeStart, + endDate: nodeEnd, + duration + }; + }; + + // Group nodes by type + const groupedNodes = filteredNodes.reduce((acc, node) => { + const type = node.type; + if (!acc[type]) acc[type] = []; + acc[type].push(node); + return acc; + }, {} as Record); + + return ( +
+ {/* Timeline Controls */} +
+
+ + + +
+ +
+
+
+ Epic +
+
+
+ Feature +
+
+
+ Task +
+
+
+ Bug +
+
+
+ Milestone +
+
+
+ + {/* Timeline Grid */} +
+
+ {/* Enhanced Month Headers */} +
+
+
+
Tasks & Timeline
+
{filteredNodes.length} items
+
+
+ {months.map((month, index) => ( +
+
+ {month.name} {month.year} +
+
+ {month.days}d + + {month.weeks}w +
+
+ ))} + + {/* Today indicator in header */} + {(() => { + const todayOffset = Math.ceil((today.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)); + const todayPosition = (todayOffset / totalDays) * 100; + if (todayPosition >= 0 && todayPosition <= 100) { + return ( +
+
+
+ ); + } + return null; + })()} +
+
+
+ + {/* Task Rows */} +
+ {Object.entries(groupedNodes).map(([type, nodes]) => ( +
+ {/* Type Header */} +
+ + {type} ({nodes.length}) + +
+ + {/* Task Items */} + {nodes.map((node) => { + const position = getNodePosition(node); + const isMilestone = node.type === 'MILESTONE'; + + return ( +
+ {/* Task Name */} +
+
+ {getStatusIcon(node.status)} + + {node.title} + +
+ {node.assignee && ( +
{node.assignee}
+ )} +
+ + {/* Timeline Bar */} +
+
+ {/* Progress Track */} +
+ + {/* Current Date Indicator */} + {(() => { + const todayOffset = Math.ceil((today.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)); + const todayPosition = (todayOffset / totalDays) * 100; + if (todayPosition >= 0 && todayPosition <= 100) { + return ( +
+ ); + } + return null; + })()} + + {/* Enhanced Task Bar or Milestone */} + {isMilestone ? ( +
+
+ {/* Milestone diamond with enhanced styling */} +
+
+ + {/* Milestone pulse effect */} +
+ + {/* Milestone label on hover */} +
+
{node.title}
+
{position.endDate.toLocaleDateString()}
+
+
+
+ ) : ( +
+ {/* Enhanced Priority Indicator */} +
+ + {/* Task Name with better handling */} + {position.width > 6 && ( + + {position.width > 15 ? node.title : node.title.substring(0, Math.floor(position.width / 1.5))} + + )} + + {/* Enhanced Progress Bar */} + {node.estimatedHours && node.actualHours && ( +
+
+
+ )} + + {/* Completion Status Indicator */} + {node.status === 'COMPLETED' && ( +
+
+
+ )} + + {/* Blocked Status Indicator */} + {node.status === 'BLOCKED' && ( +
+
+
+ )} +
+ )} + + {/* Enhanced Dependencies Lines */} + {mockProjectEdges + .filter(edge => edge.source === node.id && ['DEPENDS_ON', 'BLOCKS', 'ENABLES'].includes(edge.type)) + .map(edge => { + const targetNode = filteredNodes.find(n => n.id === edge.target); + if (!targetNode) return null; + const targetPosition = getNodePosition(targetNode); + + // Calculate dependency line style based on type + const lineStyle = { + DEPENDS_ON: { color: '#ef4444', dasharray: '6 3', width: 2, opacity: 0.7 }, + BLOCKS: { color: '#dc2626', dasharray: '4 4', width: 2.5, opacity: 0.8 }, + ENABLES: { color: '#3b82f6', dasharray: '8 2', width: 1.5, opacity: 0.6 } + }[edge.type] || { color: '#6b7280', dasharray: '2 2', width: 1, opacity: 0.4 }; + + // Calculate arrow positions + const sourceX = position.left + position.width; + const targetX = targetPosition.left; + const midX = sourceX + (targetX - sourceX) / 2; + + return ( + + + + + + + + {/* Curved dependency line */} + + + {/* Dependency type label */} + + {edge.type.replace('_', ' ')} + + + ); + })} +
+
+
+ ); + })} +
+ ))} +
+
+
+ + {/* Enhanced Timeline Footer */} +
+
+
+
+ {filteredNodes.length} items across {months.length} months +
+ + {/* Project Statistics */} +
+ {(() => { + const stats = filteredNodes.reduce((acc, node) => { + acc[node.status] = (acc[node.status] || 0) + 1; + if (node.estimatedHours) acc.totalEstimated += node.estimatedHours; + if (node.actualHours) acc.totalActual += node.actualHours; + return acc; + }, { totalEstimated: 0, totalActual: 0 } as any); + + return ( + <> +
+
+ {stats.COMPLETED || 0} done +
+
+
+ {stats.IN_PROGRESS || 0} active +
+
+
+ {stats.PLANNED || 0} planned +
+ {stats.BLOCKED > 0 && ( +
+
+ {stats.BLOCKED} blocked +
+ )} +
+ {Math.round(stats.totalActual)}h / {Math.round(stats.totalEstimated)}h logged +
+ + ); + })()} +
+
+ +
+
+
+
+ Dependencies +
+
+
+ Blocks +
+
+
+ Enables +
+
+
Hover for details • Click to edit
+
+
+
+
+ ); + }; + + // Enhanced Calendar View + const renderCalendarView = () => { + const today = new Date(); + const currentMonth = 7; // August (0-indexed) + const currentYear = 2025; + + const firstDay = new Date(currentYear, currentMonth, 1); + const lastDay = new Date(currentYear, currentMonth + 1, 0); + const daysInMonth = lastDay.getDate(); + const startingDayOfWeek = firstDay.getDay(); + + // Enhanced calendar days with previous/next month context + const calendarDays = []; + const prevMonth = new Date(currentYear, currentMonth - 1, 0); + const nextMonth = new Date(currentYear, currentMonth + 1, 1); + + // Previous month's trailing days + for (let i = startingDayOfWeek - 1; i >= 0; i--) { + calendarDays.push({ + day: prevMonth.getDate() - i, + isCurrentMonth: false, + isPrevMonth: true, + date: new Date(currentYear, currentMonth - 1, prevMonth.getDate() - i) + }); + } + + // Current month days + for (let day = 1; day <= daysInMonth; day++) { + calendarDays.push({ + day: day, + isCurrentMonth: true, + isPrevMonth: false, + date: new Date(currentYear, currentMonth, day) + }); + } + + // Next month's leading days + const remainingSlots = 42 - calendarDays.length; // 6 weeks * 7 days + for (let day = 1; day <= remainingSlots; day++) { + calendarDays.push({ + day: day, + isCurrentMonth: false, + isPrevMonth: false, + date: new Date(currentYear, currentMonth + 1, day) + }); + } + + const monthNames = ["January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"]; + + // Calculate month statistics + const monthStats = filteredNodes.reduce((acc, node) => { + if (node.dueDate) { + const dueDate = new Date(node.dueDate); + if (dueDate.getMonth() === currentMonth && dueDate.getFullYear() === currentYear) { + acc.totalItems++; + acc[node.status] = (acc[node.status] || 0) + 1; + if (node.estimatedHours) acc.totalHours += node.estimatedHours; + } + } + return acc; + }, { totalItems: 0, totalHours: 0 } as any); + + // Group nodes by due date + const nodesByDate = filteredNodes.reduce((acc, node) => { + if (node.dueDate) { + const dueDate = new Date(node.dueDate); + if (dueDate.getMonth() === currentMonth && dueDate.getFullYear() === currentYear) { + const dateKey = dueDate.getDate(); + if (!acc[dateKey]) acc[dateKey] = []; + acc[dateKey].push(node); + } + } + return acc; + }, {} as Record); + + return ( +
+ {/* Enhanced Calendar Header */} +
+
+
+
+ + +
+

+ {monthNames[currentMonth]} {currentYear} +

+
+ {monthStats.totalItems} items scheduled • {Math.round(monthStats.totalHours)}h estimated +
+
+ + +
+ + +
+ + {/* Enhanced Priority Legend */} +
+
+
+
+ Critical + ({monthStats.BLOCKED || 0}) +
+
+
+ Medium + ({monthStats.IN_PROGRESS || 0}) +
+
+
+ Low + ({monthStats.COMPLETED || 0}) +
+
+ + {/* Calendar View Options */} +
+ + +
+
+
+ + {/* Month Progress Bar */} +
+
+ Month Progress + {Math.round((today.getDate() / daysInMonth) * 100)}% complete +
+
+
+
+
+
+ + {/* Enhanced Calendar Grid */} +
+ {/* Enhanced Day Headers */} +
+ {['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'].map((day, index) => ( +
+
{day.substring(0, 3)}
+
{day.substring(3)}
+
+ ))} +
+ + {/* Enhanced Calendar Days Grid */} +
+ {calendarDays.map((dayData, index) => { + const isToday = dayData.isCurrentMonth && dayData.day === today.getDate(); + const isPastDay = dayData.isCurrentMonth && dayData.day < today.getDate(); + const isWeekend = index % 7 === 0 || index % 7 === 6; + + return ( +
+ {/* Enhanced Day Number */} +
+ + {dayData.day} + + + {/* Day Status Indicator */} + {dayData.isCurrentMonth && nodesByDate[dayData.day] && ( +
+
n.status === 'BLOCKED') ? 'bg-red-500' : + nodesByDate[dayData.day].some(n => n.status === 'IN_PROGRESS') ? 'bg-blue-500' : + nodesByDate[dayData.day].every(n => n.status === 'COMPLETED') ? 'bg-green-500' : + 'bg-yellow-500' + }`}>
+ {nodesByDate[dayData.day].length} +
+ )} +
+ + {/* Enhanced Task Items */} + {dayData.isCurrentMonth && nodesByDate[dayData.day] && ( +
+ {nodesByDate[dayData.day].slice(0, 3).map((node) => ( +
+
+
+ + {node.type.charAt(0)} + +
+
+ {node.title.length > 25 ? `${node.title.substring(0, 25)}...` : node.title} +
+ {node.assignee && ( +
+ {node.assignee.split(' ')[0]} +
+ )} +
+ ))} + + {/* More Items Indicator */} + {nodesByDate[dayData.day].length > 3 && ( +
+ +{nodesByDate[dayData.day].length - 3} more items +
+ )} +
+ )} + + {/* Empty Day Indicator */} + {dayData.isCurrentMonth && !nodesByDate[dayData.day] && ( +
+ +
+ )} +
+ ); + })} +
+
+ + {/* Enhanced Items without due dates */} + {filteredNodes.filter(node => !node.dueDate).length > 0 && ( +
+
+
+
+

Backlog Items

+

+ {filteredNodes.filter(node => !node.dueDate).length} items without scheduled due dates +

+
+ +
+ + +
+
+ +
+ {filteredNodes.filter(node => !node.dueDate).map(node => ( +
+ {/* Enhanced Header */} +
+
+ + {node.type} + +
+
+ +
+ +
+
+ + {/* Enhanced Content */} +

+ {node.title} +

+ + {node.description && ( +

+ {node.description.length > 60 ? `${node.description.substring(0, 60)}...` : node.description} +

+ )} + + {/* Enhanced Status and Details */} +
+
+ {getStatusIcon(node.status)} + {node.status.replace('_', ' ')} +
+ + {node.assignee && ( +
+
+ + {node.assignee.split(' ').map(n => n[0]).join('')} + +
+ {node.assignee} +
+ )} + + {node.estimatedHours && ( +
+ + + + {node.estimatedHours}h estimated +
+ )} + + {node.tags.length > 0 && ( +
+ {node.tags.slice(0, 2).map(tag => ( + + {tag} + + ))} + {node.tags.length > 2 && ( + + +{node.tags.length - 2} + + )} +
+ )} +
+ + {/* Priority Indicator */} +
+
+ Priority +
+
+
+
+ {Math.round(node.priority.computed * 100)}% +
+
+
+
+ ))} +
+ + {/* Backlog Statistics */} +
+
+ {(() => { + const backlogNodes = filteredNodes.filter(node => !node.dueDate); + const backlogStats = backlogNodes.reduce((acc, node) => { + acc[node.status] = (acc[node.status] || 0) + 1; + if (node.estimatedHours) acc.totalHours += node.estimatedHours; + return acc; + }, { totalHours: 0 } as any); + + return ( + <> +
+
{backlogStats.PROPOSED || 0}
+
Proposed
+
+
+
{backlogStats.PLANNED || 0}
+
Planned
+
+
+
{backlogStats.IN_PROGRESS || 0}
+
In Progress
+
+
+
{Math.round(backlogStats.totalHours)}h
+
Total Effort
+
+ + ); + })()} +
+
+
+
+ )} +
+ ); + }; + + // Enhanced Activity Feed View + const renderActivityView = () => { + // Generate enhanced activities from nodes + const activities = filteredNodes.flatMap(node => { + const baseActivities = []; + + // Created activity + baseActivities.push({ + id: `${node.id}-created`, + type: 'created', + action: 'created', + title: `Created ${node.type.toLowerCase()}`, + description: node.title, + user: node.assignee || 'System', + timestamp: node.createdAt, + node: node, + icon: , + priority: 'normal', + category: 'content', + color: 'bg-green-600' + }); + + // Status change activity + baseActivities.push({ + id: `${node.id}-status`, + type: 'status_change', + action: 'updated status', + title: `Status changed to ${node.status.replace('_', ' ').toLowerCase()}`, + description: node.title, + user: node.assignee || 'System', + timestamp: node.updatedAt, + node: node, + icon: getStatusIcon(node.status), + priority: node.status === 'BLOCKED' ? 'high' : node.status === 'COMPLETED' ? 'high' : 'normal', + category: 'status', + color: 'bg-blue-600', + details: `From planned to ${node.status.replace('_', ' ').toLowerCase()}` + }); + + // Assignment activity + if (node.assignee) { + baseActivities.push({ + id: `${node.id}-assigned`, + type: 'assigned', + action: 'assigned', + title: `Assigned to ${node.assignee}`, + description: node.title, + user: 'System', + timestamp: node.updatedAt, + node: node, + icon: , + priority: 'normal', + category: 'assignment', + color: 'bg-purple-600' + }); + } + + // Progress activity + if (node.estimatedHours && node.actualHours) { + baseActivities.push({ + id: `${node.id}-progress`, + type: 'progress', + action: 'logged time', + title: `Logged ${node.actualHours}h of work`, + description: node.title, + user: node.assignee || 'System', + timestamp: node.updatedAt, + node: node, + icon: , + priority: 'normal', + category: 'progress', + color: 'bg-orange-600', + details: `${node.actualHours}h / ${node.estimatedHours}h (${Math.round((node.actualHours / node.estimatedHours) * 100)}%)` + }); + } + + return baseActivities; + }).sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); + + // Filter activities based on selected filters + const filteredActivities = activities.filter(activity => { + if (activityFilter !== 'all' && activity.category !== activityFilter) return false; + + const activityDate = new Date(activity.timestamp); + const now = new Date(); + const daysDiff = Math.floor((now.getTime() - activityDate.getTime()) / (1000 * 60 * 60 * 24)); + + switch (activityTimeRange) { + case 'today': return daysDiff === 0; + case 'week': return daysDiff <= 7; + case 'month': return daysDiff <= 30; + default: return true; + } + }); + + // Group activities by date + const groupedActivities = filteredActivities.reduce((acc, activity) => { + const date = new Date(activity.timestamp).toDateString(); + if (!acc[date]) acc[date] = []; + acc[date].push(activity); + return acc; + }, {} as Record); + + // Calculate activity statistics + const activityStats = activities.reduce((acc, activity) => { + acc.total++; + acc[activity.category] = (acc[activity.category] || 0) + 1; + if (activity.priority === 'high') acc.highPriority++; + return acc; + }, { total: 0, highPriority: 0 } as any); + + return ( +
+
+ {/* Professional Activity Feed Header */} +
+
+
+

Activity Feed

+

+ {filteredActivities.length} activities across {Object.keys(groupedActivities).length} days + {activityStats.highPriority > 0 && ( + + {activityStats.highPriority} high priority + + )} +

+
+ +
+ {/* Real-time indicator */} +
+
+ Live +
+ + {/* Export button */} + +
+
+ + {/* Enhanced Filters */} +
+
+ {/* Time Range Dropdown */} +
+ Time: +
+ + + {isTimeDropdownOpen && ( +
+
+ {[ + { id: 'today', label: 'Today', description: 'Activities from today' }, + { id: 'week', label: 'Week', description: 'Last 7 days' }, + { id: 'month', label: 'Month', description: 'Last 30 days' }, + { id: 'all', label: 'All', description: 'All activities' } + ].map((range) => ( + + ))} +
+
+ )} +
+
+ + {/* Category Type Dropdown */} +
+ Type: +
+ + + {isTypeDropdownOpen && ( +
+
+ {[ + { + id: 'all', + label: 'All', + count: activityStats.total, + description: 'All activity types', + color: 'bg-gray-500' + }, + { + id: 'content', + label: 'Created', + count: activityStats.content || 0, + description: 'Item creation activities', + color: 'bg-green-500' + }, + { + id: 'status', + label: 'Status', + count: activityStats.status || 0, + description: 'Status change activities', + color: 'bg-blue-500' + }, + { + id: 'assignment', + label: 'Assigned', + count: activityStats.assignment || 0, + description: 'Assignment activities', + color: 'bg-purple-500' + }, + { + id: 'progress', + label: 'Progress', + count: activityStats.progress || 0, + description: 'Time and progress logs', + color: 'bg-orange-500' + } + ].map((category) => ( + + ))} +
+
+ )} +
+
+
+ + {/* Activity Statistics */} +
+
+
+ {activityStats.status || 0} status changes +
+
+
+ {activityStats.assignment || 0} assignments +
+
+
+ {activityStats.content || 0} created +
+
+
+
+ + {/* Professional Activity Timeline */} +
+ {Object.keys(groupedActivities).length > 0 ? ( +
+ {Object.entries(groupedActivities).map(([date, dayActivities]) => ( +
+ {/* Date Header */} +
+
+
+ {new Date(date).toLocaleDateString('en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric' + })} +
+
+ {dayActivities.length} {dayActivities.length === 1 ? 'activity' : 'activities'} +
+
+
+
+ {new Date(date) >= new Date().setDate(new Date().getDate() - 1) ? 'Recent' : 'Historical'} +
+
+ + {/* Activity Items */} +
+ {dayActivities.map((activity, index) => ( +
console.log('Activity clicked:', activity)} + > + {/* Timeline connector */} + {index < dayActivities.length - 1 && ( +
+ )} + + {/* Enhanced Activity Card */} +
+ {/* Activity Icon */} +
+
+ {activity.icon} +
+
+ + {/* Priority Indicator */} + {activity.priority === 'high' && ( +
+ )} + +
+
+ {/* Activity Header */} +
+
+
+ + {activity.user.split(' ').map(n => n[0]).join('')} + +
+ {activity.user} +
+ {activity.action} + + {activity.node.type} + +
+ + {/* Activity Content */} +
+

+ {activity.description} +

+ {activity.details && ( +

{activity.details}

+ )} +
+ + {/* Activity Metadata */} +
+
+
+ {getStatusIcon(activity.node.status)} + {activity.node.status.replace('_', ' ')} +
+ {activity.node.assignee && ( + Assignee: {activity.node.assignee} + )} + {activity.node.priority && ( + Priority: {Math.round(activity.node.priority.computed * 100)}% + )} +
+ +
+ {new Date(activity.timestamp).toLocaleTimeString('en-US', { + hour: '2-digit', + minute: '2-digit' + })} + +
+
+
+
+
+
+ ))} +
+
+ ))} + + {/* Load More Activities */} + {filteredActivities.length > 20 && ( +
+ +
+ )} +
+ ) : ( +
+
+ +
+

No Activity Found

+

+ {activityFilter === 'all' + ? "No recent activity to display. Activities will appear here as team members work on projects." + : `No ${activityFilter} activities found. Try adjusting your filters.` + } +

+ +
+ )} +
+
+
+ ); + }; + + const currentViewOption = timelineViewOptions.find(option => option.id === currentView)!; + + if (!currentGraph) { + return ( +
+
+
+ +
+

No Graph Selected

+

Select a graph to view its timeline.

+
+
+ ); + } + + return ( +
+ {/* Header */} +
+
+
+

+ {currentGraph.name} +

+ + {/* View Selector */} +
+ + + {isViewDropdownOpen && ( +
+
+ {timelineViewOptions.map((option) => ( + + ))} +
+
+ )} +
+
+ +
+
+

Project Overview

+ + {/* Progress Bar */} +
+
+ Overall Progress + {Math.round((filteredNodes.filter(node => node.status === 'COMPLETED').length / filteredNodes.length) * 100)}% +
+
+
node.status === 'COMPLETED').length / filteredNodes.length) * 100}%` }} + >
+
+
+ + {/* Task Count */} +
+
{filteredNodes.length}
+
Total Tasks
+
+
+
+
+ + {/* Search */} +
+
+ + setSearchTerm(e.target.value)} + className="w-full pl-10 pr-4 py-2 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent text-white placeholder-gray-400" + /> +
+
+
+ + {/* Main Content */} +
+ {currentView === 'gantt' && renderGanttView()} + {currentView === 'calendar' && renderCalendarView()} + {currentView === 'activity' && renderActivityView()} +
+
+ ); +} \ No newline at end of file diff --git a/packages/web/src/index.css b/packages/web/src/index.css index 0a1b1987..c370a227 100644 --- a/packages/web/src/index.css +++ b/packages/web/src/index.css @@ -129,4 +129,91 @@ svg text { .priority-low { @apply text-green-600; +} + +/* Custom dark scrollbar styles */ +.scrollbar-dark { + scrollbar-width: thin; + scrollbar-color: #374151 #1f2937; +} + +.scrollbar-dark::-webkit-scrollbar { + height: 8px; + width: 8px; +} + +.scrollbar-dark::-webkit-scrollbar-track { + background: #1f2937; + border-radius: 4px; +} + +.scrollbar-dark::-webkit-scrollbar-thumb { + background: #374151; + border-radius: 4px; + border: 1px solid #1f2937; +} + +.scrollbar-dark::-webkit-scrollbar-thumb:hover { + background: #4b5563; +} + +.scrollbar-dark::-webkit-scrollbar-corner { + background: #1f2937; +} + +/* Custom gray scrollbar for dropdowns */ +.scrollbar-gray { + scrollbar-width: thin; + scrollbar-color: #6b7280 #374151; +} + +.scrollbar-gray::-webkit-scrollbar { + width: 8px; +} + +.scrollbar-gray::-webkit-scrollbar-track { + background: #374151; + border-radius: 4px; +} + +.scrollbar-gray::-webkit-scrollbar-thumb { + background: #6b7280; + border-radius: 4px; +} + +.scrollbar-gray::-webkit-scrollbar-thumb:hover { + background: #9ca3af; +} + +.scrollbar-gray::-webkit-scrollbar-corner { + background: #374151; +} + +/* Thicker gray scrollbar for timeline */ +.scrollbar-gray-thick { + scrollbar-width: auto; + scrollbar-color: #6b7280 #374151; +} + +.scrollbar-gray-thick::-webkit-scrollbar { + width: 12px; + height: 12px; +} + +.scrollbar-gray-thick::-webkit-scrollbar-track { + background: #374151; + border-radius: 6px; +} + +.scrollbar-gray-thick::-webkit-scrollbar-thumb { + background: #6b7280; + border-radius: 6px; +} + +.scrollbar-gray-thick::-webkit-scrollbar-thumb:hover { + background: #9ca3af; +} + +.scrollbar-gray-thick::-webkit-scrollbar-corner { + background: #374151; } \ No newline at end of file diff --git a/packages/web/src/pages/Workspace.tsx b/packages/web/src/pages/Workspace.tsx index b05349ea..9d7e5467 100644 --- a/packages/web/src/pages/Workspace.tsx +++ b/packages/web/src/pages/Workspace.tsx @@ -1,6 +1,8 @@ import { useState } from 'react'; import { Plus, Zap, RotateCcw, Share2, Users, Filter } from 'lucide-react'; import { InteractiveGraphVisualization } from '../components/InteractiveGraphVisualization'; +import { ListView } from '../components/ListView'; +import { TimelineView } from '../components/TimelineView'; import { CreateNodeModal } from '../components/CreateNodeModal'; import { useGraph } from '../contexts/GraphContext'; import { useAuth } from '../contexts/AuthContext'; @@ -166,17 +168,14 @@ export function Workspace() { ) : viewMode === 'graph' ? ( ) : viewMode === 'list' ? ( -
-
-

List View

-

Detailed list view coming soon...

-
-
+ + ) : viewMode === 'timeline' ? ( + ) : (
-

Timeline View

-

Timeline view for project progress coming soon...

+

Unknown View

+

This view is not yet implemented.

)} diff --git a/packages/web/src/types/projectData.ts b/packages/web/src/types/projectData.ts index 877632bc..46458fee 100644 --- a/packages/web/src/types/projectData.ts +++ b/packages/web/src/types/projectData.ts @@ -51,211 +51,398 @@ export interface MockEdge { // Mock project management data export const mockProjectNodes: MockNode[] = [ { - id: 'node-1', + id: 'epic-1', title: 'User Authentication System', - description: 'Implement secure user login and registration', + description: 'Complete user authentication and authorization system', type: 'EPIC', status: 'IN_PROGRESS', priority: { executive: 0.9, individual: 0.8, community: 0.85, computed: 0.85 }, position: { x: 0, y: 0, z: 0 }, assignee: 'Alice Johnson', - estimatedHours: 40, - actualHours: 25, - dueDate: '2024-04-15', + estimatedHours: 120, + actualHours: 80, + dueDate: '2025-09-30', tags: ['security', 'backend', 'critical'], - createdAt: '2024-03-01T10:00:00Z', - updatedAt: '2024-03-20T15:30:00Z' + createdAt: '2025-08-01T10:00:00Z', + updatedAt: '2025-08-17T15:30:00Z' }, { - id: 'node-2', - title: 'Login API Endpoint', - description: 'REST API for user authentication', - type: 'TASK', + id: 'feature-1', + title: 'Login & Registration', + description: 'Basic login and user registration functionality', + type: 'FEATURE', status: 'COMPLETED', priority: { executive: 0.8, individual: 0.9, community: 0.7, computed: 0.8 }, position: { x: -200, y: -100, z: 0 }, assignee: 'Bob Smith', - estimatedHours: 8, - actualHours: 6, - tags: ['api', 'backend'], - createdAt: '2024-03-02T09:00:00Z', - updatedAt: '2024-03-15T11:20:00Z' + estimatedHours: 40, + actualHours: 38, + dueDate: '2025-08-25', + tags: ['auth', 'frontend', 'backend'], + createdAt: '2025-08-05T09:00:00Z', + updatedAt: '2025-08-25T11:20:00Z' + }, + { + id: 'task-1', + title: 'Database Schema Design', + description: 'Design user and session tables with security considerations', + type: 'TASK', + status: 'COMPLETED', + priority: { executive: 0.9, individual: 0.7, community: 0.8, computed: 0.8 }, + position: { x: -300, y: 0, z: 0 }, + assignee: 'David Wilson', + estimatedHours: 16, + actualHours: 14, + dueDate: '2025-08-15', + tags: ['database', 'infrastructure'], + createdAt: '2025-08-01T08:00:00Z', + updatedAt: '2025-08-15T10:30:00Z' }, { - id: 'node-3', - title: 'Registration Form UI', - description: 'User-friendly registration interface', + id: 'task-2', + title: 'Frontend Login Components', + description: 'React components for login and registration forms', type: 'TASK', status: 'IN_PROGRESS', priority: { executive: 0.6, individual: 0.8, community: 0.75, computed: 0.72 }, position: { x: 200, y: -100, z: 0 }, assignee: 'Carol Davis', - estimatedHours: 12, - actualHours: 8, - tags: ['ui', 'frontend'], - createdAt: '2024-03-03T14:00:00Z', - updatedAt: '2024-03-19T16:45:00Z' + estimatedHours: 24, + actualHours: 16, + dueDate: '2025-09-05', + tags: ['ui', 'frontend', 'react'], + createdAt: '2025-08-10T14:00:00Z', + updatedAt: '2025-08-17T16:45:00Z' }, { - id: 'node-4', - title: 'Password Reset Flow', - description: 'Allow users to reset forgotten passwords', + id: 'feature-2', + title: 'Password Reset System', + description: 'Email-based password reset with secure tokens', type: 'FEATURE', status: 'PLANNED', priority: { executive: 0.5, individual: 0.6, community: 0.65, computed: 0.58 }, position: { x: 0, y: 150, z: 0 }, - estimatedHours: 16, - tags: ['security', 'feature'], - createdAt: '2024-03-05T11:00:00Z', - updatedAt: '2024-03-15T13:10:00Z' + assignee: 'Bob Smith', + estimatedHours: 32, + dueDate: '2025-09-20', + tags: ['security', 'email', 'tokens'], + createdAt: '2025-08-05T11:00:00Z', + updatedAt: '2025-08-15T13:10:00Z' + }, + { + id: 'epic-2', + title: 'E-commerce Platform', + description: 'Complete online shopping platform with cart and checkout', + type: 'EPIC', + status: 'PLANNED', + priority: { executive: 0.85, individual: 0.9, community: 0.8, computed: 0.85 }, + position: { x: 400, y: 0, z: 0 }, + assignee: 'Emma Rodriguez', + estimatedHours: 200, + dueDate: '2025-12-15', + tags: ['ecommerce', 'shopping', 'payments'], + createdAt: '2025-08-01T10:00:00Z', + updatedAt: '2025-08-17T15:30:00Z' + }, + { + id: 'feature-3', + title: 'Product Catalog', + description: 'Product browsing, search, and filtering functionality', + type: 'FEATURE', + status: 'IN_PROGRESS', + priority: { executive: 0.7, individual: 0.8, community: 0.75, computed: 0.75 }, + position: { x: 300, y: -200, z: 0 }, + assignee: 'Michael Chen', + estimatedHours: 48, + actualHours: 12, + dueDate: '2025-10-15', + tags: ['catalog', 'search', 'frontend'], + createdAt: '2025-08-15T09:00:00Z', + updatedAt: '2025-08-17T14:20:00Z' + }, + { + id: 'feature-4', + title: 'Shopping Cart & Checkout', + description: 'Add to cart, modify quantities, and secure checkout process', + type: 'FEATURE', + status: 'PROPOSED', + priority: { executive: 0.8, individual: 0.7, community: 0.75, computed: 0.75 }, + position: { x: 500, y: -100, z: 0 }, + estimatedHours: 64, + dueDate: '2025-11-30', + tags: ['cart', 'checkout', 'payments'], + createdAt: '2025-08-01T10:00:00Z', + updatedAt: '2025-08-17T15:30:00Z' }, { - id: 'node-5', - title: 'Database Schema', - description: 'User and session tables design', + id: 'task-3', + title: 'Payment Gateway Integration', + description: 'Integrate Stripe for secure payment processing', type: 'TASK', - status: 'COMPLETED', - priority: { executive: 0.9, individual: 0.7, community: 0.8, computed: 0.8 }, - position: { x: -300, y: 0, z: 0 }, - assignee: 'David Wilson', - estimatedHours: 6, - actualHours: 4, - tags: ['database', 'infrastructure'], - createdAt: '2024-02-28T08:00:00Z', - updatedAt: '2024-03-10T10:30:00Z' + status: 'PLANNED', + priority: { executive: 0.9, individual: 0.8, community: 0.85, computed: 0.85 }, + position: { x: 450, y: 100, z: 0 }, + assignee: 'Sarah Kim', + estimatedHours: 40, + dueDate: '2025-11-15', + tags: ['payments', 'stripe', 'security'], + createdAt: '2025-08-10T11:00:00Z', + updatedAt: '2025-08-17T09:30:00Z' }, { - id: 'node-6', + id: 'task-4', title: 'Security Testing', - description: 'Penetration testing and vulnerability assessment', + description: 'Comprehensive security audit and penetration testing', type: 'TASK', status: 'PLANNED', priority: { executive: 0.8, individual: 0.5, community: 0.7, computed: 0.67 }, position: { x: 300, y: 100, z: 0 }, - estimatedHours: 20, - tags: ['testing', 'security'], - createdAt: '2024-03-10T15:00:00Z', - updatedAt: '2024-03-18T09:15:00Z' + assignee: 'Alex Thompson', + estimatedHours: 32, + dueDate: '2025-10-30', + tags: ['testing', 'security', 'audit'], + createdAt: '2025-08-10T15:00:00Z', + updatedAt: '2025-08-17T09:15:00Z' }, { - id: 'node-7', - title: 'Launch Milestone', - description: 'Authentication system ready for production', + id: 'milestone-1', + title: 'Authentication MVP Launch', + description: 'Basic authentication system ready for production', type: 'MILESTONE', - status: 'PROPOSED', + status: 'PLANNED', priority: { executive: 0.95, individual: 0.8, community: 0.9, computed: 0.88 }, position: { x: 0, y: 300, z: 0 }, - dueDate: '2024-04-30', - tags: ['milestone', 'launch'], - createdAt: '2024-03-01T10:00:00Z', - updatedAt: '2024-03-20T15:30:00Z' + dueDate: '2025-09-30', + tags: ['milestone', 'launch', 'mvp'], + createdAt: '2025-08-01T10:00:00Z', + updatedAt: '2025-08-17T15:30:00Z' }, { - id: 'node-8', - title: 'Login Bug Fix', - description: 'Fix session timeout issue', + id: 'milestone-2', + title: 'E-commerce Beta Release', + description: 'Full e-commerce platform ready for beta testing', + type: 'MILESTONE', + status: 'PROPOSED', + priority: { executive: 0.9, individual: 0.8, community: 0.85, computed: 0.85 }, + position: { x: 400, y: 300, z: 0 }, + dueDate: '2025-12-15', + tags: ['milestone', 'beta', 'ecommerce'], + createdAt: '2025-08-01T10:00:00Z', + updatedAt: '2025-08-17T15:30:00Z' + }, + { + id: 'bug-1', + title: 'Session Timeout Issue', + description: 'Users getting logged out unexpectedly', type: 'BUG', status: 'BLOCKED', priority: { executive: 0.7, individual: 0.9, community: 0.8, computed: 0.8 }, position: { x: 100, y: 50, z: 0 }, assignee: 'Alice Johnson', - estimatedHours: 4, - tags: ['bug', 'urgent'], - createdAt: '2024-03-18T14:00:00Z', - updatedAt: '2024-03-20T10:00:00Z' + estimatedHours: 8, + actualHours: 3, + dueDate: '2025-08-25', + tags: ['bug', 'urgent', 'session'], + createdAt: '2025-08-16T14:00:00Z', + updatedAt: '2025-08-17T10:00:00Z' + }, + { + id: 'task-5', + title: 'API Rate Limiting', + description: 'Implement rate limiting for API endpoints', + type: 'TASK', + status: 'BLOCKED', + priority: { executive: 0.6, individual: 0.7, community: 0.65, computed: 0.65 }, + position: { x: 150, y: 200, z: 0 }, + assignee: 'Michael Chen', + estimatedHours: 20, + actualHours: 5, + dueDate: '2025-09-10', + tags: ['api', 'security', 'backend'], + createdAt: '2025-08-12T09:00:00Z', + updatedAt: '2025-08-17T14:00:00Z' + }, + { + id: 'epic-3', + title: 'Mobile App Development', + description: 'React Native mobile app for iOS and Android', + type: 'EPIC', + status: 'PROPOSED', + priority: { executive: 0.6, individual: 0.7, community: 0.65, computed: 0.65 }, + position: { x: -400, y: 200, z: 0 }, + estimatedHours: 300, + dueDate: '2026-03-31', + tags: ['mobile', 'react-native', 'ios', 'android'], + createdAt: '2025-08-01T10:00:00Z', + updatedAt: '2025-08-17T15:30:00Z' } ]; export const mockProjectEdges: MockEdge[] = [ + // Database schema must be completed before any auth features { id: 'edge-1', - source: 'node-1', - target: 'node-2', - type: 'PART_OF', - strength: 0.9, - description: 'Login API is part of the auth system', - createdAt: '2024-03-02T09:00:00Z' + source: 'task-1', + target: 'feature-1', + type: 'DEPENDS_ON', + strength: 1.0, + description: 'Login features need database schema', + createdAt: '2025-08-01T09:00:00Z' }, { id: 'edge-2', - source: 'node-1', - target: 'node-3', - type: 'PART_OF', - strength: 0.8, - description: 'Registration UI is part of the auth system', - createdAt: '2024-03-03T14:00:00Z' + source: 'task-1', + target: 'feature-2', + type: 'DEPENDS_ON', + strength: 1.0, + description: 'Password reset needs database schema', + createdAt: '2025-08-01T09:00:00Z' }, + // Auth epic contains login and password reset features { id: 'edge-3', - source: 'node-5', - target: 'node-2', - type: 'DEPENDS_ON', - strength: 1.0, - description: 'Login API needs database schema', - createdAt: '2024-03-02T09:00:00Z' + source: 'feature-1', + target: 'epic-1', + type: 'PART_OF', + strength: 0.9, + description: 'Login is part of auth system', + createdAt: '2025-08-01T10:00:00Z' }, { id: 'edge-4', - source: 'node-2', - target: 'node-3', - type: 'ENABLES', - strength: 0.7, - description: 'Registration UI can use login API patterns', - createdAt: '2024-03-03T14:00:00Z' + source: 'feature-2', + target: 'epic-1', + type: 'PART_OF', + strength: 0.8, + description: 'Password reset is part of auth system', + createdAt: '2025-08-01T10:00:00Z' }, { id: 'edge-5', - source: 'node-1', - target: 'node-4', - type: 'ENABLES', - strength: 0.6, - description: 'Auth system enables password reset', - createdAt: '2024-03-05T11:00:00Z' + source: 'task-2', + target: 'epic-1', + type: 'PART_OF', + strength: 0.7, + description: 'Frontend components part of auth system', + createdAt: '2025-08-10T14:00:00Z' }, + // Frontend depends on completed login feature { id: 'edge-6', - source: 'node-1', - target: 'node-6', + source: 'feature-1', + target: 'task-2', type: 'DEPENDS_ON', strength: 0.8, - description: 'Security testing depends on auth implementation', - createdAt: '2024-03-10T15:00:00Z' + description: 'Frontend components need login API', + createdAt: '2025-08-10T14:00:00Z' }, + // Bug blocking login reliability { id: 'edge-7', - source: 'node-6', - target: 'node-7', - type: 'DEPENDS_ON', + source: 'bug-1', + target: 'feature-1', + type: 'BLOCKS', strength: 0.9, - description: 'Launch depends on security testing', - createdAt: '2024-03-10T15:00:00Z' + description: 'Session bug affects login reliability', + createdAt: '2025-08-16T14:00:00Z' }, + // Auth system must be complete before milestone { id: 'edge-8', - source: 'node-1', - target: 'node-7', + source: 'epic-1', + target: 'milestone-1', type: 'DEPENDS_ON', strength: 1.0, - description: 'Launch depends on auth system completion', - createdAt: '2024-03-01T10:00:00Z' + description: 'MVP launch needs auth system', + createdAt: '2025-08-01T10:00:00Z' }, + // E-commerce features and dependencies { id: 'edge-9', - source: 'node-2', - target: 'node-8', - type: 'BLOCKS', + source: 'feature-3', + target: 'epic-2', + type: 'PART_OF', strength: 0.9, - description: 'Login bug blocks API reliability', - createdAt: '2024-03-18T14:00:00Z' + description: 'Product catalog part of e-commerce', + createdAt: '2025-08-15T09:00:00Z' }, { id: 'edge-10', - source: 'node-3', - target: 'node-4', - type: 'PARALLEL_WITH', - strength: 0.5, - description: 'Registration and password reset can be developed in parallel', - createdAt: '2024-03-05T11:00:00Z' + source: 'feature-4', + target: 'epic-2', + type: 'PART_OF', + strength: 0.9, + description: 'Shopping cart part of e-commerce', + createdAt: '2025-08-01T10:00:00Z' + }, + { + id: 'edge-11', + source: 'task-3', + target: 'epic-2', + type: 'PART_OF', + strength: 0.8, + description: 'Payment integration part of e-commerce', + createdAt: '2025-08-10T11:00:00Z' + }, + // Catalog must be ready before checkout + { + id: 'edge-12', + source: 'feature-3', + target: 'feature-4', + type: 'DEPENDS_ON', + strength: 0.8, + description: 'Checkout needs product catalog', + createdAt: '2025-08-15T09:00:00Z' + }, + // Payment gateway needed for checkout + { + id: 'edge-13', + source: 'task-3', + target: 'feature-4', + type: 'DEPENDS_ON', + strength: 1.0, + description: 'Checkout needs payment processing', + createdAt: '2025-08-10T11:00:00Z' + }, + // Auth system needed for e-commerce (user accounts) + { + id: 'edge-14', + source: 'epic-1', + target: 'epic-2', + type: 'DEPENDS_ON', + strength: 0.7, + description: 'E-commerce needs user authentication', + createdAt: '2025-08-01T10:00:00Z' + }, + // Security testing before e-commerce launch + { + id: 'edge-15', + source: 'task-4', + target: 'epic-2', + type: 'VALIDATES', + strength: 0.8, + description: 'Security testing validates e-commerce', + createdAt: '2025-08-10T15:00:00Z' + }, + // E-commerce completion needed for beta milestone + { + id: 'edge-16', + source: 'epic-2', + target: 'milestone-2', + type: 'DEPENDS_ON', + strength: 1.0, + description: 'Beta release needs e-commerce platform', + createdAt: '2025-08-01T10:00:00Z' + }, + // Security testing blocks beta release + { + id: 'edge-17', + source: 'task-4', + target: 'milestone-2', + type: 'DEPENDS_ON', + strength: 0.9, + description: 'Beta release needs security validation', + createdAt: '2025-08-10T15:00:00Z' } ];