"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, JoinTableConfig } 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-management/tables"); console.log("[loadTables] API 응답:", response.data); let tableList: any[] = []; if (response.data?.success && Array.isArray(response.data?.data)) { tableList = response.data.data; } else if (Array.isArray(response.data?.data)) { tableList = response.data.data; } else if (Array.isArray(response.data)) { tableList = response.data; } console.log("[loadTables] 추출된 테이블 목록:", tableList); if (tableList.length > 0) { // 백엔드에서 카멜케이스(tableName)로 반환하므로 둘 다 처리 const transformedTables = tableList.map((t: any) => ({ table_name: t.tableName ?? t.table_name ?? t.name ?? "", table_comment: t.displayName ?? t.table_comment ?? t.description ?? "", })); console.log("[loadTables] 변환된 테이블 목록:", transformedTables); setTables(transformedTables); } else { console.warn("[loadTables] 테이블 목록이 비어있습니다"); setTables([]); } } catch (error) { console.error("테이블 목록 로드 실패:", error); setTables([]); } finally { setTablesLoading(false); } }, []); // 화면 목록 로드 const loadScreens = useCallback(async () => { setScreensLoading(true); try { // size를 크게 설정하여 모든 화면 가져오기 const response = await apiClient.get("/screen-management/screens?size=1000"); console.log("[loadScreens] API 응답:", response.data); // API 응답 구조: { success, data: [...], total, page, size } let screenList: any[] = []; if (response.data?.success && Array.isArray(response.data?.data)) { screenList = response.data.data; } else if (Array.isArray(response.data?.data)) { screenList = response.data.data; } else if (Array.isArray(response.data)) { screenList = response.data; } console.log("[loadScreens] 추출된 화면 목록:", screenList); if (screenList.length > 0) { // 백엔드에서 카멜케이스(screenId, screenName)로 반환하므로 둘 다 처리 const transformedScreens = screenList.map((s: any) => ({ screen_id: s.screenId ?? s.screen_id ?? s.id, screen_name: s.screenName ?? s.screen_name ?? s.name ?? `화면 ${s.screenId || s.screen_id || s.id}`, screen_code: s.screenCode ?? s.screen_code ?? s.code ?? "", })); console.log("[loadScreens] 변환된 화면 목록:", transformedScreens); setScreens(transformedScreens); } else { console.warn("[loadScreens] 화면 목록이 비어있습니다"); setScreens([]); } } catch (error) { console.error("화면 목록 로드 실패:", error); setScreens([]); } finally { setScreensLoading(false); } }, []); // 컬럼 목록 로드 const loadColumns = useCallback(async (tableName: string, side: "left" | "right") => { if (!tableName) return; try { const response = await apiClient.get(`/table-management/tables/${tableName}/columns?size=200`); console.log(`[loadColumns] ${side} API 응답:`, response.data); // API 응답 구조: { success, data: { columns: [...], total, page, totalPages } } let columnList: any[] = []; if (response.data?.success && response.data?.data?.columns) { columnList = response.data.data.columns; } else if (Array.isArray(response.data?.data?.columns)) { columnList = response.data.data.columns; } else if (Array.isArray(response.data?.data)) { columnList = response.data.data; } else if (Array.isArray(response.data)) { columnList = response.data; } console.log(`[loadColumns] ${side} 추출된 컬럼 목록:`, columnList); if (columnList.length > 0) { // 백엔드에서 카멜케이스(columnName)로 반환하므로 둘 다 처리 const transformedColumns = columnList.map((c: any) => ({ column_name: c.columnName ?? c.column_name ?? c.name ?? "", data_type: c.dataType ?? c.data_type ?? c.type ?? "", column_comment: c.displayName ?? c.column_comment ?? c.label ?? "", })); console.log(`[loadColumns] ${side} 변환된 컬럼 목록:`, transformedColumns); if (side === "left") { setLeftColumns(transformedColumns); } else { setRightColumns(transformedColumns); } } else { console.warn(`[loadColumns] ${side} 컬럼 목록이 비어있습니다`); if (side === "left") { setLeftColumns([]); } else { setRightColumns([]); } } } catch (error) { console.error(`${side} 컬럼 목록 로드 실패:`, error); if (side === "left") { setLeftColumns([]); } else { setRightColumns([]); } } }, []); // 초기 로드 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]); // 조인 테이블 컬럼도 우측 컬럼 목록에 추가 useEffect(() => { const loadJoinTableColumns = async () => { const joinTables = config.rightPanel?.joinTables || []; if (joinTables.length === 0 || !config.rightPanel?.tableName) return; // 메인 테이블 컬럼 먼저 로드 try { const mainResponse = await apiClient.get(`/table-management/tables/${config.rightPanel.tableName}/columns?size=200`); let mainColumns: ColumnInfo[] = []; if (mainResponse.data?.success) { const columnList = mainResponse.data.data?.columns || mainResponse.data.data || []; mainColumns = columnList.map((c: any) => ({ column_name: c.columnName ?? c.column_name ?? c.name ?? "", data_type: c.dataType ?? c.data_type ?? c.type ?? "", column_comment: c.displayName ?? c.column_comment ?? c.label ?? "", })); } // 조인 테이블들의 선택된 컬럼 추가 const joinColumns: ColumnInfo[] = []; for (const jt of joinTables) { if (jt.joinTable && jt.selectColumns && jt.selectColumns.length > 0) { try { const joinResponse = await apiClient.get(`/table-management/tables/${jt.joinTable}/columns?size=200`); if (joinResponse.data?.success) { const columnList = joinResponse.data.data?.columns || joinResponse.data.data || []; const transformedColumns = columnList.map((c: any) => ({ column_name: c.columnName ?? c.column_name ?? c.name ?? "", data_type: c.dataType ?? c.data_type ?? c.type ?? "", column_comment: c.displayName ?? c.column_comment ?? c.label ?? "", })); // 선택된 컬럼 추가 (테이블명으로 구분, 유니크 키 생성) jt.selectColumns.forEach((selCol) => { const col = transformedColumns.find((c: ColumnInfo) => c.column_name === selCol); if (col) { joinColumns.push({ ...col, // 유니크 키를 위해 테이블명_컬럼명 형태로 저장 column_name: `${jt.joinTable}.${col.column_name}`, column_comment: col.column_comment ? `${col.column_comment} (${jt.joinTable})` : `${col.column_name} (${jt.joinTable})`, }); } }); } } catch (error) { console.error(`조인 테이블 ${jt.joinTable} 컬럼 로드 실패:`, error); } } } // 메인 + 조인 컬럼 합치기 setRightColumns([...mainColumns, ...joinColumns]); console.log(`[loadJoinTableColumns] 우측 컬럼 로드 완료: 메인 ${mainColumns.length}개 + 조인 ${joinColumns.length}개`); } catch (error) { console.error("조인 테이블 컬럼 로드 실패:", error); } }; loadJoinTableColumns(); }, [config.rightPanel?.tableName, config.rightPanel?.joinTables]); // 테이블 선택 컴포넌트 const TableSelect: React.FC<{ value: string; onValueChange: (value: string) => void; placeholder: string; open: boolean; onOpenChange: (open: boolean) => void; }> = ({ value, onValueChange, placeholder, open, onOpenChange }) => { const selectedTable = tables.find((t) => t.table_name === value); return ( {tables.length === 0 ? "테이블 목록을 불러오는 중..." : "검색 결과가 없습니다"} {tables.map((table, index) => ( { 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 }) => { const selectedScreen = screens.find((s) => s.screen_id === value); return ( {screens.length === 0 ? "화면 목록을 불러오는 중..." : "검색 결과가 없습니다"} {screens.map((screen, index) => ( { const screenId = parseInt(selectedValue.split("-")[0]); console.log("[ScreenSelect] onSelect:", { selectedValue, screenId, screen }); onValueChange(isNaN(screenId) ? undefined : 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; showTableName?: boolean; // 테이블명 표시 여부 tableName?: string; // 메인 테이블명 (조인 컬럼과 구분용) }> = ({ columns, value, onValueChange, placeholder, showTableName = false, tableName }) => { // 현재 선택된 값의 라벨 찾기 const selectedColumn = columns.find((col) => col.column_name === value); const displayValue = selectedColumn ? selectedColumn.column_comment || selectedColumn.column_name : value || ""; // 컬럼이 조인 테이블에서 온 것인지 확인 (column_comment에 괄호가 있으면 조인 테이블) const isJoinColumn = (col: ColumnInfo) => col.column_comment?.includes("(") && col.column_comment?.includes(")"); // 컬럼 표시 텍스트 생성 const getColumnDisplayText = (col: ColumnInfo) => { const label = col.column_comment || col.column_name; if (showTableName && tableName && !isJoinColumn(col)) { // 메인 테이블 컬럼에 테이블명 추가 return `${label} (${tableName})`; } return label; }; return ( ); }; // 조인 테이블 아이템 컴포넌트 const JoinTableItem: React.FC<{ index: number; joinTable: JoinTableConfig; tables: TableInfo[]; mainTableColumns: ColumnInfo[]; onUpdate: (field: keyof JoinTableConfig | Partial, value?: any) => void; onRemove: () => void; }> = ({ index, joinTable, tables, mainTableColumns, onUpdate, onRemove }) => { const [joinTableColumns, setJoinTableColumns] = useState([]); const [joinTableOpen, setJoinTableOpen] = useState(false); // 조인 테이블 선택 시 해당 테이블의 컬럼 로드 useEffect(() => { const loadJoinTableColumns = async () => { if (!joinTable.joinTable) { setJoinTableColumns([]); return; } try { const response = await apiClient.get(`/table-management/tables/${joinTable.joinTable}/columns?size=200`); let columnList: any[] = []; if (response.data?.success && response.data?.data?.columns) { columnList = response.data.data.columns; } else if (Array.isArray(response.data?.data?.columns)) { columnList = response.data.data.columns; } else if (Array.isArray(response.data?.data)) { columnList = response.data.data; } const transformedColumns = columnList.map((c: any) => ({ column_name: c.columnName ?? c.column_name ?? c.name ?? "", data_type: c.dataType ?? c.data_type ?? c.type ?? "", column_comment: c.displayName ?? c.column_comment ?? c.label ?? "", })); setJoinTableColumns(transformedColumns); } catch (error) { console.error("조인 테이블 컬럼 로드 실패:", error); setJoinTableColumns([]); } }; loadJoinTableColumns(); }, [joinTable.joinTable]); const selectedTable = tables.find((t) => t.table_name === joinTable.joinTable); return (
조인 {index + 1}
{/* 조인 테이블 선택 */}
검색 결과가 없습니다 {tables.map((table) => ( { // cmdk가 value를 소문자로 변환하므로 직접 table.table_name 사용 // 여러 필드를 한 번에 업데이트 (연속 호출 시 덮어쓰기 방지) onUpdate({ joinTable: table.table_name, selectColumns: [], // 테이블 변경 시 선택 컬럼 초기화 }); setJoinTableOpen(false); }} className="text-xs" > {table.table_comment || table.table_name} {table.table_name} ))}
{/* 조인 타입 */}
{/* 조인 조건 */}
onUpdate("mainColumn", value)} placeholder="메인 테이블 컬럼" />
=
onUpdate("joinColumn", value)} placeholder="조인 테이블 컬럼" />
{/* 가져올 컬럼 선택 */}

조인 테이블에서 표시할 컬럼들을 선택하세요

{(joinTable.selectColumns || []).map((col, colIndex) => (
{ const current = [...(joinTable.selectColumns || [])]; current[colIndex] = value; onUpdate("selectColumns", current); }} placeholder="컬럼 선택" />
))} {(joinTable.selectColumns || []).length === 0 && (
가져올 컬럼을 추가하세요
)}
); }; // 표시 컬럼 추가 const addDisplayColumn = (side: "left" | "right") => { const path = side === "left" ? "leftPanel.displayColumns" : "rightPanel.displayColumns"; const currentColumns = side === "left" ? config.leftPanel?.displayColumns || [] : config.rightPanel?.displayColumns || []; // 기본 테이블 설정 (메인 테이블) const defaultTable = side === "left" ? config.leftPanel?.tableName : config.rightPanel?.tableName; updateConfig(path, [...currentColumns, { name: "", label: "", sourceTable: defaultTable || "" }]); }; // 표시 컬럼 삭제 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, fieldOrPartial: keyof ColumnConfig | Partial, value?: any ) => { const path = side === "left" ? "leftPanel.displayColumns" : "rightPanel.displayColumns"; const currentColumns = side === "left" ? [...(config.leftPanel?.displayColumns || [])] : [...(config.rightPanel?.displayColumns || [])]; if (currentColumns[index]) { if (typeof fieldOrPartial === "object") { // 여러 필드를 한 번에 업데이트 currentColumns[index] = { ...currentColumns[index], ...fieldOrPartial }; } else { // 단일 필드 업데이트 currentColumns[index] = { ...currentColumns[index], [fieldOrPartial]: 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) => (
컬럼 {index + 1}
updateDisplayColumn("left", index, "name", value)} placeholder="컬럼 선택" />
updateDisplayColumn("left", index, "label", e.target.value)} placeholder="라벨명 (미입력 시 컬럼명 사용)" className="h-8 text-xs" />
))} {(config.leftPanel?.displayColumns || []).length === 0 && (
표시할 컬럼을 추가하세요
)}
updateConfig("leftPanel.showSearch", checked)} />
{config.leftPanel?.showSearch && (
{(config.leftPanel?.searchColumns || []).map((searchCol, index) => (
{ const current = [...(config.leftPanel?.searchColumns || [])]; current[index] = { ...current[index], columnName: value }; updateConfig("leftPanel.searchColumns", current); }} placeholder="컬럼 선택" />
))} {(config.leftPanel?.searchColumns || []).length === 0 && (
검색할 컬럼을 추가하세요
)}
)}
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?.joinTables || []).map((joinTable, index) => ( { const current = [...(config.rightPanel?.joinTables || [])]; if (typeof fieldOrPartial === "object") { // 여러 필드를 한 번에 업데이트 current[index] = { ...current[index], ...fieldOrPartial }; } else { // 단일 필드 업데이트 current[index] = { ...current[index], [fieldOrPartial]: value }; } updateConfig("rightPanel.joinTables", current); }} onRemove={() => { const current = config.rightPanel?.joinTables || []; updateConfig( "rightPanel.joinTables", current.filter((_, i) => i !== index) ); }} /> ))}
{/* 표시 컬럼 */}

테이블을 선택한 후 해당 테이블의 컬럼을 선택하세요.

{(config.rightPanel?.displayColumns || []).map((col, index) => { // 선택 가능한 테이블 목록: 메인 테이블 + 조인 테이블들 const availableTables = [ config.rightPanel?.tableName, ...(config.rightPanel?.joinTables || []).map((jt) => jt.joinTable), ].filter(Boolean) as string[]; // 선택된 테이블의 컬럼만 필터링 const selectedSourceTable = col.sourceTable || config.rightPanel?.tableName; const filteredColumns = rightColumns.filter((c) => { // 조인 테이블 컬럼인지 확인 (column_name이 "테이블명.컬럼명" 형태) const isJoinColumn = c.column_name.includes("."); if (selectedSourceTable === config.rightPanel?.tableName) { // 메인 테이블 선택 시: 조인 컬럼 아닌 것만 return !isJoinColumn; } else { // 조인 테이블 선택 시: 해당 테이블 컬럼만 (테이블명.컬럼명 형태) return c.column_name.startsWith(`${selectedSourceTable}.`); } }); // 테이블 라벨 가져오기 const getTableLabel = (tableName: string) => { const table = tables.find((t) => t.table_name === tableName); return table?.table_comment || tableName; }; return (
컬럼 {index + 1}
{/* 테이블 선택 */}
{/* 컬럼 선택 */}
{/* 표시 라벨 */}
updateDisplayColumn("right", index, "label", e.target.value)} placeholder="라벨명 (미입력 시 컬럼명 사용)" className="h-8 text-xs" />
{/* 표시 위치 */}
); })} {(config.rightPanel?.displayColumns || []).length === 0 && (
표시할 컬럼을 추가하세요
)}
updateConfig("rightPanel.showSearch", checked)} />
{config.rightPanel?.showSearch && (

표시할 컬럼 중 검색에 사용할 컬럼을 선택하세요.

{(config.rightPanel?.searchColumns || []).map((searchCol, index) => { // 표시할 컬럼 정보를 가져와서 테이블명과 함께 표시 const displayColumns = config.rightPanel?.displayColumns || []; // 유효한 컬럼만 필터링 (name이 있는 것만) const validDisplayColumns = displayColumns.filter((dc) => dc.name && dc.name.trim() !== ""); // 현재 선택된 컬럼의 표시 정보 const selectedDisplayCol = validDisplayColumns.find((dc) => dc.name === searchCol.columnName); const selectedColInfo = rightColumns.find((c) => c.column_name === searchCol.columnName); const selectedLabel = selectedDisplayCol?.label || selectedColInfo?.column_comment?.replace(/\s*\([^)]+\)$/, "") || searchCol.columnName; const selectedTableName = selectedDisplayCol?.sourceTable || config.rightPanel?.tableName || ""; const selectedTableLabel = tables.find((t) => t.table_name === selectedTableName)?.table_comment || selectedTableName; return (
); })} {(config.rightPanel?.displayColumns || []).length === 0 && (
먼저 표시할 컬럼을 추가하세요
)} {(config.rightPanel?.displayColumns || []).length > 0 && (config.rightPanel?.searchColumns || []).length === 0 && (
검색할 컬럼을 추가하세요
)}
)}
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} />
)} {/* 표시 모드 설정 */}

