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

@@ -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);
}}