diff --git a/backend-node/src/database/MariaDBConnector.ts b/backend-node/src/database/MariaDBConnector.ts index 3f469330..d104b9e3 100644 --- a/backend-node/src/database/MariaDBConnector.ts +++ b/backend-node/src/database/MariaDBConnector.ts @@ -1,7 +1,11 @@ -import { DatabaseConnector, ConnectionConfig, QueryResult } from '../interfaces/DatabaseConnector'; -import { ConnectionTestResult, TableInfo } from '../types/externalDbTypes'; +import { + DatabaseConnector, + ConnectionConfig, + QueryResult, +} from "../interfaces/DatabaseConnector"; +import { ConnectionTestResult, TableInfo } from "../types/externalDbTypes"; // @ts-ignore -import * as mysql from 'mysql2/promise'; +import * as mysql from "mysql2/promise"; export class MariaDBConnector implements DatabaseConnector { private connection: mysql.Connection | null = null; @@ -20,7 +24,7 @@ export class MariaDBConnector implements DatabaseConnector { password: this.config.password, database: this.config.database, connectTimeout: this.config.connectionTimeoutMillis, - ssl: typeof this.config.ssl === 'boolean' ? undefined : this.config.ssl, + ssl: typeof this.config.ssl === "boolean" ? undefined : this.config.ssl, }); } } @@ -36,7 +40,9 @@ export class MariaDBConnector implements DatabaseConnector { const startTime = Date.now(); try { await this.connect(); - const [rows] = await this.connection!.query("SELECT VERSION() as version"); + const [rows] = await this.connection!.query( + "SELECT VERSION() as version" + ); const version = (rows as any[])[0]?.version || "Unknown"; const responseTime = Date.now() - startTime; await this.disconnect(); @@ -111,21 +117,28 @@ export class MariaDBConnector implements DatabaseConnector { console.log(`[MariaDBConnector] getColumns 호출: tableName=${tableName}`); await this.connect(); console.log(`[MariaDBConnector] 연결 완료, 쿼리 실행 시작`); - - const [rows] = await this.connection!.query(` + + const [rows] = await this.connection!.query( + ` SELECT COLUMN_NAME as column_name, DATA_TYPE as data_type, IS_NULLABLE as is_nullable, - COLUMN_DEFAULT as column_default + COLUMN_DEFAULT as column_default, + COLUMN_COMMENT as description FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION; - `, [tableName]); - + `, + [tableName] + ); + console.log(`[MariaDBConnector] 쿼리 결과:`, rows); - console.log(`[MariaDBConnector] 결과 개수:`, Array.isArray(rows) ? rows.length : 'not array'); - + console.log( + `[MariaDBConnector] 결과 개수:`, + Array.isArray(rows) ? rows.length : "not array" + ); + await this.disconnect(); return rows as any[]; } catch (error: any) { diff --git a/frontend/components/admin/dashboard/widgets/yard-3d/DigitalTwinEditor.tsx b/frontend/components/admin/dashboard/widgets/yard-3d/DigitalTwinEditor.tsx index ac9aac19..5443d150 100644 --- a/frontend/components/admin/dashboard/widgets/yard-3d/DigitalTwinEditor.tsx +++ b/frontend/components/admin/dashboard/widgets/yard-3d/DigitalTwinEditor.tsx @@ -95,7 +95,7 @@ export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: Digi // 동적 계층 구조 설정 const [hierarchyConfig, setHierarchyConfig] = useState(null); - const [availableTables, setAvailableTables] = useState([]); + const [availableTables, setAvailableTables] = useState>([]); const [loadingTables, setLoadingTables] = useState(false); // 레거시: 테이블 매핑 (구 Area/Location 방식 호환용) @@ -210,9 +210,9 @@ export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: Digi const { getTables } = await import("@/lib/api/digitalTwin"); const response = await getTables(selectedDbConnection); if (response.success && response.data) { - const tableNames = response.data.map((t) => t.table_name); - setAvailableTables(tableNames); - console.log("📋 테이블 목록:", tableNames); + // 테이블 정보 전체 저장 (이름 + 설명) + setAvailableTables(response.data); + console.log("📋 테이블 목록:", response.data); } } catch (error) { console.error("테이블 목록 조회 실패:", error); @@ -1266,10 +1266,12 @@ export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: Digi try { const response = await ExternalDbConnectionAPI.getTableColumns(selectedDbConnection, tableName); if (response.success && response.data) { - // 객체 배열을 문자열 배열로 변환 - return response.data.map((col: any) => - typeof col === "string" ? col : col.column_name || col.COLUMN_NAME || String(col), - ); + // 컬럼 정보 객체 배열로 반환 (이름 + 설명) + return response.data.map((col: any) => ({ + column_name: typeof col === "string" ? col : col.column_name || col.COLUMN_NAME || String(col), + data_type: col.data_type || col.DATA_TYPE, + description: col.description || col.COLUMN_COMMENT || undefined, + })); } return []; } catch (error) { diff --git a/frontend/components/admin/dashboard/widgets/yard-3d/HierarchyConfigPanel.tsx b/frontend/components/admin/dashboard/widgets/yard-3d/HierarchyConfigPanel.tsx index 4def424c..00473450 100644 --- a/frontend/components/admin/dashboard/widgets/yard-3d/HierarchyConfigPanel.tsx +++ b/frontend/components/admin/dashboard/widgets/yard-3d/HierarchyConfigPanel.tsx @@ -40,13 +40,24 @@ export interface HierarchyConfig { }; } +interface TableInfo { + table_name: string; + description?: string; +} + +interface ColumnInfo { + column_name: string; + data_type?: string; + description?: string; +} + interface HierarchyConfigPanelProps { externalDbConnectionId: number | null; hierarchyConfig: HierarchyConfig | null; onHierarchyConfigChange: (config: HierarchyConfig) => void; - availableTables: string[]; + availableTables: TableInfo[]; onLoadTables: () => Promise; - onLoadColumns: (tableName: string) => Promise; + onLoadColumns: (tableName: string) => Promise; } export default function HierarchyConfigPanel({ @@ -65,7 +76,7 @@ export default function HierarchyConfigPanel({ ); const [loadingColumns, setLoadingColumns] = useState(false); - const [columnsCache, setColumnsCache] = useState<{ [tableName: string]: string[] }>({}); + const [columnsCache, setColumnsCache] = useState<{ [tableName: string]: ColumnInfo[] }>({}); // 외부에서 변경된 경우 동기화 useEffect(() => { @@ -187,8 +198,13 @@ export default function HierarchyConfigPanel({ {availableTables.map((table) => ( - - {table} + +
+ {table.table_name} + {table.description && ( + {table.description} + )} +
))}
@@ -209,8 +225,13 @@ export default function HierarchyConfigPanel({ {columnsCache[localConfig.warehouse.tableName].map((col) => ( - - {col} + +
+ {col.column_name} + {col.description && ( + {col.description} + )} +
))}
@@ -228,8 +249,13 @@ export default function HierarchyConfigPanel({ {columnsCache[localConfig.warehouse.tableName].map((col) => ( - - {col} + +
+ {col.column_name} + {col.description && ( + {col.description} + )} +
))}
@@ -287,8 +313,13 @@ export default function HierarchyConfigPanel({ {availableTables.map((table) => ( - - {table} + +
+ {table.table_name} + {table.description && ( + {table.description} + )} +
))}
@@ -308,8 +339,13 @@ export default function HierarchyConfigPanel({ {columnsCache[level.tableName].map((col) => ( - - {col} + +
+ {col.column_name} + {col.description && ( + {col.description} + )} +
))}
@@ -327,8 +363,13 @@ export default function HierarchyConfigPanel({ {columnsCache[level.tableName].map((col) => ( - - {col} + +
+ {col.column_name} + {col.description && ( + {col.description} + )} +
))}
@@ -409,8 +450,13 @@ export default function HierarchyConfigPanel({ {availableTables.map((table) => ( - - {table} + +
+ {table.table_name} + {table.description && ( + {table.description} + )} +
))}
@@ -430,8 +476,13 @@ export default function HierarchyConfigPanel({ {columnsCache[localConfig.material.tableName].map((col) => ( - - {col} + +
+ {col.column_name} + {col.description && ( + {col.description} + )} +
))}
@@ -449,8 +500,13 @@ export default function HierarchyConfigPanel({ {columnsCache[localConfig.material.tableName].map((col) => ( - - {col} + +
+ {col.column_name} + {col.description && ( + {col.description} + )} +
))}
@@ -469,8 +525,13 @@ export default function HierarchyConfigPanel({ 없음 {columnsCache[localConfig.material.tableName].map((col) => ( - - {col} + +
+ {col.column_name} + {col.description && ( + {col.description} + )} +
))}
@@ -489,8 +550,13 @@ export default function HierarchyConfigPanel({ 없음 {columnsCache[localConfig.material.tableName].map((col) => ( - - {col} + +
+ {col.column_name} + {col.description && ( + {col.description} + )} +
))}
@@ -507,30 +573,35 @@ export default function HierarchyConfigPanel({

{columnsCache[localConfig.material.tableName].map((col) => { - const displayItem = localConfig.material?.displayColumns?.find((d) => d.column === col); + const displayItem = localConfig.material?.displayColumns?.find((d) => d.column === col.column_name); const isSelected = !!displayItem; return ( -
+
{ const currentDisplay = localConfig.material?.displayColumns || []; const newDisplay = e.target.checked - ? [...currentDisplay, { column: col, label: col }] - : currentDisplay.filter((d) => d.column !== col); + ? [...currentDisplay, { column: col.column_name, label: col.column_name }] + : currentDisplay.filter((d) => d.column !== col.column_name); handleMaterialChange("displayColumns", newDisplay); }} className="h-3 w-3 shrink-0" /> - {col} +
+ {col.column_name} + {col.description && ( + {col.description} + )} +
{isSelected && ( { const currentDisplay = localConfig.material?.displayColumns || []; const newDisplay = currentDisplay.map((d) => - d.column === col ? { ...d, label: e.target.value } : d, + d.column === col.column_name ? { ...d, label: e.target.value } : d, ); handleMaterialChange("displayColumns", newDisplay); }}