Skip to content

Commit 033237e

Browse files
authored
Merge pull request #32 from Queryus/feature/FRT-57
[FRT-57] API 연결 (DB 스키마 패널)
2 parents b0d60ad + 3d325ab commit 033237e

File tree

3 files changed

+128
-51
lines changed

3 files changed

+128
-51
lines changed

src/renderer/src/components/workspace/db-schema-panel/db-schema-panel.tsx

Lines changed: 121 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,45 @@
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'
35
import 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-
*/
4239
const 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
*/
5855
export 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}

src/renderer/src/components/workspace/db-schema-panel/db-schema.types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { LucideIcon } from 'lucide-react'
22

3-
export type SchemaNodeType = 'database' | 'schema' | 'folder' | 'table'
3+
export type SchemaNodeType = 'database' | 'schema' | 'table' | 'column'
44

55
export interface SchemaNode {
66
id: string
@@ -19,6 +19,6 @@ export interface SchemaTreeItemProps {
1919
export interface SchemaIconMap {
2020
database: LucideIcon
2121
schema: LucideIcon
22-
folder: LucideIcon
2322
table: LucideIcon
23+
column: LucideIcon
2424
}

src/renderer/src/components/workspace/db-schema-panel/schema-tree-item.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ import { cn } from '@/lib/utils'
55
const ICONS: SchemaIconMap = {
66
database: TableProperties,
77
schema: ListTree,
8-
folder: FolderOpen,
9-
table: TableProperties
8+
table: FolderOpen,
9+
column: TableProperties
1010
}
1111

1212
const ICON_COLORS: Record<string, string> = {
1313
database: 'stroke-[#808080]',
1414
schema: 'stroke-[#808080]',
15-
folder: 'stroke-[#73B2FF]',
16-
table: 'stroke-[#9F73FF]'
15+
table: 'stroke-[#73B2FF]',
16+
column: 'stroke-[#9F73FF]'
1717
}
1818

1919
/**
@@ -47,7 +47,7 @@ export default function SchemaTreeItem({
4747
<div
4848
data-state="Default"
4949
className="self-stretch pr-1 py-1 rounded inline-flex justify-start items-center gap-2 cursor-pointer"
50-
style={{ paddingLeft: `${level * 1.5 + 0.25}rem` }}
50+
style={{ paddingLeft: `${level * 20 + 4}px` }}
5151
onClick={handleToggle}
5252
>
5353
<div className="size-3 flex items-center justify-center">

0 commit comments

Comments
 (0)