diff --git a/frontend/src/components/DependencyGraph/DirectoryNode.tsx b/frontend/src/components/DependencyGraph/DirectoryNode.tsx new file mode 100644 index 0000000..a224afa --- /dev/null +++ b/frontend/src/components/DependencyGraph/DirectoryNode.tsx @@ -0,0 +1,99 @@ +import { memo } from 'react' +import { Handle, Position } from 'reactflow' +import type { NodeProps } from 'reactflow' +import { Folder, FolderOpen, ChevronRight } from 'lucide-react' +import { cn } from '@/lib/utils' +import { Badge } from '@/components/ui/badge' +import type { RiskLevel } from './hooks/useImpactAnalysis' + +export interface DirectoryNodeData { + label: string + fullPath: string + fileCount: number + totalDependents: number + maxRisk: RiskLevel + isExpanded: boolean + state: 'default' | 'selected' | 'direct' | 'transitive' | 'dimmed' +} + +const STATE_STYLES: Record = { + default: 'border-zinc-300 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-800/90', + selected: 'border-indigo-500 bg-indigo-50 dark:bg-indigo-950 ring-2 ring-indigo-500/50 shadow-lg shadow-indigo-500/20', + direct: 'border-rose-500 bg-rose-50 dark:bg-rose-950 ring-1 ring-rose-500/30', + transitive: 'border-amber-500 bg-amber-50 dark:bg-amber-950 ring-1 ring-amber-500/30', + dimmed: 'border-zinc-200 bg-zinc-100/50 opacity-40 dark:border-zinc-800 dark:bg-zinc-900/50', +} + +const RISK_STYLES: Record = { + low: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400', + medium: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-500/10 dark:text-yellow-400', + high: 'bg-orange-100 text-orange-700 dark:bg-orange-500/10 dark:text-orange-400', + critical: 'bg-rose-100 text-rose-700 dark:bg-rose-500/10 dark:text-rose-400', +} + +function DirectoryNodeComponent({ data }: NodeProps) { + const stateStyle = STATE_STYLES[data.state] + const FolderIcon = data.isExpanded ? FolderOpen : Folder + + return ( + <> + + +
+
+ + + {data.label}/ + + +
+ +
+ + {data.fileCount} file{data.fileCount !== 1 ? 's' : ''} + + + = 30 ? 'text-rose-600 dark:text-rose-400' : + data.totalDependents >= 10 ? 'text-amber-600 dark:text-amber-400' : + 'text-zinc-500 dark:text-zinc-400' + )}> + {data.totalDependents} deps + + {data.maxRisk !== 'low' && ( + + {data.maxRisk === 'critical' ? 'Crit' : data.maxRisk === 'high' ? 'High' : 'Med'} + + )} +
+
+ + + + ) +} + +export const DirectoryNode = memo(DirectoryNodeComponent) diff --git a/frontend/src/components/DependencyGraph/GraphToolbar.tsx b/frontend/src/components/DependencyGraph/GraphToolbar.tsx index feb7f97..7ac7050 100644 --- a/frontend/src/components/DependencyGraph/GraphToolbar.tsx +++ b/frontend/src/components/DependencyGraph/GraphToolbar.tsx @@ -1,5 +1,5 @@ import { memo } from 'react' -import { RotateCcw, Maximize2, Filter, Eye, EyeOff } from 'lucide-react' +import { RotateCcw, Maximize2, Filter, Eye, EyeOff, FolderTree } from 'lucide-react' import { cn } from '@/lib/utils' import { Button } from '@/components/ui/button' @@ -8,8 +8,10 @@ interface GraphToolbarProps { visibleFiles: number showAll: boolean showTests: boolean + clusterByDir: boolean onToggleShowAll: () => void onToggleTests: () => void + onToggleCluster: () => void onResetView: () => void onFullscreen?: () => void } @@ -19,8 +21,10 @@ function GraphToolbarComponent({ visibleFiles, showAll, showTests, + clusterByDir, onToggleShowAll, onToggleTests, + onToggleCluster, onResetView, onFullscreen, }: GraphToolbarProps) { @@ -37,21 +41,37 @@ function GraphToolbarComponent({
+ +