diff --git a/backend-node/src/services/tableManagementService.ts b/backend-node/src/services/tableManagementService.ts index 9a8623a0..e2f26138 100644 --- a/backend-node/src/services/tableManagementService.ts +++ b/backend-node/src/services/tableManagementService.ts @@ -1447,7 +1447,8 @@ export class TableManagementService { tableName, columnName, actualValue, - paramIndex + paramIndex, + operator // operator 전달 (equals면 직접 매칭) ); default: @@ -1676,7 +1677,8 @@ export class TableManagementService { tableName: string, columnName: string, value: any, - paramIndex: number + paramIndex: number, + operator: string = "contains" // 연결 필터에서 "equals"로 전달되면 직접 매칭 ): Promise<{ whereClause: string; values: any[]; @@ -1688,7 +1690,7 @@ export class TableManagementService { columnName ); - // 🆕 배열 처리: IN 절 사용 + // 배열 처리: IN 절 사용 if (Array.isArray(value)) { if (value.length === 0) { // 빈 배열이면 항상 false 조건 @@ -1720,13 +1722,35 @@ export class TableManagementService { } if (typeof value === "string" && value.trim() !== "") { - const displayColumn = entityTypeInfo.displayColumn || "name"; + // equals 연산자인 경우: 직접 값 매칭 (연결 필터에서 코드 값으로 필터링 시 사용) + if (operator === "equals") { + logger.info( + `🔍 [buildEntitySearchCondition] equals 연산자 - 직접 매칭: ${columnName} = ${value}` + ); + return { + whereClause: `${columnName} = $${paramIndex}`, + values: [value], + paramCount: 1, + }; + } + + // contains 연산자 (기본): 참조 테이블의 표시 컬럼으로 검색 const referenceColumn = entityTypeInfo.referenceColumn || "id"; + const referenceTable = entityTypeInfo.referenceTable; + + // displayColumn이 비어있거나 "none"이면 참조 테이블에서 자동 감지 (entityJoinService와 동일한 로직) + let displayColumn = entityTypeInfo.displayColumn; + if (!displayColumn || displayColumn === "none" || displayColumn === "") { + displayColumn = await this.findDisplayColumnForTable(referenceTable, referenceColumn); + logger.info( + `🔍 [buildEntitySearchCondition] displayColumn 자동 감지: ${referenceTable} -> ${displayColumn}` + ); + } // 참조 테이블의 표시 컬럼으로 검색 return { whereClause: `EXISTS ( - SELECT 1 FROM ${entityTypeInfo.referenceTable} ref + SELECT 1 FROM ${referenceTable} ref WHERE ref.${referenceColumn} = ${columnName} AND ref.${displayColumn} ILIKE $${paramIndex} )`, @@ -1754,6 +1778,66 @@ export class TableManagementService { } } + /** + * 참조 테이블에서 표시 컬럼 자동 감지 (entityJoinService와 동일한 우선순위) + * 우선순위: *_name > name > label/*_label > title > referenceColumn + */ + private async findDisplayColumnForTable( + tableName: string, + referenceColumn?: string + ): Promise { + try { + const result = await query<{ column_name: string }>( + `SELECT column_name + FROM information_schema.columns + WHERE table_name = $1 + AND table_schema = 'public' + ORDER BY ordinal_position`, + [tableName] + ); + + const allColumns = result.map((r) => r.column_name); + + // entityJoinService와 동일한 우선순위 + // 1. *_name 컬럼 (item_name, customer_name, process_name 등) - company_name 제외 + const nameColumn = allColumns.find( + (col) => col.endsWith("_name") && col !== "company_name" + ); + if (nameColumn) { + return nameColumn; + } + + // 2. name 컬럼 + if (allColumns.includes("name")) { + return "name"; + } + + // 3. label 또는 *_label 컬럼 + const labelColumn = allColumns.find( + (col) => col === "label" || col.endsWith("_label") + ); + if (labelColumn) { + return labelColumn; + } + + // 4. title 컬럼 + if (allColumns.includes("title")) { + return "title"; + } + + // 5. 참조 컬럼 (referenceColumn) + if (referenceColumn && allColumns.includes(referenceColumn)) { + return referenceColumn; + } + + // 6. 기본값: 첫 번째 비-id 컬럼 또는 id + return allColumns.find((col) => col !== "id") || "id"; + } catch (error) { + logger.error(`표시 컬럼 감지 실패: ${tableName}`, error); + return referenceColumn || "id"; // 오류 시 기본값 + } + } + /** * 불린 검색 조건 구성 */ diff --git a/frontend/lib/registry/components/card-display/CardDisplayComponent.tsx b/frontend/lib/registry/components/card-display/CardDisplayComponent.tsx index 54b32143..620715fd 100644 --- a/frontend/lib/registry/components/card-display/CardDisplayComponent.tsx +++ b/frontend/lib/registry/components/card-display/CardDisplayComponent.tsx @@ -233,18 +233,20 @@ export const CardDisplayComponent: React.FC = ({ linkedFilterValues = splitPanelContext.getLinkedFilterValues(); // 현재 테이블에 해당하는 필터만 추출 (테이블명.컬럼명 형식에서) + // 연결 필터는 코드 값이므로 정확한 매칭(equals)을 사용해야 함 const tableSpecificFilters: Record = {}; for (const [key, value] of Object.entries(linkedFilterValues)) { // key가 "테이블명.컬럼명" 형식인 경우 if (key.includes(".")) { const [tblName, columnName] = key.split("."); if (tblName === tableNameToUse) { - tableSpecificFilters[columnName] = value; + // 연결 필터는 코드 값이므로 equals 연산자 사용 + tableSpecificFilters[columnName] = { value, operator: "equals" }; hasLinkedFiltersConfigured = true; } } else { - // 테이블명 없이 컬럼명만 있는 경우 그대로 사용 - tableSpecificFilters[key] = value; + // 테이블명 없이 컬럼명만 있는 경우 그대로 사용 (equals) + tableSpecificFilters[key] = { value, operator: "equals" }; } } linkedFilterValues = tableSpecificFilters; diff --git a/frontend/lib/registry/components/table-list/TableListComponent.tsx b/frontend/lib/registry/components/table-list/TableListComponent.tsx index 651675b8..20aafd7f 100644 --- a/frontend/lib/registry/components/table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/table-list/TableListComponent.tsx @@ -1526,16 +1526,18 @@ export const TableListComponent: React.FC = ({ console.log("🔗 [TableList] 연결 필터 원본:", allLinkedFilters); // 현재 테이블에 해당하는 필터만 추출 (테이블명.컬럼명 형식에서) + // 연결 필터는 코드 값이므로 정확한 매칭(equals)을 사용해야 함 for (const [key, value] of Object.entries(allLinkedFilters)) { if (key.includes(".")) { const [tableName, columnName] = key.split("."); if (tableName === tableConfig.selectedTable) { - linkedFilterValues[columnName] = value; + // 연결 필터는 코드 값이므로 equals 연산자 사용 + linkedFilterValues[columnName] = { value, operator: "equals" }; hasLinkedFiltersConfigured = true; // 이 테이블에 대한 필터가 있음 } } else { - // 테이블명 없이 컬럼명만 있는 경우 그대로 사용 - linkedFilterValues[key] = value; + // 테이블명 없이 컬럼명만 있는 경우 그대로 사용 (equals) + linkedFilterValues[key] = { value, operator: "equals" }; } } @@ -1560,7 +1562,8 @@ export const TableListComponent: React.FC = ({ // 현재 테이블에 동일한 컬럼이 있는지 확인 if (tableColumns.includes(colName)) { - linkedFilterValues[colName] = colValue; + // 자동 컬럼 매칭도 equals 연산자 사용 + linkedFilterValues[colName] = { value: colValue, operator: "equals" }; hasLinkedFiltersConfigured = true; console.log(`🔗 [TableList] 자동 컬럼 매칭: ${colName} = ${colValue}`); }