From e937ba9161640d5558107bebd00aa3dba305948f Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 15 Jan 2026 16:21:55 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=EC=84=A4=EC=A0=95=20UI=20=ED=91=9C=EC=A4=80?= =?UTF-8?q?=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=98=EA=B3=A0,=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=EC=84=A0=ED=83=9D=20Combobox=20=EB=B0=8F?= =?UTF-8?q?=20=EC=9D=BD=EA=B8=B0=EC=A0=84=EC=9A=A9=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=EC=9D=84=20=EA=B5=AC=ED=98=84=ED=96=88?= =?UTF-8?q?=EC=8A=B5=EB=8B=88=EB=8B=A4.=20=EB=98=90=ED=95=9C,=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=EC=9D=B4=EB=A6=84=20=EA=B3=84=EC=82=B0=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=9D=84=20=EA=B0=9C=EC=84=A0=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=EC=82=AC=EC=9A=A9=20=EC=8B=9C=20=EC=98=AC=EB=B0=94?= =?UTF-8?q?=EB=A5=B8=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=EC=9D=84=20=ED=91=9C=EC=8B=9C=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=ED=96=88=EC=8A=B5=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursor/rules/component-development-guide.mdc | 85 +- .../src/services/tableManagementService.ts | 7 +- .../table-list/TableListConfigPanel.tsx | 1010 ++++------------- 3 files changed, 338 insertions(+), 764 deletions(-) diff --git a/.cursor/rules/component-development-guide.mdc b/.cursor/rules/component-development-guide.mdc index f9a807a7..046d231f 100644 --- a/.cursor/rules/component-development-guide.mdc +++ b/.cursor/rules/component-development-guide.mdc @@ -67,7 +67,90 @@ interface UnifiedRepeaterConfig { } ``` -### 저장 테이블 설정 UI 표준 +### 조회 테이블 설정 UI 표준 (테이블 리스트) + +테이블 리스트 등 조회용 컴포넌트의 ConfigPanel에서: + +```tsx +// 현재 선택된 테이블 카드 형태로 표시 +
+ +
+
+ {config.customTableName || screenTableName || "테이블 미선택"} +
+
+ {config.useCustomTable ? "커스텀 테이블" : "화면 기본 테이블"} +
+
+
+ +// 테이블 선택 Combobox (기본/전체 그룹) + + + + + + + + + {/* 그룹 1: 화면 기본 테이블 */} + {screenTableName && ( + + { + handleChange("useCustomTable", false); + handleChange("customTableName", undefined); + handleChange("selectedTable", screenTableName); + handleChange("columns", []); // 테이블 변경 시 컬럼 초기화 + }} + > + + {screenTableName} + + + )} + + {/* 그룹 2: 전체 테이블 */} + + {availableTables + .filter((table) => table.tableName !== screenTableName) + .map((table) => ( + { + handleChange("useCustomTable", true); + handleChange("customTableName", table.tableName); + handleChange("selectedTable", table.tableName); + handleChange("columns", []); // 테이블 변경 시 컬럼 초기화 + }} + > + + {table.displayName || table.tableName} + + ))} + + + + + + +// 읽기전용 설정 +
+ handleChange("isReadOnly", checked)} + /> + +
+``` + +### 저장 테이블 설정 UI 표준 (리피터) 리피터 등 저장 기능이 있는 컴포넌트의 ConfigPanel에서: diff --git a/backend-node/src/services/tableManagementService.ts b/backend-node/src/services/tableManagementService.ts index 4e4d918c..3e3414a3 100644 --- a/backend-node/src/services/tableManagementService.ts +++ b/backend-node/src/services/tableManagementService.ts @@ -4076,12 +4076,17 @@ export class TableManagementService { // table_type_columns에서 입력타입 정보 조회 // 회사별 설정 우선, 없으면 기본 설정(*) fallback + // detail_settings 컬럼에 유효하지 않은 JSON이 있을 수 있으므로 안전하게 처리 const rawInputTypes = await query( `SELECT DISTINCT ON (ttc.column_name) ttc.column_name as "columnName", COALESCE(cl.column_label, ttc.column_name) as "displayName", ttc.input_type as "inputType", - COALESCE(ttc.detail_settings::jsonb, '{}'::jsonb) as "detailSettings", + CASE + WHEN ttc.detail_settings IS NULL OR ttc.detail_settings = '' THEN '{}'::jsonb + WHEN ttc.detail_settings ~ '^\\s*\\{.*\\}\\s*$' THEN ttc.detail_settings::jsonb + ELSE '{}'::jsonb + END as "detailSettings", ttc.is_nullable as "isNullable", ic.data_type as "dataType", ttc.company_code as "companyCode" diff --git a/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx b/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx index 3c9aab47..9e7f63c4 100644 --- a/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx +++ b/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx @@ -10,7 +10,7 @@ import { TableListConfig, ColumnConfig } from "./types"; import { entityJoinApi } from "@/lib/api/entityJoin"; import { tableTypeApi } from "@/lib/api/screen"; import { tableManagementApi } from "@/lib/api/tableManagement"; -import { Plus, Trash2, ArrowUp, ArrowDown, ChevronsUpDown, Check, Lock, Unlock } from "lucide-react"; +import { Plus, Trash2, ArrowUp, ArrowDown, ChevronsUpDown, Check, Lock, Unlock, Database, Table2, Link2 } from "lucide-react"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { cn } from "@/lib/utils"; @@ -48,6 +48,7 @@ export const TableListConfigPanel: React.FC = ({ const [availableTables, setAvailableTables] = useState>([]); const [loadingTables, setLoadingTables] = useState(false); + const [tableComboboxOpen, setTableComboboxOpen] = useState(false); // 테이블 Combobox 열림 상태 const [availableColumns, setAvailableColumns] = useState< Array<{ columnName: string; dataType: string; label?: string; input_type?: string }> >([]); @@ -141,31 +142,45 @@ export const TableListConfigPanel: React.FC = ({ fetchTables(); }, []); + // 🆕 실제 사용할 테이블 이름 계산 (customTableName 우선) + const targetTableName = React.useMemo(() => { + if (config.useCustomTable && config.customTableName) { + return config.customTableName; + } + return config.selectedTable || screenTableName; + }, [config.useCustomTable, config.customTableName, config.selectedTable, screenTableName]); + // 선택된 테이블의 컬럼 목록 설정 useEffect(() => { console.log( - "🔍 useEffect 실행됨 - config.selectedTable:", + "🔍 useEffect 실행됨 - targetTableName:", + targetTableName, + "config.useCustomTable:", + config.useCustomTable, + "config.customTableName:", + config.customTableName, + "config.selectedTable:", config.selectedTable, "screenTableName:", screenTableName, ); - // 컴포넌트에 명시적으로 테이블이 선택되었거나, 화면에 연결된 테이블이 있는 경우 컬럼 목록 표시 - const shouldShowColumns = config.selectedTable || screenTableName; - - if (!shouldShowColumns) { + if (!targetTableName) { console.log("🔧 컬럼 목록 숨김 - 테이블이 선택되지 않음"); setAvailableColumns([]); return; } - // tableColumns prop을 우선 사용 - if (tableColumns && tableColumns.length > 0) { + // 🆕 customTableName이 설정된 경우 반드시 API에서 가져오기 + // tableColumns prop은 화면의 기본 테이블 컬럼이므로, customTableName 사용 시 무시 + const shouldUseTableColumnsProp = !config.useCustomTable && 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, // 🆕 input_type 추가 + input_type: column.input_type || column.inputType, })); setAvailableColumns(mappedColumns); @@ -177,56 +192,58 @@ export const TableListConfigPanel: React.FC = ({ columns: config.columns || [], }); } - } else if (config.selectedTable || screenTableName) { - // API에서 컬럼 정보 가져오기 + } else { + // API에서 컬럼 정보 가져오기 - tableManagementApi 사용 const fetchColumns = async () => { - const tableName = config.selectedTable || screenTableName; - if (!tableName) { - setAvailableColumns([]); - return; - } - - console.log("🔧 API에서 컬럼 정보 가져오기:", tableName); + console.log("🔧 API에서 컬럼 정보 가져오기 (tableManagementApi):", targetTableName); try { - const response = await fetch(`/api/tables/${tableName}/columns`); - if (response.ok) { - const result = await response.json(); - if (result.success && result.data) { - console.log("🔧 API 응답 컬럼 데이터:", result.data); + const result = await tableManagementApi.getColumnList(targetTableName); + console.log("🔧 tableManagementApi 응답:", result); + + if (result.success && result.data) { + // API 응답 구조: { columns: [...], total, page, ... } + const columns = Array.isArray(result.data) ? result.data : result.data.columns; + console.log("🔧 컬럼 배열:", columns); + + if (columns && Array.isArray(columns)) { setAvailableColumns( - result.data.map((col: any) => ({ + columns.map((col: any) => ({ columnName: col.columnName, dataType: col.dataType, - label: col.displayName || col.columnName, - input_type: col.input_type || col.inputType, // 🆕 input_type 추가 + label: col.displayName || col.columnLabel || col.columnName, + input_type: col.input_type || col.inputType, })), ); + } else { + console.error("🔧 컬럼 배열을 찾을 수 없음:", result.data); + setAvailableColumns([]); } + } else { + console.error("🔧 컬럼 조회 실패:", result.message); + setAvailableColumns([]); } } catch (error) { console.error("컬럼 목록 가져오기 실패:", error); + setAvailableColumns([]); } }; fetchColumns(); - } else { - setAvailableColumns([]); } - }, [config.selectedTable, screenTableName, tableColumns]); + }, [targetTableName, config.useCustomTable, tableColumns]); - // Entity 조인 컬럼 정보 가져오기 + // Entity 조인 컬럼 정보 가져오기 - targetTableName 사용 useEffect(() => { const fetchEntityJoinColumns = async () => { - const tableName = config.selectedTable || screenTableName; - if (!tableName) { + if (!targetTableName) { setEntityJoinColumns({ availableColumns: [], joinTables: [] }); return; } setLoadingEntityJoins(true); try { - console.log("🔗 Entity 조인 컬럼 정보 가져오기:", tableName); - const result = await entityJoinApi.getEntityJoinColumns(tableName); + console.log("🔗 Entity 조인 컬럼 정보 가져오기:", targetTableName); + const result = await entityJoinApi.getEntityJoinColumns(targetTableName); console.log("✅ Entity 조인 컬럼 응답:", result); setEntityJoinColumns({ @@ -242,7 +259,7 @@ export const TableListConfigPanel: React.FC = ({ }; fetchEntityJoinColumns(); - }, [config.selectedTable, screenTableName]); + }, [targetTableName]); // 🆕 제외 필터용 참조 테이블 컬럼 가져오기 useEffect(() => { @@ -752,121 +769,118 @@ export const TableListConfigPanel: React.FC = ({
{/* 커스텀 테이블 사용 여부 */} + {/* 현재 선택된 테이블 표시 (카드 형태) */} +
+ +
+
+ {config.customTableName || config.selectedTable || screenTableName || "테이블 미선택"} +
+
+ {config.useCustomTable ? "커스텀 테이블" : "화면 기본 테이블"} + {config.isReadOnly && " (읽기전용)"} +
+
+
+ + {/* 테이블 선택 Combobox (기본/전체 그룹) */} + + + + + + + + + 테이블을 찾을 수 없습니다 + + {/* 그룹 1: 화면 기본 테이블 */} + {screenTableName && ( + + { + console.log("🔍 기본 테이블 선택:", currentValue, screenTableName); + onChange({ + ...config, + useCustomTable: false, + customTableName: undefined, + selectedTable: screenTableName, + columns: [], + }); + setTableComboboxOpen(false); + }} + className="text-xs cursor-pointer" + > + + + {screenTableName} + + + )} + + {/* 그룹 2: 전체 테이블 */} + + {availableTables + .filter((table) => table.tableName !== screenTableName) + .map((table) => ( + { + console.log("🔍 전체 테이블 선택:", currentValue, table.tableName); + onChange({ + ...config, + useCustomTable: true, + customTableName: table.tableName, + selectedTable: table.tableName, + columns: [], + }); + setTableComboboxOpen(false); + }} + className="text-xs cursor-pointer" + > + + + {table.displayName || table.tableName} + + ))} + + + + + + + {/* 읽기전용 설정 */}
{ - handleChange("useCustomTable", checked); - if (!checked) { - // 커스텀 테이블 해제 시 화면 메인 테이블로 복원 - handleChange("customTableName", undefined); - handleChange("selectedTable", screenTableName); - } - }} + id="isReadOnly" + checked={config.isReadOnly || false} + onCheckedChange={(checked) => handleChange("isReadOnly", checked)} /> - -
- - {config.useCustomTable && ( -
- {/* 테이블 선택 */} -
- - - - - - - - - - 테이블을 찾을 수 없습니다 - - {availableTables.map((table) => ( - { - handleChange("customTableName", table.tableName); - handleChange("selectedTable", table.tableName); - // 테이블 변경 시 컬럼 초기화 - handleChange("columns", []); - }} - className="text-xs" - > - - {table.displayName || table.tableName} - - ))} - - - - - -
- - {/* 읽기전용 설정 */} -
- handleChange("isReadOnly", checked)} - /> - -
- - {config.customTableName && ( -
- 현재 설정: {config.customTableName} 테이블 사용 - {config.isReadOnly && " (읽기전용)"} -
- )} -
- )} - - {!config.useCustomTable && screenTableName && ( -
- 현재: 화면 메인 테이블 ({screenTableName}) 사용 -
- )} - - - {/* 테이블 제목 설정 */} -
-
-

테이블 제목

-
-
-
- - handleChange("title", e.target.value)} - placeholder="테이블 제목 입력..." - className="h-8 text-xs" - /> -

우선순위: 사용자 입력 제목 → 테이블 라벨명 → 테이블명

+
@@ -1202,26 +1216,48 @@ export const TableListConfigPanel: React.FC = ({ <>
-

컬럼 추가

+

컬럼 선택

+

표시할 컬럼을 선택하세요


{availableColumns.length > 0 ? ( -
- {availableColumns - .filter((col) => !config.columns?.find((c) => c.columnName === col.columnName)) - .map((column) => ( - - ))} + { + if (isAdded) { + handleChange("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} + {column.input_type || column.dataType} +
+ ); + })}
) : (
컬럼 정보를 불러오는 중...
@@ -1232,41 +1268,67 @@ export const TableListConfigPanel: React.FC = ({ {entityJoinColumns.joinTables.length > 0 && (
-

Entity 조인 컬럼 추가

+

Entity 조인 컬럼

+

연관 테이블의 컬럼을 선택하세요


{entityJoinColumns.joinTables.map((joinTable, tableIndex) => (
-
- {joinTable.tableName} +
+ + {joinTable.tableName} {joinTable.currentDisplayColumn}
- {joinTable.availableColumns.map((column, colIndex) => { - const matchingJoinColumn = entityJoinColumns.availableColumns.find( - (jc) => jc.tableName === joinTable.tableName && jc.columnName === column.columnName, - ); +
+ {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, - ); + const isAlreadyAdded = config.columns?.some( + (col) => col.columnName === matchingJoinColumn?.joinAlias, + ); - return matchingJoinColumn && !isAlreadyAdded ? ( - - ) : null; - })} + if (!matchingJoinColumn) return null; + + return ( +
{ + if (isAlreadyAdded) { + // 컬럼 제거 + handleChange("columns", config.columns?.filter((c) => c.columnName !== matchingJoinColumn.joinAlias) || []); + } else { + // 컬럼 추가 + addEntityColumn(matchingJoinColumn); + } + }} + > + { + if (isAlreadyAdded) { + handleChange("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} +
+ ); + })} +
))}
@@ -1275,178 +1337,6 @@ export const TableListConfigPanel: React.FC = ({ )} - {screenTableName && ( -
-
-

컬럼 설정

-

컬럼별 표시 옵션을 설정하세요

-
-
- - {/* 간결한 리스트 형식 컬럼 설정 */} -
- {config.columns?.map((column, index) => { - // 해당 컬럼의 input_type 확인 - const columnInfo = availableColumns.find((col) => col.columnName === column.columnName); - const isNumberType = columnInfo?.input_type === "number" || columnInfo?.input_type === "decimal"; - - return ( -
-
- {/* 컬럼명 */} - - {columnInfo?.label || column.displayName || column.columnName} - - - {/* 숫자 타입인 경우 천단위 구분자 설정 */} - {isNumberType && ( -
- { - updateColumn(column.columnName, { thousandSeparator: checked as boolean }); - }} - className="h-3 w-3" - /> - -
- )} -
- - {/* 편집 가능 여부 + 필터 체크박스 */} -
- {/* 🆕 편집 가능 여부 토글 */} - - - {/* 필터 체크박스 */} - f.columnName === column.columnName) || false} - onCheckedChange={(checked) => { - const currentFilters = config.filter?.filters || []; - const columnLabel = columnInfo?.label || column.displayName || column.columnName; - - if (checked) { - // 필터 추가 - handleChange("filter", { - ...config.filter, - enabled: true, - filters: [ - ...currentFilters, - { - columnName: column.columnName, - label: columnLabel, - type: "text", - }, - ], - }); - } else { - // 필터 제거 - handleChange("filter", { - ...config.filter, - filters: currentFilters.filter((f) => f.columnName !== column.columnName), - }); - } - }} - className="h-3 w-3" - title="필터에 추가" - /> -
- - {/* 순서 변경 + 삭제 버튼 */} -
- - - -
-
- ); - })} -
-
- )} - - {/* 필터 간격 설정 */} - {config.filter?.enabled && ( -
-
-

필터 간격

-
-
-
- - { - const value = Math.max(0, Math.min(200, parseInt(e.target.value) || 40)); - handleChange("filter", { - ...config.filter, - bottomSpacing: value, - }); - }} - min={0} - max={200} - step={10} - placeholder="40" - className="h-8 text-xs" - /> -

기본값: 40px (0-200px 범위, 10px 단위 권장)

-
-
- )} - {/* 🆕 데이터 필터링 설정 */}
@@ -1470,410 +1360,6 @@ export const TableListConfigPanel: React.FC = ({ />
- {/* 🆕 연결된 필터 설정 (셀렉트박스 등 다른 컴포넌트 값으로 필터링) */} -
-
-

연결된 필터

-

- 셀렉트박스 등 다른 컴포넌트의 값으로 테이블 데이터를 실시간 필터링합니다 -

-
-
- - {/* 연결된 필터 목록 */} -
- {(config.linkedFilters || []).map((filter, index) => ( -
-
-
- { - const newFilters = [...(config.linkedFilters || [])]; - newFilters[index] = { ...filter, sourceComponentId: e.target.value }; - handleChange("linkedFilters", newFilters); - }} - className="h-7 flex-1 text-xs" - /> - - - - - - - - - - 컬럼을 찾을 수 없습니다 - - {availableColumns.map((col) => ( - { - const newFilters = [...(config.linkedFilters || [])]; - newFilters[index] = { ...filter, targetColumn: col.columnName }; - handleChange("linkedFilters", newFilters); - }} - className="text-xs" - > - - {col.label || col.columnName} - - ))} - - - - - -
-
- -
- ))} - - {/* 연결된 필터 추가 버튼 */} - - -

- 예: 셀렉트박스(ID: select-basic-123)의 값으로 테이블의 inbound_type 컬럼을 필터링 -

-
-
- - {/* 🆕 제외 필터 설정 (다른 테이블에 이미 존재하는 데이터 제외) */} -
-
-

제외 필터

-

- 다른 테이블에 이미 존재하는 데이터를 목록에서 제외합니다 -

-
-
- - {/* 제외 필터 활성화 */} -
- { - handleChange("excludeFilter", { - ...config.excludeFilter, - enabled: checked as boolean, - }); - }} - /> - -
- - {config.excludeFilter?.enabled && ( -
- {/* 참조 테이블 선택 */} -
- - - - - - - - - - 테이블을 찾을 수 없습니다 - - {availableTables.map((table) => ( - { - handleChange("excludeFilter", { - ...config.excludeFilter, - referenceTable: table.tableName, - referenceColumn: undefined, - sourceColumn: undefined, - filterColumn: undefined, - filterValueField: undefined, - }); - }} - className="text-xs" - > - - {table.displayName || table.tableName} - - ))} - - - - - -
- - {config.excludeFilter?.referenceTable && ( - <> - {/* 비교 컬럼 설정 - 한 줄에 두 개 */} -
- {/* 참조 컬럼 (매핑 테이블) */} -
- - - - - - - - - - 없음 - - {referenceTableColumns.map((col) => ( - { - handleChange("excludeFilter", { - ...config.excludeFilter, - referenceColumn: col.columnName, - }); - }} - className="text-xs" - > - - {col.label || col.columnName} - - ))} - - - - - -
- - {/* 소스 컬럼 (현재 테이블) */} -
- - - - - - - - - - 없음 - - {availableColumns.map((col) => ( - { - handleChange("excludeFilter", { - ...config.excludeFilter, - sourceColumn: col.columnName, - }); - }} - className="text-xs" - > - - {col.label || col.columnName} - - ))} - - - - - -
-
- - {/* 조건 필터 - 특정 조건의 데이터만 제외 */} -
- -

- 특정 조건의 데이터만 제외하려면 설정하세요 (예: 특정 거래처의 품목만) -

-
- {/* 필터 컬럼 (매핑 테이블) */} - - - - - - - - - 없음 - - { - handleChange("excludeFilter", { - ...config.excludeFilter, - filterColumn: undefined, - filterValueField: undefined, - }); - }} - className="text-muted-foreground text-xs" - > - - 사용 안함 - - {referenceTableColumns.map((col) => ( - { - // 필터 컬럼 선택 시 같은 이름의 필드를 자동으로 설정 - handleChange("excludeFilter", { - ...config.excludeFilter, - filterColumn: col.columnName, - filterValueField: col.columnName, // 같은 이름으로 자동 설정 - filterValueSource: "url", - }); - }} - className="text-xs" - > - - {col.label || col.columnName} - - ))} - - - - - - - {/* 필터 값 필드명 (부모 화면에서 전달받는 필드) */} - { - handleChange("excludeFilter", { - ...config.excludeFilter, - filterValueField: e.target.value, - }); - }} - disabled={!config.excludeFilter?.filterColumn} - className="h-8 text-xs" - /> -
-
- - )} - - {/* 설정 요약 */} - {config.excludeFilter?.referenceTable && - config.excludeFilter?.referenceColumn && - config.excludeFilter?.sourceColumn && ( -
- 설정 요약: {config.selectedTable || screenTableName}. - {config.excludeFilter.sourceColumn} 가 {config.excludeFilter.referenceTable}. - {config.excludeFilter.referenceColumn} 에 - {config.excludeFilter.filterColumn && config.excludeFilter.filterValueField && ( - <> - {" "} - ({config.excludeFilter.filterColumn}=URL의 {config.excludeFilter.filterValueField}일 때) - - )}{" "} - 이미 있으면 제외 -
- )} -
- )} -
);