반응형 및 테이블 리스트 컴포넌트 오류 수정

This commit is contained in:
kjs
2025-10-17 15:31:23 +09:00
parent 54e9f45823
commit 2a8081a253
21 changed files with 886 additions and 262 deletions

View File

@@ -295,6 +295,54 @@ export class DynamicFormService {
}
});
// 📝 RepeaterInput 데이터 처리 (JSON 배열을 개별 레코드로 분해)
const repeaterData: Array<{
data: Record<string, any>[];
targetTable?: string;
componentId: string;
}> = [];
Object.keys(dataToInsert).forEach((key) => {
const value = dataToInsert[key];
// RepeaterInput 데이터인지 확인 (JSON 배열 문자열)
if (
typeof value === "string" &&
value.trim().startsWith("[") &&
value.trim().endsWith("]")
) {
try {
const parsedArray = JSON.parse(value);
if (Array.isArray(parsedArray) && parsedArray.length > 0) {
console.log(
`🔄 RepeaterInput 데이터 감지: ${key}, ${parsedArray.length}개 항목`
);
// 컴포넌트 설정에서 targetTable 추출 (컴포넌트 ID를 통해)
// 프론트엔드에서 { data: [...], targetTable: "..." } 형식으로 전달될 수 있음
let targetTable: string | undefined;
let actualData = parsedArray;
// 첫 번째 항목에 _targetTable이 있는지 확인 (프론트엔드에서 메타데이터 전달)
if (parsedArray[0] && parsedArray[0]._targetTable) {
targetTable = parsedArray[0]._targetTable;
actualData = parsedArray.map(
({ _targetTable, ...item }) => item
);
}
repeaterData.push({
data: actualData,
targetTable,
componentId: key,
});
delete dataToInsert[key]; // 원본 배열 데이터는 제거
}
} catch (parseError) {
console.log(`⚠️ JSON 파싱 실패: ${key}`);
}
}
});
// 존재하지 않는 컬럼 제거
Object.keys(dataToInsert).forEach((key) => {
if (!tableColumns.includes(key)) {
@@ -305,6 +353,9 @@ export class DynamicFormService {
}
});
// RepeaterInput 데이터 처리 로직은 메인 저장 후에 처리
// (각 Repeater가 다른 테이블에 저장될 수 있으므로)
console.log("🎯 실제 테이블에 삽입할 데이터:", {
tableName,
dataToInsert,
@@ -388,6 +439,111 @@ export class DynamicFormService {
// 결과를 표준 형식으로 변환
const insertedRecord = Array.isArray(result) ? result[0] : result;
// 📝 RepeaterInput 데이터 저장 (각 Repeater를 해당 테이블에 저장)
if (repeaterData.length > 0) {
console.log(
`🔄 RepeaterInput 데이터 저장 시작: ${repeaterData.length}개 Repeater`
);
for (const repeater of repeaterData) {
const targetTableName = repeater.targetTable || tableName;
console.log(
`📝 Repeater "${repeater.componentId}" → 테이블 "${targetTableName}"에 ${repeater.data.length}개 항목 저장`
);
// 대상 테이블의 컬럼 및 기본키 정보 조회
const targetTableColumns =
await this.getTableColumns(targetTableName);
const targetPrimaryKeys = await this.getPrimaryKeys(targetTableName);
// 컬럼명만 추출
const targetColumnNames = targetTableColumns.map(
(col) => col.columnName
);
// 각 항목을 저장
for (let i = 0; i < repeater.data.length; i++) {
const item = repeater.data[i];
const itemData: Record<string, any> = {
...item,
created_by,
updated_by,
regdate: new Date(),
};
// 대상 테이블에 존재하는 컬럼만 필터링
Object.keys(itemData).forEach((key) => {
if (!targetColumnNames.includes(key)) {
delete itemData[key];
}
});
// 타입 변환 적용
Object.keys(itemData).forEach((columnName) => {
const column = targetTableColumns.find(
(col) => col.columnName === columnName
);
if (column) {
itemData[columnName] = this.convertValueForPostgreSQL(
itemData[columnName],
column.dataType
);
}
});
// UPSERT 쿼리 생성
const itemColumns = Object.keys(itemData);
const itemValues: any[] = Object.values(itemData);
const itemPlaceholders = itemValues
.map((_, index) => `$${index + 1}`)
.join(", ");
let itemUpsertQuery: string;
if (targetPrimaryKeys.length > 0) {
const conflictColumns = targetPrimaryKeys.join(", ");
const updateSet = itemColumns
.filter((col) => !targetPrimaryKeys.includes(col))
.map((col) => `${col} = EXCLUDED.${col}`)
.join(", ");
if (updateSet) {
itemUpsertQuery = `
INSERT INTO ${targetTableName} (${itemColumns.join(", ")})
VALUES (${itemPlaceholders})
ON CONFLICT (${conflictColumns})
DO UPDATE SET ${updateSet}
RETURNING *
`;
} else {
itemUpsertQuery = `
INSERT INTO ${targetTableName} (${itemColumns.join(", ")})
VALUES (${itemPlaceholders})
ON CONFLICT (${conflictColumns})
DO NOTHING
RETURNING *
`;
}
} else {
itemUpsertQuery = `
INSERT INTO ${targetTableName} (${itemColumns.join(", ")})
VALUES (${itemPlaceholders})
RETURNING *
`;
}
console.log(
` 📝 항목 ${i + 1}/${repeater.data.length} 저장:`,
itemData
);
await query<any>(itemUpsertQuery, itemValues);
}
console.log(` ✅ Repeater "${repeater.componentId}" 저장 완료`);
}
console.log(`✅ 모든 RepeaterInput 데이터 저장 완료`);
}
// 🔥 조건부 연결 실행 (INSERT 트리거)
try {
if (company_code) {
@@ -1114,6 +1270,31 @@ export class DynamicFormService {
}
}
/**
* 테이블의 기본키 컬럼명 목록 조회
*/
async getPrimaryKeys(tableName: string): Promise<string[]> {
try {
console.log("🔑 서비스: 테이블 기본키 조회 시작:", { tableName });
const result = await query<{ column_name: string }>(
`SELECT a.attname AS column_name
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]
);
const primaryKeys = result.map((row) => row.column_name);
console.log("✅ 서비스: 테이블 기본키 조회 성공:", primaryKeys);
return primaryKeys;
} catch (error) {
console.error("❌ 서비스: 테이블 기본키 조회 실패:", error);
throw new Error(`테이블 기본키 조회 실패: ${error}`);
}
}
/**
* 제어관리 실행 (화면에 설정된 경우)
*/