"use client"; /** * V2TableList 설정 패널 * 토스식 단계별 UX: 데이터 소스 -> 컬럼 선택 -> 조인 컬럼 -> 순서/라벨 -> 고급 설정(접힘) * 기존 TableListConfigPanel의 모든 기능을 자체 UI로 완전 구현 */ import React, { useState, useEffect, useMemo, useCallback, useRef } from "react"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Checkbox } from "@/components/ui/checkbox"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { Separator } from "@/components/ui/separator"; import { Table2, Database, Link2, GripVertical, X, Check, ChevronsUpDown, Lock, Unlock, Settings, ChevronDown, Loader2, Columns3, ArrowUpDown, Filter, LayoutGrid, CheckSquare, Wrench, ScrollText, } from "lucide-react"; import { cn } from "@/lib/utils"; import { entityJoinApi } from "@/lib/api/entityJoin"; import { tableTypeApi } from "@/lib/api/screen"; import { tableManagementApi } from "@/lib/api/tableManagement"; import { DataFilterConfigPanel } from "@/components/screen/config-panels/DataFilterConfigPanel"; import { DndContext, closestCenter, type DragEndEvent } from "@dnd-kit/core"; import { SortableContext, useSortable, verticalListSortingStrategy, arrayMove } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import type { TableListConfig, ColumnConfig } from "@/lib/registry/components/v2-table-list/types"; // ─── DnD 정렬 가능한 컬럼 행 (접이식) ─── function SortableColumnRow({ id, col, index, isEntityJoin, onLabelChange, onWidthChange, onRemove, }: { id: string; col: ColumnConfig; index: number; isEntityJoin?: boolean; onLabelChange: (value: string) => void; onWidthChange: (value: number) => void; onRemove: () => void; }) { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id }); const style = { transform: CSS.Transform.toString(transform), transition }; const [expanded, setExpanded] = useState(false); return (
{isEntityJoin ? ( ) : ( #{index + 1} )} {col.width && ( {col.width}px )}
{expanded && (
onLabelChange(e.target.value)} placeholder="표시명" className="h-7 min-w-0 text-xs" /> onWidthChange(parseInt(e.target.value) || 100)} placeholder="너비" className="h-7 shrink-0 text-xs text-center" />
)}
); } // ─── 섹션 헤더 컴포넌트 ─── function SectionHeader({ icon: Icon, title, description }: { icon: React.ComponentType<{ className?: string }>; title: string; description?: string }) { return (

{title}

{description &&

{description}

}
); } // ─── 수평 Switch Row (토스 패턴) ─── function SwitchRow({ label, description, checked, onCheckedChange }: { label: string; description?: string; checked: boolean; onCheckedChange: (checked: boolean) => void; }) { return (

{label}

