From 57d86c8ef1975d7b346f0735b5cc24ed6797093d Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 15 Jan 2026 17:00:21 +0900 Subject: [PATCH] =?UTF-8?q?=EC=83=88=EB=A1=9C=EC=9A=B4=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=20=ED=8C=8C=EC=9D=BC=EC=9D=84=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=ED=98=84=EC=9E=AC=20=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=9C=2016=EA=B0=9C=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=EC=9D=98=20=EB=8B=A4=EA=B5=AD=EC=96=B4=20?= =?UTF-8?q?=EC=A7=80=EC=9B=90=20=EB=B0=8F=20=ED=85=8C=EC=9D=B4=EB=B8=94=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=ED=98=84=ED=99=A9?= =?UTF-8?q?=EC=9D=84=20=EC=A0=95=EB=A6=AC=ED=96=88=EC=8A=B5=EB=8B=88?= =?UTF-8?q?=EB=8B=A4.=20=EA=B0=81=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?= =?UTF-8?q?=EB=B3=84=20=EC=83=81=EC=84=B8=20=ED=98=84=ED=99=A9=EA=B3=BC=20?= =?UTF-8?q?=EC=9A=B0=EC=84=A0=EC=88=9C=EC=9C=84=20=EC=9E=91=EC=97=85=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=EC=9D=84=20=ED=8F=AC=ED=95=A8=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EA=B8=B0=EB=8A=A5=20=EC=A0=81=EC=9A=A9=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EB=A5=BC=20=EB=AA=85=ED=99=95=ED=9E=88=20=ED=95=98?= =?UTF-8?q?=EC=98=80=EC=8A=B5=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/컴포넌트_기능_현황.md | 148 +++++ .../screen/panels/ComponentsPanel.tsx | 18 +- .../config-panels/UnifiedListConfigPanel.tsx | 610 ++++-------------- 3 files changed, 293 insertions(+), 483 deletions(-) create mode 100644 docs/컴포넌트_기능_현황.md diff --git a/docs/컴포넌트_기능_현황.md b/docs/컴포넌트_기능_현황.md new file mode 100644 index 00000000..bf59454a --- /dev/null +++ b/docs/컴포넌트_기능_현황.md @@ -0,0 +1,148 @@ +# 컴포넌트 기능 현황 + +> 작성일: 2026-01-15 +> 현재 사용 가능한 16개 컴포넌트의 다국어 지원 및 테이블 설정 기능 현황 + +--- + +## 요약 + +| 기능 | 적용 완료 | 미적용 | 해당없음 | +|------|----------|--------|---------| +| **다국어 지원** | 3개 | 10개 | 3개 | +| **컴포넌트별 테이블 설정** | 4개 | 6개 | 6개 | + +--- + +## 컴포넌트별 상세 현황 + +### 데이터 표시 (Display) - 4개 + +| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 | +|---------|:----------:|:----------:|------| +| **테이블 리스트** | ✅ 적용 | ✅ 적용 | `customTableName`, `useCustomTable` 지원 | +| **카드 디스플레이** | ❌ 미적용 | ⚠️ 부분 | `screenTableName`만 사용, 컴포넌트별 테이블 선택 UI 없음 | +| **텍스트 표시** | ❌ 미적용 | ➖ 해당없음 | 정적 텍스트 표시용 | +| **피벗 그리드** | ❌ 미적용 | ⚠️ 부분 | `tableName` 설정 가능하나 Combobox UI 없음 | + +--- + +### 데이터 입력 (Data) - 2개 + +| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 | +|---------|:----------:|:----------:|------| +| **통합 반복 데이터** | ❌ 미적용 | ✅ 적용 | `mainTableName`, `foreignKeyColumn` 지원, Combobox UI 적용 | +| **반복 화면 모달** | ❌ 미적용 | ⚠️ 부분 | `tableName` 설정 가능하나 Combobox UI 없음 | + +--- + +### 액션 (Action) - 1개 + +| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 | +|---------|:----------:|:----------:|------| +| **기본 버튼** | ✅ 적용 | ➖ 해당없음 | `langKeyId`, `langKey` 지원 | + +--- + +### 레이아웃 (Layout) - 5개 + +| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 | +|---------|:----------:|:----------:|------| +| **분할 패널** | ✅ 적용 | ⚠️ 부분 | 다국어 지원, 테이블 설정은 하위 패널에서 처리 | +| **탭 컴포넌트** | ❌ 미적용 | ➖ 해당없음 | 화면 전환용 컨테이너 | +| **Section Card** | ❌ 미적용 | ➖ 해당없음 | 그룹화 컨테이너 | +| **Section Paper** | ❌ 미적용 | ➖ 해당없음 | 그룹화 컨테이너 | +| **구분선** | ❌ 미적용 | ➖ 해당없음 | 시각적 구분용 | + +--- + +### 유틸리티 (Utility) - 4개 + +| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 | +|---------|:----------:|:----------:|------| +| **코드 채번 규칙** | ❌ 미적용 | ➖ 해당없음 | 채번 규칙 관리 전용 | +| **렉 구조 설정** | ❌ 미적용 | ➖ 해당없음 | 창고 렉 설정 전용 | +| **출발지/도착지 선택** | ❌ 미적용 | ⚠️ 부분 | `customTableName` 지원하나 Combobox UI 없음 | +| **검색 필터** | ❌ 미적용 | ⚠️ 부분 | `screenTableName` 자동 감지 | + +--- + +## 상세 설명 + +### 다국어 지원 (`langKeyId`, `langKey`) + +다국어 지원이란 컴포넌트의 라벨, 플레이스홀더 등 텍스트 속성에 다국어 키를 연결하여 언어별로 다른 텍스트를 표시하는 기능입니다. + +**적용 완료 (3개)** +- `table-list`: 컬럼 라벨 다국어 지원 +- `button-primary`: 버튼 텍스트 다국어 지원 +- `split-panel-layout`: 패널 제목 다국어 지원 + +**미적용 (10개)** +- `card-display`, `text-display`, `pivot-grid` +- `unified-repeater`, `repeat-screen-modal` +- `tabs`, `section-card`, `section-paper`, `divider-line` +- `numbering-rule`, `rack-structure`, `location-swap-selector`, `table-search-widget` + +--- + +### 컴포넌트별 테이블 설정 (`customTableName`, `useCustomTable`) + +컴포넌트별 테이블 설정이란 화면의 메인 테이블과 별개로 컴포넌트가 자체적으로 사용할 테이블을 지정할 수 있는 기능입니다. + +**완전 적용 (4개)** + +| 컴포넌트 | 적용 방식 | +|---------|----------| +| `table-list` | Combobox UI로 테이블 선택, `customTableName`, `useCustomTable`, `isReadOnly` 지원 | +| `unified-repeater` | Combobox UI로 테이블 선택, `mainTableName`, `foreignKeyColumn` 지원, FK 자동 연결 | +| `unified-list` | `TableListConfigPanel` 래핑하여 동일 기능 제공 | + +**부분 적용 (6개)** + +| 컴포넌트 | 현재 상태 | 필요 작업 | +|---------|----------|----------| +| `card-display` | `screenTableName` 사용 | Combobox UI 추가 필요 | +| `pivot-grid` | `tableName` 설정 가능 | Combobox UI 추가 필요 | +| `repeat-screen-modal` | `tableName` 설정 가능 | Combobox UI 추가 필요 | +| `split-panel-layout` | 하위 패널에서 처리 | 하위 컴포넌트에 위임 | +| `location-swap-selector` | `customTableName` 지원 | Combobox UI 추가 필요 | +| `table-search-widget` | `screenTableName` 자동 감지 | 현재 방식 유지 가능 | + +**해당없음 (6개)** +- `text-display`, `divider-line`: 정적 컴포넌트 +- `tabs`, `section-card`, `section-paper`: 레이아웃 컨테이너 +- `numbering-rule`, `rack-structure`: 특수 목적 컴포넌트 + +--- + +## 우선순위 작업 목록 + +### 1순위: 데이터 컴포넌트 테이블 설정 UI 통일 + +| 컴포넌트 | 작업 내용 | +|---------|----------| +| `card-display` | Combobox UI 추가, `customTableName` 지원 | +| `pivot-grid` | Combobox UI 추가 | +| `repeat-screen-modal` | Combobox UI 추가 | + +### 2순위: 다국어 지원 확대 + +| 컴포넌트 | 작업 내용 | +|---------|----------| +| `unified-repeater` | 컬럼 라벨 `langKeyId` 지원 | +| `card-display` | 필드 라벨 `langKeyId` 지원 | +| `tabs` | 탭 이름 `langKeyId` 지원 | +| `section-card` | 제목 `langKeyId` 지원 | + +--- + +## 범례 + +| 기호 | 의미 | +|-----|------| +| ✅ | 완전 적용 | +| ⚠️ | 부분 적용 (기능은 있으나 UI 미비) | +| ❌ | 미적용 | +| ➖ | 해당없음 (기능 불필요) | + diff --git a/frontend/components/screen/panels/ComponentsPanel.tsx b/frontend/components/screen/panels/ComponentsPanel.tsx index 9024f5fc..95ab8f41 100644 --- a/frontend/components/screen/panels/ComponentsPanel.tsx +++ b/frontend/components/screen/panels/ComponentsPanel.tsx @@ -66,14 +66,7 @@ export function ComponentsPanel({ // unified-date: 테이블 컬럼 드래그 시 자동 생성되므로 숨김 처리 // unified-layout: 중첩 드래그앤드롭 기능 미구현으로 숨김 처리 // unified-group: 중첩 드래그앤드롭 기능 미구현으로 숨김 처리 - { - id: "unified-list", - name: "통합 목록", - description: "테이블, 카드 등 다양한 데이터 표시 방식 지원", - category: "display" as ComponentCategory, - tags: ["table", "list", "card", "unified"], - defaultSize: { width: 600, height: 400 }, - }, + // unified-list: table-list, card-display로 분리하여 숨김 처리 // unified-media 제거 - 테이블 컬럼의 image/file 입력 타입으로 사용 // unified-biz 제거 - 개별 컴포넌트(flow-widget, rack-structure, numbering-rule)로 직접 표시 // unified-hierarchy 제거 - 현재 미사용 @@ -113,8 +106,7 @@ export function ComponentsPanel({ // 특수 업무용 컴포넌트 (일반 화면에서 불필요) "tax-invoice-list", // 세금계산서 전용 "customer-item-mapping", // 고객-품목 매핑 전용 - // unified-list로 통합됨 - "card-display", // → unified-list (card 모드) + // card-display는 별도 컴포넌트로 유지 // unified-media로 통합됨 "image-display", // → unified-media (image) // 공통코드관리로 통합 예정 @@ -128,6 +120,12 @@ export function ComponentsPanel({ "universal-form-modal", // 범용 폼 모달 // 통합 미디어 (테이블 컬럼 입력 타입으로 사용) "unified-media", // → 테이블 컬럼의 image/file 입력 타입으로 사용 + // 플로우 위젯 숨김 처리 + "flow-widget", + // 선택 항목 상세입력 - 기존 컴포넌트 조합으로 대체 가능 + "selected-items-detail-input", + // 연관 데이터 버튼 - unified-repeater로 대체 가능 + "related-data-buttons", ]; return { diff --git a/frontend/components/unified/config-panels/UnifiedListConfigPanel.tsx b/frontend/components/unified/config-panels/UnifiedListConfigPanel.tsx index 5eb19bd5..cc66dbfb 100644 --- a/frontend/components/unified/config-panels/UnifiedListConfigPanel.tsx +++ b/frontend/components/unified/config-panels/UnifiedListConfigPanel.tsx @@ -2,21 +2,13 @@ /** * UnifiedList 설정 패널 - * 통합 목록 컴포넌트의 세부 설정을 관리합니다. - * - 현재 화면의 테이블 데이터를 사용 - * - 테이블 컬럼 + 엔티티 조인 컬럼 선택 지원 + * TableListConfigPanel을 래핑하여 동일한 설정 기능을 제공합니다. + * 카드 표시는 별도의 card-display 컴포넌트를 사용합니다. */ -import React, { useState, useEffect, useMemo } from "react"; -import { Label } from "@/components/ui/label"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Separator } from "@/components/ui/separator"; -import { Checkbox } from "@/components/ui/checkbox"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Database, Link2, GripVertical, ChevronDown, ChevronRight } from "lucide-react"; -import { tableTypeApi } from "@/lib/api/screen"; -import { cn } from "@/lib/utils"; +import React, { useMemo } from "react"; +import { TableListConfigPanel } from "@/lib/registry/components/table-list/TableListConfigPanel"; +import { TableListConfig } from "@/lib/registry/components/table-list/types"; interface UnifiedListConfigPanelProps { config: Record; @@ -25,476 +17,148 @@ interface UnifiedListConfigPanelProps { currentTableName?: string; } -interface ColumnOption { - columnName: string; - displayName: string; - isJoinColumn?: boolean; - sourceTable?: string; - inputType?: string; -} - +/** + * UnifiedList 설정 패널 + * TableListConfigPanel과 동일한 기능을 제공 + */ export const UnifiedListConfigPanel: React.FC = ({ config, onChange, currentTableName, }) => { - // 컬럼 목록 (테이블 컬럼 + 엔티티 조인 컬럼) - const [columns, setColumns] = useState([]); - const [loadingColumns, setLoadingColumns] = useState(false); - const [expandedJoinSections, setExpandedJoinSections] = useState>(new Set()); + // UnifiedList config를 TableListConfig 형식으로 변환 + const tableListConfig: TableListConfig = useMemo(() => { + // 컬럼 형식 변환: UnifiedList columns -> TableList columns + const columns = (config.columns || []).map((col: any, index: number) => ({ + columnName: col.key || col.columnName || col.field || "", + displayName: col.title || col.header || col.displayName || col.key || col.columnName || col.field || "", + width: col.width ? parseInt(col.width, 10) : undefined, + visible: col.visible !== false, + sortable: col.sortable !== false, + searchable: col.searchable !== false, + align: col.align || "left", + order: index, + isEntityJoin: col.isJoinColumn || col.isEntityJoin || false, + thousandSeparator: col.thousandSeparator, + editable: col.editable, + entityDisplayConfig: col.entityDisplayConfig, + })); - // 설정 업데이트 핸들러 - const updateConfig = (field: string, value: any) => { - const newConfig = { ...config, [field]: value }; - console.log("⚙️ UnifiedListConfigPanel updateConfig:", { field, value, newConfig }); + return { + selectedTable: config.tableName || config.dataSource?.table || currentTableName, + tableName: config.tableName || config.dataSource?.table || currentTableName, + columns, + useCustomTable: config.useCustomTable, + customTableName: config.customTableName, + isReadOnly: config.isReadOnly !== false, // UnifiedList는 기본적으로 읽기 전용 + displayMode: "table", // 테이블 모드 고정 (카드는 card-display 컴포넌트 사용) + pagination: config.pagination !== false ? { + enabled: true, + pageSize: config.pageSize || 10, + position: "bottom", + showPageSize: true, + pageSizeOptions: [5, 10, 20, 50, 100], + } : { + enabled: false, + pageSize: 10, + position: "bottom", + showPageSize: false, + pageSizeOptions: [10], + }, + filter: config.filter, + dataFilter: config.dataFilter, + checkbox: { + enabled: true, + position: "left", + showHeader: true, + }, + height: "auto", + autoWidth: true, + stickyHeader: true, + autoLoad: true, + horizontalScroll: { + enabled: true, + minColumnWidth: 100, + maxColumnWidth: 300, + }, + }; + }, [config, currentTableName]); + + // TableListConfig 변경을 UnifiedList config 형식으로 변환 + const handleConfigChange = (partialConfig: Partial) => { + const newConfig: Record = { ...config }; + + // 테이블 설정 변환 + if (partialConfig.selectedTable !== undefined) { + newConfig.tableName = partialConfig.selectedTable; + if (!newConfig.dataSource) { + newConfig.dataSource = {}; + } + newConfig.dataSource.table = partialConfig.selectedTable; + } + if (partialConfig.tableName !== undefined) { + newConfig.tableName = partialConfig.tableName; + if (!newConfig.dataSource) { + newConfig.dataSource = {}; + } + newConfig.dataSource.table = partialConfig.tableName; + } + if (partialConfig.useCustomTable !== undefined) { + newConfig.useCustomTable = partialConfig.useCustomTable; + } + if (partialConfig.customTableName !== undefined) { + newConfig.customTableName = partialConfig.customTableName; + } + if (partialConfig.isReadOnly !== undefined) { + newConfig.isReadOnly = partialConfig.isReadOnly; + } + + // 컬럼 형식 변환: TableList columns -> UnifiedList columns + if (partialConfig.columns !== undefined) { + newConfig.columns = partialConfig.columns.map((col: any) => ({ + key: col.columnName, + field: col.columnName, + title: col.displayName, + header: col.displayName, + width: col.width ? String(col.width) : undefined, + visible: col.visible, + sortable: col.sortable, + searchable: col.searchable, + align: col.align, + isJoinColumn: col.isEntityJoin, + isEntityJoin: col.isEntityJoin, + thousandSeparator: col.thousandSeparator, + editable: col.editable, + entityDisplayConfig: col.entityDisplayConfig, + })); + } + + // 페이지네이션 변환 + if (partialConfig.pagination !== undefined) { + newConfig.pagination = partialConfig.pagination?.enabled; + newConfig.pageSize = partialConfig.pagination?.pageSize || 10; + } + + // 필터 변환 + if (partialConfig.filter !== undefined) { + newConfig.filter = partialConfig.filter; + } + + // 데이터 필터 변환 + if (partialConfig.dataFilter !== undefined) { + newConfig.dataFilter = partialConfig.dataFilter; + } + + console.log("⚙️ UnifiedListConfigPanel handleConfigChange:", { partialConfig, newConfig }); onChange(newConfig); }; - // 테이블명 (현재 화면의 테이블 사용) - const tableName = currentTableName || config.tableName; - - // 화면의 테이블명을 config에 자동 저장 - useEffect(() => { - if (currentTableName && config.tableName !== currentTableName) { - onChange({ ...config, tableName: currentTableName }); - } - }, [currentTableName]); - - // 테이블 컬럼 및 엔티티 조인 컬럼 로드 - useEffect(() => { - const loadColumns = async () => { - if (!tableName) { - setColumns([]); - return; - } - - setLoadingColumns(true); - try { - // 1. 테이블 컬럼 로드 - const columnData = await tableTypeApi.getColumns(tableName); - const baseColumns: ColumnOption[] = columnData.map((c: any) => ({ - columnName: c.columnName || c.column_name, - displayName: c.displayName || c.columnLabel || c.columnName || c.column_name, - isJoinColumn: false, - inputType: c.inputType || c.input_type || c.webType || c.web_type, - })); - - // 2. 엔티티 타입 컬럼 찾기 및 조인 컬럼 정보 로드 - const entityColumns = columnData.filter((c: any) => (c.inputType || c.input_type) === "entity"); - - const joinColumnOptions: ColumnOption[] = []; - - for (const entityCol of entityColumns) { - const colName = entityCol.columnName || entityCol.column_name; - - // referenceTable 우선순위: - // 1. 컬럼의 reference_table 필드 - // 2. detailSettings.referenceTable - let referenceTable = entityCol.referenceTable || entityCol.reference_table; - - if (!referenceTable) { - let detailSettings = entityCol.detailSettings || entityCol.detail_settings; - if (typeof detailSettings === "string") { - try { - detailSettings = JSON.parse(detailSettings); - } catch { - continue; - } - } - referenceTable = detailSettings?.referenceTable; - } - - if (referenceTable) { - try { - const refColumnData = await tableTypeApi.getColumns(referenceTable); - - refColumnData.forEach((refCol: any) => { - const refColName = refCol.columnName || refCol.column_name; - const refDisplayName = refCol.displayName || refCol.columnLabel || refColName; - - joinColumnOptions.push({ - columnName: `${colName}.${refColName}`, - displayName: refDisplayName, - isJoinColumn: true, - sourceTable: referenceTable, - }); - }); - } catch (error) { - console.error(`참조 테이블 ${referenceTable} 컬럼 로드 실패:`, error); - } - } - } - - setColumns([...baseColumns, ...joinColumnOptions]); - } catch (error) { - console.error("컬럼 목록 로드 실패:", error); - setColumns([]); - } finally { - setLoadingColumns(false); - } - }; - - loadColumns(); - }, [tableName]); - - // 컬럼 설정 - const configColumns: Array<{ - key: string; - title: string; - width?: string; - isJoinColumn?: boolean; - inputType?: string; - thousandSeparator?: boolean; - }> = config.columns || []; - - // 컬럼이 추가되었는지 확인 - const isColumnAdded = (columnName: string) => { - return configColumns.some((col) => col.key === columnName); - }; - - // 컬럼 토글 (추가/제거) - const toggleColumn = (column: ColumnOption) => { - if (isColumnAdded(column.columnName)) { - // 제거 - const newColumns = configColumns.filter((col) => col.key !== column.columnName); - updateConfig("columns", newColumns); - } else { - // 추가 - const isNumberType = ["number", "decimal", "integer", "float", "double", "numeric", "currency"].includes( - column.inputType || "", - ); - const newColumn = { - key: column.columnName, - title: column.displayName, - width: "", - isJoinColumn: column.isJoinColumn || false, - inputType: column.inputType, - thousandSeparator: isNumberType ? true : undefined, // 숫자 타입은 기본적으로 천단위 구분자 사용 - }; - updateConfig("columns", [...configColumns, newColumn]); - } - }; - - // 컬럼 천단위 구분자 토글 - const toggleThousandSeparator = (columnKey: string, checked: boolean) => { - const newColumns = configColumns.map((col) => (col.key === columnKey ? { ...col, thousandSeparator: checked } : col)); - updateConfig("columns", newColumns); - }; - - // 숫자 타입 컬럼인지 확인 - const isNumberColumn = (columnKey: string) => { - const colInfo = columns.find((c) => c.columnName === columnKey); - const configCol = configColumns.find((c) => c.key === columnKey); - const inputType = configCol?.inputType || colInfo?.inputType || ""; - return ["number", "decimal", "integer", "float", "double", "numeric", "currency"].includes(inputType); - }; - - // 컬럼 제목 수정 - const updateColumnTitle = (columnKey: string, title: string) => { - const newColumns = configColumns.map((col) => (col.key === columnKey ? { ...col, title } : col)); - updateConfig("columns", newColumns); - }; - - // 그룹별 컬럼 분리 - const baseColumns = useMemo(() => columns.filter((col) => !col.isJoinColumn), [columns]); - - // 조인 컬럼을 소스 테이블별로 그룹화 - const joinColumnsByTable = useMemo(() => { - const grouped: Record = {}; - columns - .filter((col) => col.isJoinColumn) - .forEach((col) => { - const table = col.sourceTable || "unknown"; - if (!grouped[table]) { - grouped[table] = []; - } - grouped[table].push(col); - }); - return grouped; - }, [columns]); - - // 조인 섹션 토글 - const toggleJoinSection = (tableName: string) => { - setExpandedJoinSections((prev) => { - const newSet = new Set(prev); - if (newSet.has(tableName)) { - newSet.delete(tableName); - } else { - newSet.add(tableName); - } - return newSet; - }); - }; - return ( -
- {/* 뷰 모드 */} -
- - -
- - {/* 카드 모드 설정 */} - {config.viewMode === "card" && ( - <> - -
- - - {/* 제목 컬럼 */} -
- - -
- - {/* 부제목 컬럼 */} -
- - -
- - {/* 행당 카드 수 */} -
- - -
-
- - )} - - - - {/* 컬럼 선택 */} -
- - - {loadingColumns ? ( -

컬럼 로딩 중...

- ) : !tableName ? ( -

테이블을 선택해주세요

- ) : ( -
- {/* 테이블 컬럼 */} -
- {baseColumns.map((column) => ( -
toggleColumn(column)} - > - toggleColumn(column)} - className="pointer-events-none h-3.5 w-3.5 flex-shrink-0" - /> - - {column.displayName} -
- ))} -
- - {/* 조인 컬럼 (테이블별 그룹) */} - {Object.keys(joinColumnsByTable).length > 0 && ( -
-
- - 엔티티 조인 컬럼 -
- {Object.entries(joinColumnsByTable).map(([refTable, refColumns]) => ( -
-
toggleJoinSection(refTable)} - > - {expandedJoinSections.has(refTable) ? ( - - ) : ( - - )} - {refTable} - ({refColumns.length}) -
- - {expandedJoinSections.has(refTable) && ( -
- {refColumns.map((column) => ( -
toggleColumn(column)} - > - toggleColumn(column)} - className="pointer-events-none h-3.5 w-3.5 flex-shrink-0" - /> - {column.displayName} -
- ))} -
- )} -
- ))} -
- )} -
- )} -
- - {/* 선택된 컬럼 상세 설정 */} - {configColumns.length > 0 && ( - <> - -
- -
- {configColumns.map((column, index) => { - const colInfo = columns.find((c) => c.columnName === column.key); - const showThousandSeparator = isNumberColumn(column.key); - return ( -
-
- - {column.isJoinColumn ? ( - - ) : ( - - )} - updateColumnTitle(column.key, e.target.value)} - placeholder="제목" - className="h-6 flex-1 text-xs" - /> - -
- {/* 숫자 컬럼인 경우 천단위 구분자 옵션 표시 */} - {showThousandSeparator && ( -
- toggleThousandSeparator(column.key, !!checked)} - className="h-3 w-3" - /> - -
- )} -
- ); - })} -
-
- - )} - - - - {/* 페이지네이션 설정 */} -
-
- updateConfig("pagination", checked)} - /> - -
- - {config.pagination !== false && ( -
- - -
- )} -
-
+ ); };