카드형: 카드 형태로 정보 표시, 테이블형: 표 형태로 정보 표시

{/* 카드 모드 전용 옵션 */} {(config.rightPanel?.displayMode || "card") === "card" && (

라벨: 값 형식으로 표시

updateConfig("rightPanel.showLabels", checked)} />
)} {/* 체크박스 표시 */}

항목 선택 기능 활성화

updateConfig("rightPanel.showCheckbox", checked)} />
{/* 수정/삭제 버튼 */}
updateConfig("rightPanel.showEditButton", checked)} />
updateConfig("rightPanel.showDeleteButton", checked)} />
{/* 수정 모달 화면 (수정 버튼 활성화 시) */} {config.rightPanel?.showEditButton && (
updateConfig("rightPanel.editModalScreenId", value)} placeholder="수정 모달 화면 선택 (미선택 시 추가 모달 사용)" open={false} onOpenChange={() => {}} />

미선택 시 추가 모달 화면을 수정용으로 사용

)} {/* 기본키 컬럼 */}
updateConfig("rightPanel.primaryKeyColumn", value)} placeholder="기본키 컬럼 선택 (기본: id)" />

수정/삭제 시 사용할 기본키 컬럼 (미선택 시 id 사용)

{/* 복수 액션 버튼 설정 */}

