"use client"; import React, { useState, useEffect, useCallback } from "react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Switch } from "@/components/ui/switch"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command"; import { Check, ChevronsUpDown, Plus, X } from "lucide-react"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; import type { SplitPanelLayout2Config, ColumnConfig, DataTransferField } from "./types"; // lodash set 대체 함수 const setPath = (obj: any, path: string, value: any): any => { const keys = path.split("."); const result = { ...obj }; let current = result; for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; current[key] = current[key] ? { ...current[key] } : {}; current = current[key]; } current[keys[keys.length - 1]] = value; return result; }; interface SplitPanelLayout2ConfigPanelProps { config: SplitPanelLayout2Config; onChange: (config: SplitPanelLayout2Config) => void; } interface TableInfo { table_name: string; table_comment?: string; } interface ColumnInfo { column_name: string; data_type: string; column_comment?: string; } interface ScreenInfo { screen_id: number; screen_name: string; screen_code: string; } export const SplitPanelLayout2ConfigPanel: React.FC = ({ config, onChange, }) => { // updateConfig 헬퍼 함수: 경로 기반으로 config를 업데이트 const updateConfig = useCallback((path: string, value: any) => { console.log(`[SplitPanelLayout2ConfigPanel] updateConfig: ${path} =`, value); const newConfig = setPath(config, path, value); console.log("[SplitPanelLayout2ConfigPanel] newConfig:", newConfig); onChange(newConfig); }, [config, onChange]); // 상태 const [tables, setTables] = useState([]); const [leftColumns, setLeftColumns] = useState([]); const [rightColumns, setRightColumns] = useState([]); const [screens, setScreens] = useState([]); const [tablesLoading, setTablesLoading] = useState(false); const [screensLoading, setScreensLoading] = useState(false); // Popover 상태 const [leftTableOpen, setLeftTableOpen] = useState(false); const [rightTableOpen, setRightTableOpen] = useState(false); const [leftModalOpen, setLeftModalOpen] = useState(false); const [rightModalOpen, setRightModalOpen] = useState(false); // 테이블 목록 로드 const loadTables = useCallback(async () => { setTablesLoading(true); try { const response = await apiClient.get("/table/list?userLang=KR"); const tableList = response.data?.data || response.data || []; if (Array.isArray(tableList)) { setTables(tableList); } } catch (error) { console.error("테이블 목록 로드 실패:", error); } finally { setTablesLoading(false); } }, []); // 화면 목록 로드 const loadScreens = useCallback(async () => { setScreensLoading(true); try { const response = await apiClient.get("/screen/list"); console.log("[loadScreens] API 응답:", response.data); const screenList = response.data?.data || response.data || []; if (Array.isArray(screenList)) { const transformedScreens = screenList.map((s: any) => ({ screen_id: s.screen_id || s.id, screen_name: s.screen_name || s.name, screen_code: s.screen_code || s.code || "", })); console.log("[loadScreens] 변환된 화면 목록:", transformedScreens); setScreens(transformedScreens); } } catch (error) { console.error("화면 목록 로드 실패:", error); } finally { setScreensLoading(false); } }, []); // 컬럼 목록 로드 const loadColumns = useCallback(async (tableName: string, side: "left" | "right") => { if (!tableName) return; try { const response = await apiClient.get(`/table/${tableName}/columns`); const columnList = response.data?.data || response.data || []; if (Array.isArray(columnList)) { if (side === "left") { setLeftColumns(columnList); } else { setRightColumns(columnList); } } } catch (error) { console.error(`${side} 컬럼 목록 로드 실패:`, error); } }, []); // 초기 로드 useEffect(() => { loadTables(); loadScreens(); }, [loadTables, loadScreens]); // 테이블 변경 시 컬럼 로드 useEffect(() => { if (config.leftPanel?.tableName) { loadColumns(config.leftPanel.tableName, "left"); } }, [config.leftPanel?.tableName, loadColumns]); useEffect(() => { if (config.rightPanel?.tableName) { loadColumns(config.rightPanel.tableName, "right"); } }, [config.rightPanel?.tableName, loadColumns]); // 테이블 선택 컴포넌트 const TableSelect: React.FC<{ value: string; onValueChange: (value: string) => void; placeholder: string; open: boolean; onOpenChange: (open: boolean) => void; }> = ({ value, onValueChange, placeholder, open, onOpenChange }) => ( 테이블이 없습니다 {tables.map((table) => ( { onValueChange(selectedValue); onOpenChange(false); }} > {table.table_comment || table.table_name} {table.table_name} ))} ); // 화면 선택 컴포넌트 const ScreenSelect: React.FC<{ value: number | undefined; onValueChange: (value: number | undefined) => void; placeholder: string; open: boolean; onOpenChange: (open: boolean) => void; }> = ({ value, onValueChange, placeholder, open, onOpenChange }) => ( 화면이 없습니다 {screens.map((screen, index) => ( { const screenId = parseInt(selectedValue.split("-")[0]); console.log("[ScreenSelect] onSelect:", { selectedValue, screenId, screen }); onValueChange(screenId); onOpenChange(false); }} className="flex items-center" >
{screen.screen_name} {screen.screen_code}
))}
); // 컬럼 선택 컴포넌트 const ColumnSelect: React.FC<{ columns: ColumnInfo[]; value: string; onValueChange: (value: string) => void; placeholder: string; }> = ({ columns, value, onValueChange, placeholder }) => ( ); // 표시 컬럼 추가 const addDisplayColumn = (side: "left" | "right") => { const path = side === "left" ? "leftPanel.displayColumns" : "rightPanel.displayColumns"; const currentColumns = side === "left" ? config.leftPanel?.displayColumns || [] : config.rightPanel?.displayColumns || []; updateConfig(path, [...currentColumns, { name: "", label: "" }]); }; // 표시 컬럼 삭제 const removeDisplayColumn = (side: "left" | "right", index: number) => { const path = side === "left" ? "leftPanel.displayColumns" : "rightPanel.displayColumns"; const currentColumns = side === "left" ? config.leftPanel?.displayColumns || [] : config.rightPanel?.displayColumns || []; updateConfig(path, currentColumns.filter((_, i) => i !== index)); }; // 표시 컬럼 업데이트 const updateDisplayColumn = (side: "left" | "right", index: number, field: keyof ColumnConfig, value: any) => { const path = side === "left" ? "leftPanel.displayColumns" : "rightPanel.displayColumns"; const currentColumns = side === "left" ? [...(config.leftPanel?.displayColumns || [])] : [...(config.rightPanel?.displayColumns || [])]; if (currentColumns[index]) { currentColumns[index] = { ...currentColumns[index], [field]: value }; updateConfig(path, currentColumns); } }; // 데이터 전달 필드 추가 const addDataTransferField = () => { const currentFields = config.dataTransferFields || []; updateConfig("dataTransferFields", [...currentFields, { sourceColumn: "", targetColumn: "" }]); }; // 데이터 전달 필드 삭제 const removeDataTransferField = (index: number) => { const currentFields = config.dataTransferFields || []; updateConfig("dataTransferFields", currentFields.filter((_, i) => i !== index)); }; // 데이터 전달 필드 업데이트 const updateDataTransferField = (index: number, field: keyof DataTransferField, value: string) => { const currentFields = [...(config.dataTransferFields || [])]; if (currentFields[index]) { currentFields[index] = { ...currentFields[index], [field]: value }; updateConfig("dataTransferFields", currentFields); } }; return (
{/* 좌측 패널 설정 */}

