Add trace files.
This commit is contained in:
103
docs/plans/sprint_1_story_2_task_4.md
Normal file
103
docs/plans/sprint_1_story_2_task_4.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Task 8: Add Hierarchy Visualization
|
||||
|
||||
**Task ID**: TASK-008 | **Story**: [STORY-002](sprint_1_story_2.md) | **Sprint**: [Sprint 1](sprint_1.md)
|
||||
**Estimated Hours**: 4h | **Assignee**: Frontend Developer 2 | **Priority**: P0 | **Status**: Not Started
|
||||
|
||||
## Task Description
|
||||
Build tree view component to visualize Epic → Story → Task hierarchy with expand/collapse and breadcrumb navigation.
|
||||
|
||||
## Implementation
|
||||
|
||||
### Component: HierarchyTree.tsx
|
||||
```typescript
|
||||
import React, { useState } from 'react';
|
||||
import { Epic, Story, Task } from '../api/types';
|
||||
|
||||
interface HierarchyTreeProps {
|
||||
epics: Epic[];
|
||||
stories: Story[];
|
||||
tasks: Task[];
|
||||
}
|
||||
|
||||
export const HierarchyTree: React.FC<HierarchyTreeProps> = ({ epics, stories, tasks }) => {
|
||||
const [expandedEpics, setExpandedEpics] = useState<Set<string>>(new Set());
|
||||
const [expandedStories, setExpandedStories] = useState<Set<string>>(new Set());
|
||||
|
||||
const toggleEpic = (epicId: string) => {
|
||||
setExpandedEpics(prev => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(epicId)) next.delete(epicId);
|
||||
else next.add(epicId);
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const getStoriesForEpic = (epicId: string) => {
|
||||
return stories.filter(s => s.parentEpicId === epicId);
|
||||
};
|
||||
|
||||
const getTasksForStory = (storyId: string) => {
|
||||
return tasks.filter(t => t.parentStoryId === storyId);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="hierarchy-tree">
|
||||
{epics.map(epic => (
|
||||
<div key={epic.id} className="epic-node">
|
||||
<div className="flex items-center gap-2 p-2 hover:bg-gray-50 cursor-pointer" onClick={() => toggleEpic(epic.id)}>
|
||||
<span className="text-purple-600">📊</span>
|
||||
<span className="font-semibold">{epic.title}</span>
|
||||
<span className="text-sm text-gray-500">
|
||||
({getStoriesForEpic(epic.id).length} stories)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{expandedEpics.has(epic.id) && (
|
||||
<div className="ml-6 border-l-2 border-gray-200">
|
||||
{getStoriesForEpic(epic.id).map(story => (
|
||||
<div key={story.id} className="story-node">
|
||||
<div className="flex items-center gap-2 p-2">
|
||||
<span className="text-blue-600">📝</span>
|
||||
<span>{story.title}</span>
|
||||
<span className="text-sm text-gray-500">
|
||||
({getTasksForStory(story.id).length} tasks)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="ml-6">
|
||||
{getTasksForStory(story.id).map(task => (
|
||||
<div key={task.id} className="task-node flex items-center gap-2 p-2">
|
||||
<span className="text-green-600">✓</span>
|
||||
<span className="text-sm">{task.title}</span>
|
||||
<span className={`px-2 py-1 text-xs rounded ${task.status === 'Done' ? 'bg-green-100' : 'bg-gray-100'}`}>
|
||||
{task.status}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ] Tree view displays Epic → Story → Task structure
|
||||
- [ ] Expand/collapse Epic nodes
|
||||
- [ ] Show child count for each node
|
||||
- [ ] Icons differentiate Epic/Story/Task
|
||||
- [ ] Click to navigate to detail page
|
||||
- [ ] Breadcrumb navigation component
|
||||
|
||||
## Deliverables
|
||||
1. HierarchyTree.tsx component
|
||||
2. Breadcrumb.tsx component
|
||||
3. CSS styles for tree layout
|
||||
4. Unit tests (8+ tests)
|
||||
|
||||
**Status**: Not Started | **Created**: 2025-11-04
|
||||
Reference in New Issue
Block a user