diff --git a/frontend/components/screen/EditModal.tsx b/frontend/components/screen/EditModal.tsx index 48bf898f..106787cf 100644 --- a/frontend/components/screen/EditModal.tsx +++ b/frontend/components/screen/EditModal.tsx @@ -1039,8 +1039,15 @@ export const EditModal: React.FC = ({ className }) => { } ); + // 🆕 _tableSection_ 데이터가 있는지 확인 (TableSectionRenderer 사용 시) + // _tableSection_ 데이터가 있으면 buttonActions.ts의 handleUniversalFormModalTableSectionSave가 처리 + const hasTableSectionData = Object.keys(formData).some(k => + k.startsWith("_tableSection_") || k.startsWith("__tableSection_") + ); + // 🆕 그룹 데이터가 있으면 EditModal.handleSave 사용 (일괄 저장) - const shouldUseEditModalSave = groupData.length > 0 || !hasUniversalFormModal; + // 단, _tableSection_ 데이터가 있으면 EditModal.handleSave 사용하지 않음 (buttonActions.ts가 처리) + const shouldUseEditModalSave = !hasTableSectionData && (groupData.length > 0 || !hasUniversalFormModal); // 🔑 첨부파일 컴포넌트가 행(레코드) 단위로 파일을 저장할 수 있도록 tableName 추가 const enrichedFormData = { diff --git a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx index 939bb5d5..5fc24920 100644 --- a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx +++ b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx @@ -439,6 +439,12 @@ export function UniversalFormModalComponent({ event.detail.formData[normalizedKey] = value; console.log(`[UniversalFormModal] 테이블 섹션 병합: ${key} → ${normalizedKey}, ${value.length}개 항목`); } + + // 🆕 원본 테이블 섹션 데이터도 병합 (삭제 추적용) + if (key.startsWith("_originalTableSectionData_") && Array.isArray(value)) { + event.detail.formData[key] = value; + console.log(`[UniversalFormModal] 원본 테이블 섹션 데이터 병합: ${key}, ${value.length}개 항목`); + } } // 🆕 수정 모드: 원본 그룹 데이터 전달 (UPDATE/DELETE 추적용) @@ -928,17 +934,19 @@ export function UniversalFormModalComponent({ newFormData[tableSectionKey] = items; console.log(`[initializeForm] 테이블 섹션 ${section.id}: formData[${tableSectionKey}]에 저장됨`); - // 🆕 원본 그룹 데이터 저장 (삭제 추적용) - // groupedDataInitializedRef가 false일 때만 설정 (true면 _groupedData useEffect에서 이미 처리됨) - // DB에서 로드한 데이터를 originalGroupedData에 저장해야 삭제 시 비교 가능 + // 🆕 테이블 섹션 원본 데이터 저장 (삭제 추적용) + // 각 테이블 섹션별로 별도의 키에 원본 데이터 저장 (groupedDataInitializedRef와 무관하게 항상 저장) + const originalTableSectionKey = `_originalTableSectionData_${section.id}`; + newFormData[originalTableSectionKey] = JSON.parse(JSON.stringify(items)); + console.log(`[initializeForm] 테이블 섹션 ${section.id}: formData[${originalTableSectionKey}]에 원본 ${items.length}건 저장`); + + // 기존 originalGroupedData에도 추가 (하위 호환성) if (!groupedDataInitializedRef.current) { setOriginalGroupedData((prev) => { const newOriginal = [...prev, ...JSON.parse(JSON.stringify(items))]; console.log(`[initializeForm] 테이블 섹션 ${section.id}: originalGroupedData에 ${items.length}건 추가 (총 ${newOriginal.length}건)`); return newOriginal; }); - } else { - console.log(`[initializeForm] 테이블 섹션 ${section.id}: _groupedData로 이미 초기화됨, originalGroupedData 설정 스킵`); } } } catch (error) { diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index a4b6074c..b8d37c19 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -522,21 +522,7 @@ export class ButtonActionExecutor { } console.log("✅ [handleSave] 필수 항목 검증 통과"); - // 🆕 EditModal 등에서 전달된 onSave 콜백이 있으면 우선 사용 - if (onSave) { - console.log("✅ [handleSave] onSave 콜백 발견 - 콜백 실행"); - try { - await onSave(); - return true; - } catch (error) { - console.error("❌ [handleSave] onSave 콜백 실행 오류:", error); - throw error; - } - } - - console.log("⚠️ [handleSave] onSave 콜백 없음 - 기본 저장 로직 실행"); - - // 🆕 저장 전 이벤트 발생 (SelectedItemsDetailInput 등에서 최신 데이터 수집) + // 🆕 저장 전 이벤트 먼저 발생 (UniversalFormModal의 __tableSection_ 데이터 병합을 위해) // context.formData를 이벤트 detail에 포함하여 직접 수정 가능하게 함 // skipDefaultSave 플래그를 통해 기본 저장 로직을 건너뛸 수 있음 const beforeSaveEventDetail = { @@ -552,13 +538,38 @@ export class ButtonActionExecutor { // 약간의 대기 시간을 주어 이벤트 핸들러가 formData를 업데이트할 수 있도록 함 await new Promise((resolve) => setTimeout(resolve, 100)); + console.log("📦 [handleSave] beforeFormSave 이벤트 후 formData keys:", Object.keys(context.formData || {})); + // 🔧 skipDefaultSave 플래그 확인 - SelectedItemsDetailInput 등에서 자체 UPSERT 처리 시 기본 저장 건너뛰기 if (beforeSaveEventDetail.skipDefaultSave) { console.log("🚫 [handleSave] skipDefaultSave=true - 기본 저장 로직 건너뛰기 (컴포넌트에서 자체 처리)"); return true; } - console.log("📦 [handleSave] beforeFormSave 이벤트 후 formData:", context.formData); + // 🆕 _tableSection_ 데이터가 있는지 확인 (TableSectionRenderer 사용 시) + // beforeFormSave 이벤트 후에 체크해야 UniversalFormModal에서 병합된 데이터를 확인할 수 있음 + const hasTableSectionData = Object.keys(context.formData || {}).some(k => + k.startsWith("_tableSection_") || k.startsWith("__tableSection_") + ); + + if (hasTableSectionData) { + console.log("📋 [handleSave] _tableSection_ 데이터 감지 - onSave 콜백 건너뛰고 테이블 섹션 저장 로직 사용"); + } + + // 🆕 EditModal 등에서 전달된 onSave 콜백이 있으면 우선 사용 + // 단, _tableSection_ 데이터가 있으면 건너뛰기 (handleUniversalFormModalTableSectionSave가 처리) + if (onSave && !hasTableSectionData) { + console.log("✅ [handleSave] onSave 콜백 발견 - 콜백 실행 (테이블 섹션 데이터 없음)"); + try { + await onSave(); + return true; + } catch (error) { + console.error("❌ [handleSave] onSave 콜백 실행 오류:", error); + throw error; + } + } + + console.log("⚠️ [handleSave] 기본 저장 로직 실행 (onSave 콜백 없음 또는 _tableSection_ 데이터 있음)"); // 🆕 렉 구조 컴포넌트 일괄 저장 감지 let rackStructureLocations: any[] | undefined; @@ -2238,9 +2249,24 @@ export class ButtonActionExecutor { } // 3️⃣ 삭제된 품목 DELETE (원본에는 있지만 현재에는 없는 항목) + // 🆕 테이블 섹션별 원본 데이터 사용 (우선), 없으면 전역 originalGroupedData 사용 + const sectionOriginalKey = `_originalTableSectionData_${sectionId}`; + const sectionOriginalData: any[] = modalData[sectionOriginalKey] || formData[sectionOriginalKey] || []; + + // 섹션별 원본 데이터가 있으면 사용, 없으면 전역 originalGroupedData 사용 + const originalDataForDelete = sectionOriginalData.length > 0 ? sectionOriginalData : originalGroupedData; + + console.log(`🔍 [DELETE 비교] 섹션 ${sectionId}:`, { + sectionOriginalKey, + sectionOriginalCount: sectionOriginalData.length, + globalOriginalCount: originalGroupedData.length, + usingData: sectionOriginalData.length > 0 ? "섹션별 원본" : "전역 원본", + currentCount: currentItems.length + }); + // ⚠️ id 타입 통일: 문자열로 변환하여 비교 (숫자 vs 문자열 불일치 방지) const currentIds = new Set(currentItems.map((item) => String(item.id)).filter(Boolean)); - const deletedItems = originalGroupedData.filter((orig) => orig.id && !currentIds.has(String(orig.id))); + const deletedItems = originalDataForDelete.filter((orig) => orig.id && !currentIds.has(String(orig.id))); for (const deletedItem of deletedItems) { console.log(`🗑️ [DELETE] 품목 삭제: id=${deletedItem.id}, tableName=${saveTableName}`);