Merge pull request 'feature/screen-management' (#83) from feature/screen-management into main
Reviewed-on: http://39.117.244.52:3000/kjs/ERP-node/pulls/83
This commit is contained in:
commit
63ee0fbb5a
|
|
@ -1,7 +1,11 @@
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as oracledb from 'oracledb';
|
import * as oracledb from "oracledb";
|
||||||
import { DatabaseConnector, ConnectionConfig, QueryResult } from '../interfaces/DatabaseConnector';
|
import {
|
||||||
import { ConnectionTestResult, TableInfo } from '../types/externalDbTypes';
|
DatabaseConnector,
|
||||||
|
ConnectionConfig,
|
||||||
|
QueryResult,
|
||||||
|
} from "../interfaces/DatabaseConnector";
|
||||||
|
import { ConnectionTestResult, TableInfo } from "../types/externalDbTypes";
|
||||||
|
|
||||||
export class OracleConnector implements DatabaseConnector {
|
export class OracleConnector implements DatabaseConnector {
|
||||||
private connection: oracledb.Connection | null = null;
|
private connection: oracledb.Connection | null = null;
|
||||||
|
|
@ -9,7 +13,7 @@ export class OracleConnector implements DatabaseConnector {
|
||||||
|
|
||||||
constructor(config: ConnectionConfig) {
|
constructor(config: ConnectionConfig) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
|
||||||
// Oracle XE 21c 특화 설정
|
// Oracle XE 21c 특화 설정
|
||||||
// oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT;
|
// oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT;
|
||||||
// oracledb.autoCommit = true;
|
// oracledb.autoCommit = true;
|
||||||
|
|
@ -19,31 +23,31 @@ export class OracleConnector implements DatabaseConnector {
|
||||||
try {
|
try {
|
||||||
// Oracle XE 21c 연결 문자열 구성
|
// Oracle XE 21c 연결 문자열 구성
|
||||||
const connectionString = this.buildConnectionString();
|
const connectionString = this.buildConnectionString();
|
||||||
|
|
||||||
const connectionConfig: any = {
|
const connectionConfig: any = {
|
||||||
user: this.config.user,
|
user: this.config.user,
|
||||||
password: this.config.password,
|
password: this.config.password,
|
||||||
connectString: connectionString
|
connectString: connectionString,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.connection = await oracledb.getConnection(connectionConfig);
|
this.connection = await oracledb.getConnection(connectionConfig);
|
||||||
console.log('Oracle XE 21c 연결 성공');
|
console.log("Oracle XE 21c 연결 성공");
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Oracle XE 21c 연결 실패:', error);
|
console.error("Oracle XE 21c 연결 실패:", error);
|
||||||
throw new Error(`Oracle 연결 실패: ${error.message}`);
|
throw new Error(`Oracle 연결 실패: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildConnectionString(): string {
|
private buildConnectionString(): string {
|
||||||
const { host, port, database } = this.config;
|
const { host, port, database } = this.config;
|
||||||
|
|
||||||
// Oracle XE 21c는 기본적으로 XE 서비스명을 사용
|
// Oracle XE 21c는 기본적으로 XE 서비스명을 사용
|
||||||
// 다양한 연결 문자열 형식 지원
|
// 다양한 연결 문자열 형식 지원
|
||||||
if (database.includes('/') || database.includes(':')) {
|
if (database.includes("/") || database.includes(":")) {
|
||||||
// 이미 완전한 연결 문자열인 경우
|
// 이미 완전한 연결 문자열인 경우
|
||||||
return database;
|
return database;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Oracle XE 21c 표준 형식
|
// Oracle XE 21c 표준 형식
|
||||||
return `${host}:${port}/${database}`;
|
return `${host}:${port}/${database}`;
|
||||||
}
|
}
|
||||||
|
|
@ -53,9 +57,9 @@ export class OracleConnector implements DatabaseConnector {
|
||||||
try {
|
try {
|
||||||
await this.connection.close();
|
await this.connection.close();
|
||||||
this.connection = null;
|
this.connection = null;
|
||||||
console.log('Oracle 연결 해제됨');
|
console.log("Oracle 연결 해제됨");
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Oracle 연결 해제 실패:', error);
|
console.error("Oracle 연결 해제 실패:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -65,28 +69,28 @@ export class OracleConnector implements DatabaseConnector {
|
||||||
if (!this.connection) {
|
if (!this.connection) {
|
||||||
await this.connect();
|
await this.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Oracle XE 21c 버전 확인 쿼리
|
// Oracle XE 21c 버전 확인 쿼리
|
||||||
const result = await this.connection!.execute(
|
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 {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: '연결 성공',
|
message: "연결 성공",
|
||||||
details: {
|
details: {
|
||||||
server_version: (result.rows as any)?.[0]?.BANNER || 'Unknown'
|
server_version: (result.rows as any)?.[0]?.BANNER || "Unknown",
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Oracle 연결 테스트 실패:', error);
|
console.error("Oracle 연결 테스트 실패:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: '연결 실패',
|
message: "연결 실패",
|
||||||
details: {
|
details: {
|
||||||
server_version: error.message
|
server_version: error.message,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -98,52 +102,64 @@ export class OracleConnector implements DatabaseConnector {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
// 쿼리 타입 확인 (DML인지 SELECT인지)
|
||||||
|
const isDML = /^\s*(INSERT|UPDATE|DELETE|MERGE)/i.test(query);
|
||||||
|
|
||||||
// Oracle XE 21c 쿼리 실행 옵션
|
// Oracle XE 21c 쿼리 실행 옵션
|
||||||
const options: any = {
|
const options: any = {
|
||||||
outFormat: (oracledb as any).OUT_FORMAT_OBJECT, // OBJECT format
|
outFormat: (oracledb as any).OUT_FORMAT_OBJECT, // OBJECT format
|
||||||
maxRows: 10000, // XE 제한 고려
|
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 result = await this.connection!.execute(query, params, options);
|
||||||
const executionTime = Date.now() - startTime;
|
const executionTime = Date.now() - startTime;
|
||||||
|
|
||||||
console.log('Oracle 쿼리 실행 결과:', {
|
console.log("Oracle 쿼리 실행 결과:", {
|
||||||
query,
|
query,
|
||||||
rowCount: result.rows?.length || 0,
|
rowCount: result.rows?.length || 0,
|
||||||
|
rowsAffected: result.rowsAffected,
|
||||||
metaData: result.metaData?.length || 0,
|
metaData: result.metaData?.length || 0,
|
||||||
executionTime: `${executionTime}ms`,
|
executionTime: `${executionTime}ms`,
|
||||||
actualRows: result.rows,
|
actualRows: result.rows,
|
||||||
metaDataInfo: result.metaData
|
metaDataInfo: result.metaData,
|
||||||
|
autoCommit: options.autoCommit,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rows: result.rows || [],
|
rows: result.rows || [],
|
||||||
rowCount: result.rowsAffected || (result.rows?.length || 0),
|
rowCount: result.rowsAffected || result.rows?.length || 0,
|
||||||
fields: this.extractFieldInfo(result.metaData || [])
|
fields: this.extractFieldInfo(result.metaData || []),
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Oracle 쿼리 실행 실패:', error);
|
console.error("Oracle 쿼리 실행 실패:", error);
|
||||||
throw new Error(`쿼리 실행 실패: ${error.message}`);
|
throw new Error(`쿼리 실행 실패: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extractFieldInfo(metaData: any[]): any[] {
|
private extractFieldInfo(metaData: any[]): any[] {
|
||||||
return metaData.map(field => ({
|
return metaData.map((field) => ({
|
||||||
name: field.name,
|
name: field.name,
|
||||||
type: this.mapOracleType(field.dbType),
|
type: this.mapOracleType(field.dbType),
|
||||||
length: field.precision || field.byteSize,
|
length: field.precision || field.byteSize,
|
||||||
nullable: field.nullable
|
nullable: field.nullable,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapOracleType(oracleType: any): string {
|
private mapOracleType(oracleType: any): string {
|
||||||
// Oracle XE 21c 타입 매핑 (간단한 방식)
|
// Oracle XE 21c 타입 매핑 (간단한 방식)
|
||||||
if (typeof oracleType === 'string') {
|
if (typeof oracleType === "string") {
|
||||||
return oracleType;
|
return oracleType;
|
||||||
}
|
}
|
||||||
return 'UNKNOWN';
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTables(): Promise<TableInfo[]> {
|
async getTables(): Promise<TableInfo[]> {
|
||||||
|
|
@ -155,22 +171,21 @@ export class OracleConnector implements DatabaseConnector {
|
||||||
ORDER BY table_name
|
ORDER BY table_name
|
||||||
`;
|
`;
|
||||||
|
|
||||||
console.log('Oracle 테이블 조회 시작 - 사용자:', this.config.user);
|
console.log("Oracle 테이블 조회 시작 - 사용자:", this.config.user);
|
||||||
|
|
||||||
const result = await this.executeQuery(query);
|
const result = await this.executeQuery(query);
|
||||||
console.log('사용자 스키마 테이블 조회 결과:', result.rows);
|
console.log("사용자 스키마 테이블 조회 결과:", result.rows);
|
||||||
|
|
||||||
const tables = result.rows.map((row: any) => ({
|
const tables = result.rows.map((row: any) => ({
|
||||||
table_name: row.TABLE_NAME,
|
table_name: row.TABLE_NAME,
|
||||||
columns: [],
|
columns: [],
|
||||||
description: null
|
description: null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log(`총 ${tables.length}개의 사용자 테이블을 찾았습니다.`);
|
console.log(`총 ${tables.length}개의 사용자 테이블을 찾았습니다.`);
|
||||||
return tables;
|
return tables;
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Oracle 테이블 목록 조회 실패:', error);
|
console.error("Oracle 테이블 목록 조회 실패:", error);
|
||||||
throw new Error(`테이블 목록 조회 실패: ${error.message}`);
|
throw new Error(`테이블 목록 조회 실패: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -178,7 +193,7 @@ export class OracleConnector implements DatabaseConnector {
|
||||||
async getColumns(tableName: string): Promise<any[]> {
|
async getColumns(tableName: string): Promise<any[]> {
|
||||||
try {
|
try {
|
||||||
console.log(`[OracleConnector] getColumns 호출: tableName=${tableName}`);
|
console.log(`[OracleConnector] getColumns 호출: tableName=${tableName}`);
|
||||||
|
|
||||||
const query = `
|
const query = `
|
||||||
SELECT
|
SELECT
|
||||||
column_name,
|
column_name,
|
||||||
|
|
@ -192,41 +207,44 @@ export class OracleConnector implements DatabaseConnector {
|
||||||
WHERE table_name = UPPER(:tableName)
|
WHERE table_name = UPPER(:tableName)
|
||||||
ORDER BY column_id
|
ORDER BY column_id
|
||||||
`;
|
`;
|
||||||
|
|
||||||
console.log(`[OracleConnector] 쿼리 실행 시작: ${query}`);
|
console.log(`[OracleConnector] 쿼리 실행 시작: ${query}`);
|
||||||
const result = await this.executeQuery(query, [tableName]);
|
const result = await this.executeQuery(query, [tableName]);
|
||||||
|
|
||||||
console.log(`[OracleConnector] 쿼리 결과:`, result.rows);
|
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) => ({
|
const mappedResult = result.rows.map((row: any) => ({
|
||||||
column_name: row.COLUMN_NAME,
|
column_name: row.COLUMN_NAME,
|
||||||
data_type: this.formatOracleDataType(row),
|
data_type: this.formatOracleDataType(row),
|
||||||
is_nullable: row.NULLABLE === 'Y' ? 'YES' : 'NO',
|
is_nullable: row.NULLABLE === "Y" ? "YES" : "NO",
|
||||||
column_default: row.DATA_DEFAULT
|
column_default: row.DATA_DEFAULT,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log(`[OracleConnector] 매핑된 결과:`, mappedResult);
|
console.log(`[OracleConnector] 매핑된 결과:`, mappedResult);
|
||||||
return mappedResult;
|
return mappedResult;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('[OracleConnector] getColumns 오류:', error);
|
console.error("[OracleConnector] getColumns 오류:", error);
|
||||||
throw new Error(`테이블 컬럼 조회 실패: ${error.message}`);
|
throw new Error(`테이블 컬럼 조회 실패: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private formatOracleDataType(row: any): string {
|
private formatOracleDataType(row: any): string {
|
||||||
const { DATA_TYPE, DATA_LENGTH, DATA_PRECISION, DATA_SCALE } = row;
|
const { DATA_TYPE, DATA_LENGTH, DATA_PRECISION, DATA_SCALE } = row;
|
||||||
|
|
||||||
switch (DATA_TYPE) {
|
switch (DATA_TYPE) {
|
||||||
case 'NUMBER':
|
case "NUMBER":
|
||||||
if (DATA_PRECISION && DATA_SCALE !== null) {
|
if (DATA_PRECISION && DATA_SCALE !== null) {
|
||||||
return `NUMBER(${DATA_PRECISION},${DATA_SCALE})`;
|
return `NUMBER(${DATA_PRECISION},${DATA_SCALE})`;
|
||||||
} else if (DATA_PRECISION) {
|
} else if (DATA_PRECISION) {
|
||||||
return `NUMBER(${DATA_PRECISION})`;
|
return `NUMBER(${DATA_PRECISION})`;
|
||||||
}
|
}
|
||||||
return 'NUMBER';
|
return "NUMBER";
|
||||||
case 'VARCHAR2':
|
case "VARCHAR2":
|
||||||
case 'CHAR':
|
case "CHAR":
|
||||||
return `${DATA_TYPE}(${DATA_LENGTH})`;
|
return `${DATA_TYPE}(${DATA_LENGTH})`;
|
||||||
default:
|
default:
|
||||||
return DATA_TYPE;
|
return DATA_TYPE;
|
||||||
|
|
|
||||||
|
|
@ -268,17 +268,19 @@ const ActionConditionBuilder: React.FC<ActionConditionBuilderProps> = ({
|
||||||
{fromColumns.length > 0 && (
|
{fromColumns.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className="text-muted-foreground px-2 py-1 text-xs font-medium">FROM 테이블</div>
|
<div className="text-muted-foreground px-2 py-1 text-xs font-medium">FROM 테이블</div>
|
||||||
{fromColumns.map((column) => (
|
{fromColumns
|
||||||
<SelectItem key={`from_${column.columnName}`} value={`from.${column.columnName}`}>
|
.filter((column) => column.columnName) // 빈 문자열 제외
|
||||||
<div className="flex items-center gap-2">
|
.map((column) => (
|
||||||
<span className="text-blue-600">📤</span>
|
<SelectItem key={`from_${column.columnName}`} value={`from.${column.columnName}`}>
|
||||||
<span>{column.displayName || column.columnName}</span>
|
<div className="flex items-center gap-2">
|
||||||
<Badge variant="outline" className="text-xs">
|
<span className="text-blue-600">📤</span>
|
||||||
{column.webType || column.dataType}
|
<span>{column.displayName || column.columnName}</span>
|
||||||
</Badge>
|
<Badge variant="outline" className="text-xs">
|
||||||
</div>
|
{column.webType || column.dataType}
|
||||||
</SelectItem>
|
</Badge>
|
||||||
))}
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -286,15 +288,17 @@ const ActionConditionBuilder: React.FC<ActionConditionBuilderProps> = ({
|
||||||
{toColumns.length > 0 && (
|
{toColumns.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className="text-muted-foreground px-2 py-1 text-xs font-medium">TO 테이블</div>
|
<div className="text-muted-foreground px-2 py-1 text-xs font-medium">TO 테이블</div>
|
||||||
{toColumns.map((column) => (
|
{toColumns
|
||||||
<SelectItem key={`to_${column.columnName}`} value={`to.${column.columnName}`}>
|
.filter((column) => column.columnName) // 빈 문자열 제외
|
||||||
<div className="flex items-center gap-2">
|
.map((column) => (
|
||||||
<span className="text-green-600">📥</span>
|
<SelectItem key={`to_${column.columnName}`} value={`to.${column.columnName}`}>
|
||||||
<span>{column.displayName || column.columnName}</span>
|
<div className="flex items-center gap-2">
|
||||||
<Badge variant="outline" className="text-xs">
|
<span className="text-green-600">📥</span>
|
||||||
{column.webType || column.dataType}
|
<span>{column.displayName || column.columnName}</span>
|
||||||
</Badge>
|
<Badge variant="outline" className="text-xs">
|
||||||
</div>
|
{column.webType || column.dataType}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|
@ -488,14 +492,16 @@ const ActionConditionBuilder: React.FC<ActionConditionBuilderProps> = ({
|
||||||
{fromColumns.length > 0 && (
|
{fromColumns.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className="text-muted-foreground px-2 py-1 text-xs font-medium">FROM 테이블</div>
|
<div className="text-muted-foreground px-2 py-1 text-xs font-medium">FROM 테이블</div>
|
||||||
{fromColumns.map((column) => (
|
{fromColumns
|
||||||
<SelectItem key={`from_${column.columnName}`} value={`from.${column.columnName}`}>
|
.filter((column) => column.columnName) // 빈 문자열 제외
|
||||||
<div className="flex items-center gap-2">
|
.map((column) => (
|
||||||
<span className="text-blue-600">📤</span>
|
<SelectItem key={`from_${column.columnName}`} value={`from.${column.columnName}`}>
|
||||||
<span>{column.displayName || column.columnName}</span>
|
<div className="flex items-center gap-2">
|
||||||
</div>
|
<span className="text-blue-600">📤</span>
|
||||||
</SelectItem>
|
<span>{column.displayName || column.columnName}</span>
|
||||||
))}
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -503,14 +509,16 @@ const ActionConditionBuilder: React.FC<ActionConditionBuilderProps> = ({
|
||||||
{toColumns.length > 0 && (
|
{toColumns.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className="text-muted-foreground px-2 py-1 text-xs font-medium">TO 테이블</div>
|
<div className="text-muted-foreground px-2 py-1 text-xs font-medium">TO 테이블</div>
|
||||||
{toColumns.map((column) => (
|
{toColumns
|
||||||
<SelectItem key={`to_${column.columnName}`} value={`to.${column.columnName}`}>
|
.filter((column) => column.columnName) // 빈 문자열 제외
|
||||||
<div className="flex items-center gap-2">
|
.map((column) => (
|
||||||
<span className="text-green-600">📥</span>
|
<SelectItem key={`to_${column.columnName}`} value={`to.${column.columnName}`}>
|
||||||
<span>{column.displayName || column.columnName}</span>
|
<div className="flex items-center gap-2">
|
||||||
</div>
|
<span className="text-green-600">📥</span>
|
||||||
</SelectItem>
|
<span>{column.displayName || column.columnName}</span>
|
||||||
))}
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|
@ -612,14 +620,16 @@ const ActionConditionBuilder: React.FC<ActionConditionBuilderProps> = ({
|
||||||
<div className="text-muted-foreground px-2 py-1 text-xs font-medium">
|
<div className="text-muted-foreground px-2 py-1 text-xs font-medium">
|
||||||
FROM 테이블
|
FROM 테이블
|
||||||
</div>
|
</div>
|
||||||
{fromColumns.map((column) => (
|
{fromColumns
|
||||||
<SelectItem key={`from_${column.columnName}`} value={`from.${column.columnName}`}>
|
.filter((column) => column.columnName) // 빈 문자열 제외
|
||||||
<div className="flex items-center gap-2">
|
.map((column) => (
|
||||||
<span className="text-blue-600">📤</span>
|
<SelectItem key={`from_${column.columnName}`} value={`from.${column.columnName}`}>
|
||||||
<span>{column.displayName || column.columnName}</span>
|
<div className="flex items-center gap-2">
|
||||||
</div>
|
<span className="text-blue-600">📤</span>
|
||||||
</SelectItem>
|
<span>{column.displayName || column.columnName}</span>
|
||||||
))}
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -627,13 +637,15 @@ const ActionConditionBuilder: React.FC<ActionConditionBuilderProps> = ({
|
||||||
{toColumns.length > 0 && (
|
{toColumns.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className="text-muted-foreground px-2 py-1 text-xs font-medium">TO 테이블</div>
|
<div className="text-muted-foreground px-2 py-1 text-xs font-medium">TO 테이블</div>
|
||||||
{toColumns.map((column) => (
|
{toColumns
|
||||||
<SelectItem key={`to_${column.columnName}`} value={`to.${column.columnName}`}>
|
.filter((column) => column.columnName) // 빈 문자열 제외
|
||||||
<div className="flex items-center gap-2">
|
.map((column) => (
|
||||||
<span className="text-green-600">📥</span>
|
<SelectItem key={`to_${column.columnName}`} value={`to.${column.columnName}`}>
|
||||||
<span>{column.displayName || column.columnName}</span>
|
<div className="flex items-center gap-2">
|
||||||
</div>
|
<span className="text-green-600">📥</span>
|
||||||
</SelectItem>
|
<span>{column.displayName || column.columnName}</span>
|
||||||
|
</div>
|
||||||
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
@ -729,16 +741,18 @@ const ActionConditionBuilder: React.FC<ActionConditionBuilderProps> = ({
|
||||||
<SelectValue placeholder="대상 필드" />
|
<SelectValue placeholder="대상 필드" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{getAvailableFieldsForMapping(index).map((column) => (
|
{getAvailableFieldsForMapping(index)
|
||||||
<SelectItem key={column.columnName} value={column.columnName}>
|
.filter((column) => column.columnName) // 빈 문자열 제외
|
||||||
<div className="flex items-center gap-2">
|
.map((column) => (
|
||||||
<span>{column.displayName || column.columnName}</span>
|
<SelectItem key={column.columnName} value={column.columnName}>
|
||||||
<Badge variant="outline" className="text-xs">
|
<div className="flex items-center gap-2">
|
||||||
{column.webType || column.dataType}
|
<span>{column.displayName || column.columnName}</span>
|
||||||
</Badge>
|
<Badge variant="outline" className="text-xs">
|
||||||
</div>
|
{column.webType || column.dataType}
|
||||||
</SelectItem>
|
</Badge>
|
||||||
))}
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ export const EditModal: React.FC<EditModalProps> = ({
|
||||||
|
|
||||||
console.log(`🎯 계산된 모달 크기: ${maxWidth}px x ${maxHeight}px`);
|
console.log(`🎯 계산된 모달 크기: ${maxWidth}px x ${maxHeight}px`);
|
||||||
console.log(
|
console.log(
|
||||||
`📍 컴포넌트 위치들:`,
|
"📍 컴포넌트 위치들:",
|
||||||
components.map((c) => ({ x: c.position?.x, y: c.position?.y, w: c.size?.width, h: c.size?.height })),
|
components.map((c) => ({ x: c.position?.x, y: c.position?.y, w: c.size?.width, h: c.size?.height })),
|
||||||
);
|
);
|
||||||
return { width: maxWidth, height: maxHeight };
|
return { width: maxWidth, height: maxHeight };
|
||||||
|
|
@ -85,7 +85,7 @@ export const EditModal: React.FC<EditModalProps> = ({
|
||||||
// 스크롤 완전 제거
|
// 스크롤 완전 제거
|
||||||
if (modalContent) {
|
if (modalContent) {
|
||||||
modalContent.style.overflow = "hidden";
|
modalContent.style.overflow = "hidden";
|
||||||
console.log(`🚫 스크롤 완전 비활성화`);
|
console.log("🚫 스크롤 완전 비활성화");
|
||||||
}
|
}
|
||||||
}, 100); // 100ms 지연으로 렌더링 완료 후 실행
|
}, 100); // 100ms 지연으로 렌더링 완료 후 실행
|
||||||
}
|
}
|
||||||
|
|
@ -152,7 +152,7 @@ export const EditModal: React.FC<EditModalProps> = ({
|
||||||
|
|
||||||
// 코드 타입인 경우 특별히 로깅
|
// 코드 타입인 경우 특별히 로깅
|
||||||
if ((comp as any).widgetType === "code") {
|
if ((comp as any).widgetType === "code") {
|
||||||
console.log(` 🔍 코드 타입 세부정보:`, {
|
console.log(" 🔍 코드 타입 세부정보:", {
|
||||||
columnName: comp.columnName,
|
columnName: comp.columnName,
|
||||||
componentId: comp.id,
|
componentId: comp.id,
|
||||||
formValue,
|
formValue,
|
||||||
|
|
@ -275,22 +275,21 @@ export const EditModal: React.FC<EditModalProps> = ({
|
||||||
{components.map((component, index) => (
|
{components.map((component, index) => (
|
||||||
<div
|
<div
|
||||||
key={component.id}
|
key={component.id}
|
||||||
className="rounded-xl border border-gray-200/60 bg-gradient-to-br from-white to-gray-50/30 p-4 shadow-sm transition-all duration-200 hover:shadow-md"
|
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: component.position?.y || 0,
|
top: component.position?.y || 0,
|
||||||
left: component.position?.x || 0,
|
left: component.position?.x || 0,
|
||||||
width: component.size?.width || 200,
|
width: component.size?.width || 200,
|
||||||
height: component.size?.height || 40,
|
height: component.size?.height || 40,
|
||||||
zIndex: component.position?.z || (1000 + index), // 모달 내부에서 충분히 높은 z-index
|
zIndex: component.position?.z || 1000 + index, // 모달 내부에서 충분히 높은 z-index
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* 위젯 컴포넌트는 InteractiveScreenViewer 사용 (라벨 표시를 위해) */}
|
{/* 위젯 컴포넌트는 InteractiveScreenViewer 사용 (라벨 표시) */}
|
||||||
{component.type === "widget" ? (
|
{component.type === "widget" ? (
|
||||||
<InteractiveScreenViewer
|
<InteractiveScreenViewer
|
||||||
component={component}
|
component={component}
|
||||||
allComponents={components}
|
allComponents={components}
|
||||||
hideLabel={true} // 라벨 숨김 (원래 화면과 동일하게)
|
hideLabel={false} // ✅ 라벨 표시
|
||||||
formData={formData}
|
formData={formData}
|
||||||
onFormDataChange={(fieldName, value) => {
|
onFormDataChange={(fieldName, value) => {
|
||||||
console.log("📝 폼 데이터 변경:", fieldName, value);
|
console.log("📝 폼 데이터 변경:", fieldName, value);
|
||||||
|
|
@ -314,7 +313,7 @@ export const EditModal: React.FC<EditModalProps> = ({
|
||||||
...component,
|
...component,
|
||||||
style: {
|
style: {
|
||||||
...component.style,
|
...component.style,
|
||||||
labelDisplay: false, // 라벨 숨김 (원래 화면과 동일하게)
|
labelDisplay: true, // ✅ 라벨 표시
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
screenId={screenId}
|
screenId={screenId}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue