'use client'; import { useState, useMemo } from 'react'; import type { BeadIssue } from '../../lib/types'; import { useResponsive } from '../../hooks/use-responsive'; import { cn } from '../../lib/utils'; export interface LeftPanelProps { issues: BeadIssue[]; selectedEpicId?: string | null; onEpicSelect?: (epicId: string | null) => void; } interface EpicNode { epic: BeadIssue; children: BeadIssue[]; } function buildEpicTree(issues: BeadIssue[]): EpicNode[] { const epics = issues.filter(issue => issue.issue_type === 'epic'); const epicMap = new Map(); for (const epic of epics) { epicMap.set(epic.id, { epic, children: [] }); } for (const issue of issues) { if (issue.issue_type === 'epic') continue; const parentDep = issue.dependencies.find(dep => dep.type === 'parent'); if (parentDep && epicMap.has(parentDep.target)) { epicMap.get(parentDep.target)!.children.push(issue); } } return Array.from(epicMap.values()).sort((a, b) => a.epic.id.localeCompare(b.epic.id) ); } export function LeftPanel({ issues, selectedEpicId, onEpicSelect, }: LeftPanelProps) { const [expandedEpics, setExpandedEpics] = useState>(new Set()); const { isDesktop, isTablet } = useResponsive(); const epicTree = useMemo(() => buildEpicTree(issues), [issues]); 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 handleEpicClick = (epicId: string) => { onEpicSelect?.(epicId); toggleEpic(epicId); }; if (isTablet) { return (
{epicTree.map(({ epic }) => ( ))}
); } return (
Channels
{epicTree.map(({ epic, children }) => { const isExpanded = expandedEpics.has(epic.id); const isSelected = selectedEpicId === epic.id; const childCount = children.length; return (
{isExpanded && childCount > 0 && (
{children.map(child => { const childSelected = selectedEpicId === child.id; return ( ); })}
)}
); })} {epicTree.length === 0 && (
No epics found
)}
Scope
); } export default LeftPanel;