fix: update file handling and improve query logging

- Added mes-architecture-guide.md to .gitignore to prevent unnecessary tracking.
- Enhanced NodeFlowExecutionService to merge context data for WHERE clause, improving query accuracy.
- Updated logging to include values in SQL query logs for better debugging.
- Removed redundant event dispatches in V2Repeater to streamline save operations.
- Adjusted DynamicComponentRenderer to conditionally refresh keys based on component type.
- Improved FileUploadComponent to clear localStorage only for modal components, preventing unintended resets in non-modal contexts.

These changes aim to enhance the overall functionality and maintainability of the application, ensuring better data handling and user experience.
This commit is contained in:
kjs
2026-03-18 17:43:03 +09:00
parent 359bf0e614
commit c634e1e054
8 changed files with 95 additions and 87 deletions

View File

@@ -542,15 +542,6 @@ export class ButtonActionExecutor {
this.saveCallCount++;
const callId = this.saveCallCount;
// 🔧 디버그: context.formData 확인 (handleSave 진입 시점)
console.log("🔍 [handleSave] 진입 시 context.formData:", {
keys: Object.keys(context.formData || {}),
hasCompanyImage: "company_image" in (context.formData || {}),
hasCompanyLogo: "company_logo" in (context.formData || {}),
companyImageValue: context.formData?.company_image,
companyLogoValue: context.formData?.company_logo,
});
const { formData, originalData, tableName, screenId, onSave } = context;
// 🆕 중복 호출 방지: 같은 screenId + tableName + formData 조합으로 2초 내 재호출 시 무시
@@ -621,6 +612,18 @@ export class ButtonActionExecutor {
if (onSave) {
try {
await onSave();
// 모달 저장 후에도 제어관리 실행 (onSave 경로에서도 dataflow 지원)
if (config.enableDataflowControl && config.dataflowConfig) {
console.log("📦 [handleSave] onSave 콜백 후 제어관리 실행 시작");
const contextWithSavedData = {
...context,
savedData: context.formData,
selectedRowsData: context.selectedRowsData || [],
};
await this.executeAfterSaveControl(config, contextWithSavedData);
}
return true;
} catch (error: any) {
console.error("❌ [handleSave] onSave 콜백 실행 오류:", error);
@@ -636,13 +639,6 @@ export class ButtonActionExecutor {
console.log("⚠️ [handleSave] onSave 콜백 없음 - 기본 저장 로직 실행");
// 🔧 디버그: beforeFormSave 이벤트 후 formData 확인
console.log("🔍 [handleSave] beforeFormSave 이벤트 후:", {
keys: Object.keys(context.formData || {}),
hasCompanyImage: "company_image" in (context.formData || {}),
companyImageValue: context.formData?.company_image,
});
// skipDefaultSave 플래그 확인
if (beforeSaveEventDetail.skipDefaultSave) {
return true;
@@ -749,11 +745,6 @@ export class ButtonActionExecutor {
return await this.handleBatchSave(config, context, selectedItemsKeys);
} else {
console.log("⚠️ [handleSave] SelectedItemsDetailInput 데이터 감지 실패 - 일반 저장 진행");
// 🔧 디버그: formData 상세 확인
console.log("🔍 [handleSave] formData 키 목록:", Object.keys(context.formData || {}));
console.log("🔍 [handleSave] formData.company_image:", context.formData?.company_image);
console.log("🔍 [handleSave] formData.company_logo:", context.formData?.company_logo);
console.log("⚠️ [handleSave] formData 전체 내용:", context.formData);
}
// 🆕 RepeaterFieldGroup JSON 문자열 파싱 및 저장 처리
@@ -1011,6 +1002,9 @@ export class ButtonActionExecutor {
// saveResult를 상위 스코프에서 정의 (repeaterSave 이벤트에서 사용)
let saveResult: { success: boolean; data?: any; message?: string } | undefined;
// 제어 실행 데이터를 상위 스코프에서 정의 (리피터 저장 완료 후 실행 위해)
let pendingDataflowControl: { config: ButtonActionConfig; context: ButtonActionContext } | null = null;
if (tableName && screenId) {
// DB에서 실제 기본키 조회하여 INSERT/UPDATE 자동 판단
const primaryKeyResult = await DynamicFormApi.getTablePrimaryKeys(tableName);
@@ -1758,25 +1752,20 @@ export class ButtonActionExecutor {
return false;
}
// 🔥 저장 성공 후 연결된 제어 실행 (dataflowTiming이 'after'인 경우)
// 제어 실행 준비 (실제 실행은 리피터 저장 완료 후로 지연)
console.log("🔍 [handleSave] 제어관리 설정 체크:", {
enableDataflowControl: config.enableDataflowControl,
hasDataflowConfig: !!config.dataflowConfig,
flowControls: config.dataflowConfig?.flowControls?.length || 0,
});
if (config.enableDataflowControl && config.dataflowConfig) {
// 테이블 섹션 데이터 파싱 (comp_로 시작하는 필드에 JSON 배열이 있는 경우)
// 입고 화면 등에서 품목 목록이 comp_xxx 필드에 JSON 문자열로 저장됨
// 🔧 수정: saveResult.data가 3단계로 중첩된 경우 실제 폼 데이터 추출
// saveResult.data = API 응답 { success, data, message }
// saveResult.data.data = 저장된 레코드 { id, screenId, tableName, data, createdAt... }
// saveResult.data.data.data = 실제 폼 데이터 { sabun, user_name... }
const savedRecord = saveResult?.data?.data || saveResult?.data || {};
const actualFormData = savedRecord?.data || savedRecord;
const formData: Record<string, any> = (
Object.keys(actualFormData).length > 0 ? actualFormData : context.formData || {}
) as Record<string, any>;
console.log("📦 [executeAfterSaveControl] savedRecord 구조:", Object.keys(savedRecord));
console.log("📦 [executeAfterSaveControl] actualFormData 추출:", Object.keys(formData));
console.log("📦 [executeAfterSaveControl] formData.sabun:", formData.sabun);
let parsedSectionData: any[] = [];
// comp_로 시작하는 필드에서 테이블 섹션 데이터 찾기
const compFieldKey = Object.keys(formData).find(
(key) => key.startsWith("comp_") && typeof formData[key] === "string",
);
@@ -1785,11 +1774,8 @@ export class ButtonActionExecutor {
try {
const sectionData = JSON.parse(formData[compFieldKey]);
if (Array.isArray(sectionData) && sectionData.length > 0) {
// 공통 필드와 섹션 데이터 병합
parsedSectionData = sectionData.map((item: any) => {
// 섹션 데이터에서 불필요한 내부 필드 제거
const { _isNewItem, _targetTable, _existingRecord, ...cleanItem } = item;
// 공통 필드(comp_ 필드 제외) + 섹션 아이템 병합
const commonFields: Record<string, any> = {};
Object.keys(formData).forEach((key) => {
if (!key.startsWith("comp_") && !key.endsWith("_numberingRuleId")) {
@@ -1804,14 +1790,14 @@ export class ButtonActionExecutor {
}
}
// 저장된 데이터를 context에 추가하여 플로우에 전달
const contextWithSavedData = {
...context,
savedData: formData,
// 파싱된 섹션 데이터가 있으면 selectedRowsData로 전달
selectedRowsData: parsedSectionData.length > 0 ? parsedSectionData : context.selectedRowsData,
pendingDataflowControl = {
config,
context: {
...context,
savedData: formData,
selectedRowsData: parsedSectionData.length > 0 ? parsedSectionData : context.selectedRowsData,
},
};
await this.executeAfterSaveControl(config, contextWithSavedData);
}
} else {
throw new Error("저장에 필요한 정보가 부족합니다. (테이블명 또는 화면ID 누락)");
@@ -1935,13 +1921,26 @@ export class ButtonActionExecutor {
await repeaterSavePromise;
}
// 리피터 저장 완료 후 제어관리 실행 (디테일 레코드가 DB에 있는 상태에서 실행)
console.log("🔍 [handleSave] 리피터 저장 완료, pendingDataflowControl:", !!pendingDataflowControl);
if (pendingDataflowControl) {
console.log("📦 [handleSave] 리피터 저장 완료 후 제어관리 실행 시작");
await this.executeAfterSaveControl(
pendingDataflowControl.config,
pendingDataflowControl.context,
);
}
// 테이블과 플로우 새로고침 (모달 닫기 전에 실행)
context.onRefresh?.();
context.onFlowRefresh?.();
// 저장 성공 후 모달 닫기 이벤트 발생
window.dispatchEvent(new CustomEvent("closeEditModal"));
window.dispatchEvent(new CustomEvent("saveSuccessInModal"));
// 저장 성공 후 모달 닫기 이벤트 발생 (모달 내부에서만)
// 비모달 화면에서 이 이벤트를 발행하면 ScreenModal이 반응하여 컴포넌트 트리 재마운트 발생
if (context.onClose) {
window.dispatchEvent(new CustomEvent("closeEditModal"));
window.dispatchEvent(new CustomEvent("saveSuccessInModal"));
}
return true;
} catch (error: any) {