From 77faba7e77751dc687c76100dfa843193290fa07 Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 12 Nov 2025 16:39:50 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EB=B6=84=ED=95=A0=20=ED=8C=A8=EB=84=90?= =?UTF-8?q?=20=ED=95=84=ED=84=B0=EB=A7=81=20=EC=88=98=EC=A0=95=20=EB=B0=8F?= =?UTF-8?q?=20=EB=94=94=EB=B2=84=EA=B9=85=20=EB=A1=9C=EA=B7=B8=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 문제: - 분할 패널에서 필터 입력 시 검색이 제대로 작동하지 않음 - 백엔드가 {value: '전자', operator: 'contains'} 형태를 처리하지 못함 원인: - buildAdvancedSearchCondition이 필터 객체의 value 속성을 추출하지 않음 - 객체를 직접 문자열로 변환하여 '[object Object]'로 검색됨 해결: 1. tableManagementService.buildAdvancedSearchCondition 수정: - {value, operator} 형태의 필터 객체 감지 - actualValue 추출 및 operator 처리 - 모든 웹타입 케이스에 actualValue 전달 2. 프론트엔드 디버깅 로그 제거: - SplitPanelLayoutComponent의 console.log 제거 - 필터, 컬럼 가시성, API 호출 로그 정리 테스트 필요: - 분할 패널에서 필터 입력 → 정상 검색 확인 - 텍스트, 날짜, 숫자, 코드 타입 필터 동작 확인 --- .../src/services/tableManagementService.ts | 65 ++++++++++++++----- .../SplitPanelLayoutComponent.tsx | 34 ---------- 2 files changed, 48 insertions(+), 51 deletions(-) diff --git a/backend-node/src/services/tableManagementService.ts b/backend-node/src/services/tableManagementService.ts index 8bcec704..ac8b62fd 100644 --- a/backend-node/src/services/tableManagementService.ts +++ b/backend-node/src/services/tableManagementService.ts @@ -1069,12 +1069,28 @@ export class TableManagementService { paramCount: number; } | null> { try { + // 🔧 {value, operator} 형태의 필터 객체 처리 + let actualValue = value; + let operator = "contains"; // 기본값 + + if (typeof value === "object" && value !== null && "value" in value) { + actualValue = value.value; + operator = value.operator || "contains"; + + logger.info("🔍 필터 객체 처리:", { + columnName, + originalValue: value, + actualValue, + operator, + }); + } + // "__ALL__" 값이거나 빈 값이면 필터 조건을 적용하지 않음 if ( - value === "__ALL__" || - value === "" || - value === null || - value === undefined + actualValue === "__ALL__" || + actualValue === "" || + actualValue === null || + actualValue === undefined ) { return null; } @@ -1083,12 +1099,22 @@ export class TableManagementService { const columnInfo = await this.getColumnWebTypeInfo(tableName, columnName); if (!columnInfo) { - // 컬럼 정보가 없으면 기본 문자열 검색 - return { - whereClause: `${columnName}::text ILIKE $${paramIndex}`, - values: [`%${value}%`], - paramCount: 1, - }; + // 컬럼 정보가 없으면 operator에 따른 기본 검색 + switch (operator) { + case "equals": + return { + whereClause: `${columnName}::text = $${paramIndex}`, + values: [actualValue], + paramCount: 1, + }; + case "contains": + default: + return { + whereClause: `${columnName}::text ILIKE $${paramIndex}`, + values: [`%${actualValue}%`], + paramCount: 1, + }; + } } const webType = columnInfo.webType; @@ -1097,17 +1123,17 @@ export class TableManagementService { switch (webType) { case "date": case "datetime": - return this.buildDateRangeCondition(columnName, value, paramIndex); + return this.buildDateRangeCondition(columnName, actualValue, paramIndex); case "number": case "decimal": - return this.buildNumberRangeCondition(columnName, value, paramIndex); + return this.buildNumberRangeCondition(columnName, actualValue, paramIndex); case "code": return await this.buildCodeSearchCondition( tableName, columnName, - value, + actualValue, paramIndex ); @@ -1115,15 +1141,15 @@ export class TableManagementService { return await this.buildEntitySearchCondition( tableName, columnName, - value, + actualValue, paramIndex ); default: - // 기본 문자열 검색 + // 기본 문자열 검색 (actualValue 사용) return { whereClause: `${columnName}::text ILIKE $${paramIndex}`, - values: [`%${value}%`], + values: [`%${actualValue}%`], paramCount: 1, }; } @@ -1133,9 +1159,14 @@ export class TableManagementService { error ); // 오류 시 기본 검색으로 폴백 + let fallbackValue = value; + if (typeof value === "object" && value !== null && "value" in value) { + fallbackValue = value.value; + } + return { whereClause: `${columnName}::text ILIKE $${paramIndex}`, - values: [`%${value}%`], + values: [`%${fallbackValue}%`], paramCount: 1, }; } diff --git a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx index 68a686b8..91947094 100644 --- a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx +++ b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx @@ -185,11 +185,6 @@ export const SplitPanelLayoutComponent: React.FC // 🔄 컬럼 가시성 및 순서 처리 const visibleLeftColumns = useMemo(() => { const displayColumns = componentConfig.leftPanel?.columns || []; - console.log("🔍 [분할패널] visibleLeftColumns 계산:", { - displayColumns: displayColumns.length, - leftColumnVisibility: leftColumnVisibility.length, - leftColumnOrder: leftColumnOrder.length, - }); if (displayColumns.length === 0) return []; @@ -202,7 +197,6 @@ export const SplitPanelLayoutComponent: React.FC const colName = typeof col === 'string' ? col : (col.name || col.columnName); return visibilityMap.get(colName) !== false; }); - console.log("✅ [분할패널] 가시성 적용 후:", columns.length); } // 🔧 컬럼 순서 적용 @@ -215,7 +209,6 @@ export const SplitPanelLayoutComponent: React.FC const bIndex = orderMap.get(bName) ?? 999; return aIndex - bIndex; }); - console.log("✅ [분할패널] 순서 적용 후:", columns.map((c: any) => typeof c === 'string' ? c : (c.name || c.columnName))); } return columns; @@ -258,11 +251,6 @@ export const SplitPanelLayoutComponent: React.FC // 🎯 필터 조건을 API에 전달 (entityJoinApi 사용) const filters = Object.keys(searchValues).length > 0 ? searchValues : undefined; - console.log("📡 [분할패널] API 호출 시작:", { - tableName: leftTableName, - filters, - searchValues, - }); const result = await entityJoinApi.getTableDataWithJoins(leftTableName, { page: 1, @@ -271,11 +259,6 @@ export const SplitPanelLayoutComponent: React.FC enableEntityJoin: true, // 엔티티 조인 활성화 }); - console.log("📡 [분할패널] API 응답:", { - success: result.success, - dataLength: result.data?.length || 0, - totalItems: result.totalItems, - }); // 가나다순 정렬 (좌측 패널의 표시 컬럼 기준) const leftColumn = componentConfig.rightPanel?.relation?.leftColumn; @@ -946,12 +929,6 @@ export const SplitPanelLayoutComponent: React.FC // 🔧 컬럼 가시성 변경 시 localStorage에 저장 및 순서 업데이트 useEffect(() => { const leftTableName = componentConfig.leftPanel?.tableName; - console.log("🔍 [분할패널] 컬럼 가시성 변경 감지:", { - leftColumnVisibility: leftColumnVisibility.length, - leftTableName, - currentUserId, - visibility: leftColumnVisibility, - }); if (leftColumnVisibility.length > 0 && leftTableName && currentUserId) { // 순서 업데이트 @@ -959,13 +936,11 @@ export const SplitPanelLayoutComponent: React.FC .map((cv) => cv.columnName) .filter((name) => name !== "__checkbox__"); // 체크박스 제외 - console.log("✅ [분할패널] 컬럼 순서 업데이트:", newOrder); setLeftColumnOrder(newOrder); // localStorage에 저장 const storageKey = `table_column_visibility_${leftTableName}_${currentUserId}`; localStorage.setItem(storageKey, JSON.stringify(leftColumnVisibility)); - console.log("💾 [분할패널] localStorage 저장:", storageKey); } }, [leftColumnVisibility, componentConfig.leftPanel?.tableName, currentUserId]); @@ -979,16 +954,7 @@ export const SplitPanelLayoutComponent: React.FC // 🔄 필터 변경 시 데이터 다시 로드 useEffect(() => { - console.log("🔍 [분할패널] 필터 변경 감지:", { - leftFilters: leftFilters.length, - filters: leftFilters, - isDesignMode, - autoLoad: componentConfig.autoLoad, - searchValues, - }); - if (!isDesignMode && componentConfig.autoLoad !== false) { - console.log("✅ [분할패널] loadLeftData 호출 (필터 변경)"); loadLeftData(); } // eslint-disable-next-line react-hooks/exhaustive-deps