'use client'; import { useState } from 'react'; import { ChevronRight, ChevronDown, Folder, FileText, CheckSquare } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Skeleton } from '@/components/ui/skeleton'; import { useEpics } from '@/lib/hooks/use-epics'; import { useStories } from '@/lib/hooks/use-stories'; import { useTasks } from '@/lib/hooks/use-tasks'; import type { Epic, Story, Task, WorkItemStatus, WorkItemPriority } from '@/types/project'; interface HierarchyTreeProps { projectId: string; onEpicClick?: (epic: Epic) => void; onStoryClick?: (story: Story) => void; onTaskClick?: (task: Task) => void; } export function HierarchyTree({ projectId, onEpicClick, onStoryClick, onTaskClick, }: HierarchyTreeProps) { const { data: epics = [], isLoading: epicsLoading } = useEpics(projectId); if (epicsLoading) { return ; } if (epics.length === 0) { return (

No Epics Found

Create your first epic to start organizing work

); } return (
{epics.map((epic) => ( ))}
); } interface EpicNodeProps { epic: Epic; onEpicClick?: (epic: Epic) => void; onStoryClick?: (story: Story) => void; onTaskClick?: (task: Task) => void; } function EpicNode({ epic, onEpicClick, onStoryClick, onTaskClick }: EpicNodeProps) { const [isExpanded, setIsExpanded] = useState(false); const { data: stories = [], isLoading: storiesLoading } = useStories( isExpanded ? epic.id : undefined ); return (
setIsExpanded(!isExpanded)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setIsExpanded(!isExpanded); } }} >
{isExpanded && (
{storiesLoading ? (
) : stories.length === 0 ? (
No stories in this epic
) : ( stories.map((story) => ( )) )}
)}
); } interface StoryNodeProps { story: Story; onStoryClick?: (story: Story) => void; onTaskClick?: (task: Task) => void; } function StoryNode({ story, onStoryClick, onTaskClick }: StoryNodeProps) { const [isExpanded, setIsExpanded] = useState(false); const { data: tasks = [], isLoading: tasksLoading } = useTasks( isExpanded ? story.id : undefined ); return (
setIsExpanded(!isExpanded)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setIsExpanded(!isExpanded); } }} >
{isExpanded && (
{tasksLoading ? (
) : tasks.length === 0 ? (
No tasks in this story
) : ( tasks.map((task) => ) )}
)}
); } interface TaskNodeProps { task: Task; onTaskClick?: (task: Task) => void; } function TaskNode({ task, onTaskClick }: TaskNodeProps) { return (
onTaskClick?.(task)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onTaskClick?.(task); } }} aria-label={`View task: ${task.title}`} >
); } interface StatusBadgeProps { status: WorkItemStatus; size?: 'default' | 'sm' | 'xs'; } function StatusBadge({ status, size = 'default' }: StatusBadgeProps) { const variants: Record = { Backlog: 'secondary', Todo: 'outline', InProgress: 'default', Done: 'outline', }; const sizeClasses = { default: 'text-xs', sm: 'text-xs px-1.5 py-0', xs: 'text-[10px] px-1 py-0', }; return ( {status} ); } interface PriorityBadgeProps { priority: WorkItemPriority; size?: 'default' | 'sm' | 'xs'; } function PriorityBadge({ priority, size = 'default' }: PriorityBadgeProps) { const colors: Record = { Low: 'bg-gray-100 text-gray-700', Medium: 'bg-blue-100 text-blue-700', High: 'bg-orange-100 text-orange-700', Critical: 'bg-red-100 text-red-700', }; const sizeClasses = { default: 'text-xs', sm: 'text-xs px-1.5 py-0', xs: 'text-[10px] px-1 py-0', }; return {priority}; } function HierarchyTreeSkeleton() { return (
{[1, 2, 3].map((i) => (
))}
); }