3d에서 테이블 가져올 때 테이블, 컬럼 코멘트 같이 가져오기

This commit is contained in:
dohyeons 2025-11-21 15:44:52 +09:00
parent 6ccaa85413
commit dd913d3ecf
3 changed files with 137 additions and 51 deletions

View File

@ -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();
@ -112,19 +118,26 @@ export class MariaDBConnector implements DatabaseConnector {
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[];

View File

@ -95,7 +95,7 @@ export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: Digi
// 동적 계층 구조 설정
const [hierarchyConfig, setHierarchyConfig] = useState<HierarchyConfig | null>(null);
const [availableTables, setAvailableTables] = useState<string[]>([]);
const [availableTables, setAvailableTables] = useState<Array<{ table_name: string; description?: string }>>([]);
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) {

View File

@ -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<void>;
onLoadColumns: (tableName: string) => Promise<string[]>;
onLoadColumns: (tableName: string) => Promise<ColumnInfo[]>;
}
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({
</SelectTrigger>
<SelectContent>
{availableTables.map((table) => (
<SelectItem key={table} value={table} className="text-[10px]">
{table}
<SelectItem key={table.table_name} value={table.table_name} className="text-[10px]">
<div className="flex flex-col">
<span>{table.table_name}</span>
{table.description && (
<span className="text-[9px] text-muted-foreground">{table.description}</span>
)}
</div>
</SelectItem>
))}
</SelectContent>
@ -209,8 +225,13 @@ export default function HierarchyConfigPanel({
</SelectTrigger>
<SelectContent>
{columnsCache[localConfig.warehouse.tableName].map((col) => (
<SelectItem key={col} value={col} className="text-[10px]">
{col}
<SelectItem key={col.column_name} value={col.column_name} className="text-[10px]">
<div className="flex flex-col">
<span>{col.column_name}</span>
{col.description && (
<span className="text-[8px] text-muted-foreground">{col.description}</span>
)}
</div>
</SelectItem>
))}
</SelectContent>
@ -228,8 +249,13 @@ export default function HierarchyConfigPanel({
</SelectTrigger>
<SelectContent>
{columnsCache[localConfig.warehouse.tableName].map((col) => (
<SelectItem key={col} value={col} className="text-[10px]">
{col}
<SelectItem key={col.column_name} value={col.column_name} className="text-[10px]">
<div className="flex flex-col">
<span>{col.column_name}</span>
{col.description && (
<span className="text-[8px] text-muted-foreground">{col.description}</span>
)}
</div>
</SelectItem>
))}
</SelectContent>
@ -287,8 +313,13 @@ export default function HierarchyConfigPanel({
</SelectTrigger>
<SelectContent>
{availableTables.map((table) => (
<SelectItem key={table} value={table} className="text-xs">
{table}
<SelectItem key={table.table_name} value={table.table_name} className="text-xs">
<div className="flex flex-col">
<span>{table.table_name}</span>
{table.description && (
<span className="text-[10px] text-muted-foreground">{table.description}</span>
)}
</div>
</SelectItem>
))}
</SelectContent>
@ -308,8 +339,13 @@ export default function HierarchyConfigPanel({
</SelectTrigger>
<SelectContent>
{columnsCache[level.tableName].map((col) => (
<SelectItem key={col} value={col} className="text-xs">
{col}
<SelectItem key={col.column_name} value={col.column_name} className="text-xs">
<div className="flex flex-col">
<span>{col.column_name}</span>
{col.description && (
<span className="text-[9px] text-muted-foreground">{col.description}</span>
)}
</div>
</SelectItem>
))}
</SelectContent>
@ -327,8 +363,13 @@ export default function HierarchyConfigPanel({
</SelectTrigger>
<SelectContent>
{columnsCache[level.tableName].map((col) => (
<SelectItem key={col} value={col} className="text-xs">
{col}
<SelectItem key={col.column_name} value={col.column_name} className="text-xs">
<div className="flex flex-col">
<span>{col.column_name}</span>
{col.description && (
<span className="text-[9px] text-muted-foreground">{col.description}</span>
)}
</div>
</SelectItem>
))}
</SelectContent>
@ -409,8 +450,13 @@ export default function HierarchyConfigPanel({
</SelectTrigger>
<SelectContent>
{availableTables.map((table) => (
<SelectItem key={table} value={table} className="text-xs">
{table}
<SelectItem key={table.table_name} value={table.table_name} className="text-xs">
<div className="flex flex-col">
<span>{table.table_name}</span>
{table.description && (
<span className="text-[10px] text-muted-foreground">{table.description}</span>
)}
</div>
</SelectItem>
))}
</SelectContent>
@ -430,8 +476,13 @@ export default function HierarchyConfigPanel({
</SelectTrigger>
<SelectContent>
{columnsCache[localConfig.material.tableName].map((col) => (
<SelectItem key={col} value={col} className="text-xs">
{col}
<SelectItem key={col.column_name} value={col.column_name} className="text-xs">
<div className="flex flex-col">
<span>{col.column_name}</span>
{col.description && (
<span className="text-[9px] text-muted-foreground">{col.description}</span>
)}
</div>
</SelectItem>
))}
</SelectContent>
@ -449,8 +500,13 @@ export default function HierarchyConfigPanel({
</SelectTrigger>
<SelectContent>
{columnsCache[localConfig.material.tableName].map((col) => (
<SelectItem key={col} value={col} className="text-xs">
{col}
<SelectItem key={col.column_name} value={col.column_name} className="text-xs">
<div className="flex flex-col">
<span>{col.column_name}</span>
{col.description && (
<span className="text-[9px] text-muted-foreground">{col.description}</span>
)}
</div>
</SelectItem>
))}
</SelectContent>
@ -469,8 +525,13 @@ export default function HierarchyConfigPanel({
<SelectContent>
<SelectItem value="__none__"></SelectItem>
{columnsCache[localConfig.material.tableName].map((col) => (
<SelectItem key={col} value={col} className="text-xs">
{col}
<SelectItem key={col.column_name} value={col.column_name} className="text-xs">
<div className="flex flex-col">
<span>{col.column_name}</span>
{col.description && (
<span className="text-[9px] text-muted-foreground">{col.description}</span>
)}
</div>
</SelectItem>
))}
</SelectContent>
@ -489,8 +550,13 @@ export default function HierarchyConfigPanel({
<SelectContent>
<SelectItem value="__none__"></SelectItem>
{columnsCache[localConfig.material.tableName].map((col) => (
<SelectItem key={col} value={col} className="text-xs">
{col}
<SelectItem key={col.column_name} value={col.column_name} className="text-xs">
<div className="flex flex-col">
<span>{col.column_name}</span>
{col.description && (
<span className="text-[9px] text-muted-foreground">{col.description}</span>
)}
</div>
</SelectItem>
))}
</SelectContent>
@ -507,30 +573,35 @@ export default function HierarchyConfigPanel({
</p>
<div className="max-h-60 space-y-2 overflow-y-auto rounded border p-2">
{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 (
<div key={col} className="flex items-center gap-2">
<div key={col.column_name} className="flex items-center gap-2">
<input
type="checkbox"
checked={isSelected}
onChange={(e) => {
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"
/>
<span className="w-20 shrink-0 text-[10px]">{col}</span>
<div className="flex w-24 shrink-0 flex-col">
<span className="text-[10px]">{col.column_name}</span>
{col.description && (
<span className="text-[8px] text-muted-foreground">{col.description}</span>
)}
</div>
{isSelected && (
<Input
value={displayItem?.label ?? ""}
onChange={(e) => {
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);
}}