1- import { useState } from 'react'
2- import { SchemaNode } from './db-schema.types'
1+ import { useEffect , useState } from 'react'
2+ import { toast } from 'sonner'
3+ import { api } from '@renderer/utils/api'
4+ import { SchemaNode , SchemaNodeType } from './db-schema.types'
35import SchemaTreeItem from './schema-tree-item'
46
5- // TODO: 추후 API 연동을 통해 실제 DB 스키마 데이터로 교체
6- const MOCK_SCHEMA_DATA : SchemaNode [ ] = [
7- {
8- id : 'db-1' ,
9- name : 'DEMO_DATA' ,
10- type : 'database' ,
11- children : [
12- {
13- id : 'schema-1' ,
14- name : 'INFORMATION_SCHEMA' ,
15- type : 'schema' ,
16- children : [
17- {
18- id : 'folder-1' ,
19- name : 'tables' ,
20- type : 'folder' ,
21- children : [
22- { id : 'table-1' , name : 'ITEM' , type : 'table' } ,
23- { id : 'table-2' , name : 'CUSTOMER' , type : 'table' } ,
24- { id : 'table-3' , name : 'ORDERS' , type : 'table' } ,
25- { id : 'table-4' , name : 'PRODUCT' , type : 'table' } ,
26- { id : 'table-5' , name : 'SALES' , type : 'table' } ,
27- { id : 'table-6' , name : 'EMPLOYEE' , type : 'table' }
28- ]
29- }
30- ]
31- }
32- ]
33- }
34- ]
7+ // API 응답 타입을 위한 인터페이스 정의
8+ interface ApiResponse < T > {
9+ code : string
10+ message : string
11+ data : T
12+ }
13+
14+ interface DbProfile {
15+ id : string
16+ view_name ?: string
17+ name ?: string
18+ }
19+
20+ interface ColumnInfo {
21+ name : string
22+ }
23+
24+ interface TableInfo {
25+ name : string
26+ columns : ColumnInfo [ ]
27+ }
28+
29+ interface SchemaInfo {
30+ schema_name : string
31+ tables : TableInfo [ ]
32+ }
33+
34+ interface DbInfo {
35+ db_name : string
36+ schemas : SchemaInfo [ ]
37+ }
3538
36- /**
37- * @author nahyeongjin1
38- * @summary 재귀적으로 모든 노드의 ID를 수집하여 초기 펼침 상태를 설정하는 함수
39- * @param nodes 스키마 노드 배열
40- * @returns 모든 노드 ID를 키로, true를 값으로 갖는 객체
41- */
4239const initializeExpandedState = ( nodes : SchemaNode [ ] ) : Record < string , boolean > => {
4340 let state : Record < string , boolean > = { }
4441 nodes . forEach ( ( node ) => {
45- state [ node . id ] = true // 기본적으로 모든 노드를 펼침 상태로 설정
42+ state [ node . id ] = true
4643 if ( node . children ) {
4744 state = { ...state , ...initializeExpandedState ( node . children ) }
4845 }
@@ -56,9 +53,81 @@ const initializeExpandedState = (nodes: SchemaNode[]): Record<string, boolean> =
5653 * @returns JSX.Element
5754 */
5855export default function DbSchemaPanel ( ) : React . JSX . Element {
59- const [ expandedNodes , setExpandedNodes ] = useState < Record < string , boolean > > ( ( ) =>
60- initializeExpandedState ( MOCK_SCHEMA_DATA )
61- )
56+ const [ schemaData , setSchemaData ] = useState < SchemaNode [ ] > ( [ ] )
57+ const [ isLoading , setIsLoading ] = useState ( true )
58+ const [ expandedNodes , setExpandedNodes ] = useState < Record < string , boolean > > ( { } )
59+
60+ useEffect ( ( ) => {
61+ const fetchSchemaData = async ( ) : Promise < void > => {
62+ try {
63+ const profilesRes = ( await api . get ( '/api/user/db/find/all' ) ) as unknown as ApiResponse <
64+ DbProfile [ ]
65+ >
66+ const profiles = profilesRes . data
67+
68+ if ( ! profiles || ! Array . isArray ( profiles ) ) {
69+ throw new Error ( 'Invalid profile data received' )
70+ }
71+
72+ const allSchemasPromises = profiles . map ( ( profile ) =>
73+ api . get ( `/api/user/db/find/hierarchical-schema/${ profile . id } ` )
74+ )
75+ const allSchemasRes = ( await Promise . all ( allSchemasPromises ) ) as unknown as ApiResponse <
76+ DbInfo [ ]
77+ > [ ]
78+
79+ const transformedData = profiles
80+ . map ( ( profile , index ) => {
81+ const response = allSchemasRes [ index ]
82+
83+ if (
84+ response . code !== '2000' ||
85+ ! response . data ||
86+ ! Array . isArray ( response . data ) ||
87+ response . data . length === 0
88+ ) {
89+ console . warn (
90+ `Could not fetch schema for ${ profile . view_name } : ${ response . message || 'Empty data' } `
91+ )
92+ return null
93+ }
94+
95+ const dbInfo = response . data [ 0 ]
96+ return {
97+ id : profile . id ,
98+ name : profile . view_name || dbInfo . db_name || profile . id ,
99+ type : 'database' as SchemaNodeType ,
100+ children : ( dbInfo . schemas || [ ] ) . map ( ( schema ) => ( {
101+ id : `${ profile . id } -${ schema . schema_name } ` ,
102+ name : schema . schema_name ,
103+ type : 'schema' as SchemaNodeType ,
104+ children : ( schema . tables || [ ] ) . map ( ( table ) => ( {
105+ id : `${ profile . id } -${ schema . schema_name } -${ table . name } ` ,
106+ name : table . name ,
107+ type : 'table' as SchemaNodeType ,
108+ children : ( table . columns || [ ] ) . map ( ( column ) => ( {
109+ id : `${ profile . id } -${ schema . schema_name } -${ table . name } -${ column . name } ` ,
110+ name : column . name ,
111+ type : 'column' as SchemaNodeType
112+ } ) )
113+ } ) )
114+ } ) )
115+ }
116+ } )
117+ . filter ( Boolean ) as SchemaNode [ ]
118+
119+ setSchemaData ( transformedData )
120+ setExpandedNodes ( initializeExpandedState ( transformedData ) )
121+ } catch ( error ) {
122+ toast . error ( '데이터베이스 스키마 정보를 불러오는 데 실패했습니다.' )
123+ console . error ( error )
124+ } finally {
125+ setIsLoading ( false )
126+ }
127+ }
128+
129+ fetchSchemaData ( )
130+ } , [ ] )
62131
63132 const handleToggle = ( nodeId : string ) : void => {
64133 setExpandedNodes ( ( prev ) => ( {
@@ -67,9 +136,17 @@ export default function DbSchemaPanel(): React.JSX.Element {
67136 } ) )
68137 }
69138
139+ if ( isLoading ) {
140+ return (
141+ < div className = "w-56 h-full p-3 bg-neutral-800 flex items-center justify-center" >
142+ < p className = "text-neutral-400 text-sm" > 로딩 중...</ p >
143+ </ div >
144+ )
145+ }
146+
70147 return (
71- < div className = "w-56 h-full p-3 bg-neutral-800 outline-1 outline-offset-[-1px] outline-neutral-700 flex-col justify-start items-start inline-flex" >
72- { MOCK_SCHEMA_DATA . map ( ( node ) => (
148+ < div className = "w-56 h-full p-3 bg-neutral-800 outline-1 outline-offset-[-1px] outline-neutral-700 flex-col justify-start items-start inline-flex overflow-y-auto " >
149+ { schemaData . map ( ( node ) => (
73150 < SchemaTreeItem
74151 key = { node . id }
75152 node = { node }
0 commit comments