From 3c4e251e9b2cc6aebe166c134c8cf1f2fd47f322 Mon Sep 17 00:00:00 2001 From: kjs Date: Tue, 30 Dec 2025 12:33:17 +0900 Subject: [PATCH] =?UTF-8?q?=ED=8F=BC=20=EB=8B=A4=EC=A4=91=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=EC=A0=80=EC=9E=A5=20=EC=A7=80=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UniversalFormModalComponent.tsx | 82 +++-- frontend/lib/utils/buttonActions.ts | 309 +++++++++++++----- 2 files changed, 284 insertions(+), 107 deletions(-) diff --git a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx index b4921a51..9edf4054 100644 --- a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx +++ b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx @@ -963,6 +963,13 @@ export function UniversalFormModalComponent({ } } + // 별도 테이블에 저장해야 하는 테이블 섹션 목록 + const tableSectionsForSeparateTable = config.sections.filter( + (s) => s.type === "table" && + s.tableConfig?.saveConfig?.targetTable && + s.tableConfig.saveConfig.targetTable !== config.saveConfig.tableName + ); + // 테이블 섹션이 있고 메인 테이블에 품목별로 저장하는 경우 (공통 + 개별 병합 저장) // targetTable이 없거나 메인 테이블과 같은 경우 const tableSectionsForMainTable = config.sections.filter( @@ -971,6 +978,12 @@ export function UniversalFormModalComponent({ s.tableConfig.saveConfig.targetTable === config.saveConfig.tableName) ); + console.log("[saveSingleRow] 메인 테이블:", config.saveConfig.tableName); + console.log("[saveSingleRow] 메인 테이블에 저장할 테이블 섹션:", tableSectionsForMainTable.map(s => s.id)); + console.log("[saveSingleRow] 별도 테이블에 저장할 테이블 섹션:", tableSectionsForSeparateTable.map(s => s.id)); + console.log("[saveSingleRow] 테이블 섹션 데이터 키:", Object.keys(tableSectionData)); + console.log("[saveSingleRow] dataToSave 키:", Object.keys(dataToSave)); + if (tableSectionsForMainTable.length > 0) { // 공통 저장 필드 수집 (sectionSaveModes 설정에 따라) const commonFieldsData: Record = {}; @@ -1050,35 +1063,51 @@ export function UniversalFormModalComponent({ // 메인 레코드 ID가 필요한 경우 (response.data에서 가져오기) const mainRecordId = response.data?.data?.id; - // 공통 저장 필드 수집 (sectionSaveModes 설정에 따라) + // 공통 저장 필드 수집: 다른 섹션(필드 타입)에서 공통 저장으로 설정된 필드 값 + // 기본값: 필드 타입 섹션은 'common', 테이블 타입 섹션은 'individual' const commonFieldsData: Record = {}; const { sectionSaveModes } = config.saveConfig; - if (sectionSaveModes && sectionSaveModes.length > 0) { - // 다른 섹션에서 공통 저장으로 설정된 필드 값 수집 - for (const otherSection of config.sections) { - if (otherSection.id === section.id) continue; // 현재 테이블 섹션은 건너뛰기 - - const sectionMode = sectionSaveModes.find((s) => s.sectionId === otherSection.id); - const defaultMode = otherSection.type === "table" ? "individual" : "common"; - const sectionSaveMode = sectionMode?.saveMode || defaultMode; - - // 필드 타입 섹션의 필드들 처리 - if (otherSection.type !== "table" && otherSection.fields) { - for (const field of otherSection.fields) { - // 필드별 오버라이드 확인 - const fieldOverride = sectionMode?.fieldOverrides?.find((f) => f.fieldName === field.columnName); - const fieldSaveMode = fieldOverride?.saveMode || sectionSaveMode; - - // 공통 저장이면 formData에서 값을 가져와 모든 품목에 적용 - if (fieldSaveMode === "common" && formData[field.columnName] !== undefined) { - commonFieldsData[field.columnName] = formData[field.columnName]; + // 다른 섹션에서 공통 저장으로 설정된 필드 값 수집 + for (const otherSection of config.sections) { + if (otherSection.id === section.id) continue; // 현재 테이블 섹션은 건너뛰기 + + const sectionMode = sectionSaveModes?.find((s) => s.sectionId === otherSection.id); + // 기본값: 필드 타입 섹션은 'common', 테이블 타입 섹션은 'individual' + const defaultMode = otherSection.type === "table" ? "individual" : "common"; + const sectionSaveMode = sectionMode?.saveMode || defaultMode; + + // 필드 타입 섹션의 필드들 처리 + if (otherSection.type !== "table" && otherSection.fields) { + for (const field of otherSection.fields) { + // 필드별 오버라이드 확인 + const fieldOverride = sectionMode?.fieldOverrides?.find((f) => f.fieldName === field.columnName); + const fieldSaveMode = fieldOverride?.saveMode || sectionSaveMode; + + // 공통 저장이면 formData에서 값을 가져와 모든 품목에 적용 + if (fieldSaveMode === "common" && formData[field.columnName] !== undefined) { + commonFieldsData[field.columnName] = formData[field.columnName]; + } + } + } + + // 🆕 선택적 필드 그룹 (optionalFieldGroups)도 처리 + if (otherSection.optionalFieldGroups && otherSection.optionalFieldGroups.length > 0) { + for (const optGroup of otherSection.optionalFieldGroups) { + if (optGroup.fields) { + for (const field of optGroup.fields) { + // 선택적 필드 그룹은 기본적으로 common 저장 + if (formData[field.columnName] !== undefined) { + commonFieldsData[field.columnName] = formData[field.columnName]; + } } } } } } + console.log("[saveSingleRow] 별도 테이블 저장 - 공통 필드:", Object.keys(commonFieldsData)); + for (const item of sectionData) { // 공통 필드 병합 + 개별 품목 데이터 const itemToSave = { ...commonFieldsData, ...item }; @@ -1091,15 +1120,26 @@ export function UniversalFormModalComponent({ } } + // _sourceData 등 내부 메타데이터 제거 + Object.keys(itemToSave).forEach((key) => { + if (key.startsWith("_")) { + delete itemToSave[key]; + } + }); + // 메인 레코드와 연결이 필요한 경우 if (mainRecordId && config.saveConfig.primaryKeyColumn) { itemToSave[config.saveConfig.primaryKeyColumn] = mainRecordId; } - await apiClient.post( + const saveResponse = await apiClient.post( `/table-management/tables/${section.tableConfig.saveConfig.targetTable}/add`, itemToSave ); + + if (!saveResponse.data?.success) { + throw new Error(saveResponse.data?.message || `${section.title || "테이블 섹션"} 저장 실패`); + } } } } diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index de98028a..9a6a606e 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -1491,6 +1491,7 @@ export class ButtonActionExecutor { * 🆕 Universal Form Modal 테이블 섹션 병합 저장 처리 * 범용_폼_모달 내부의 공통 필드 + _tableSection_ 데이터를 병합하여 품목별로 저장 * 수정 모드: INSERT/UPDATE/DELETE 지원 + * 🆕 섹션별 저장 테이블(targetTable) 지원 추가 */ private static async handleUniversalFormModalTableSectionSave( config: ButtonActionConfig, @@ -1514,7 +1515,66 @@ export class ButtonActionExecutor { console.log("🎯 [handleUniversalFormModalTableSectionSave] Universal Form Modal 감지:", universalFormModalKey); const modalData = formData[universalFormModalKey]; - + + // 🆕 universal-form-modal 컴포넌트 설정 가져오기 + // 1. componentConfigs에서 컴포넌트 ID로 찾기 + // 2. allComponents에서 columnName으로 찾기 + // 3. 화면 레이아웃 API에서 가져오기 + let modalComponentConfig = context.componentConfigs?.[universalFormModalKey]; + + // componentConfigs에서 직접 찾지 못한 경우, allComponents에서 columnName으로 찾기 + if (!modalComponentConfig && context.allComponents) { + const modalComponent = context.allComponents.find( + (comp: any) => + comp.columnName === universalFormModalKey || comp.properties?.columnName === universalFormModalKey, + ); + if (modalComponent) { + modalComponentConfig = modalComponent.componentConfig || modalComponent.properties?.componentConfig; + console.log("🎯 [handleUniversalFormModalTableSectionSave] allComponents에서 설정 찾음:", modalComponent.id); + } + } + + // 🆕 아직도 설정을 찾지 못했으면 화면 레이아웃 API에서 가져오기 + if (!modalComponentConfig && screenId) { + try { + console.log("🔍 [handleUniversalFormModalTableSectionSave] 화면 레이아웃 API에서 설정 조회:", screenId); + const { screenApi } = await import("@/lib/api/screen"); + const layoutData = await screenApi.getLayout(screenId); + + if (layoutData && layoutData.components) { + // 레이아웃에서 universal-form-modal 컴포넌트 찾기 + const modalLayout = (layoutData.components as any[]).find( + (comp) => + comp.properties?.columnName === universalFormModalKey || comp.columnName === universalFormModalKey, + ); + if (modalLayout) { + modalComponentConfig = modalLayout.properties?.componentConfig || modalLayout.componentConfig; + console.log( + "🎯 [handleUniversalFormModalTableSectionSave] 화면 레이아웃에서 설정 찾음:", + modalLayout.componentId, + ); + } + } + } catch (error) { + console.warn("⚠️ [handleUniversalFormModalTableSectionSave] 화면 레이아웃 조회 실패:", error); + } + } + + const sections: any[] = modalComponentConfig?.sections || []; + const saveConfig = modalComponentConfig?.saveConfig || {}; + + console.log("🎯 [handleUniversalFormModalTableSectionSave] 컴포넌트 설정:", { + hasComponentConfig: !!modalComponentConfig, + sectionsCount: sections.length, + mainTableName: saveConfig.tableName || tableName, + sectionSaveModes: saveConfig.sectionSaveModes, + sectionDetails: sections.map((s: any) => ({ + id: s.id, + type: s.type, + targetTable: s.tableConfig?.saveConfig?.targetTable, + })), + }); + // _tableSection_ 데이터 추출 const tableSectionData: Record = {}; const commonFieldsData: Record = {}; @@ -1564,10 +1624,64 @@ export class ButtonActionExecutor { let insertedCount = 0; let updatedCount = 0; let deletedCount = 0; + let mainRecordId: number | null = null; + + // 🆕 먼저 메인 테이블에 공통 데이터 저장 (별도 테이블이 있는 경우에만) + const hasSeparateTargetTable = sections.some( + (s) => + s.type === "table" && + s.tableConfig?.saveConfig?.targetTable && + s.tableConfig.saveConfig.targetTable !== tableName, + ); + + if (hasSeparateTargetTable && Object.keys(commonFieldsData).length > 0) { + console.log("📦 [handleUniversalFormModalTableSectionSave] 메인 테이블에 공통 데이터 저장:", tableName); + + const mainRowToSave = { ...commonFieldsData, ...userInfo }; + + // 메타데이터 제거 + Object.keys(mainRowToSave).forEach((key) => { + if (key.startsWith("_")) { + delete mainRowToSave[key]; + } + }); + + console.log("📦 [handleUniversalFormModalTableSectionSave] 메인 테이블 저장 데이터:", mainRowToSave); + + const mainSaveResult = await DynamicFormApi.saveFormData({ + screenId: screenId!, + tableName: tableName!, + data: mainRowToSave, + }); + + if (!mainSaveResult.success) { + throw new Error(mainSaveResult.message || "메인 데이터 저장 실패"); + } + + mainRecordId = mainSaveResult.data?.id || null; + console.log("✅ [handleUniversalFormModalTableSectionSave] 메인 테이블 저장 완료, ID:", mainRecordId); + } // 각 테이블 섹션 처리 for (const [sectionId, currentItems] of Object.entries(tableSectionData)) { - console.log(`🔄 [handleUniversalFormModalTableSectionSave] 섹션 ${sectionId} 처리 시작: ${currentItems.length}개 품목`); + console.log( + `🔄 [handleUniversalFormModalTableSectionSave] 섹션 ${sectionId} 처리 시작: ${currentItems.length}개 품목`, + ); + + // 🆕 해당 섹션의 설정 찾기 + const sectionConfig = sections.find((s) => s.id === sectionId); + const targetTableName = sectionConfig?.tableConfig?.saveConfig?.targetTable; + + // 🆕 실제 저장할 테이블 결정 + // - targetTable이 있으면 해당 테이블에 저장 + // - targetTable이 없으면 메인 테이블에 저장 + const saveTableName = targetTableName || tableName!; + + console.log(`📊 [handleUniversalFormModalTableSectionSave] 섹션 ${sectionId} 저장 테이블:`, { + targetTableName, + saveTableName, + isMainTable: saveTableName === tableName, + }); // 1️⃣ 신규 품목 INSERT (id가 없는 항목) const newItems = currentItems.filter((item) => !item.id); @@ -1581,11 +1695,16 @@ export class ButtonActionExecutor { } }); - console.log("➕ [INSERT] 신규 품목:", rowToSave); + // 🆕 메인 레코드 ID 연결 (별도 테이블에 저장하는 경우) + if (targetTableName && mainRecordId && saveConfig.primaryKeyColumn) { + rowToSave[saveConfig.primaryKeyColumn] = mainRecordId; + } + + console.log("➕ [INSERT] 신규 품목:", { tableName: saveTableName, data: rowToSave }); const saveResult = await DynamicFormApi.saveFormData({ screenId: screenId!, - tableName: tableName!, + tableName: saveTableName, data: rowToSave, }); @@ -1612,9 +1731,14 @@ export class ButtonActionExecutor { }); delete rowToSave.id; // id 제거하여 INSERT + // 🆕 메인 레코드 ID 연결 (별도 테이블에 저장하는 경우) + if (targetTableName && mainRecordId && saveConfig.primaryKeyColumn) { + rowToSave[saveConfig.primaryKeyColumn] = mainRecordId; + } + const saveResult = await DynamicFormApi.saveFormData({ screenId: screenId!, - tableName: tableName!, + tableName: saveTableName, data: rowToSave, }); @@ -1631,14 +1755,14 @@ export class ButtonActionExecutor { const hasChanges = this.checkForChanges(originalItem, currentDataWithCommon); if (hasChanges) { - console.log(`🔄 [UPDATE] 품목 수정: id=${item.id}`); + console.log(`🔄 [UPDATE] 품목 수정: id=${item.id}, tableName=${saveTableName}`); // 변경된 필드만 추출하여 부분 업데이트 const updateResult = await DynamicFormApi.updateFormDataPartial( item.id, originalItem, currentDataWithCommon, - tableName!, + saveTableName, ); if (!updateResult.success) { @@ -1656,9 +1780,9 @@ export class ButtonActionExecutor { const deletedItems = originalGroupedData.filter((orig) => orig.id && !currentIds.has(orig.id)); for (const deletedItem of deletedItems) { - console.log(`🗑️ [DELETE] 품목 삭제: id=${deletedItem.id}`); + console.log(`🗑️ [DELETE] 품목 삭제: id=${deletedItem.id}, tableName=${saveTableName}`); - const deleteResult = await DynamicFormApi.deleteFormDataFromTable(tableName!, deletedItem.id); + const deleteResult = await DynamicFormApi.deleteFormDataFromTable(saveTableName, deletedItem.id); if (!deleteResult.success) { throw new Error(deleteResult.message || "품목 삭제 실패"); @@ -1670,6 +1794,7 @@ export class ButtonActionExecutor { // 결과 메시지 생성 const resultParts: string[] = []; + if (mainRecordId) resultParts.push("메인 데이터 저장"); if (insertedCount > 0) resultParts.push(`${insertedCount}개 추가`); if (updatedCount > 0) resultParts.push(`${updatedCount}개 수정`); if (deletedCount > 0) resultParts.push(`${deletedCount}개 삭제`); @@ -2145,17 +2270,20 @@ export class ButtonActionExecutor { * 연관 데이터 버튼의 선택 데이터로 모달 열기 * RelatedDataButtons 컴포넌트에서 선택된 버튼 데이터를 모달로 전달 */ - private static async handleOpenRelatedModal(config: ButtonActionConfig, context: ButtonActionContext): Promise { + private static async handleOpenRelatedModal( + config: ButtonActionConfig, + context: ButtonActionContext, + ): Promise { // 버튼 설정에서 targetScreenId 가져오기 (여러 위치에서 확인) const targetScreenId = config.relatedModalConfig?.targetScreenId || config.targetScreenId; - + console.log("🔍 [openRelatedModal] 설정 확인:", { config, relatedModalConfig: config.relatedModalConfig, targetScreenId: config.targetScreenId, finalTargetScreenId: targetScreenId, }); - + if (!targetScreenId) { console.error("❌ [openRelatedModal] targetScreenId가 설정되지 않았습니다."); toast.error("모달 화면 ID가 설정되지 않았습니다."); @@ -2164,13 +2292,13 @@ export class ButtonActionExecutor { // RelatedDataButtons에서 선택된 데이터 가져오기 const relatedData = window.__relatedButtonsSelectedData; - + console.log("🔍 [openRelatedModal] RelatedDataButtons 데이터:", { relatedData, selectedItem: relatedData?.selectedItem, config: relatedData?.config, }); - + if (!relatedData?.selectedItem) { console.warn("⚠️ [openRelatedModal] 선택된 버튼이 없습니다."); toast.warning("먼저 버튼을 선택해주세요."); @@ -2181,14 +2309,14 @@ export class ButtonActionExecutor { // 데이터 매핑 적용 const initialData: Record = {}; - + console.log("🔍 [openRelatedModal] 매핑 설정:", { modalLink: relatedConfig?.modalLink, dataMapping: relatedConfig?.modalLink?.dataMapping, }); - + if (relatedConfig?.modalLink?.dataMapping && relatedConfig.modalLink.dataMapping.length > 0) { - relatedConfig.modalLink.dataMapping.forEach(mapping => { + relatedConfig.modalLink.dataMapping.forEach((mapping) => { console.log("🔍 [openRelatedModal] 매핑 처리:", { mapping, sourceField: mapping.sourceField, @@ -2197,7 +2325,7 @@ export class ButtonActionExecutor { selectedItemId: selectedItem.id, rawDataValue: selectedItem.rawData[mapping.sourceField], }); - + if (mapping.sourceField === "value") { initialData[mapping.targetField] = selectedItem.value; } else if (mapping.sourceField === "id") { @@ -2219,18 +2347,20 @@ export class ButtonActionExecutor { }); // 모달 열기 이벤트 발생 (ScreenModal은 editData를 사용) - window.dispatchEvent(new CustomEvent("openScreenModal", { - detail: { - screenId: targetScreenId, - title: config.modalTitle, - description: config.modalDescription, - editData: initialData, // ScreenModal은 editData로 폼 데이터를 받음 - onSuccess: () => { - // 성공 후 데이터 새로고침 - window.dispatchEvent(new CustomEvent("refreshTableData")); + window.dispatchEvent( + new CustomEvent("openScreenModal", { + detail: { + screenId: targetScreenId, + title: config.modalTitle, + description: config.modalDescription, + editData: initialData, // ScreenModal은 editData로 폼 데이터를 받음 + onSuccess: () => { + // 성공 후 데이터 새로고침 + window.dispatchEvent(new CustomEvent("refreshTableData")); + }, }, - }, - })); + }), + ); return true; } @@ -3296,10 +3426,7 @@ export class ButtonActionExecutor { * EditModal 등 외부에서도 호출 가능하도록 public으로 변경 * 다중 제어 순차 실행 지원 */ - public static async executeAfterSaveControl( - config: ButtonActionConfig, - context: ButtonActionContext, - ): Promise { + public static async executeAfterSaveControl(config: ButtonActionConfig, context: ButtonActionContext): Promise { console.log("🎯 저장 후 제어 실행:", { enableDataflowControl: config.enableDataflowControl, dataflowConfig: config.dataflowConfig, @@ -4742,7 +4869,7 @@ export class ButtonActionExecutor { // 추적 중인지 확인 (새로고침 후에도 DB 상태 기반 종료 가능하도록 수정) const isTrackingActive = !!this.trackingIntervalId; - + if (!isTrackingActive) { // 추적 중이 아니어도 DB 상태 변경은 진행 (새로고침 후 종료 지원) console.log("⚠️ [handleTrackingStop] trackingIntervalId 없음 - DB 상태 기반 종료 진행"); @@ -4758,25 +4885,26 @@ export class ButtonActionExecutor { let dbDeparture: string | null = null; let dbArrival: string | null = null; let dbVehicleId: string | null = null; - + const userId = context.userId || this.trackingUserId; if (userId) { try { const { apiClient } = await import("@/lib/api/client"); - const statusTableName = config.trackingStatusTableName || this.trackingConfig?.trackingStatusTableName || context.tableName || "vehicles"; + const statusTableName = + config.trackingStatusTableName || + this.trackingConfig?.trackingStatusTableName || + context.tableName || + "vehicles"; const keyField = config.trackingStatusKeyField || this.trackingConfig?.trackingStatusKeyField || "user_id"; - + // DB에서 현재 차량 정보 조회 - const vehicleResponse = await apiClient.post( - `/table-management/tables/${statusTableName}/data`, - { - page: 1, - size: 1, - search: { [keyField]: userId }, - autoFilter: true, - }, - ); - + const vehicleResponse = await apiClient.post(`/table-management/tables/${statusTableName}/data`, { + page: 1, + size: 1, + search: { [keyField]: userId }, + autoFilter: true, + }); + const vehicleData = vehicleResponse.data?.data?.data?.[0] || vehicleResponse.data?.data?.rows?.[0]; if (vehicleData) { dbDeparture = vehicleData.departure || null; @@ -4792,14 +4920,18 @@ export class ButtonActionExecutor { // 마지막 위치 저장 (추적 중이었던 경우에만) if (isTrackingActive) { // DB 값 우선, 없으면 formData 사용 - const departure = dbDeparture || - this.trackingContext?.formData?.[this.trackingConfig?.trackingDepartureField || "departure"] || null; - const arrival = dbArrival || - this.trackingContext?.formData?.[this.trackingConfig?.trackingArrivalField || "arrival"] || null; + const departure = + dbDeparture || + this.trackingContext?.formData?.[this.trackingConfig?.trackingDepartureField || "departure"] || + null; + const arrival = + dbArrival || this.trackingContext?.formData?.[this.trackingConfig?.trackingArrivalField || "arrival"] || null; const departureName = this.trackingContext?.formData?.["departure_name"] || null; const destinationName = this.trackingContext?.formData?.["destination_name"] || null; - const vehicleId = dbVehicleId || - this.trackingContext?.formData?.[this.trackingConfig?.trackingVehicleIdField || "vehicle_id"] || null; + const vehicleId = + dbVehicleId || + this.trackingContext?.formData?.[this.trackingConfig?.trackingVehicleIdField || "vehicle_id"] || + null; await this.saveLocationToHistory( tripId, @@ -5681,10 +5813,10 @@ export class ButtonActionExecutor { const columnMappings = quickInsertConfig.columnMappings || []; for (const mapping of columnMappings) { - console.log(`📍 매핑 처리 시작:`, mapping); - + console.log("📍 매핑 처리 시작:", mapping); + if (!mapping.targetColumn) { - console.log(`📍 targetColumn 없음, 스킵`); + console.log("📍 targetColumn 없음, 스킵"); continue; } @@ -5692,12 +5824,12 @@ export class ButtonActionExecutor { switch (mapping.sourceType) { case "component": - console.log(`📍 component 타입 처리:`, { + console.log("📍 component 타입 처리:", { sourceComponentId: mapping.sourceComponentId, sourceColumnName: mapping.sourceColumnName, targetColumn: mapping.targetColumn, }); - + // 컴포넌트의 현재 값 if (mapping.sourceComponentId) { // 1. sourceColumnName이 있으면 직접 사용 (가장 확실한 방법) @@ -5705,34 +5837,34 @@ export class ButtonActionExecutor { value = formData?.[mapping.sourceColumnName]; console.log(`📍 방법1 (sourceColumnName): ${mapping.sourceColumnName} = ${value}`); } - + // 2. 없으면 컴포넌트 ID로 직접 찾기 if (value === undefined) { value = formData?.[mapping.sourceComponentId]; console.log(`📍 방법2 (sourceComponentId): ${mapping.sourceComponentId} = ${value}`); } - + // 3. 없으면 allComponents에서 컴포넌트를 찾아 columnName으로 시도 if (value === undefined && context.allComponents) { const comp = context.allComponents.find((c: any) => c.id === mapping.sourceComponentId); - console.log(`📍 방법3 찾은 컴포넌트:`, comp); + console.log("📍 방법3 찾은 컴포넌트:", comp); if (comp?.columnName) { value = formData?.[comp.columnName]; console.log(`📍 방법3 (allComponents): ${mapping.sourceComponentId} → ${comp.columnName} = ${value}`); } } - + // 4. targetColumn과 같은 이름의 키가 formData에 있으면 사용 (폴백) if (value === undefined && mapping.targetColumn && formData?.[mapping.targetColumn] !== undefined) { value = formData[mapping.targetColumn]; console.log(`📍 방법4 (targetColumn 폴백): ${mapping.targetColumn} = ${value}`); } - + // 5. 그래도 없으면 formData의 모든 키를 확인하고 로깅 if (value === undefined) { console.log("📍 방법5: formData에서 값을 찾지 못함. formData 키들:", Object.keys(formData || {})); } - + // sourceColumn이 지정된 경우 해당 속성 추출 if (mapping.sourceColumn && value && typeof value === "object") { value = value[mapping.sourceColumn]; @@ -5742,7 +5874,7 @@ export class ButtonActionExecutor { break; case "leftPanel": - console.log(`📍 leftPanel 타입 처리:`, { + console.log("📍 leftPanel 타입 처리:", { sourceColumn: mapping.sourceColumn, selectedLeftData: splitPanelContext?.selectedLeftData, }); @@ -5775,18 +5907,18 @@ export class ButtonActionExecutor { } console.log(`📍 currentUser 값: ${value}`); break; - + default: console.log(`📍 알 수 없는 sourceType: ${mapping.sourceType}`); } console.log(`📍 매핑 결과: targetColumn=${mapping.targetColumn}, value=${value}, type=${typeof value}`); - + if (value !== undefined && value !== null && value !== "") { insertData[mapping.targetColumn] = value; console.log(`📍 insertData에 추가됨: ${mapping.targetColumn} = ${value}`); } else { - console.log(`📍 값이 비어있어서 insertData에 추가 안됨`); + console.log("📍 값이 비어있어서 insertData에 추가 안됨"); } } @@ -5794,12 +5926,12 @@ export class ButtonActionExecutor { if (splitPanelContext?.selectedLeftData) { const leftData = splitPanelContext.selectedLeftData; console.log("📍 좌측 패널 자동 매핑 시작:", leftData); - + // 대상 테이블의 컬럼 목록 조회 let targetTableColumns: string[] = []; try { const columnsResponse = await apiClient.get( - `/table-management/tables/${quickInsertConfig.targetTable}/columns` + `/table-management/tables/${quickInsertConfig.targetTable}/columns`, ); if (columnsResponse.data?.success && columnsResponse.data?.data) { const columnsData = columnsResponse.data.data.columns || columnsResponse.data.data; @@ -5809,35 +5941,35 @@ export class ButtonActionExecutor { } catch (error) { console.error("대상 테이블 컬럼 조회 실패:", error); } - + for (const [key, val] of Object.entries(leftData)) { // 이미 매핑된 컬럼은 스킵 if (insertData[key] !== undefined) { console.log(`📍 자동 매핑 스킵 (이미 존재): ${key}`); continue; } - + // 대상 테이블에 해당 컬럼이 없으면 스킵 if (targetTableColumns.length > 0 && !targetTableColumns.includes(key)) { console.log(`📍 자동 매핑 스킵 (대상 테이블에 없는 컬럼): ${key}`); continue; } - + // 시스템 컬럼 제외 (id, created_date, updated_date, writer 등) - const systemColumns = ['id', 'created_date', 'updated_date', 'writer', 'writer_name']; + const systemColumns = ["id", "created_date", "updated_date", "writer", "writer_name"]; if (systemColumns.includes(key)) { console.log(`📍 자동 매핑 스킵 (시스템 컬럼): ${key}`); continue; } - + // _label, _name 으로 끝나는 표시용 컬럼 제외 - if (key.endsWith('_label') || key.endsWith('_name')) { + if (key.endsWith("_label") || key.endsWith("_name")) { console.log(`📍 자동 매핑 스킵 (표시용 컬럼): ${key}`); continue; } - + // 값이 있으면 자동 추가 - if (val !== undefined && val !== null && val !== '') { + if (val !== undefined && val !== null && val !== "") { insertData[key] = val; console.log(`📍 자동 매핑 추가: ${key} = ${val}`); } @@ -5857,7 +5989,7 @@ export class ButtonActionExecutor { enabled: quickInsertConfig.duplicateCheck?.enabled, columns: quickInsertConfig.duplicateCheck?.columns, }); - + if (quickInsertConfig.duplicateCheck?.enabled && quickInsertConfig.duplicateCheck?.columns?.length > 0) { const duplicateCheckData: Record = {}; for (const col of quickInsertConfig.duplicateCheck.columns) { @@ -5877,15 +6009,20 @@ export class ButtonActionExecutor { page: 1, pageSize: 1, search: duplicateCheckData, - } + }, ); console.log("📍 중복 체크 응답:", checkResponse.data); // 응답 구조: { success: true, data: { data: [...], total: N } } 또는 { success: true, data: [...] } const existingData = checkResponse.data?.data?.data || checkResponse.data?.data || []; - console.log("📍 기존 데이터:", existingData, "길이:", Array.isArray(existingData) ? existingData.length : 0); - + console.log( + "📍 기존 데이터:", + existingData, + "길이:", + Array.isArray(existingData) ? existingData.length : 0, + ); + if (Array.isArray(existingData) && existingData.length > 0) { toast.error(quickInsertConfig.duplicateCheck.errorMessage || "이미 존재하는 데이터입니다."); return false; @@ -5902,20 +6039,20 @@ export class ButtonActionExecutor { // 데이터 저장 const response = await apiClient.post( `/table-management/tables/${quickInsertConfig.targetTable}/add`, - insertData + insertData, ); if (response.data?.success) { console.log("✅ Quick Insert 저장 성공"); - + // 저장 후 동작 설정 로그 console.log("📍 afterInsert 설정:", quickInsertConfig.afterInsert); - + // 🆕 데이터 새로고침 (테이블리스트, 카드 디스플레이 컴포넌트 새로고침) // refreshData가 명시적으로 false가 아니면 기본적으로 새로고침 실행 const shouldRefresh = quickInsertConfig.afterInsert?.refreshData !== false; console.log("📍 데이터 새로고침 여부:", shouldRefresh); - + if (shouldRefresh) { console.log("📍 데이터 새로고침 이벤트 발송"); // 전역 이벤트로 테이블/카드 컴포넌트들에게 새로고침 알림