3d에서 테이블 가져올 때 테이블, 컬럼 코멘트 같이 가져오기
This commit is contained in:
parent
6ccaa85413
commit
dd913d3ecf
|
|
@ -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[];
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}}
|
||||
|
|
|
|||
Loading…
Reference in New Issue