From e8fc6643520dbd733fb20e18634169f964fb345f Mon Sep 17 00:00:00 2001 From: leeheejin Date: Wed, 21 Jan 2026 10:32:37 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EB=B6=84=ED=95=A0=ED=8C=A8=EB=84=90=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B2=84=ED=8A=BC=20=ED=81=B4=EB=A6=AD=20?= =?UTF-8?q?=EC=8B=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B6=88=EB=9F=AC?= =?UTF-8?q?=EC=98=A4=EA=B8=B0=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Primary Key 컬럼명을 프론트엔드에서 백엔드로 전달하도록 개선 - 백엔드 자동 감지 실패 시에도 클라이언트 제공 값 우선 사용 - Primary Key 찾기 로직 개선 (설정값 > id > ID > non-null 필드) --- backend-node/src/routes/dataRoutes.ts | 11 ++++-- backend-node/src/services/dataService.ts | 35 ++++++++++++------- frontend/components/common/ScreenModal.tsx | 8 ++++- .../SplitPanelLayoutComponent.tsx | 35 +++++++++++++++---- 4 files changed, 66 insertions(+), 23 deletions(-) diff --git a/backend-node/src/routes/dataRoutes.ts b/backend-node/src/routes/dataRoutes.ts index 574f1cf8..a7757397 100644 --- a/backend-node/src/routes/dataRoutes.ts +++ b/backend-node/src/routes/dataRoutes.ts @@ -606,7 +606,7 @@ router.get( }); } - const { enableEntityJoin, groupByColumns } = req.query; + const { enableEntityJoin, groupByColumns, primaryKeyColumn } = req.query; const enableEntityJoinFlag = enableEntityJoin === "true" || (typeof enableEntityJoin === "boolean" && enableEntityJoin); @@ -626,17 +626,22 @@ router.get( } } + // 🆕 primaryKeyColumn 파싱 + const primaryKeyColumnStr = typeof primaryKeyColumn === "string" ? primaryKeyColumn : undefined; + console.log(`🔍 레코드 상세 조회: ${tableName}/${id}`, { enableEntityJoin: enableEntityJoinFlag, groupByColumns: groupByColumnsArray, + primaryKeyColumn: primaryKeyColumnStr, }); - // 레코드 상세 조회 (Entity Join 옵션 + 그룹핑 옵션 포함) + // 레코드 상세 조회 (Entity Join 옵션 + 그룹핑 옵션 + Primary Key 컬럼 포함) const result = await dataService.getRecordDetail( tableName, id, enableEntityJoinFlag, - groupByColumnsArray + groupByColumnsArray, + primaryKeyColumnStr // 🆕 Primary Key 컬럼명 전달 ); if (!result.success) { diff --git a/backend-node/src/services/dataService.ts b/backend-node/src/services/dataService.ts index 8c6e63f0..60de20db 100644 --- a/backend-node/src/services/dataService.ts +++ b/backend-node/src/services/dataService.ts @@ -490,7 +490,8 @@ class DataService { tableName: string, id: string | number, enableEntityJoin: boolean = false, - groupByColumns: string[] = [] + groupByColumns: string[] = [], + primaryKeyColumn?: string // 🆕 클라이언트에서 전달한 Primary Key 컬럼명 ): Promise> { try { // 테이블 접근 검증 @@ -499,20 +500,30 @@ class DataService { return validation.error!; } - // Primary Key 컬럼 찾기 - const pkResult = await query<{ attname: string }>( - `SELECT a.attname - FROM pg_index i - JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) - WHERE i.indrelid = $1::regclass AND i.indisprimary`, - [tableName] - ); + // 🆕 클라이언트에서 전달한 Primary Key 컬럼이 있으면 우선 사용 + let pkColumn = primaryKeyColumn || ""; + + // Primary Key 컬럼이 없으면 자동 감지 + if (!pkColumn) { + const pkResult = await query<{ attname: string }>( + `SELECT a.attname + FROM pg_index i + JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) + WHERE i.indrelid = $1::regclass AND i.indisprimary`, + [tableName] + ); - let pkColumn = "id"; // 기본값 - if (pkResult.length > 0) { - pkColumn = pkResult[0].attname; + pkColumn = "id"; // 기본값 + if (pkResult.length > 0) { + pkColumn = pkResult[0].attname; + } + console.log(`🔑 [getRecordDetail] 자동 감지된 Primary Key:`, pkResult); + } else { + console.log(`🔑 [getRecordDetail] 클라이언트 제공 Primary Key: ${pkColumn}`); } + console.log(`🔑 [getRecordDetail] 테이블: ${tableName}, Primary Key 컬럼: ${pkColumn}, 조회 ID: ${id}`); + // 🆕 Entity Join이 활성화된 경우 if (enableEntityJoin) { const { EntityJoinService } = await import("./entityJoinService"); diff --git a/frontend/components/common/ScreenModal.tsx b/frontend/components/common/ScreenModal.tsx index 44685dc0..fdd104df 100644 --- a/frontend/components/common/ScreenModal.tsx +++ b/frontend/components/common/ScreenModal.tsx @@ -374,8 +374,9 @@ export const ScreenModal: React.FC = ({ className }) => { const editId = urlParams.get("editId"); const tableName = urlParams.get("tableName") || screenInfo.tableName; const groupByColumnsParam = urlParams.get("groupByColumns"); + const primaryKeyColumn = urlParams.get("primaryKeyColumn"); // 🆕 Primary Key 컬럼명 - console.log("📋 URL 파라미터 확인:", { mode, editId, tableName, groupByColumnsParam }); + console.log("📋 URL 파라미터 확인:", { mode, editId, tableName, groupByColumnsParam, primaryKeyColumn }); // 수정 모드이고 editId가 있으면 해당 레코드 조회 if (mode === "edit" && editId && tableName) { @@ -414,6 +415,11 @@ export const ScreenModal: React.FC = ({ className }) => { params.groupByColumns = JSON.stringify(groupByColumns); console.log("✅ [ScreenModal] groupByColumns를 params에 추가:", params.groupByColumns); } + // 🆕 Primary Key 컬럼명 전달 (백엔드 자동 감지 실패 시 사용) + if (primaryKeyColumn) { + params.primaryKeyColumn = primaryKeyColumn; + console.log("✅ [ScreenModal] primaryKeyColumn을 params에 추가:", primaryKeyColumn); + } console.log("📡 [ScreenModal] 실제 API 요청:", { url: `/data/${tableName}/${editId}`, diff --git a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx index ab387348..9b8e7cf0 100644 --- a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx +++ b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx @@ -1590,21 +1590,40 @@ export const SplitPanelLayoutComponent: React.FC // 커스텀 모달 화면 열기 const rightTableName = componentConfig.rightPanel?.tableName || ""; - // Primary Key 찾기 (우선순위: id > ID > 첫 번째 필드) + // Primary Key 찾기 (우선순위: 설정값 > id > ID > non-null 필드) + // 🔧 설정에서 primaryKeyColumn 지정 가능 + const configuredPrimaryKey = componentConfig.rightPanel?.editButton?.primaryKeyColumn; + let primaryKeyName = "id"; let primaryKeyValue: any; - if (item.id !== undefined && item.id !== null) { + if (configuredPrimaryKey && item[configuredPrimaryKey] !== undefined && item[configuredPrimaryKey] !== null) { + // 설정된 Primary Key 사용 + primaryKeyName = configuredPrimaryKey; + primaryKeyValue = item[configuredPrimaryKey]; + } else if (item.id !== undefined && item.id !== null) { primaryKeyName = "id"; primaryKeyValue = item.id; } else if (item.ID !== undefined && item.ID !== null) { primaryKeyName = "ID"; primaryKeyValue = item.ID; } else { - // 첫 번째 필드를 Primary Key로 간주 - const firstKey = Object.keys(item)[0]; - primaryKeyName = firstKey; - primaryKeyValue = item[firstKey]; + // 🔧 첫 번째 non-null 필드를 Primary Key로 간주 + const keys = Object.keys(item); + let found = false; + for (const key of keys) { + if (item[key] !== undefined && item[key] !== null) { + primaryKeyName = key; + primaryKeyValue = item[key]; + found = true; + break; + } + } + // 모든 필드가 null이면 첫 번째 필드 사용 + if (!found && keys.length > 0) { + primaryKeyName = keys[0]; + primaryKeyValue = item[keys[0]]; + } } console.log("✅ 수정 모달 열기:", { @@ -1629,7 +1648,7 @@ export const SplitPanelLayoutComponent: React.FC hasGroupByColumns: groupByColumns.length > 0, }); - // ScreenModal 열기 이벤트 발생 (URL 파라미터로 ID + groupByColumns 전달) + // ScreenModal 열기 이벤트 발생 (URL 파라미터로 ID + groupByColumns + primaryKeyColumn 전달) window.dispatchEvent( new CustomEvent("openScreenModal", { detail: { @@ -1638,6 +1657,7 @@ export const SplitPanelLayoutComponent: React.FC mode: "edit", editId: primaryKeyValue, tableName: rightTableName, + primaryKeyColumn: primaryKeyName, // 🆕 Primary Key 컬럼명 전달 ...(groupByColumns.length > 0 && { groupByColumns: JSON.stringify(groupByColumns), }), @@ -1650,6 +1670,7 @@ export const SplitPanelLayoutComponent: React.FC screenId: modalScreenId, editId: primaryKeyValue, tableName: rightTableName, + primaryKeyColumn: primaryKeyName, groupByColumns: groupByColumns.length > 0 ? JSON.stringify(groupByColumns) : "없음", });