Skip to content

Commit c95b482

Browse files
committed
refactor(dependency-graph): use shadcn components, replace emojis with lucide icons, add light mode
1 parent f6cd0de commit c95b482

4 files changed

Lines changed: 154 additions & 133 deletions

File tree

frontend/src/components/DependencyGraph/GraphNode.tsx

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
11
import { memo } from 'react'
22
import { Handle, Position } from 'reactflow'
33
import type { NodeProps } from 'reactflow'
4-
import { FileCode2, FileJson, FileText, TestTube2, Settings, File } from 'lucide-react'
4+
import {
5+
FileCode2,
6+
FileJson,
7+
FileText,
8+
TestTube2,
9+
Settings,
10+
File,
11+
AlertTriangle,
12+
Flame,
13+
CheckCircle2,
14+
CircleAlert
15+
} from 'lucide-react'
516
import { cn } from '@/lib/utils'
17+
import { Badge } from '@/components/ui/badge'
618
import type { RiskLevel } from './hooks/useImpactAnalysis'
719

820
export interface GraphNodeData {
@@ -48,11 +60,11 @@ const STATE_STYLES: Record<GraphNodeData['state'], string> = {
4860
dimmed: 'border-zinc-200 bg-zinc-50/50 opacity-40 dark:border-zinc-800 dark:bg-zinc-900/50',
4961
}
5062

51-
const RISK_BADGES: Record<RiskLevel, { bg: string; text: string; label: string }> = {
52-
low: { bg: 'bg-emerald-100 dark:bg-emerald-500/10', text: 'text-emerald-600 dark:text-emerald-400', label: 'Low' },
53-
medium: { bg: 'bg-yellow-100 dark:bg-yellow-500/10', text: 'text-yellow-600 dark:text-yellow-400', label: 'Med' },
54-
high: { bg: 'bg-orange-100 dark:bg-orange-500/10', text: 'text-orange-600 dark:text-orange-400', label: 'High' },
55-
critical: { bg: 'bg-rose-100 dark:bg-rose-500/10', text: 'text-rose-600 dark:text-rose-400', label: 'Crit' },
63+
const RISK_CONFIG: Record<RiskLevel, { variant: 'default' | 'secondary' | 'destructive' | 'outline'; label: string; className: string }> = {
64+
low: { variant: 'secondary', label: 'Low', className: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400' },
65+
medium: { variant: 'secondary', label: 'Med', className: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-500/10 dark:text-yellow-400' },
66+
high: { variant: 'secondary', label: 'High', className: 'bg-orange-100 text-orange-700 dark:bg-orange-500/10 dark:text-orange-400' },
67+
critical: { variant: 'destructive', label: 'Crit', className: 'bg-rose-100 text-rose-700 dark:bg-rose-500/10 dark:text-rose-400' },
5668
}
5769

5870
function getFileType(path: string, language: string): string {
@@ -75,7 +87,7 @@ function GraphNodeComponent({ data }: NodeProps<GraphNodeData>) {
7587
const Icon = FILE_ICONS[fileType] || FILE_ICONS.unknown
7688
const iconColor = LANGUAGE_COLORS[fileType] || LANGUAGE_COLORS.unknown
7789
const stateStyle = STATE_STYLES[data.state]
78-
const risk = RISK_BADGES[data.riskLevel]
90+
const risk = RISK_CONFIG[data.riskLevel]
7991

8092
return (
8193
<>
@@ -93,7 +105,6 @@ function GraphNodeComponent({ data }: NodeProps<GraphNodeData>) {
93105
data.isEntryPoint && 'border-l-4 border-l-emerald-500'
94106
)}
95107
>
96-
{/* Header row */}
97108
<div className="flex items-center gap-2 mb-1">
98109
<Icon className={cn('w-4 h-4 flex-shrink-0', iconColor)} />
99110
<span
@@ -103,13 +114,12 @@ function GraphNodeComponent({ data }: NodeProps<GraphNodeData>) {
103114
{data.label}
104115
</span>
105116
{data.dependentCount > 0 && (
106-
<span className={cn('px-1.5 py-0.5 rounded text-[10px] font-medium', risk.bg, risk.text)}>
117+
<Badge variant="secondary" className={cn('text-[10px] px-1.5 py-0 h-5', risk.className)}>
107118
{risk.label}
108-
</span>
119+
</Badge>
109120
)}
110121
</div>
111122

112-
{/* Stats row */}
113123
<div className="flex items-center gap-3 text-[11px] text-zinc-500 dark:text-zinc-400">
114124
<span className={cn(
115125
'font-medium',
@@ -121,12 +131,6 @@ function GraphNodeComponent({ data }: NodeProps<GraphNodeData>) {
121131
</span>
122132
<span></span>
123133
<span>{data.importCount} imports</span>
124-
{data.loc && (
125-
<>
126-
<span></span>
127-
<span>{data.loc} LOC</span>
128-
</>
129-
)}
130134
</div>
131135
</div>
132136

frontend/src/components/DependencyGraph/GraphToolbar.tsx

Lines changed: 19 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { memo } from 'react'
22
import { RotateCcw, Maximize2, Filter, Eye, EyeOff } from 'lucide-react'
33
import { cn } from '@/lib/utils'
4+
import { Button } from '@/components/ui/button'
45

56
interface GraphToolbarProps {
67
totalFiles: number
@@ -25,7 +26,6 @@ function GraphToolbarComponent({
2526
}: GraphToolbarProps) {
2627
return (
2728
<div className="flex items-center justify-between gap-4 px-4 py-2 bg-white/80 dark:bg-zinc-900/80 border-b border-zinc-200 dark:border-zinc-800 backdrop-blur-sm">
28-
{/* Left: Stats */}
2929
<div className="flex items-center gap-4 text-sm">
3030
<span className="text-zinc-500 dark:text-zinc-400">
3131
Showing <span className="text-zinc-700 dark:text-zinc-200 font-medium">{visibleFiles}</span>
@@ -36,53 +36,37 @@ function GraphToolbarComponent({
3636
</span>
3737
</div>
3838

39-
{/* Center: Filters */}
4039
<div className="flex items-center gap-2">
41-
<button
40+
<Button
41+
variant={showAll ? 'default' : 'secondary'}
42+
size="sm"
4243
onClick={onToggleShowAll}
43-
className={cn(
44-
'flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-colors',
45-
showAll
46-
? 'bg-indigo-600 text-white'
47-
: 'bg-zinc-100 dark:bg-zinc-800 text-zinc-700 dark:text-zinc-300 hover:bg-zinc-200 dark:hover:bg-zinc-700'
48-
)}
44+
className="h-8"
4945
>
50-
<Filter className="w-3.5 h-3.5" />
46+
<Filter className="w-3.5 h-3.5 mr-1.5" />
5147
{showAll ? 'Show Top 15' : 'Show All'}
52-
</button>
48+
</Button>
5349

54-
<button
50+
<Button
51+
variant="secondary"
52+
size="sm"
5553
onClick={onToggleTests}
56-
className={cn(
57-
'flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-colors',
58-
showTests
59-
? 'bg-zinc-100 dark:bg-zinc-800 text-zinc-700 dark:text-zinc-300 hover:bg-zinc-200 dark:hover:bg-zinc-700'
60-
: 'bg-zinc-50 dark:bg-zinc-800/50 text-zinc-400 dark:text-zinc-500 hover:bg-zinc-100 dark:hover:bg-zinc-800 hover:text-zinc-600 dark:hover:text-zinc-300'
61-
)}
54+
className={cn('h-8', !showTests && 'opacity-60')}
6255
>
63-
{showTests ? <Eye className="w-3.5 h-3.5" /> : <EyeOff className="w-3.5 h-3.5" />}
56+
{showTests ? <Eye className="w-3.5 h-3.5 mr-1.5" /> : <EyeOff className="w-3.5 h-3.5 mr-1.5" />}
6457
Tests
65-
</button>
58+
</Button>
6659
</div>
6760

68-
{/* Right: Actions */}
6961
<div className="flex items-center gap-1">
70-
<button
71-
onClick={onResetView}
72-
className="p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-md transition-colors group"
73-
title="Reset View"
74-
>
75-
<RotateCcw className="w-4 h-4 text-zinc-400 dark:text-zinc-500 group-hover:text-zinc-600 dark:group-hover:text-zinc-300" />
76-
</button>
62+
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={onResetView} title="Reset View">
63+
<RotateCcw className="w-4 h-4" />
64+
</Button>
7765

7866
{onFullscreen && (
79-
<button
80-
onClick={onFullscreen}
81-
className="p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-md transition-colors group"
82-
title="Fullscreen"
83-
>
84-
<Maximize2 className="w-4 h-4 text-zinc-400 dark:text-zinc-500 group-hover:text-zinc-600 dark:group-hover:text-zinc-300" />
85-
</button>
67+
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={onFullscreen} title="Fullscreen">
68+
<Maximize2 className="w-4 h-4" />
69+
</Button>
8670
)}
8771
</div>
8872
</div>

frontend/src/components/DependencyGraph/ImpactPanel.tsx

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
import { memo, useState } from 'react'
2-
import { X, ChevronDown, ChevronRight, AlertTriangle, FileCode2, ExternalLink, Search } from 'lucide-react'
2+
import {
3+
X,
4+
ChevronDown,
5+
ChevronRight,
6+
AlertTriangle,
7+
FileCode2,
8+
ExternalLink,
9+
Search,
10+
CheckCircle2,
11+
Flame,
12+
CircleAlert,
13+
MapPin
14+
} from 'lucide-react'
315
import { cn } from '@/lib/utils'
16+
import { Button } from '@/components/ui/button'
17+
import { Card, CardContent, CardHeader } from '@/components/ui/card'
18+
import { Badge } from '@/components/ui/badge'
419
import type { RiskLevel, ImpactResult } from './hooks/useImpactAnalysis'
520

621
interface ImpactPanelProps {
@@ -13,33 +28,39 @@ interface ImpactPanelProps {
1328
onAnalyzeInSearch?: (fileId: string) => void
1429
}
1530

16-
const RISK_CONFIG: Record<RiskLevel, { bg: string; border: string; text: string; icon: string; label: string }> = {
31+
const RISK_CONFIG: Record<RiskLevel, {
32+
bg: string
33+
border: string
34+
text: string
35+
icon: typeof CheckCircle2
36+
label: string
37+
}> = {
1738
low: {
1839
bg: 'bg-emerald-50 dark:bg-emerald-500/10',
1940
border: 'border-emerald-200 dark:border-emerald-500/30',
2041
text: 'text-emerald-600 dark:text-emerald-400',
21-
icon: '✓',
42+
icon: CheckCircle2,
2243
label: 'Low Risk'
2344
},
2445
medium: {
2546
bg: 'bg-yellow-50 dark:bg-yellow-500/10',
2647
border: 'border-yellow-200 dark:border-yellow-500/30',
2748
text: 'text-yellow-600 dark:text-yellow-400',
28-
icon: '⚠',
49+
icon: CircleAlert,
2950
label: 'Medium Risk'
3051
},
3152
high: {
3253
bg: 'bg-orange-50 dark:bg-orange-500/10',
3354
border: 'border-orange-200 dark:border-orange-500/30',
3455
text: 'text-orange-600 dark:text-orange-400',
35-
icon: '⚠',
56+
icon: AlertTriangle,
3657
label: 'High Risk'
3758
},
3859
critical: {
3960
bg: 'bg-rose-50 dark:bg-rose-500/10',
4061
border: 'border-rose-200 dark:border-rose-500/30',
4162
text: 'text-rose-600 dark:text-rose-400',
42-
icon: '🔥',
63+
icon: Flame,
4364
label: 'Critical'
4465
},
4566
}
@@ -114,7 +135,7 @@ function CollapsibleSection({
114135
<ChevronRight className="w-4 h-4 text-zinc-400 dark:text-zinc-500" />
115136
)}
116137
<span className={cn('text-sm font-medium', variantStyles[variant])}>{title}</span>
117-
<span className="text-xs text-zinc-400 dark:text-zinc-500 ml-auto">{count}</span>
138+
<Badge variant="secondary" className="ml-auto text-[10px] px-1.5 py-0 h-5">{count}</Badge>
118139
</button>
119140

120141
{isOpen && (
@@ -143,12 +164,12 @@ function ImpactPanelComponent({
143164
onAnalyzeInSearch,
144165
}: ImpactPanelProps) {
145166
const risk = RISK_CONFIG[impact.riskLevel]
167+
const RiskIcon = risk.icon
146168
const totalDependents = impact.allDependents.length
147169

148170
return (
149171
<div className="w-80 bg-white dark:bg-zinc-900 border-l border-zinc-200 dark:border-zinc-800 h-full flex flex-col animate-in slide-in-from-right duration-200">
150-
{/* Header */}
151-
<div className="p-4 border-b border-zinc-200 dark:border-zinc-800">
172+
<CardHeader className="p-4 border-b border-zinc-200 dark:border-zinc-800">
152173
<div className="flex items-start justify-between gap-2">
153174
<div className="min-w-0 flex-1">
154175
<h3 className="font-semibold text-zinc-800 dark:text-zinc-100 truncate" title={fullPath}>
@@ -158,20 +179,16 @@ function ImpactPanelComponent({
158179
{fullPath}
159180
</p>
160181
</div>
161-
<button
162-
onClick={onClose}
163-
className="p-1 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded transition-colors flex-shrink-0"
164-
>
165-
<X className="w-4 h-4 text-zinc-500 dark:text-zinc-400" />
166-
</button>
182+
<Button variant="ghost" size="icon" className="h-8 w-8 flex-shrink-0" onClick={onClose}>
183+
<X className="w-4 h-4" />
184+
</Button>
167185
</div>
168186

169-
{/* Risk Badge */}
170187
<div className={cn(
171188
'mt-3 px-3 py-2 rounded-lg border flex items-center gap-2',
172189
risk.bg, risk.border
173190
)}>
174-
<span className="text-lg">{risk.icon}</span>
191+
<RiskIcon className={cn('w-5 h-5', risk.text)} />
175192
<div>
176193
<div className={cn('font-semibold text-sm', risk.text)}>{risk.label}</div>
177194
<div className="text-xs text-zinc-500 dark:text-zinc-400">
@@ -183,25 +200,22 @@ function ImpactPanelComponent({
183200
</div>
184201
</div>
185202

186-
{/* Warning for critical files */}
187203
{impact.riskLevel === 'critical' && (
188204
<div className="mt-2 flex items-start gap-2 text-xs text-rose-600 dark:text-rose-400 bg-rose-50 dark:bg-rose-500/10 px-3 py-2 rounded-lg">
189205
<AlertTriangle className="w-3.5 h-3.5 flex-shrink-0 mt-0.5" />
190206
<span>Changes to this file have high blast radius. Test thoroughly.</span>
191207
</div>
192208
)}
193209

194-
{/* Entry Point indicator */}
195210
{impact.isEntryPoint && (
196211
<div className="mt-2 flex items-center gap-2 text-xs text-emerald-600 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-500/10 px-3 py-2 rounded-lg">
197-
<span>📍</span>
198-
<span>Entry point - this file is a root of the dependency tree</span>
212+
<MapPin className="w-3.5 h-3.5" />
213+
<span>Entry point - root of the dependency tree</span>
199214
</div>
200215
)}
201-
</div>
216+
</CardHeader>
202217

203-
{/* Dependents Lists */}
204-
<div className="flex-1 overflow-y-auto p-4 space-y-3">
218+
<CardContent className="flex-1 overflow-y-auto p-4 space-y-3">
205219
<CollapsibleSection
206220
title="Direct Dependents"
207221
count={impact.directDependents.length}
@@ -221,18 +235,14 @@ function ImpactPanelComponent({
221235
onFileClick={onFileClick}
222236
onFileHover={onFileHover}
223237
/>
224-
</div>
238+
</CardContent>
225239

226-
{/* Actions Footer */}
227240
{onAnalyzeInSearch && (
228241
<div className="p-4 border-t border-zinc-200 dark:border-zinc-800">
229-
<button
230-
onClick={() => onAnalyzeInSearch(fullPath)}
231-
className="w-full flex items-center justify-center gap-2 px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white text-sm font-medium rounded-lg transition-colors"
232-
>
233-
<Search className="w-4 h-4" />
242+
<Button className="w-full" onClick={() => onAnalyzeInSearch(fullPath)}>
243+
<Search className="w-4 h-4 mr-2" />
234244
Analyze in Search
235-
</button>
245+
</Button>
236246
</div>
237247
)}
238248
</div>

0 commit comments

Comments
 (0)