좌측 패널 설정 (마스터)

updateConfig("leftPanel.title", e.target.value)} placeholder="부서" className="h-9 text-sm" />
updateConfig("leftPanel.tableName", value)} placeholder="테이블 선택" open={leftTableOpen} onOpenChange={setLeftTableOpen} />
{/* 표시 컬럼 */}
{(config.leftPanel?.displayColumns || []).map((col, index) => (
updateDisplayColumn("left", index, "name", value)} placeholder="컬럼" /> updateDisplayColumn("left", index, "label", e.target.value)} placeholder="라벨" className="h-9 text-sm flex-1" />
))}
updateConfig("leftPanel.showSearch", checked)} />
updateConfig("leftPanel.showAddButton", checked)} />
{config.leftPanel?.showAddButton && ( <>
updateConfig("leftPanel.addButtonLabel", e.target.value)} placeholder="추가" className="h-9 text-sm" />
updateConfig("leftPanel.addModalScreenId", value)} placeholder="모달 화면 선택" open={leftModalOpen} onOpenChange={setLeftModalOpen} />
)}
{/* 우측 패널 설정 */}

우측 패널 설정 (상세)

updateConfig("rightPanel.title", e.target.value)} placeholder="사원" className="h-9 text-sm" />
updateConfig("rightPanel.tableName", value)} placeholder="테이블 선택" open={rightTableOpen} onOpenChange={setRightTableOpen} />
{/* 표시 컬럼 */}
{(config.rightPanel?.displayColumns || []).map((col, index) => (
updateDisplayColumn("right", index, "name", value)} placeholder="컬럼" /> updateDisplayColumn("right", index, "label", e.target.value)} placeholder="라벨" className="h-9 text-sm flex-1" />
))}
updateConfig("rightPanel.showSearch", checked)} />
updateConfig("rightPanel.showAddButton", checked)} />
{config.rightPanel?.showAddButton && ( <>
updateConfig("rightPanel.addButtonLabel", e.target.value)} placeholder="추가" className="h-9 text-sm" />
updateConfig("rightPanel.addModalScreenId", value)} placeholder="모달 화면 선택" open={rightModalOpen} onOpenChange={setRightModalOpen} />
)}
{/* 연결 설정 */}

연결 설정 (조인)

updateConfig("joinConfig.leftColumn", value)} placeholder="조인 컬럼 선택" />
updateConfig("joinConfig.rightColumn", value)} placeholder="조인 컬럼 선택" />
{/* 데이터 전달 설정 */}

데이터 전달 설정

{(config.dataTransferFields || []).map((field, index) => (
필드 {index + 1}
updateDataTransferField(index, "sourceColumn", value)} placeholder="소스 컬럼" />
updateDataTransferField(index, "targetColumn", e.target.value)} placeholder="모달에서 사용할 필드명" className="h-9 text-sm" />
))}
{/* 레이아웃 설정 */}

레이아웃 설정

updateConfig("splitRatio", parseInt(e.target.value) || 30)} min={10} max={90} className="h-9 text-sm" />
updateConfig("resizable", checked)} />
updateConfig("autoLoad", checked)} />
); }; export default SplitPanelLayout2ConfigPanel;