diff --git a/backend-node/src/controllers/entityJoinController.ts b/backend-node/src/controllers/entityJoinController.ts index 013b2034..ed2576cd 100644 --- a/backend-node/src/controllers/entityJoinController.ts +++ b/backend-node/src/controllers/entityJoinController.ts @@ -50,6 +50,9 @@ export class EntityJoinController { // search가 문자열인 경우 JSON 파싱 searchConditions = typeof search === "string" ? JSON.parse(search) : search; + + // 🔍 디버그: 파싱된 검색 조건 로깅 + logger.info(`🔍 파싱된 검색 조건:`, JSON.stringify(searchConditions, null, 2)); } catch (error) { logger.warn("검색 조건 파싱 오류:", error); searchConditions = {}; diff --git a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx index 9fc8c161..71503e91 100644 --- a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx +++ b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx @@ -1147,8 +1147,28 @@ export const SplitPanelLayoutComponent: React.FC // 🆕 추가 탭 데이터 로딩 함수 const loadTabData = useCallback( async (tabIndex: number, leftItem: any) => { + console.log(`📥 loadTabData 호출됨: tabIndex=${tabIndex}`, { + leftItem: leftItem ? Object.keys(leftItem) : null, + additionalTabs: componentConfig.rightPanel?.additionalTabs?.length, + isDesignMode, + }); + const tabConfig = componentConfig.rightPanel?.additionalTabs?.[tabIndex - 1]; - if (!tabConfig || !leftItem || isDesignMode) return; + + console.log(`📥 tabConfig:`, { + tabIndex, + configIndex: tabIndex - 1, + tabConfig: tabConfig ? { + tableName: tabConfig.tableName, + relation: tabConfig.relation, + dataFilter: tabConfig.dataFilter + } : null, + }); + + if (!tabConfig || !leftItem || isDesignMode) { + console.log(`⚠️ loadTabData 중단:`, { hasTabConfig: !!tabConfig, hasLeftItem: !!leftItem, isDesignMode }); + return; + } const tabTableName = tabConfig.tableName; if (!tabTableName) return; @@ -1160,6 +1180,14 @@ export const SplitPanelLayoutComponent: React.FC const leftColumn = tabConfig.relation?.leftColumn || keys?.[0]?.leftColumn; const rightColumn = tabConfig.relation?.foreignKey || keys?.[0]?.rightColumn; + console.log(`🔑 [추가탭 ${tabIndex}] 조인 키 분석:`, { + hasRelation: !!tabConfig.relation, + keys, + leftColumn, + rightColumn, + willUseJoin: !!(leftColumn && rightColumn), + }); + let resultData: any[] = []; if (leftColumn && rightColumn) { @@ -1171,14 +1199,22 @@ export const SplitPanelLayoutComponent: React.FC // 복합키 keys.forEach((key) => { if (key.leftColumn && key.rightColumn && leftItem[key.leftColumn] !== undefined) { - searchConditions[key.rightColumn] = leftItem[key.leftColumn]; + // operator: "equals"를 추가하여 정확한 값 매칭 (entity 타입 컬럼에서 코드값으로 검색) + searchConditions[key.rightColumn] = { + value: leftItem[key.leftColumn], + operator: "equals", + }; } }); } else { // 단일키 const leftValue = leftItem[leftColumn]; if (leftValue !== undefined) { - searchConditions[rightColumn] = leftValue; + // operator: "equals"를 추가하여 정확한 값 매칭 (entity 타입 컬럼에서 코드값으로 검색) + searchConditions[rightColumn] = { + value: leftValue, + operator: "equals", + }; } } @@ -1193,43 +1229,68 @@ export const SplitPanelLayoutComponent: React.FC resultData = result.data || []; } else { // 조인 조건이 없는 경우: 전체 데이터 조회 (독립 탭) + console.log(`📋 [추가탭 ${tabIndex}] 조인 없이 전체 데이터 조회: ${tabTableName}`); const { entityJoinApi } = await import("@/lib/api/entityJoin"); const result = await entityJoinApi.getTableDataWithJoins(tabTableName, { enableEntityJoin: true, size: 1000, }); resultData = result.data || []; + console.log(`📋 [추가탭 ${tabIndex}] 전체 데이터 조회 결과:`, resultData.length); } // 데이터 필터 적용 const dataFilter = tabConfig.dataFilter; // filters 또는 conditions 배열 지원 (DataFilterConfigPanel은 filters 사용) const filterConditions = dataFilter?.filters || dataFilter?.conditions || []; + + console.log(`🔍 [추가탭 ${tabIndex}] 필터 설정:`, { + enabled: dataFilter?.enabled, + filterConditions, + dataBeforeFilter: resultData.length, + }); + if (dataFilter?.enabled && filterConditions.length > 0) { + const beforeCount = resultData.length; resultData = resultData.filter((item: any) => { return filterConditions.every((cond: any) => { // columnName 또는 column 지원 const columnName = cond.columnName || cond.column; const value = item[columnName]; const condValue = cond.value; + + let result = true; switch (cond.operator) { case "equals": - return value === condValue; + result = value === condValue; + break; case "notEquals": - return value !== condValue; + result = value !== condValue; + break; case "contains": - return String(value).includes(String(condValue)); + result = String(value).includes(String(condValue)); + break; case "is_null": case "NULL": - return value === null || value === undefined || value === ""; + result = value === null || value === undefined || value === ""; + break; case "is_not_null": case "NOT NULL": - return value !== null && value !== undefined && value !== ""; + result = value !== null && value !== undefined && value !== ""; + break; default: - return true; + result = true; } + + // 첫 5개 항목만 로그 출력 + if (resultData.indexOf(item) < 5) { + console.log(` 필터 체크: ${columnName}=${value}, operator=${cond.operator}, result=${result}`); + } + + return result; }); }); + console.log(`🔍 [추가탭 ${tabIndex}] 필터 적용 후: ${beforeCount} → ${resultData.length}`); } // 중복 제거 적용 @@ -1301,6 +1362,12 @@ export const SplitPanelLayoutComponent: React.FC // 🆕 탭 변경 핸들러 const handleTabChange = useCallback( (newTabIndex: number) => { + console.log(`🔄 탭 변경: ${activeTabIndex} → ${newTabIndex}`, { + selectedLeftItem: !!selectedLeftItem, + tabsData: Object.keys(tabsData), + hasTabData: !!tabsData[newTabIndex], + }); + setActiveTabIndex(newTabIndex); // 선택된 좌측 항목이 있으면 해당 탭의 데이터 로드 @@ -1311,14 +1378,15 @@ export const SplitPanelLayoutComponent: React.FC loadRightData(selectedLeftItem); } } else { - // 추가 탭: 해당 탭 데이터가 없으면 로드 - if (!tabsData[newTabIndex]) { - loadTabData(newTabIndex, selectedLeftItem); - } + // 추가 탭: 항상 새로 로드 (필터 설정 변경 반영을 위해) + console.log(`🔄 추가 탭 ${newTabIndex} 데이터 로드 (항상 새로고침)`); + loadTabData(newTabIndex, selectedLeftItem); } + } else { + console.log(`⚠️ 좌측 항목이 선택되지 않아 탭 데이터를 로드하지 않음`); } }, - [selectedLeftItem, rightData, tabsData, loadRightData, loadTabData], + [selectedLeftItem, rightData, tabsData, loadRightData, loadTabData, activeTabIndex], ); // 우측 항목 확장/축소 토글 diff --git a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel.tsx index 9810388f..048fb385 100644 --- a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel.tsx +++ b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel.tsx @@ -237,7 +237,12 @@ const AdditionalTabConfigPanel: React.FC = ({ // 탭 업데이트 헬퍼 const updateTab = (updates: Partial) => { const newTabs = [...(config.rightPanel?.additionalTabs || [])]; - newTabs[tabIndex] = { ...tab, ...updates }; + // undefined 값도 명시적으로 덮어쓰기 위해 Object.assign 대신 직접 처리 + const updatedTab = { ...tab }; + Object.keys(updates).forEach((key) => { + (updatedTab as any)[key] = (updates as any)[key]; + }); + newTabs[tabIndex] = updatedTab; updateRightPanel({ additionalTabs: newTabs }); }; @@ -393,21 +398,31 @@ const AdditionalTabConfigPanel: React.FC = ({
{ - updateTab({ - relation: { - ...tab.relation, - type: "join", - keys: [{ leftColumn: tab.relation?.keys?.[0]?.leftColumn || "", rightColumn: value }], - }, - }); + if (value === "__none__") { + // 선택 안 함 - 조인 키 제거 + updateTab({ + relation: undefined, + }); + } else { + updateTab({ + relation: { + ...tab.relation, + type: "join", + keys: [{ leftColumn: tab.relation?.keys?.[0]?.leftColumn || "", rightColumn: value }], + }, + }); + } }} > + + 선택 안 함 (전체 데이터) + {tabColumns.map((col) => ( {col.columnLabel || col.columnName}