{description &&

{description}

}
); } interface V2TableListConfigPanelProps { config: TableListConfig; onChange: (config: Partial) => void; screenTableName?: string; tableColumns?: any[]; menuObjid?: number; } export const V2TableListConfigPanel: React.FC = ({ config: configProp, onChange, screenTableName, tableColumns, menuObjid, }) => { const config = configProp || ({} as TableListConfig); // componentConfigChanged 이벤트 발행 래퍼 const handleChange = useCallback((newConfig: Partial) => { onChange(newConfig); if (typeof window !== "undefined") { window.dispatchEvent( new CustomEvent("componentConfigChanged", { detail: { config: { ...config, ...newConfig } }, }) ); } }, [onChange, config]); // key-value 형태 업데이트 헬퍼 const updateField = useCallback((key: keyof TableListConfig, value: any) => { handleChange({ ...config, [key]: value }); }, [handleChange, config]); const updateNestedField = useCallback((parentKey: keyof TableListConfig, childKey: string, value: any) => { const parentValue = config[parentKey] as any; handleChange({ ...config, [parentKey]: { ...parentValue, [childKey]: value }, }); }, [handleChange, config]); // ─── 상태 ─── const [availableTables, setAvailableTables] = useState>([]); const [loadingTables, setLoadingTables] = useState(false); const [tableComboboxOpen, setTableComboboxOpen] = useState(false); const [availableColumns, setAvailableColumns] = useState< Array<{ columnName: string; dataType: string; label?: string; input_type?: string }> >([]); const [entityJoinColumns, setEntityJoinColumns] = useState<{ availableColumns: Array<{ tableName: string; columnName: string; columnLabel: string; dataType: string; joinAlias: string; suggestedLabel: string; }>; joinTables: Array<{ tableName: string; currentDisplayColumn: string; joinConfig?: any; availableColumns: Array<{ columnName: string; columnLabel: string; dataType: string; inputType?: string; description?: string; }>; }>; }>({ availableColumns: [], joinTables: [] }); const [loadingEntityJoins, setLoadingEntityJoins] = useState(false); const [referenceTableColumns, setReferenceTableColumns] = useState< Array<{ columnName: string; dataType: string; label?: string }> >([]); const [loadingReferenceColumns, setLoadingReferenceColumns] = useState(false); const [entityDisplayConfigs, setEntityDisplayConfigs] = useState< Record; joinColumns: Array<{ columnName: string; displayName: string; dataType: string }>; selectedColumns: string[]; separator: string; }> >({}); // Collapsible 상태 const [advancedOpen, setAdvancedOpen] = useState(false); const [entityDisplayOpen, setEntityDisplayOpen] = useState(false); const [columnSelectOpen, setColumnSelectOpen] = useState(() => (config.columns?.length || 0) > 0); const [entityJoinOpen, setEntityJoinOpen] = useState(false); const [displayColumnsOpen, setDisplayColumnsOpen] = useState(() => (config.columns?.length || 0) > 0); const [columnSearchText, setColumnSearchText] = useState(""); const [entityJoinSubOpen, setEntityJoinSubOpen] = useState>({}); // 이전 컬럼 개수 추적 (엔티티 감지용) const prevColumnsLengthRef = useRef(0); // ─── 실제 사용할 테이블 이름 계산 ─── const targetTableName = useMemo(() => { if (config.useCustomTable && config.customTableName) { return config.customTableName; } return config.selectedTable || screenTableName; }, [config.useCustomTable, config.customTableName, config.selectedTable, screenTableName]); // ─── 초기화: 화면 테이블명 자동 설정 ─── useEffect(() => { if (screenTableName && !config.selectedTable) { handleChange({ ...config, selectedTable: screenTableName, columns: config.columns || [], }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [screenTableName]); // ─── 테이블 목록 가져오기 ─── useEffect(() => { const fetchTables = async () => { setLoadingTables(true); try { const response = await tableTypeApi.getTables(); setAvailableTables( response.map((table: any) => ({ tableName: table.tableName, displayName: table.displayName || table.tableName, })), ); } catch (error) { console.error("테이블 목록 가져오기 실패:", error); } finally { setLoadingTables(false); } }; fetchTables(); }, []); // ─── 선택된 테이블의 컬럼 목록 설정 ─── useEffect(() => { if (!targetTableName) { setAvailableColumns([]); return; } const isUsingDifferentTable = config.selectedTable && screenTableName && config.selectedTable !== screenTableName; const shouldUseTableColumnsProp = !config.useCustomTable && !isUsingDifferentTable && tableColumns && tableColumns.length > 0; if (shouldUseTableColumnsProp) { const mappedColumns = tableColumns.map((column: any) => ({ columnName: column.columnName || column.name, dataType: column.dataType || column.type || "text", label: column.label || column.displayName || column.columnLabel || column.columnName || column.name, input_type: column.input_type || column.inputType, })); setAvailableColumns(mappedColumns); if (!config.selectedTable && screenTableName) { handleChange({ ...config, selectedTable: screenTableName, columns: config.columns || [], }); } } else { const fetchColumns = async () => { try { const result = await tableManagementApi.getColumnList(targetTableName); if (result.success && result.data) { const columns = Array.isArray(result.data) ? result.data : result.data.columns; if (columns && Array.isArray(columns)) { setAvailableColumns( columns.map((col: any) => ({ columnName: col.columnName, dataType: col.dataType, label: col.displayName || col.columnLabel || col.columnName, input_type: col.input_type || col.inputType, })), ); } else { setAvailableColumns([]); } } else { setAvailableColumns([]); } } catch (error) { console.error("컬럼 목록 가져오기 실패:", error); setAvailableColumns([]); } }; fetchColumns(); } }, [targetTableName, config.useCustomTable, tableColumns]); // ─── Entity 조인 컬럼 정보 가져오기 ─── useEffect(() => { const fetchEntityJoinColumns = async () => { if (!targetTableName) { setEntityJoinColumns({ availableColumns: [], joinTables: [] }); return; } setLoadingEntityJoins(true); try { const result = await entityJoinApi.getEntityJoinColumns(targetTableName); setEntityJoinColumns({ availableColumns: result.availableColumns || [], joinTables: result.joinTables || [], }); } catch (error) { console.error("Entity 조인 컬럼 조회 오류:", error); setEntityJoinColumns({ availableColumns: [], joinTables: [] }); } finally { setLoadingEntityJoins(false); } }; fetchEntityJoinColumns(); }, [targetTableName]); // ─── 제외 필터용 참조 테이블 컬럼 가져오기 ─── useEffect(() => { const fetchReferenceColumns = async () => { const refTable = config.excludeFilter?.referenceTable; if (!refTable) { setReferenceTableColumns([]); return; } setLoadingReferenceColumns(true); try { const result = await tableManagementApi.getColumnList(refTable); if (result.success && result.data) { const columns = result.data.columns || []; setReferenceTableColumns( columns.map((col: any) => ({ columnName: col.columnName || col.column_name, dataType: col.dataType || col.data_type || "text", label: col.displayName || col.columnLabel || col.column_label || col.columnName || col.column_name, })), ); } } catch (error) { console.error("참조 테이블 컬럼 조회 오류:", error); setReferenceTableColumns([]); } finally { setLoadingReferenceColumns(false); } }; fetchReferenceColumns(); }, [config.excludeFilter?.referenceTable]); // ─── 엔티티 컬럼 자동 로드 ─── useEffect(() => { const entityColumns = config.columns?.filter((col) => col.isEntityJoin && col.entityDisplayConfig); if (!entityColumns || entityColumns.length === 0) return; entityColumns.forEach((column) => { if (entityDisplayConfigs[column.columnName]) return; loadEntityDisplayConfig(column); }); }, [config.columns]); // ─── 엔티티 타입 컬럼 자동 감지 ─── useEffect(() => { const currentLength = config.columns?.length || 0; const prevLength = prevColumnsLengthRef.current; if (!config.columns || !tableColumns || config.columns.length === 0) { prevColumnsLengthRef.current = currentLength; return; } if (currentLength === prevLength && prevLength > 0) return; const updatedColumns = config.columns.map((column) => { if (column.isEntityJoin) return column; const tableColumn = tableColumns.find((tc: any) => tc.columnName === column.columnName); if (tableColumn && (tableColumn.input_type === "entity" || tableColumn.web_type === "entity")) { return { ...column, isEntityJoin: true, entityJoinInfo: { sourceTable: config.selectedTable || screenTableName || "", sourceColumn: column.columnName, joinAlias: column.columnName, }, entityDisplayConfig: { displayColumns: [], separator: " - ", sourceTable: config.selectedTable || screenTableName || "", joinTable: tableColumn.reference_table || tableColumn.referenceTable || "", }, }; } return column; }); const hasChanges = updatedColumns.some((col, index) => col.isEntityJoin !== config.columns![index].isEntityJoin); if (hasChanges) { updateField("columns", updatedColumns); } prevColumnsLengthRef.current = currentLength; // eslint-disable-next-line react-hooks/exhaustive-deps }, [config.columns?.length, tableColumns, config.selectedTable]); // ─── 엔티티 컬럼의 표시 컬럼 정보 로드 ─── const loadEntityDisplayConfig = useCallback(async (column: ColumnConfig) => { const configKey = column.columnName; if (entityDisplayConfigs[configKey]) return; if (!column.isEntityJoin) { setEntityDisplayConfigs((prev) => ({ ...prev, [configKey]: { sourceColumns: [], joinColumns: [], selectedColumns: [], separator: " - " }, })); return; } const sourceTable = column.entityDisplayConfig?.sourceTable || column.entityJoinInfo?.sourceTable || config.selectedTable || screenTableName; if (!sourceTable) { setEntityDisplayConfigs((prev) => ({ ...prev, [configKey]: { sourceColumns: [], joinColumns: [], selectedColumns: column.entityDisplayConfig?.displayColumns || [], separator: column.entityDisplayConfig?.separator || " - ", }, })); return; } let joinTable = column.entityDisplayConfig?.joinTable; if (!joinTable) { try { const columnList = await tableTypeApi.getColumns(sourceTable); const columnInfo = columnList.find((col: any) => (col.column_name || col.columnName) === column.columnName); if (columnInfo?.reference_table || columnInfo?.referenceTable) { joinTable = columnInfo.reference_table || columnInfo.referenceTable; const updatedDisplayConfig = { ...column.entityDisplayConfig, sourceTable, joinTable, displayColumns: column.entityDisplayConfig?.displayColumns || [], separator: column.entityDisplayConfig?.separator || " - ", }; const updatedColumns = config.columns?.map((col) => col.columnName === column.columnName ? { ...col, entityDisplayConfig: updatedDisplayConfig } : col, ); if (updatedColumns) updateField("columns", updatedColumns); } } catch (error) { console.error("tableTypeApi 컬럼 정보 조회 실패:", error); } } try { const sourceResult = await entityJoinApi.getReferenceTableColumns(sourceTable); const sourceColumns = sourceResult.columns || []; let joinColumns: Array<{ columnName: string; displayName: string; dataType: string }> = []; if (joinTable) { try { const joinResult = await entityJoinApi.getReferenceTableColumns(joinTable); joinColumns = joinResult.columns || []; } catch { // 조인 테이블 로드 실패해도 소스 테이블 컬럼은 표시 } } setEntityDisplayConfigs((prev) => ({ ...prev, [configKey]: { sourceColumns, joinColumns, selectedColumns: column.entityDisplayConfig?.displayColumns || [], separator: column.entityDisplayConfig?.separator || " - ", }, })); } catch (error) { console.error("엔티티 표시 컬럼 정보 로드 실패:", error); setEntityDisplayConfigs((prev) => ({ ...prev, [configKey]: { sourceColumns: [], joinColumns: [], selectedColumns: column.entityDisplayConfig?.displayColumns || [], separator: column.entityDisplayConfig?.separator || " - ", }, })); } }, [entityDisplayConfigs, config.selectedTable, config.columns, screenTableName, updateField]); // ─── 엔티티 표시 컬럼 선택 토글 ─── const toggleEntityDisplayColumn = useCallback((columnName: string, selectedColumn: string) => { const configKey = columnName; const localConfig = entityDisplayConfigs[configKey]; if (!localConfig) return; const newSelectedColumns = localConfig.selectedColumns.includes(selectedColumn) ? localConfig.selectedColumns.filter((col) => col !== selectedColumn) : [...localConfig.selectedColumns, selectedColumn]; setEntityDisplayConfigs((prev) => ({ ...prev, [configKey]: { ...prev[configKey], selectedColumns: newSelectedColumns }, })); const updatedColumns = config.columns?.map((col) => { if (col.columnName === columnName && col.entityDisplayConfig) { return { ...col, entityDisplayConfig: { ...col.entityDisplayConfig, displayColumns: newSelectedColumns } }; } return col; }); if (updatedColumns) updateField("columns", updatedColumns); }, [entityDisplayConfigs, config.columns, updateField]); // ─── 엔티티 표시 구분자 업데이트 ─── const updateEntityDisplaySeparator = useCallback((columnName: string, separator: string) => { const configKey = columnName; const localConfig = entityDisplayConfigs[configKey]; if (!localConfig) return; setEntityDisplayConfigs((prev) => ({ ...prev, [configKey]: { ...prev[configKey], separator }, })); const updatedColumns = config.columns?.map((col) => { if (col.columnName === columnName && col.entityDisplayConfig) { return { ...col, entityDisplayConfig: { ...col.entityDisplayConfig, separator } }; } return col; }); if (updatedColumns) updateField("columns", updatedColumns); }, [entityDisplayConfigs, config.columns, updateField]); // ─── 컬럼 추가 ─── const addColumn = useCallback((columnName: string) => { const existingColumn = config.columns?.find((col) => col.columnName === columnName); if (existingColumn) return; const columnInfo = tableColumns?.find((col: any) => (col.columnName || col.name) === columnName); const availableColumnInfo = availableColumns.find((col) => col.columnName === columnName); const displayName = columnInfo?.label || columnInfo?.displayName || availableColumnInfo?.label || columnName; const newColumn: ColumnConfig = { columnName, displayName, visible: true, sortable: true, searchable: true, align: "left", format: "text", order: config.columns?.length || 0, }; updateField("columns", [...(config.columns || []), newColumn]); }, [config.columns, tableColumns, availableColumns, updateField]); // ─── 조인 컬럼 추가 ─── const addEntityColumn = useCallback((joinColumn: (typeof entityJoinColumns.availableColumns)[0]) => { const existingColumn = config.columns?.find((col) => col.columnName === joinColumn.joinAlias); if (existingColumn) return; const joinTableInfo = entityJoinColumns.joinTables?.find((jt: any) => jt.tableName === joinColumn.tableName); const sourceColumn = (joinTableInfo as any)?.joinConfig?.sourceColumn || ""; const newColumn: ColumnConfig = { columnName: joinColumn.joinAlias, displayName: joinColumn.columnLabel, visible: true, sortable: true, searchable: true, align: "left", format: "text", order: config.columns?.length || 0, isEntityJoin: false, additionalJoinInfo: { sourceTable: config.selectedTable || screenTableName || "", sourceColumn, referenceTable: joinColumn.tableName, joinAlias: joinColumn.joinAlias, }, }; updateField("columns", [...(config.columns || []), newColumn]); }, [config.columns, entityJoinColumns.joinTables, config.selectedTable, screenTableName, updateField]); // ─── 컬럼 제거 ─── const removeColumn = useCallback((columnName: string) => { updateField("columns", config.columns?.filter((col) => col.columnName !== columnName) || []); }, [config.columns, updateField]); // ─── 컬럼 업데이트 ─── const updateColumn = useCallback((columnName: string, updates: Partial) => { const updatedColumns = config.columns?.map((col) => col.columnName === columnName ? { ...col, ...updates } : col ) || []; updateField("columns", updatedColumns); }, [config.columns, updateField]); // ─── 테이블 변경 핸들러 ─── const handleTableChange = useCallback((newTableName: string) => { if (newTableName === targetTableName) return; handleChange({ ...config, selectedTable: newTableName, columns: [], }); setTableComboboxOpen(false); }, [targetTableName, handleChange, config]); // ─── 렌더링 ─── return (
{/* ═══════════════════════════════════════ */} {/* 1단계: 데이터 소스 (테이블 선택) */} {/* ═══════════════════════════════════════ */}
테이블을 찾을 수 없습니다. {availableTables.map((table) => ( handleTableChange(table.tableName)} className="text-xs" >
{table.displayName} {table.displayName !== table.tableName && ( {table.tableName} )}
))}
{screenTableName && targetTableName !== screenTableName && (
화면 기본 테이블({screenTableName})과 다른 테이블을 사용 중
)}
{/* ═══════════════════════════════════════ */} {/* 2단계: 컬럼 선택 (Collapsible) */} {/* ═══════════════════════════════════════ */} {targetTableName && availableColumns.length > 0 && ( <>
setColumnSearchText(e.target.value)} placeholder="컬럼 검색..." className="h-7 text-xs" />
{availableColumns .filter((column) => { if (!columnSearchText) return true; const search = columnSearchText.toLowerCase(); return ( column.columnName.toLowerCase().includes(search) || (column.label || "").toLowerCase().includes(search) ); }) .map((column) => { const isAdded = config.columns?.some((c) => c.columnName === column.columnName); return (
{ if (isAdded) { updateField("columns", config.columns?.filter((c) => c.columnName !== column.columnName) || []); } else { addColumn(column.columnName); } }} > { if (isAdded) { updateField("columns", config.columns?.filter((c) => c.columnName !== column.columnName) || []); } else { addColumn(column.columnName); } }} className="pointer-events-none h-3.5 w-3.5" /> {column.label || column.columnName} {isAdded && ( )} {column.input_type || column.dataType}
); })}
{/* Entity 조인 컬럼 (Collapsible) */} {entityJoinColumns.joinTables.length > 0 && (
{entityJoinColumns.joinTables.map((joinTable, tableIndex) => { const addedCount = joinTable.availableColumns.filter((col) => { const match = entityJoinColumns.availableColumns.find( (jc) => jc.tableName === joinTable.tableName && jc.columnName === col.columnName, ); return match && config.columns?.some((c) => c.columnName === match.joinAlias); }).length; const isSubOpen = entityJoinSubOpen[tableIndex] ?? false; return ( setEntityJoinSubOpen((prev) => ({ ...prev, [tableIndex]: open }))}>
{joinTable.availableColumns.map((column, colIndex) => { const matchingJoinColumn = entityJoinColumns.availableColumns.find( (jc) => jc.tableName === joinTable.tableName && jc.columnName === column.columnName, ); const isAlreadyAdded = config.columns?.some( (col) => col.columnName === matchingJoinColumn?.joinAlias, ); if (!matchingJoinColumn) return null; return (
{ if (isAlreadyAdded) { updateField("columns", config.columns?.filter((c) => c.columnName !== matchingJoinColumn.joinAlias) || []); } else { addEntityColumn(matchingJoinColumn); } }} > { if (isAlreadyAdded) { updateField("columns", config.columns?.filter((c) => c.columnName !== matchingJoinColumn.joinAlias) || []); } else { addEntityColumn(matchingJoinColumn); } }} className="pointer-events-none h-3.5 w-3.5" /> {column.columnLabel} {column.inputType || column.dataType}
); })}
); })}
)} )} {/* 테이블 미선택 또는 컬럼 없음 안내 */} {!targetTableName && (

테이블이 선택되지 않았습니다

위 데이터 소스에서 테이블을 선택하세요

)} {targetTableName && availableColumns.length === 0 && (

컬럼 정보를 불러오는 중...

현재 화면 테이블: {screenTableName}

)} {/* ═══════════════════════════════════════ */} {/* 3단계: 표시할 컬럼 (Collapsible + DnD) */} {/* ═══════════════════════════════════════ */} {config.columns && config.columns.length > 0 && (

드래그하여 순서 변경, 클릭하여 표시명/너비 수정

{ const { active, over } = event; if (!over || active.id === over.id) return; const columns = [...(config.columns || [])]; const oldIndex = columns.findIndex((c) => c.columnName === active.id); const newIndex = columns.findIndex((c) => c.columnName === over.id); if (oldIndex !== -1 && newIndex !== -1) { const reordered = arrayMove(columns, oldIndex, newIndex); reordered.forEach((col, idx) => { col.order = idx; }); updateField("columns", reordered); } }} > c.columnName)} strategy={verticalListSortingStrategy} >
{(config.columns || []).map((column, idx) => { const resolvedLabel = column.displayName && column.displayName !== column.columnName ? column.displayName : availableColumns.find((c) => c.columnName === column.columnName)?.label || column.displayName || column.columnName; const colWithLabel = { ...column, displayName: resolvedLabel }; return ( updateColumn(column.columnName, { displayName: value })} onWidthChange={(value) => updateColumn(column.columnName, { width: value })} onRemove={() => removeColumn(column.columnName)} /> ); })}
)} {/* ═══════════════════════════════════════ */} {/* 엔티티 컬럼 표시 설정 (접이식) */} {/* ═══════════════════════════════════════ */} {config.columns?.some((col) => col.isEntityJoin) && (
{config.columns ?.filter((col) => col.isEntityJoin && col.entityDisplayConfig) .map((column) => (
{column.displayName || column.columnName} {entityDisplayConfigs[column.columnName] ? (
{/* 구분자 */}
구분자 updateEntityDisplaySeparator(column.columnName, e.target.value)} className="h-6 w-20 text-xs" placeholder=" - " />
{/* 표시 컬럼 선택 */} {entityDisplayConfigs[column.columnName].sourceColumns.length === 0 && entityDisplayConfigs[column.columnName].joinColumns.length === 0 ? (
표시 가능한 컬럼이 없습니다. {!column.entityDisplayConfig?.joinTable && (

테이블 타입 관리에서 참조 테이블을 설정하면 더 많은 컬럼을 선택할 수 있습니다.

)}
) : (
표시할 컬럼 선택 컬럼을 찾을 수 없습니다. {entityDisplayConfigs[column.columnName].sourceColumns.length > 0 && ( {entityDisplayConfigs[column.columnName].sourceColumns.map((col) => ( toggleEntityDisplayColumn(column.columnName, col.columnName)} className="text-xs" > {col.displayName} ))} )} {entityDisplayConfigs[column.columnName].joinColumns.length > 0 && ( {entityDisplayConfigs[column.columnName].joinColumns.map((col) => ( toggleEntityDisplayColumn(column.columnName, col.columnName)} className="text-xs" > {col.displayName} ))} )}
)} {/* 참조 테이블 미설정 안내 */} {!column.entityDisplayConfig?.joinTable && entityDisplayConfigs[column.columnName].sourceColumns.length > 0 && (
현재 기본 테이블 컬럼만 표시됩니다. 테이블 타입 관리에서 참조 테이블을 설정하면 조인된 테이블의 컬럼도 선택할 수 있습니다.
)} {/* 선택된 컬럼 미리보기 */} {entityDisplayConfigs[column.columnName].selectedColumns.length > 0 && (
미리보기
{entityDisplayConfigs[column.columnName].selectedColumns.map((colName, idx) => ( {colName} {idx < entityDisplayConfigs[column.columnName].selectedColumns.length - 1 && ( {entityDisplayConfigs[column.columnName].separator} )} ))}
)}
) : (
컬럼 정보 로딩 중...
)} {config.columns?.filter((c) => c.isEntityJoin && c.entityDisplayConfig).indexOf(column) !== (config.columns?.filter((c) => c.isEntityJoin && c.entityDisplayConfig).length || 0) - 1 && ( )}
))}
)} {/* ═══════════════════════════════════════ */} {/* 4단계: 툴바 버튼 설정 (Switch 토글) */} {/* ═══════════════════════════════════════ */}
updateNestedField("toolbar", "showEditMode", checked)} /> updateNestedField("toolbar", "showExcel", checked)} /> updateNestedField("toolbar", "showPdf", checked)} /> updateNestedField("toolbar", "showCopy", checked)} /> updateNestedField("toolbar", "showSearch", checked)} /> updateNestedField("toolbar", "showFilter", checked)} /> updateNestedField("toolbar", "showRefresh", checked)} /> updateNestedField("toolbar", "showPaginationRefresh", checked)} />
{/* ═══════════════════════════════════════ */} {/* 5단계: 고급 설정 (기본 접힘) */} {/* ═══════════════════════════════════════ */}
{/* 체크박스 설정 */}
체크박스
updateNestedField("checkbox", "enabled", checked)} /> {config.checkbox?.enabled && (
updateNestedField("checkbox", "selectAll", checked)} />
체크박스 위치
)}
{/* 기본 정렬 설정 */}
기본 정렬

테이블 로드 시 기본 정렬 순서를 지정합니다

정렬 컬럼
{config.defaultSort?.columnName && (
정렬 방향
)}
{/* 가로 스크롤 */}
가로 스크롤 및 컬럼 고정
updateNestedField("horizontalScroll", "enabled", checked)} /> {config.horizontalScroll?.enabled && (

최대 표시 컬럼 수

이 수를 넘는 컬럼이 있으면 가로 스크롤이 생성됩니다

updateNestedField("horizontalScroll", "maxVisibleColumns", parseInt(e.target.value) || 8)} min={3} max={20} className="h-7 w-[80px] text-xs" />
)}
{/* ═══════════════════════════════════════ */} {/* 6단계: 데이터 필터링 */} {/* ═══════════════════════════════════════ */}
({ columnName: col.columnName, columnLabel: col.label || col.columnName, dataType: col.dataType, input_type: col.input_type, }) as any, )} config={config.dataFilter} onConfigChange={(dataFilter) => updateField("dataFilter", dataFilter)} />
); }; V2TableListConfigPanel.displayName = "V2TableListConfigPanel"; export default V2TableListConfigPanel;