From c3e6eff0fef6b8738aa1866673452451f1624ba9 Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 1 Oct 2025 17:21:08 +0900 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20ActionConditionBuilder=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=ED=82=A4=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 문제: - toColumns에 빈 문자열 columnName 중복 - 중복된 키 to_ 생성 - React 키 중복 경고 수정: - fromColumns filter 추가 - toColumns filter 추가 - 빈 문자열 컬럼 제외 에러: Encountered two children with the same key --- .../ActionConfig/ActionConditionBuilder.tsx | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/frontend/components/dataflow/connection/redesigned/RightPanel/ActionConfig/ActionConditionBuilder.tsx b/frontend/components/dataflow/connection/redesigned/RightPanel/ActionConfig/ActionConditionBuilder.tsx index 15411fdd..cf6ffed4 100644 --- a/frontend/components/dataflow/connection/redesigned/RightPanel/ActionConfig/ActionConditionBuilder.tsx +++ b/frontend/components/dataflow/connection/redesigned/RightPanel/ActionConfig/ActionConditionBuilder.tsx @@ -488,14 +488,16 @@ const ActionConditionBuilder: React.FC = ({ {fromColumns.length > 0 && ( <>
FROM 테이블
- {fromColumns.map((column) => ( - -
- 📤 - {column.displayName || column.columnName} -
-
- ))} + {fromColumns + .filter((column) => column.columnName) // 빈 문자열 제외 + .map((column) => ( + +
+ 📤 + {column.displayName || column.columnName} +
+
+ ))} )} @@ -503,14 +505,16 @@ const ActionConditionBuilder: React.FC = ({ {toColumns.length > 0 && ( <>
TO 테이블
- {toColumns.map((column) => ( - -
- 📥 - {column.displayName || column.columnName} -
-
- ))} + {toColumns + .filter((column) => column.columnName) // 빈 문자열 제외 + .map((column) => ( + +
+ 📥 + {column.displayName || column.columnName} +
+
+ ))} )} -- 2.43.0 From 7bb70bdd3b2ca305441ceab3ba879c42b625de53 Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 1 Oct 2025 17:23:18 +0900 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20ActionConditionBuilder=20=EB=AA=A8?= =?UTF-8?q?=EB=93=A0=20Select=20=EC=A4=91=EB=B3=B5=20=ED=82=A4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 수정된 위치: - line 271: fromColumns (소스 필드) - line 289: toColumns (소스 필드) - line 623: fromColumns (필드 매핑) - line 638: toColumns (필드 매핑) - line 737: getAvailableFieldsForMapping (대상 필드) 모든 .map() 앞에 .filter() 추가: - 빈 columnName 제외 - 중복 키 방지 - React 경고 해결 --- .../ActionConfig/ActionConditionBuilder.tsx | 100 ++++++++++-------- 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/frontend/components/dataflow/connection/redesigned/RightPanel/ActionConfig/ActionConditionBuilder.tsx b/frontend/components/dataflow/connection/redesigned/RightPanel/ActionConfig/ActionConditionBuilder.tsx index cf6ffed4..85c0656c 100644 --- a/frontend/components/dataflow/connection/redesigned/RightPanel/ActionConfig/ActionConditionBuilder.tsx +++ b/frontend/components/dataflow/connection/redesigned/RightPanel/ActionConfig/ActionConditionBuilder.tsx @@ -268,17 +268,19 @@ const ActionConditionBuilder: React.FC = ({ {fromColumns.length > 0 && ( <>
FROM 테이블
- {fromColumns.map((column) => ( - -
- 📤 - {column.displayName || column.columnName} - - {column.webType || column.dataType} - -
-
- ))} + {fromColumns + .filter((column) => column.columnName) // 빈 문자열 제외 + .map((column) => ( + +
+ 📤 + {column.displayName || column.columnName} + + {column.webType || column.dataType} + +
+
+ ))} )} @@ -286,15 +288,17 @@ const ActionConditionBuilder: React.FC = ({ {toColumns.length > 0 && ( <>
TO 테이블
- {toColumns.map((column) => ( - -
- 📥 - {column.displayName || column.columnName} - - {column.webType || column.dataType} - -
+ {toColumns + .filter((column) => column.columnName) // 빈 문자열 제외 + .map((column) => ( + +
+ 📥 + {column.displayName || column.columnName} + + {column.webType || column.dataType} + +
))} @@ -616,14 +620,16 @@ const ActionConditionBuilder: React.FC = ({
FROM 테이블
- {fromColumns.map((column) => ( - -
- 📤 - {column.displayName || column.columnName} -
-
- ))} + {fromColumns + .filter((column) => column.columnName) // 빈 문자열 제외 + .map((column) => ( + +
+ 📤 + {column.displayName || column.columnName} +
+
+ ))} )} @@ -631,13 +637,15 @@ const ActionConditionBuilder: React.FC = ({ {toColumns.length > 0 && ( <>
TO 테이블
- {toColumns.map((column) => ( - -
- 📥 - {column.displayName || column.columnName} -
-
+ {toColumns + .filter((column) => column.columnName) // 빈 문자열 제외 + .map((column) => ( + +
+ 📥 + {column.displayName || column.columnName} +
+
))} )} @@ -733,16 +741,18 @@ const ActionConditionBuilder: React.FC = ({ - {getAvailableFieldsForMapping(index).map((column) => ( - -
- {column.displayName || column.columnName} - - {column.webType || column.dataType} - -
-
- ))} + {getAvailableFieldsForMapping(index) + .filter((column) => column.columnName) // 빈 문자열 제외 + .map((column) => ( + +
+ {column.displayName || column.columnName} + + {column.webType || column.dataType} + +
+
+ ))}
-- 2.43.0 From 8615a358c0132901f2411182af8714c0d57b83eb Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 1 Oct 2025 17:27:24 +0900 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20EditModal=20=EB=9D=BC=EB=B2=A8=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20=EB=B0=8F=20=EC=A4=91=EB=B3=B5=20=EB=B0=95?= =?UTF-8?q?=EC=8A=A4=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 문제: 1. 라벨이 보이지 않음 (hideLabel=true, labelDisplay=false) 2. 인풋이 중복으로 보임 (불필요한 패딩/테두리) 수정: 1. hideLabel={false} ✅ 2. labelDisplay: true ✅ 3. 컴포넌트 감싸는 div에서 스타일 제거 (패딩, 테두리) ✅ 결과: - ✅ 라벨 정상 표시 - ✅ 깔끔한 인풋 렌더링 - ✅ 원본 레이아웃 유지 --- frontend/components/screen/EditModal.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/components/screen/EditModal.tsx b/frontend/components/screen/EditModal.tsx index bbd06f58..58aaaba2 100644 --- a/frontend/components/screen/EditModal.tsx +++ b/frontend/components/screen/EditModal.tsx @@ -275,7 +275,6 @@ export const EditModal: React.FC = ({ {components.map((component, index) => (
= ({ zIndex: component.position?.z || (1000 + index), // 모달 내부에서 충분히 높은 z-index }} > - {/* 위젯 컴포넌트는 InteractiveScreenViewer 사용 (라벨 표시를 위해) */} + {/* 위젯 컴포넌트는 InteractiveScreenViewer 사용 (라벨 표시) */} {component.type === "widget" ? ( { console.log("📝 폼 데이터 변경:", fieldName, value); @@ -314,7 +313,7 @@ export const EditModal: React.FC = ({ ...component, style: { ...component.style, - labelDisplay: false, // 라벨 숨김 (원래 화면과 동일하게) + labelDisplay: true, // ✅ 라벨 표시 }, }} screenId={screenId} -- 2.43.0 From 852075c7995c83ec1d3766d115845cb265fbd7fa Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 1 Oct 2025 17:34:56 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20Oracle=20INSERT=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=20=EC=BB=A4=EB=B0=8B=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 문제: - Oracle DB에 INSERT 해도 데이터가 실제로 저장되지 않음 - executeQuery에서 autoCommit 옵션이 없었음 수정: 1. 쿼리 타입 확인 (INSERT/UPDATE/DELETE/MERGE) 2. DML 쿼리는 autoCommit: true 설정 ✅ 3. SELECT 쿼리는 autoCommit: false (기본값) 로깅 추가: - isDML 확인 - autoCommit 상태 - rowsAffected 추가 결과: - ✅ INSERT 실행 후 자동 COMMIT - ✅ UPDATE/DELETE도 자동 COMMIT - ✅ SELECT는 영향 없음 - ✅ 오라클 데이터 정상 저장 --- backend-node/src/database/OracleConnector.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/backend-node/src/database/OracleConnector.ts b/backend-node/src/database/OracleConnector.ts index b9360570..e68346d8 100644 --- a/backend-node/src/database/OracleConnector.ts +++ b/backend-node/src/database/OracleConnector.ts @@ -99,23 +99,35 @@ export class OracleConnector implements DatabaseConnector { try { const startTime = Date.now(); + // 쿼리 타입 확인 (DML인지 SELECT인지) + const isDML = /^\s*(INSERT|UPDATE|DELETE|MERGE)/i.test(query); + // Oracle XE 21c 쿼리 실행 옵션 const options: any = { outFormat: (oracledb as any).OUT_FORMAT_OBJECT, // OBJECT format maxRows: 10000, // XE 제한 고려 - fetchArraySize: 100 + fetchArraySize: 100, + autoCommit: isDML // ✅ DML 쿼리는 자동 커밋 }; + console.log('Oracle 쿼리 실행:', { + query: query.substring(0, 100) + '...', + isDML, + autoCommit: options.autoCommit + }); + const result = await this.connection!.execute(query, params, options); const executionTime = Date.now() - startTime; console.log('Oracle 쿼리 실행 결과:', { query, rowCount: result.rows?.length || 0, + rowsAffected: result.rowsAffected, metaData: result.metaData?.length || 0, executionTime: `${executionTime}ms`, actualRows: result.rows, - metaDataInfo: result.metaData + metaDataInfo: result.metaData, + autoCommit: options.autoCommit }); return { -- 2.43.0 From b452f148a9a00e05726cfa02da2051cbfceb7c6c Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 1 Oct 2025 17:36:17 +0900 Subject: [PATCH 5/5] =?UTF-8?q?=EC=98=A4=EB=9D=BC=ED=81=B4=20=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend-node/src/database/OracleConnector.ts | 126 ++++++++++--------- frontend/components/screen/EditModal.tsx | 8 +- 2 files changed, 70 insertions(+), 64 deletions(-) diff --git a/backend-node/src/database/OracleConnector.ts b/backend-node/src/database/OracleConnector.ts index e68346d8..6ce169ac 100644 --- a/backend-node/src/database/OracleConnector.ts +++ b/backend-node/src/database/OracleConnector.ts @@ -1,7 +1,11 @@ // @ts-ignore -import * as oracledb from 'oracledb'; -import { DatabaseConnector, ConnectionConfig, QueryResult } from '../interfaces/DatabaseConnector'; -import { ConnectionTestResult, TableInfo } from '../types/externalDbTypes'; +import * as oracledb from "oracledb"; +import { + DatabaseConnector, + ConnectionConfig, + QueryResult, +} from "../interfaces/DatabaseConnector"; +import { ConnectionTestResult, TableInfo } from "../types/externalDbTypes"; export class OracleConnector implements DatabaseConnector { private connection: oracledb.Connection | null = null; @@ -9,7 +13,7 @@ export class OracleConnector implements DatabaseConnector { constructor(config: ConnectionConfig) { this.config = config; - + // Oracle XE 21c 특화 설정 // oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT; // oracledb.autoCommit = true; @@ -19,31 +23,31 @@ export class OracleConnector implements DatabaseConnector { try { // Oracle XE 21c 연결 문자열 구성 const connectionString = this.buildConnectionString(); - + const connectionConfig: any = { user: this.config.user, password: this.config.password, - connectString: connectionString + connectString: connectionString, }; this.connection = await oracledb.getConnection(connectionConfig); - console.log('Oracle XE 21c 연결 성공'); + console.log("Oracle XE 21c 연결 성공"); } catch (error: any) { - console.error('Oracle XE 21c 연결 실패:', error); + console.error("Oracle XE 21c 연결 실패:", error); throw new Error(`Oracle 연결 실패: ${error.message}`); } } private buildConnectionString(): string { const { host, port, database } = this.config; - + // Oracle XE 21c는 기본적으로 XE 서비스명을 사용 // 다양한 연결 문자열 형식 지원 - if (database.includes('/') || database.includes(':')) { + if (database.includes("/") || database.includes(":")) { // 이미 완전한 연결 문자열인 경우 return database; } - + // Oracle XE 21c 표준 형식 return `${host}:${port}/${database}`; } @@ -53,9 +57,9 @@ export class OracleConnector implements DatabaseConnector { try { await this.connection.close(); this.connection = null; - console.log('Oracle 연결 해제됨'); + console.log("Oracle 연결 해제됨"); } catch (error: any) { - console.error('Oracle 연결 해제 실패:', error); + console.error("Oracle 연결 해제 실패:", error); } } } @@ -65,28 +69,28 @@ export class OracleConnector implements DatabaseConnector { if (!this.connection) { await this.connect(); } - + // Oracle XE 21c 버전 확인 쿼리 const result = await this.connection!.execute( - 'SELECT BANNER FROM V$VERSION WHERE BANNER LIKE \'Oracle%\'' + "SELECT BANNER FROM V$VERSION WHERE BANNER LIKE 'Oracle%'" ); - - console.log('Oracle 버전:', result.rows); + + console.log("Oracle 버전:", result.rows); return { success: true, - message: '연결 성공', + message: "연결 성공", details: { - server_version: (result.rows as any)?.[0]?.BANNER || 'Unknown' - } + server_version: (result.rows as any)?.[0]?.BANNER || "Unknown", + }, }; } catch (error: any) { - console.error('Oracle 연결 테스트 실패:', error); + console.error("Oracle 연결 테스트 실패:", error); return { success: false, - message: '연결 실패', + message: "연결 실패", details: { - server_version: error.message - } + server_version: error.message, + }, }; } } @@ -98,28 +102,28 @@ export class OracleConnector implements DatabaseConnector { try { const startTime = Date.now(); - + // 쿼리 타입 확인 (DML인지 SELECT인지) const isDML = /^\s*(INSERT|UPDATE|DELETE|MERGE)/i.test(query); - + // Oracle XE 21c 쿼리 실행 옵션 const options: any = { outFormat: (oracledb as any).OUT_FORMAT_OBJECT, // OBJECT format maxRows: 10000, // XE 제한 고려 fetchArraySize: 100, - autoCommit: isDML // ✅ DML 쿼리는 자동 커밋 + autoCommit: isDML, // ✅ DML 쿼리는 자동 커밋 }; - console.log('Oracle 쿼리 실행:', { - query: query.substring(0, 100) + '...', + console.log("Oracle 쿼리 실행:", { + query: query.substring(0, 100) + "...", isDML, - autoCommit: options.autoCommit + autoCommit: options.autoCommit, }); const result = await this.connection!.execute(query, params, options); const executionTime = Date.now() - startTime; - console.log('Oracle 쿼리 실행 결과:', { + console.log("Oracle 쿼리 실행 결과:", { query, rowCount: result.rows?.length || 0, rowsAffected: result.rowsAffected, @@ -127,35 +131,35 @@ export class OracleConnector implements DatabaseConnector { executionTime: `${executionTime}ms`, actualRows: result.rows, metaDataInfo: result.metaData, - autoCommit: options.autoCommit + autoCommit: options.autoCommit, }); return { rows: result.rows || [], - rowCount: result.rowsAffected || (result.rows?.length || 0), - fields: this.extractFieldInfo(result.metaData || []) + rowCount: result.rowsAffected || result.rows?.length || 0, + fields: this.extractFieldInfo(result.metaData || []), }; } catch (error: any) { - console.error('Oracle 쿼리 실행 실패:', error); + console.error("Oracle 쿼리 실행 실패:", error); throw new Error(`쿼리 실행 실패: ${error.message}`); } } private extractFieldInfo(metaData: any[]): any[] { - return metaData.map(field => ({ + return metaData.map((field) => ({ name: field.name, type: this.mapOracleType(field.dbType), length: field.precision || field.byteSize, - nullable: field.nullable + nullable: field.nullable, })); } private mapOracleType(oracleType: any): string { // Oracle XE 21c 타입 매핑 (간단한 방식) - if (typeof oracleType === 'string') { + if (typeof oracleType === "string") { return oracleType; } - return 'UNKNOWN'; + return "UNKNOWN"; } async getTables(): Promise { @@ -167,22 +171,21 @@ export class OracleConnector implements DatabaseConnector { ORDER BY table_name `; - console.log('Oracle 테이블 조회 시작 - 사용자:', this.config.user); - + console.log("Oracle 테이블 조회 시작 - 사용자:", this.config.user); + const result = await this.executeQuery(query); - console.log('사용자 스키마 테이블 조회 결과:', result.rows); - + console.log("사용자 스키마 테이블 조회 결과:", result.rows); + const tables = result.rows.map((row: any) => ({ table_name: row.TABLE_NAME, columns: [], - description: null + description: null, })); console.log(`총 ${tables.length}개의 사용자 테이블을 찾았습니다.`); return tables; - } catch (error: any) { - console.error('Oracle 테이블 목록 조회 실패:', error); + console.error("Oracle 테이블 목록 조회 실패:", error); throw new Error(`테이블 목록 조회 실패: ${error.message}`); } } @@ -190,7 +193,7 @@ export class OracleConnector implements DatabaseConnector { async getColumns(tableName: string): Promise { try { console.log(`[OracleConnector] getColumns 호출: tableName=${tableName}`); - + const query = ` SELECT column_name, @@ -204,41 +207,44 @@ export class OracleConnector implements DatabaseConnector { WHERE table_name = UPPER(:tableName) ORDER BY column_id `; - + console.log(`[OracleConnector] 쿼리 실행 시작: ${query}`); const result = await this.executeQuery(query, [tableName]); - + console.log(`[OracleConnector] 쿼리 결과:`, result.rows); - console.log(`[OracleConnector] 결과 개수:`, result.rows ? result.rows.length : 'null/undefined'); - + console.log( + `[OracleConnector] 결과 개수:`, + result.rows ? result.rows.length : "null/undefined" + ); + const mappedResult = result.rows.map((row: any) => ({ column_name: row.COLUMN_NAME, data_type: this.formatOracleDataType(row), - is_nullable: row.NULLABLE === 'Y' ? 'YES' : 'NO', - column_default: row.DATA_DEFAULT + is_nullable: row.NULLABLE === "Y" ? "YES" : "NO", + column_default: row.DATA_DEFAULT, })); - + console.log(`[OracleConnector] 매핑된 결과:`, mappedResult); return mappedResult; } catch (error: any) { - console.error('[OracleConnector] getColumns 오류:', error); + console.error("[OracleConnector] getColumns 오류:", error); throw new Error(`테이블 컬럼 조회 실패: ${error.message}`); } } private formatOracleDataType(row: any): string { const { DATA_TYPE, DATA_LENGTH, DATA_PRECISION, DATA_SCALE } = row; - + switch (DATA_TYPE) { - case 'NUMBER': + case "NUMBER": if (DATA_PRECISION && DATA_SCALE !== null) { return `NUMBER(${DATA_PRECISION},${DATA_SCALE})`; } else if (DATA_PRECISION) { return `NUMBER(${DATA_PRECISION})`; } - return 'NUMBER'; - case 'VARCHAR2': - case 'CHAR': + return "NUMBER"; + case "VARCHAR2": + case "CHAR": return `${DATA_TYPE}(${DATA_LENGTH})`; default: return DATA_TYPE; diff --git a/frontend/components/screen/EditModal.tsx b/frontend/components/screen/EditModal.tsx index 58aaaba2..63ba80e8 100644 --- a/frontend/components/screen/EditModal.tsx +++ b/frontend/components/screen/EditModal.tsx @@ -51,7 +51,7 @@ export const EditModal: React.FC = ({ console.log(`🎯 계산된 모달 크기: ${maxWidth}px x ${maxHeight}px`); console.log( - `📍 컴포넌트 위치들:`, + "📍 컴포넌트 위치들:", components.map((c) => ({ x: c.position?.x, y: c.position?.y, w: c.size?.width, h: c.size?.height })), ); return { width: maxWidth, height: maxHeight }; @@ -85,7 +85,7 @@ export const EditModal: React.FC = ({ // 스크롤 완전 제거 if (modalContent) { modalContent.style.overflow = "hidden"; - console.log(`🚫 스크롤 완전 비활성화`); + console.log("🚫 스크롤 완전 비활성화"); } }, 100); // 100ms 지연으로 렌더링 완료 후 실행 } @@ -152,7 +152,7 @@ export const EditModal: React.FC = ({ // 코드 타입인 경우 특별히 로깅 if ((comp as any).widgetType === "code") { - console.log(` 🔍 코드 타입 세부정보:`, { + console.log(" 🔍 코드 타입 세부정보:", { columnName: comp.columnName, componentId: comp.id, formValue, @@ -281,7 +281,7 @@ export const EditModal: React.FC = ({ left: component.position?.x || 0, width: component.size?.width || 200, height: component.size?.height || 40, - zIndex: component.position?.z || (1000 + index), // 모달 내부에서 충분히 높은 z-index + zIndex: component.position?.z || 1000 + index, // 모달 내부에서 충분히 높은 z-index }} > {/* 위젯 컴포넌트는 InteractiveScreenViewer 사용 (라벨 표시) */} -- 2.43.0