From dd913d3ecfb595891727fd5d7e1ebaa8a15b0170 Mon Sep 17 00:00:00 2001
From: dohyeons
Date: Fri, 21 Nov 2025 15:44:52 +0900
Subject: [PATCH] =?UTF-8?q?3d=EC=97=90=EC=84=9C=20=ED=85=8C=EC=9D=B4?=
=?UTF-8?q?=EB=B8=94=20=EA=B0=80=EC=A0=B8=EC=98=AC=20=EB=95=8C=20=ED=85=8C?=
=?UTF-8?q?=EC=9D=B4=EB=B8=94,=20=EC=BB=AC=EB=9F=BC=20=EC=BD=94=EB=A9=98?=
=?UTF-8?q?=ED=8A=B8=20=EA=B0=99=EC=9D=B4=20=EA=B0=80=EC=A0=B8=EC=98=A4?=
=?UTF-8?q?=EA=B8=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend-node/src/database/MariaDBConnector.ts | 37 +++--
.../widgets/yard-3d/DigitalTwinEditor.tsx | 18 +--
.../widgets/yard-3d/HierarchyConfigPanel.tsx | 133 ++++++++++++++----
3 files changed, 137 insertions(+), 51 deletions(-)
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);
}}