복수의 버튼을 추가하면 기존 단일 추가 버튼 대신 사용됩니다

{(config.rightPanel?.actionButtons || []).map((btn, index) => (
버튼 {index + 1}
{ const current = [...(config.rightPanel?.actionButtons || [])]; current[index] = { ...current[index], label: e.target.value }; updateConfig("rightPanel.actionButtons", current); }} placeholder="버튼 라벨" className="h-8 text-xs" />
{btn.action === "add" && (
{ const current = [...(config.rightPanel?.actionButtons || [])]; current[index] = { ...current[index], modalScreenId: value }; updateConfig("rightPanel.actionButtons", current); }} placeholder="모달 화면 선택" open={false} onOpenChange={() => {}} />
)}
))} {(config.rightPanel?.actionButtons || []).length === 0 && (
액션 버튼을 추가하세요 (선택사항)
)}
{/* 연결 설정 */}

연결 설정 (조인)

{/* 설명 */}

좌측 패널 선택 시 우측 패널 데이터 필터링

좌측에서 항목을 선택하면 좌측 조인 컬럼의 값으로 우측 테이블을 필터링합니다.

예: 부서(dept_code) 선택 시 해당 부서의 사원만 표시

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

데이터 전달 설정

{/* 설명 */}

우측 패널 추가 버튼 클릭 시 모달로 데이터 전달

좌측에서 선택한 항목의 값을 모달 폼에 자동으로 채워줍니다.

예: dept_code를 모달의 dept_code 필드에 자동 입력

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

레이아웃 설정

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;