@@ -14,6 +14,29 @@ import { useApi } from '@/hooks/useApi';
1414import { api } from '@/lib/api' ;
1515import type { StatsResponse } from '@/types/api' ;
1616
17+ /** Flatten a value into a Record<string, number>. Handles nested objects, numbers, arrays. */
18+ function flattenToRecord ( val : unknown ) : Record < string , number > {
19+ if ( ! val || typeof val !== 'object' ) return { } ;
20+ const result : Record < string , number > = { } ;
21+ for ( const [ k , v ] of Object . entries ( val as Record < string , unknown > ) ) {
22+ if ( typeof v === 'number' ) {
23+ result [ k ] = v ;
24+ } else if ( typeof v === 'object' && v !== null && ! Array . isArray ( v ) ) {
25+ // Nested object — flatten one level
26+ for ( const [ k2 , v2 ] of Object . entries ( v as Record < string , unknown > ) ) {
27+ if ( typeof v2 === 'number' ) {
28+ result [ `${ k } /${ k2 } ` ] = v2 ;
29+ }
30+ }
31+ }
32+ }
33+ return result ;
34+ }
35+
36+ function sumValues ( rec : Record < string , number > ) : number {
37+ return Object . values ( rec ) . reduce ( ( a , b ) => a + b , 0 ) ;
38+ }
39+
1740interface StatCardProps {
1841 title : string ;
1942 value : number | string ;
@@ -25,21 +48,24 @@ interface StatCardProps {
2548function StatCard ( { title, value, icon, detail, detailTitle } : StatCardProps ) {
2649 const [ open , setOpen ] = useState ( false ) ;
2750
28- const tableData = detail
29- ? Object . entries ( detail )
51+ // Only allow click if detail has entries and total > 0
52+ const hasDetail = detail && Object . keys ( detail ) . length > 0 && sumValues ( detail ) > 0 ;
53+
54+ const tableData = hasDetail
55+ ? Object . entries ( detail ! )
3056 . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] )
3157 . map ( ( [ name , count ] ) => ( { key : name , name, count } ) )
3258 : [ ] ;
3359
3460 return (
3561 < >
3662 < Card
37- hoverable = { ! ! detail }
38- onClick = { ( ) => detail && setOpen ( true ) }
39- style = { { cursor : detail ? 'pointer' : 'default' } }
63+ hoverable = { ! ! hasDetail }
64+ onClick = { ( ) => hasDetail && setOpen ( true ) }
65+ style = { { cursor : hasDetail ? 'pointer' : 'default' } }
4066 >
4167 < Statistic title = { title } value = { value } prefix = { icon } />
42- { detail && (
68+ { hasDetail && (
4369 < Typography . Text type = "secondary" style = { { fontSize : 12 } } >
4470 Click for breakdown
4571 </ Typography . Text >
@@ -76,9 +102,9 @@ function isComputedStats(s: StatsResponse): s is StatsResponse & {
76102 graph : { nodes : number ; edges : number ; files : number } ;
77103 languages : Record < string , number > ;
78104 frameworks : Record < string , number > ;
79- connections ?: { rest ?: Record < string , number > } ;
80- auth ?: Record < string , number > ;
81- architecture ?: Record < string , number > ;
105+ connections ?: unknown ;
106+ auth ?: unknown ;
107+ architecture ?: unknown ;
82108} {
83109 return 'graph' in s ;
84110}
@@ -95,14 +121,13 @@ export default function Dashboard() {
95121 const nodeCount = computed ?. graph ?. nodes ?? queryStats ?. node_count ?? 0 ;
96122 const edgeCount = computed ?. graph ?. edges ?? queryStats ?. edge_count ?? 0 ;
97123 const fileCount = computed ?. graph ?. files ?? 0 ;
98- const languages = computed ?. languages ?? { } ;
124+ const languages = flattenToRecord ( computed ?. languages ) ;
99125 const langCount = Object . keys ( languages ) . length ;
100- const frameworks = computed ?. frameworks ?? { } ;
101- const connections = computed ?. connections ?. rest ?? { } ;
102- const auth = computed ?. auth ?? { } ;
103- const architecture = computed ?. architecture ?? { } ;
126+ const frameworks = flattenToRecord ( computed ?. frameworks ) ;
127+ const connections = flattenToRecord ( computed ?. connections ) ;
128+ const auth = flattenToRecord ( computed ?. auth ) ;
129+ const architecture = flattenToRecord ( computed ?. architecture ) ;
104130
105- // Build node kind breakdown from kinds API
106131 const nodeKindBreakdown : Record < string , number > = { } ;
107132 if ( kinds ?. kinds ) {
108133 for ( const k of kinds . kinds ) {
@@ -167,7 +192,7 @@ export default function Dashboard() {
167192 < Col xs = { 12 } sm = { 8 } md = { 6 } >
168193 < StatCard
169194 title = "REST Endpoints"
170- value = { Object . values ( connections ) . reduce ( ( a , b ) => a + b , 0 ) }
195+ value = { sumValues ( connections ) }
171196 icon = { < ApiOutlined /> }
172197 detail = { connections }
173198 detailTitle = "REST Endpoints by Method"
@@ -176,7 +201,7 @@ export default function Dashboard() {
176201 < Col xs = { 12 } sm = { 8 } md = { 6 } >
177202 < StatCard
178203 title = "Auth Guards"
179- value = { Object . values ( auth ) . reduce ( ( a , b ) => a + b , 0 ) }
204+ value = { sumValues ( auth ) }
180205 icon = { < SafetyOutlined /> }
181206 detail = { auth }
182207 detailTitle = "Auth Patterns"
@@ -193,7 +218,6 @@ export default function Dashboard() {
193218 </ Col >
194219 </ Row >
195220
196- { /* Frameworks as tags for quick glance */ }
197221 { Object . keys ( frameworks ) . length > 0 && (
198222 < Card title = "Detected Frameworks" style = { { marginTop : 16 } } size = "small" >
199223 < div style = { { display : 'flex' , flexWrap : 'wrap' , gap : 8 } } >
0 commit comments