"use client"; import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Loader2, Plus, Trash2, GripVertical } from "lucide-react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; // 계층 레벨 설정 인터페이스 export interface HierarchyLevel { level: number; name: string; tableName: string; keyColumn: string; nameColumn: string; parentKeyColumn: string; typeColumn?: string; objectTypes: string[]; } // 전체 계층 구조 설정 export interface HierarchyConfig { warehouseKey: string; // 이 레이아웃이 속한 창고 키 (예: "DY99") warehouse?: { tableName: string; // 창고 테이블명 (예: "MWARMA") keyColumn: string; nameColumn: string; }; levels: HierarchyLevel[]; material?: { tableName: string; keyColumn: string; locationKeyColumn: string; layerColumn?: string; quantityColumn?: string; displayColumns?: Array<{ column: string; label: string }>; // 우측 패널에 표시할 컬럼들 (컬럼명 + 표시명) }; } interface TableInfo { table_name: string; description?: string; } interface ColumnInfo { column_name: string; data_type?: string; description?: string; // 백엔드에서 내려주는 Primary Key 플래그 ("YES"/"NO" 또는 boolean) is_primary_key?: string | boolean; } interface HierarchyConfigPanelProps { externalDbConnectionId: number | null; hierarchyConfig: HierarchyConfig | null; onHierarchyConfigChange: (config: HierarchyConfig) => void; availableTables: TableInfo[]; onLoadTables: () => Promise; onLoadColumns: (tableName: string) => Promise; } export default function HierarchyConfigPanel({ externalDbConnectionId, hierarchyConfig, onHierarchyConfigChange, availableTables, onLoadTables, onLoadColumns, }: HierarchyConfigPanelProps) { const [localConfig, setLocalConfig] = useState( hierarchyConfig || { warehouseKey: "", levels: [], }, ); const [loadingColumns, setLoadingColumns] = useState(false); const [columnsCache, setColumnsCache] = useState<{ [tableName: string]: ColumnInfo[] }>({}); // 동일한 column_name 이 여러 번 내려오는 경우(조인 중복 등) 제거 const normalizeColumns = (columns: ColumnInfo[]): ColumnInfo[] => { const map = new Map(); for (const col of columns) { const key = col.column_name; if (!map.has(key)) { map.set(key, col); } } return Array.from(map.values()); }; // 외부에서 변경된 경우 동기화 및 컬럼 자동 로드 // 외부에서 변경된 경우 동기화 및 컬럼 자동 로드 useEffect(() => { if (hierarchyConfig) { setLocalConfig(hierarchyConfig); // 저장된 설정의 테이블들에 대한 컬럼 자동 로드 const loadSavedColumns = async () => { const tablesToLoad: string[] = []; // 창고 테이블 if (hierarchyConfig.warehouse?.tableName) { tablesToLoad.push(hierarchyConfig.warehouse.tableName); } // 계층 레벨 테이블들 hierarchyConfig.levels?.forEach((level) => { if (level.tableName) { tablesToLoad.push(level.tableName); } }); // 자재 테이블 if (hierarchyConfig.material?.tableName) { tablesToLoad.push(hierarchyConfig.material.tableName); } // 중복 제거 후 로드 const uniqueTables = [...new Set(tablesToLoad)]; for (const tableName of uniqueTables) { if (!columnsCache[tableName]) { try { const columns = await onLoadColumns(tableName); const normalized = normalizeColumns(columns); setColumnsCache((prev) => ({ ...prev, [tableName]: normalized })); } catch (error) { console.error(`컬럼 로드 실패 (${tableName}):`, error); } } } }; if (externalDbConnectionId) { loadSavedColumns(); } } }, [hierarchyConfig, externalDbConnectionId]); // 지정된 컬럼이 Primary Key 인지 여부 const isPrimaryKey = (col: ColumnInfo): boolean => { if (col.is_primary_key === true) return true; if (typeof col.is_primary_key === "string") { const v = col.is_primary_key.toUpperCase(); return v === "YES" || v === "Y" || v === "TRUE" || v === "PK"; } return false; }; // 테이블 선택 시 컬럼 로드 + PK 기반 기본값 설정 const handleTableChange = async (tableName: string, type: "warehouse" | "material" | number) => { let loadedColumns = columnsCache[tableName]; // 아직 캐시에 없으면 먼저 컬럼 조회 if (!loadedColumns) { setLoadingColumns(true); try { const fetched = await onLoadColumns(tableName); loadedColumns = normalizeColumns(fetched); setColumnsCache((prev) => ({ ...prev, [tableName]: loadedColumns! })); } catch (error) { console.error("컬럼 로드 실패:", error); loadedColumns = []; } finally { setLoadingColumns(false); } } const columns = loadedColumns || []; // PK 기반으로 keyColumn 기본값 자동 설정 (이미 값이 있으면 건드리지 않음) // PK 정보가 없으면 첫 번째 컬럼을 기본값으로 사용 setLocalConfig((prev) => { const next = { ...prev }; const primaryColumns = columns.filter((col) => isPrimaryKey(col)); const pkName = (primaryColumns[0] || columns[0])?.column_name; if (!pkName) { return next; } if (type === "warehouse") { const wh = { ...(next.warehouse || { tableName }), tableName: next.warehouse?.tableName || tableName, }; if (!wh.keyColumn) { wh.keyColumn = pkName; } next.warehouse = wh; } else if (type === "material") { const material = { ...(next.material || { tableName }), tableName: next.material?.tableName || tableName, }; if (!material.keyColumn) { material.keyColumn = pkName; } next.material = material as NonNullable; } else if (typeof type === "number") { // 계층 레벨 next.levels = next.levels.map((lvl) => { if (lvl.level !== type) return lvl; const updated: HierarchyLevel = { ...lvl, tableName: lvl.tableName || tableName, }; if (!updated.keyColumn) { updated.keyColumn = pkName; } return updated; }); } return next; }); }; // 창고 키 변경 (제거됨 - 상위 컴포넌트에서 처리) // 레벨 추가 const handleAddLevel = () => { const maxLevel = localConfig.levels.length > 0 ? Math.max(...localConfig.levels.map((l) => l.level)) : 0; const newLevel: HierarchyLevel = { level: maxLevel + 1, name: `레벨 ${maxLevel + 1}`, tableName: "", keyColumn: "", nameColumn: "", parentKeyColumn: "", objectTypes: [], }; const newConfig = { ...localConfig, levels: [...localConfig.levels, newLevel], }; setLocalConfig(newConfig); // onHierarchyConfigChange(newConfig); // 즉시 전달하지 않음 }; // 레벨 삭제 const handleRemoveLevel = (level: number) => { const newConfig = { ...localConfig, levels: localConfig.levels.filter((l) => l.level !== level), }; setLocalConfig(newConfig); // onHierarchyConfigChange(newConfig); // 즉시 전달하지 않음 }; // 레벨 설정 변경 const handleLevelChange = (level: number, field: keyof HierarchyLevel, value: any) => { const newConfig = { ...localConfig, levels: localConfig.levels.map((l) => (l.level === level ? { ...l, [field]: value } : l)), }; setLocalConfig(newConfig); // onHierarchyConfigChange(newConfig); // 즉시 전달하지 않음 }; // 자재 설정 변경 const handleMaterialChange = (field: keyof NonNullable, value: string) => { const newConfig = { ...localConfig, material: { ...localConfig.material, [field]: value, } as NonNullable, }; setLocalConfig(newConfig); // onHierarchyConfigChange(newConfig); // 즉시 전달하지 않음 }; // 창고 설정 변경 const handleWarehouseChange = (field: keyof NonNullable, value: string) => { const newWarehouse = { ...localConfig.warehouse, [field]: value, } as NonNullable; setLocalConfig({ ...localConfig, warehouse: newWarehouse }); }; // 설정 적용 const handleApplyConfig = () => { onHierarchyConfigChange(localConfig); }; if (!externalDbConnectionId) { return
외부 DB를 먼저 선택하세요
; } return (
{/* 창고 설정 */} 창고 설정 창고 테이블 및 컬럼 매핑 {/* 창고 테이블 선택 */}
{!localConfig.warehouse?.tableName && (

ℹ️ 창고 테이블을 선택하고 "설정 적용"을 눌러주세요

)}
{/* 창고 컬럼 매핑 */} {localConfig.warehouse?.tableName && columnsCache[localConfig.warehouse.tableName] && (
)} {localConfig.warehouse?.tableName && !columnsCache[localConfig.warehouse.tableName] && loadingColumns && (
컬럼 정보를 불러오는 중입니다...
)}
{/* 계층 레벨 목록 */} 계층 레벨 영역, 하위 영역 등 {localConfig.levels.length === 0 && (
레벨을 추가하세요
)} {localConfig.levels.map((level) => (
handleLevelChange(level.level, "name", e.target.value)} className="h-7 w-32 text-xs" placeholder="레벨명" />
{level.tableName && columnsCache[level.tableName] && ( <>
)} {level.tableName && !columnsCache[level.tableName] && loadingColumns && (
컬럼 정보를 불러오는 중입니다...
)}
))}
{/* 자재 설정 */} 자재 설정 최하위 레벨의 데이터
{localConfig.material?.tableName && columnsCache[localConfig.material.tableName] && ( <>
{/* 표시 컬럼 선택 */}

자재 클릭 시 표시할 정보를 선택하고 라벨을 입력하세요

{columnsCache[localConfig.material.tableName].map((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.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.column_name} {col.description && ( {col.description} )}
{isSelected && ( { const currentDisplay = localConfig.material?.displayColumns || []; const newDisplay = currentDisplay.map((d) => d.column === col.column_name ? { ...d, label: e.target.value } : d, ); handleMaterialChange("displayColumns", newDisplay); }} placeholder="표시명 입력..." className="h-6 flex-1 text-[10px]" /> )}
); })}
)} {localConfig.material?.tableName && !columnsCache[localConfig.material.tableName] && loadingColumns && (
컬럼 정보를 불러오는 중입니다...
)}
{/* 적용 버튼 */}
); }