'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)} >
{ e.stopPropagation(); onEpicClick?.(epic); }} > {epic.name}
{epic.description && (

{epic.description}

)}
{epic.estimatedHours && (
{epic.estimatedHours}h {epic.actualHours && ` / ${epic.actualHours}h`}
)}
{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)} >
{ e.stopPropagation(); onStoryClick?.(story); }} > {story.title}
{story.description && (

{story.description}

)}
{story.estimatedHours && (
{story.estimatedHours}h {story.actualHours && ` / ${story.actualHours}h`}
)}
{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)} >
{task.title}
{task.description && (

{task.description}

)}
{task.estimatedHours && (
{task.estimatedHours}h {task.actualHours && ` / ${task.actualHours}h`}
)}
); } 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) => (
))}
); }