Skip to content

Commit 545c8e5

Browse files
authored
Merge pull request #41 from DevanshuNEU/feature/keyboard-shortcuts-polish
feat(frontend): Keyboard Shortcuts + Polish + Caching (#36)
2 parents 3bdd2d0 + e712d52 commit 545c8e5

12 files changed

Lines changed: 541 additions & 103 deletions

File tree

frontend/package-lock.json

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@radix-ui/react-progress": "^1.1.8",
1616
"@radix-ui/react-slot": "^1.2.4",
1717
"@supabase/supabase-js": "^2.39.0",
18+
"@tanstack/react-query": "^5.90.12",
1819
"@types/dagre": "^0.7.53",
1920
"@types/react-syntax-highlighter": "^15.5.13",
2021
"class-variance-authority": "^0.7.1",

frontend/src/components/DependencyGraph.tsx

Lines changed: 70 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import ReactFlow, {
1111
} from 'reactflow'
1212
import dagre from 'dagre'
1313
import 'reactflow/dist/style.css'
14+
import { useDependencyGraph } from '../hooks/useCachedQuery'
1415

1516
interface DependencyGraphProps {
1617
repoId: string
@@ -47,17 +48,25 @@ const getLayoutedElements = (nodes: Node[], edges: Edge[]) => {
4748
export function DependencyGraph({ repoId, apiUrl, apiKey }: DependencyGraphProps) {
4849
const [nodes, setNodes, onNodesChange] = useNodesState([])
4950
const [edges, setEdges, onEdgesChange] = useEdgesState([])
50-
const [loading, setLoading] = useState(true)
5151
const [metrics, setMetrics] = useState<any>(null)
5252
const [filterCritical, setFilterCritical] = useState(false)
5353
const [minDeps, setMinDeps] = useState(0)
5454
const [highlightedNode, setHighlightedNode] = useState<string | null>(null)
5555
const [allNodes, setAllNodes] = useState<Node[]>([])
5656
const [allEdges, setAllEdges] = useState<Edge[]>([])
5757

58+
// Use cached query for dependencies
59+
const { data, isLoading: loading, isFetching } = useDependencyGraph({
60+
repoId,
61+
apiKey
62+
})
63+
64+
// Process data when it arrives
5865
useEffect(() => {
59-
loadGraph()
60-
}, [repoId])
66+
if (data) {
67+
processGraphData(data)
68+
}
69+
}, [data])
6170

6271
useEffect(() => {
6372
if (allNodes.length > 0) {
@@ -89,83 +98,67 @@ export function DependencyGraph({ repoId, apiUrl, apiKey }: DependencyGraphProps
8998
setEdges(layoutedEdges)
9099
}
91100

92-
const loadGraph = async () => {
93-
setLoading(true)
94-
try {
95-
const response = await fetch(`${apiUrl}/api/repos/${repoId}/dependencies`, {
96-
headers: {
97-
'Authorization': `Bearer ${apiKey}`
98-
}
99-
})
101+
const processGraphData = (data: any) => {
102+
const flowNodes: Node[] = data.nodes.map((node: any) => {
103+
const fileName = node.label || node.id.split('/').pop()
104+
const fullPath = node.id
105+
const importCount = node.import_count || node.imports || 0
100106

101-
const data = await response.json()
102-
103-
const flowNodes: Node[] = data.nodes.map((node: any) => {
104-
const fileName = node.label || node.id.split('/').pop()
105-
const fullPath = node.id
106-
const importCount = node.import_count || node.imports || 0
107-
108-
return {
109-
id: node.id,
110-
type: 'default',
111-
data: {
112-
label: (
113-
<div title={fullPath} style={{ cursor: 'pointer' }}>
114-
<div style={{ fontWeight: 600, fontSize: '11px', marginBottom: '4px' }}>
115-
{fileName}
116-
</div>
117-
{importCount > 0 && (
118-
<div style={{ fontSize: '9px', opacity: 0.8 }}>
119-
{importCount} imports
120-
</div>
121-
)}
107+
return {
108+
id: node.id,
109+
type: 'default',
110+
data: {
111+
label: (
112+
<div title={fullPath} style={{ cursor: 'pointer' }}>
113+
<div style={{ fontWeight: 600, fontSize: '11px', marginBottom: '4px' }}>
114+
{fileName}
122115
</div>
123-
),
124-
language: node.language,
125-
imports: importCount
126-
},
127-
position: { x: 0, y: 0 },
128-
style: {
129-
background: getLanguageColor(node.language),
130-
color: 'white',
131-
border: '2px solid #3b82f6',
132-
borderRadius: '8px',
133-
padding: '8px 12px',
134-
fontSize: '11px',
135-
fontFamily: 'monospace',
136-
width: 180,
137-
height: 60
138-
}
139-
}
140-
})
141-
142-
const flowEdges: Edge[] = data.edges.map((edge: any) => ({
143-
id: `${edge.source}-${edge.target}`,
144-
source: edge.source,
145-
target: edge.target,
146-
animated: false,
147-
style: { stroke: '#4b5563', strokeWidth: 1.5 },
148-
markerEnd: {
149-
type: MarkerType.ArrowClosed,
150-
color: '#4b5563',
116+
{importCount > 0 && (
117+
<div style={{ fontSize: '9px', opacity: 0.8 }}>
118+
{importCount} imports
119+
</div>
120+
)}
121+
</div>
122+
),
123+
language: node.language,
124+
imports: importCount
151125
},
152-
}))
153-
154-
setAllNodes(flowNodes)
155-
setAllEdges(flowEdges)
156-
setMetrics(data.metrics)
157-
158-
const { nodes: layoutedNodes, edges: layoutedEdges } =
159-
getLayoutedElements(flowNodes, flowEdges)
160-
161-
setNodes(layoutedNodes)
162-
setEdges(layoutedEdges)
163-
164-
} catch (error) {
165-
console.error('Error loading graph:', error)
166-
} finally {
167-
setLoading(false)
168-
}
126+
position: { x: 0, y: 0 },
127+
style: {
128+
background: getLanguageColor(node.language),
129+
color: 'white',
130+
border: '2px solid #3b82f6',
131+
borderRadius: '8px',
132+
padding: '8px 12px',
133+
fontSize: '11px',
134+
fontFamily: 'monospace',
135+
width: 180,
136+
height: 60
137+
}
138+
}
139+
})
140+
141+
const flowEdges: Edge[] = data.edges.map((edge: any) => ({
142+
id: `${edge.source}-${edge.target}`,
143+
source: edge.source,
144+
target: edge.target,
145+
animated: false,
146+
style: { stroke: '#4b5563', strokeWidth: 1.5 },
147+
markerEnd: {
148+
type: MarkerType.ArrowClosed,
149+
color: '#4b5563',
150+
},
151+
}))
152+
153+
setAllNodes(flowNodes)
154+
setAllEdges(flowEdges)
155+
setMetrics(data.metrics)
156+
157+
const { nodes: layoutedNodes, edges: layoutedEdges } =
158+
getLayoutedElements(flowNodes, flowEdges)
159+
160+
setNodes(layoutedNodes)
161+
setEdges(layoutedEdges)
169162
}
170163

171164
const handleNodeClick = useCallback((event: any, node: Node) => {

frontend/src/components/RepoList.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import type { Repository } from '../types'
2+
import { RepoGridSkeleton } from './ui/Skeleton'
23

34
interface RepoListProps {
45
repos: Repository[]
56
selectedRepo: string | null
67
onSelect: (repoId: string) => void
8+
loading?: boolean
79
}
810

911
// Status indicator with glow effect
@@ -40,7 +42,11 @@ const StatusIndicator = ({ status }: { status: string }) => {
4042
)
4143
}
4244

43-
export function RepoList({ repos, selectedRepo, onSelect }: RepoListProps) {
45+
export function RepoList({ repos, selectedRepo, onSelect, loading }: RepoListProps) {
46+
if (loading) {
47+
return <RepoGridSkeleton count={3} />
48+
}
49+
4450
if (repos.length === 0) {
4551
return (
4652
<div className="bg-[#111113] border border-white/5 rounded-2xl p-16 text-center">
@@ -57,19 +63,20 @@ export function RepoList({ repos, selectedRepo, onSelect }: RepoListProps) {
5763

5864
return (
5965
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
60-
{repos.map((repo) => {
66+
{repos.map((repo, index) => {
6167
const isSelected = selectedRepo === repo.id
6268

6369
return (
6470
<button
6571
key={repo.id}
6672
onClick={() => onSelect(repo.id)}
6773
className={`group relative text-left rounded-2xl p-5 transition-all duration-300
68-
bg-[#111113] border overflow-hidden
74+
bg-[#111113] border overflow-hidden opacity-0 animate-fade-in focus-ring
6975
${isSelected
7076
? 'border-blue-500/50 shadow-lg shadow-blue-500/10'
7177
: 'border-white/5 hover:border-white/10 hover:bg-[#151518]'
7278
}`}
79+
style={{ animationDelay: `${index * 0.05}s` }}
7380
>
7481
{/* Subtle gradient overlay on hover */}
7582
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/5 via-transparent to-purple-500/5 opacity-0 group-hover:opacity-100 transition-opacity duration-300" />

frontend/src/components/RepoOverview.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { toast } from 'sonner'
33
import { Progress } from '@/components/ui/progress'
44
import type { Repository } from '../types'
55
import { WS_URL } from '../config/api'
6+
import { useInvalidateRepoCache } from '../hooks/useCachedQuery'
67

78
interface RepoOverviewProps {
89
repo: Repository
@@ -23,6 +24,9 @@ export function RepoOverview({ repo, onReindex, apiUrl, apiKey }: RepoOverviewPr
2324
const [progress, setProgress] = useState<IndexProgress | null>(null)
2425
const wsRef = useRef<WebSocket | null>(null)
2526
const completedRef = useRef(false)
27+
28+
// Cache invalidation hook
29+
const invalidateCache = useInvalidateRepoCache()
2630

2731
useEffect(() => {
2832
return () => {
@@ -62,6 +66,8 @@ export function RepoOverview({ repo, onReindex, apiUrl, apiKey }: RepoOverviewPr
6266
toast.success(`Indexing complete! ${data.total_functions} functions indexed.`, { id: 'reindex' })
6367
setIndexing(false)
6468
setProgress(null)
69+
// Invalidate caches after re-index
70+
invalidateCache(repo.id)
6571
onReindex()
6672
} else if (data.type === 'error') {
6773
completedRef.current = true

frontend/src/components/StyleInsights.tsx

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useState } from 'react'
1+
import { useStyleAnalysis } from '../hooks/useCachedQuery'
22

33
interface StyleInsightsProps {
44
repoId: string
@@ -7,27 +7,8 @@ interface StyleInsightsProps {
77
}
88

99
export function StyleInsights({ repoId, apiUrl, apiKey }: StyleInsightsProps) {
10-
const [data, setData] = useState<any>(null)
11-
const [loading, setLoading] = useState(true)
12-
13-
useEffect(() => {
14-
loadStyleData()
15-
}, [repoId])
16-
17-
const loadStyleData = async () => {
18-
setLoading(true)
19-
try {
20-
const response = await fetch(`${apiUrl}/api/repos/${repoId}/style-analysis`, {
21-
headers: { 'Authorization': `Bearer ${apiKey}` }
22-
})
23-
const result = await response.json()
24-
setData(result)
25-
} catch (error) {
26-
console.error('Error loading style data:', error)
27-
} finally {
28-
setLoading(false)
29-
}
30-
}
10+
// Use cached query for style analysis
11+
const { data, isLoading: loading } = useStyleAnalysis({ repoId, apiKey })
3112

3213
if (loading) {
3314
return (

frontend/src/components/dashboard/DashboardHome.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export function DashboardHome() {
2020
const [selectedRepo, setSelectedRepo] = useState<string | null>(null)
2121
const [activeTab, setActiveTab] = useState<RepoTab>('overview')
2222
const [loading, setLoading] = useState(false)
23+
const [reposLoading, setReposLoading] = useState(true)
2324
const [showPerformance, setShowPerformance] = useState(false)
2425

2526
const fetchRepos = async () => {
@@ -33,6 +34,8 @@ export function DashboardHome() {
3334
setRepos(data.repositories || [])
3435
} catch (error) {
3536
console.error('Error fetching repos:', error)
37+
} finally {
38+
setReposLoading(false)
3639
}
3740
}
3841

@@ -150,6 +153,7 @@ export function DashboardHome() {
150153
<RepoList
151154
repos={repos}
152155
selectedRepo={selectedRepo}
156+
loading={reposLoading}
153157
onSelect={(id) => {
154158
setSelectedRepo(id)
155159
setActiveTab('overview')

0 commit comments

Comments
 (0)