diff --git a/frontend/components/screen/EditModal.tsx b/frontend/components/screen/EditModal.tsx index 4e756600..3280891f 100644 --- a/frontend/components/screen/EditModal.tsx +++ b/frontend/components/screen/EditModal.tsx @@ -305,84 +305,173 @@ export const EditModal: React.FC = ({ className }) => { } try { - // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ: ๋ชจ๋“  ํ’ˆ๋ชฉ ์ผ๊ด„ ์ˆ˜์ • - if (groupData.length > 0) { - console.log("๐Ÿ”„ ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์ผ๊ด„ ์ˆ˜์ • ์‹œ์ž‘:", { + // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ: ๋ชจ๋“  ํ’ˆ๋ชฉ ์ผ๊ด„ ์ฒ˜๋ฆฌ (์ถ”๊ฐ€/์ˆ˜์ •/์‚ญ์ œ) + if (groupData.length > 0 || originalGroupData.length > 0) { + console.log("๐Ÿ”„ ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์ผ๊ด„ ์ฒ˜๋ฆฌ ์‹œ์ž‘:", { groupDataLength: groupData.length, originalGroupDataLength: originalGroupData.length, + groupData, + originalGroupData, + tableName: screenData.screenInfo.tableName, + screenId: modalState.screenId, }); + let insertedCount = 0; let updatedCount = 0; + let deletedCount = 0; - for (let i = 0; i < groupData.length; i++) { - const currentData = groupData[i]; - const originalItemData = originalGroupData[i]; + // ๐Ÿ†• sales_order_mng ํ…Œ์ด๋ธ”์˜ ์‹ค์ œ ์ปฌ๋Ÿผ๋งŒ ํฌํ•จ (์กฐ์ธ๋œ ์ปฌ๋Ÿผ ์ œ์™ธ) + const salesOrderColumns = [ + "id", + "order_no", + "customer_code", + "customer_name", + "order_date", + "delivery_date", + "item_code", + "quantity", + "unit_price", + "amount", + "status", + "notes", + "created_at", + "updated_at", + "company_code", + ]; - if (!originalItemData) { - console.warn(`์›๋ณธ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค (index: ${i})`); - continue; - } + // 1๏ธโƒฃ ์‹ ๊ทœ ํ’ˆ๋ชฉ ์ถ”๊ฐ€ (id๊ฐ€ ์—†๋Š” ํ•ญ๋ชฉ) + for (const currentData of groupData) { + if (!currentData.id) { + console.log("โž• ์‹ ๊ทœ ํ’ˆ๋ชฉ ์ถ”๊ฐ€:", currentData); - // ๋ณ€๊ฒฝ๋œ ํ•„๋“œ๋งŒ ์ถ”์ถœ - const changedData: Record = {}; - - // ๐Ÿ†• sales_order_mng ํ…Œ์ด๋ธ”์˜ ์‹ค์ œ ์ปฌ๋Ÿผ๋งŒ ํฌํ•จ (์กฐ์ธ๋œ ์ปฌ๋Ÿผ ์ œ์™ธ) - const salesOrderColumns = [ - "id", - "order_no", - "customer_code", - "customer_name", - "order_date", - "delivery_date", - "item_code", - "quantity", - "unit_price", - "amount", - "status", - "notes", - "created_at", - "updated_at", - "company_code", - ]; - - Object.keys(currentData).forEach((key) => { - // sales_order_mng ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ๋งŒ ์ฒ˜๋ฆฌ (์กฐ์ธ ์ปฌ๋Ÿผ ์ œ์™ธ) - if (!salesOrderColumns.includes(key)) { - return; + // ์‹ค์ œ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ๋งŒ ์ถ”์ถœ + const insertData: Record = {}; + Object.keys(currentData).forEach((key) => { + if (salesOrderColumns.includes(key) && key !== "id") { + insertData[key] = currentData[key]; + } + }); + + // ๐Ÿ†• groupByColumns์˜ ๊ฐ’์„ ๊ฐ•์ œ๋กœ ํฌํ•จ (order_no ๋“ฑ) + if (modalState.groupByColumns && modalState.groupByColumns.length > 0) { + modalState.groupByColumns.forEach((colName) => { + // ๊ธฐ์กด ํ’ˆ๋ชฉ(groupData[0])์—์„œ groupByColumns ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ + const referenceData = originalGroupData[0] || groupData.find(item => item.id); + if (referenceData && referenceData[colName]) { + insertData[colName] = referenceData[colName]; + console.log(`๐Ÿ”‘ [์‹ ๊ทœ ํ’ˆ๋ชฉ] ${colName} ๊ฐ’ ์ถ”๊ฐ€:`, referenceData[colName]); + } + }); } - - if (currentData[key] !== originalItemData[key]) { - changedData[key] = currentData[key]; + + console.log("๐Ÿ“ฆ [์‹ ๊ทœ ํ’ˆ๋ชฉ] ์ตœ์ข… insertData:", insertData); + + try { + const response = await dynamicFormApi.saveFormData({ + screenId: modalState.screenId || 0, + tableName: screenData.screenInfo.tableName, + data: insertData, + }); + + if (response.success) { + insertedCount++; + console.log("โœ… ์‹ ๊ทœ ํ’ˆ๋ชฉ ์ถ”๊ฐ€ ์„ฑ๊ณต:", response.data); + } else { + console.error("โŒ ์‹ ๊ทœ ํ’ˆ๋ชฉ ์ถ”๊ฐ€ ์‹คํŒจ:", response.message); + } + } catch (error: any) { + console.error("โŒ ์‹ ๊ทœ ํ’ˆ๋ชฉ ์ถ”๊ฐ€ ์˜ค๋ฅ˜:", error); } - }); - - // ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์—†์œผ๋ฉด ์Šคํ‚ต - if (Object.keys(changedData).length === 0) { - console.log(`๋ณ€๊ฒฝ์‚ฌํ•ญ ์—†์Œ (index: ${i})`); - continue; - } - - // ๊ธฐ๋ณธํ‚ค ํ™•์ธ - const recordId = originalItemData.id || Object.values(originalItemData)[0]; - - // UPDATE ์‹คํ–‰ - const response = await dynamicFormApi.updateFormDataPartial( - recordId, - originalItemData, - changedData, - screenData.screenInfo.tableName, - ); - - if (response.success) { - updatedCount++; - console.log(`โœ… ํ’ˆ๋ชฉ ${i + 1} ์ˆ˜์ • ์„ฑ๊ณต (id: ${recordId})`); - } else { - console.error(`โŒ ํ’ˆ๋ชฉ ${i + 1} ์ˆ˜์ • ์‹คํŒจ (id: ${recordId}):`, response.message); } } - if (updatedCount > 0) { - toast.success(`${updatedCount}๊ฐœ์˜ ํ’ˆ๋ชฉ์ด ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`); + // 2๏ธโƒฃ ๊ธฐ์กด ํ’ˆ๋ชฉ ์ˆ˜์ • (id๊ฐ€ ์žˆ๋Š” ํ•ญ๋ชฉ) + for (const currentData of groupData) { + if (currentData.id) { + // id ๊ธฐ๋ฐ˜ ๋งค์นญ (์ธ๋ฑ์Šค ๊ธฐ๋ฐ˜ X) + const originalItemData = originalGroupData.find( + (orig) => orig.id === currentData.id + ); + + if (!originalItemData) { + console.warn(`์›๋ณธ ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค (id: ${currentData.id})`); + continue; + } + + // ๋ณ€๊ฒฝ๋œ ํ•„๋“œ๋งŒ ์ถ”์ถœ + const changedData: Record = {}; + Object.keys(currentData).forEach((key) => { + // sales_order_mng ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ๋งŒ ์ฒ˜๋ฆฌ (์กฐ์ธ ์ปฌ๋Ÿผ ์ œ์™ธ) + if (!salesOrderColumns.includes(key)) { + return; + } + + if (currentData[key] !== originalItemData[key]) { + changedData[key] = currentData[key]; + } + }); + + // ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์—†์œผ๋ฉด ์Šคํ‚ต + if (Object.keys(changedData).length === 0) { + console.log(`๋ณ€๊ฒฝ์‚ฌํ•ญ ์—†์Œ (id: ${currentData.id})`); + continue; + } + + // UPDATE ์‹คํ–‰ + try { + const response = await dynamicFormApi.updateFormDataPartial( + currentData.id, + originalItemData, + changedData, + screenData.screenInfo.tableName, + ); + + if (response.success) { + updatedCount++; + console.log(`โœ… ํ’ˆ๋ชฉ ์ˆ˜์ • ์„ฑ๊ณต (id: ${currentData.id})`); + } else { + console.error(`โŒ ํ’ˆ๋ชฉ ์ˆ˜์ • ์‹คํŒจ (id: ${currentData.id}):`, response.message); + } + } catch (error: any) { + console.error(`โŒ ํ’ˆ๋ชฉ ์ˆ˜์ • ์˜ค๋ฅ˜ (id: ${currentData.id}):`, error); + } + } + } + + // 3๏ธโƒฃ ์‚ญ์ œ๋œ ํ’ˆ๋ชฉ ์ œ๊ฑฐ (์›๋ณธ์—๋Š” ์žˆ์ง€๋งŒ ํ˜„์žฌ ๋ฐ์ดํ„ฐ์—๋Š” ์—†๋Š” ํ•ญ๋ชฉ) + const currentIds = new Set(groupData.map((item) => item.id).filter(Boolean)); + const deletedItems = originalGroupData.filter( + (orig) => orig.id && !currentIds.has(orig.id) + ); + + for (const deletedItem of deletedItems) { + console.log("๐Ÿ—‘๏ธ ํ’ˆ๋ชฉ ์‚ญ์ œ:", deletedItem); + + try { + const response = await dynamicFormApi.deleteFormDataFromTable( + deletedItem.id, + screenData.screenInfo.tableName + ); + + if (response.success) { + deletedCount++; + console.log(`โœ… ํ’ˆ๋ชฉ ์‚ญ์ œ ์„ฑ๊ณต (id: ${deletedItem.id})`); + } else { + console.error(`โŒ ํ’ˆ๋ชฉ ์‚ญ์ œ ์‹คํŒจ (id: ${deletedItem.id}):`, response.message); + } + } catch (error: any) { + console.error(`โŒ ํ’ˆ๋ชฉ ์‚ญ์ œ ์˜ค๋ฅ˜ (id: ${deletedItem.id}):`, error); + } + } + + // ๊ฒฐ๊ณผ ๋ฉ”์‹œ์ง€ + const messages: string[] = []; + if (insertedCount > 0) messages.push(`${insertedCount}๊ฐœ ์ถ”๊ฐ€`); + if (updatedCount > 0) messages.push(`${updatedCount}๊ฐœ ์ˆ˜์ •`); + if (deletedCount > 0) messages.push(`${deletedCount}๊ฐœ ์‚ญ์ œ`); + + if (messages.length > 0) { + toast.success(`ํ’ˆ๋ชฉ์ด ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค (${messages.join(", ")})`); // ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์˜ onSave ์ฝœ๋ฐฑ ์‹คํ–‰ (ํ…Œ์ด๋ธ” ์ƒˆ๋กœ๊ณ ์นจ) if (modalState.onSave) { @@ -585,6 +674,7 @@ export const EditModal: React.FC = ({ className }) => { tableName: screenData.screenInfo?.tableName, }} onSave={handleSave} + isInModal={true} // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ๋ฅผ ModalRepeaterTable์— ์ „๋‹ฌ groupedData={groupData.length > 0 ? groupData : undefined} /> diff --git a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx index d1cd2a5f..fb5046c3 100644 --- a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx +++ b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx @@ -48,6 +48,8 @@ interface InteractiveScreenViewerProps { companyCode?: string; // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ (EditModal์—์„œ ์ „๋‹ฌ) groupedData?: Record[]; + // ๐Ÿ†• EditModal ๋‚ด๋ถ€์ธ์ง€ ์—ฌ๋ถ€ (button-primary๊ฐ€ EditModal์˜ handleSave ์‚ฌ์šฉํ•˜๋„๋ก) + isInModal?: boolean; } export const InteractiveScreenViewerDynamic: React.FC = ({ @@ -64,6 +66,7 @@ export const InteractiveScreenViewerDynamic: React.FC { const { isPreviewMode } = useScreenPreview(); // ํ”„๋ฆฌ๋ทฐ ๋ชจ๋“œ ํ™•์ธ const { userName: authUserName, user: authUser } = useAuth(); @@ -329,6 +332,7 @@ export const InteractiveScreenViewerDynamic: React.FC { @@ -401,6 +405,8 @@ export const InteractiveScreenViewerDynamic: React.FC { diff --git a/frontend/lib/registry/DynamicComponentRenderer.tsx b/frontend/lib/registry/DynamicComponentRenderer.tsx index 92fd89e8..bf2b6ecb 100644 --- a/frontend/lib/registry/DynamicComponentRenderer.tsx +++ b/frontend/lib/registry/DynamicComponentRenderer.tsx @@ -105,6 +105,7 @@ export interface DynamicComponentRendererProps { companyCode?: string; // ๐Ÿ†• ํ˜„์žฌ ์‚ฌ์šฉ์ž์˜ ํšŒ์‚ฌ ์ฝ”๋“œ onRefresh?: () => void; onClose?: () => void; + onSave?: () => Promise; // ๐Ÿ†• EditModal์˜ handleSave ์ฝœ๋ฐฑ // ํ…Œ์ด๋ธ” ์„ ํƒ๋œ ํ–‰ ์ •๋ณด (๋‹ค์ค‘ ์„ ํƒ ์•ก์…˜์šฉ) selectedRows?: any[]; // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ (EditModal โ†’ ModalRepeaterTable) @@ -244,6 +245,7 @@ export const DynamicComponentRenderer: React.FC = selectedScreen, // ๐Ÿ†• ํ™”๋ฉด ์ •๋ณด onRefresh, onClose, + onSave, // ๐Ÿ†• EditModal์˜ handleSave ์ฝœ๋ฐฑ screenId, userId, // ๐Ÿ†• ์‚ฌ์šฉ์ž ID userName, // ๐Ÿ†• ์‚ฌ์šฉ์ž ์ด๋ฆ„ @@ -358,6 +360,7 @@ export const DynamicComponentRenderer: React.FC = selectedScreen, // ๐Ÿ†• ํ™”๋ฉด ์ •๋ณด onRefresh, onClose, + onSave, // ๐Ÿ†• EditModal์˜ handleSave ์ฝœ๋ฐฑ screenId, userId, // ๐Ÿ†• ์‚ฌ์šฉ์ž ID userName, // ๐Ÿ†• ์‚ฌ์šฉ์ž ์ด๋ฆ„ diff --git a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx index 112a285c..d2b69074 100644 --- a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx +++ b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx @@ -35,6 +35,7 @@ export interface ButtonPrimaryComponentProps extends ComponentRendererProps { onRefresh?: () => void; onClose?: () => void; onFlowRefresh?: () => void; + onSave?: () => Promise; // ๐Ÿ†• EditModal์˜ handleSave ์ฝœ๋ฐฑ // ํผ ๋ฐ์ดํ„ฐ ๊ด€๋ จ originalData?: Record; // ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ์šฉ ์›๋ณธ ๋ฐ์ดํ„ฐ @@ -83,6 +84,7 @@ export const ButtonPrimaryComponent: React.FC = ({ onRefresh, onClose, onFlowRefresh, + onSave, // ๐Ÿ†• EditModal์˜ handleSave ์ฝœ๋ฐฑ sortBy, // ๐Ÿ†• ์ •๋ ฌ ์ปฌ๋Ÿผ sortOrder, // ๐Ÿ†• ์ •๋ ฌ ๋ฐฉํ–ฅ columnOrder, // ๐Ÿ†• ์ปฌ๋Ÿผ ์ˆœ์„œ @@ -95,6 +97,10 @@ export const ButtonPrimaryComponent: React.FC = ({ ...props }) => { const { isPreviewMode } = useScreenPreview(); // ํ”„๋ฆฌ๋ทฐ ๋ชจ๋“œ ํ™•์ธ + + // ๐Ÿ†• props์—์„œ onSave ์ถ”์ถœ (๋ช…์‹œ์ ์œผ๋กœ ์„ ์–ธ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ...props์—์„œ ์ถ”์ถœ) + const propsOnSave = (props as any).onSave as (() => Promise) | undefined; + const finalOnSave = onSave || propsOnSave; // ๐Ÿ†• ํ”Œ๋กœ์šฐ ๋‹จ๊ณ„๋ณ„ ํ‘œ์‹œ ์ œ์–ด const flowConfig = (component as any).webTypeConfig?.flowVisibilityConfig; @@ -415,6 +421,7 @@ export const ButtonPrimaryComponent: React.FC = ({ onRefresh, onClose, onFlowRefresh, // ํ”Œ๋กœ์šฐ ์ƒˆ๋กœ๊ณ ์นจ ์ฝœ๋ฐฑ ์ถ”๊ฐ€ + onSave: finalOnSave, // ๐Ÿ†• EditModal์˜ handleSave ์ฝœ๋ฐฑ (props์—์„œ๋„ ์ถ”์ถœ) // ํ…Œ์ด๋ธ” ์„ ํƒ๋œ ํ–‰ ์ •๋ณด ์ถ”๊ฐ€ selectedRows, selectedRowsData, diff --git a/frontend/lib/registry/components/conditional-container/ConditionalContainerComponent.tsx b/frontend/lib/registry/components/conditional-container/ConditionalContainerComponent.tsx index 2589026f..626ee137 100644 --- a/frontend/lib/registry/components/conditional-container/ConditionalContainerComponent.tsx +++ b/frontend/lib/registry/components/conditional-container/ConditionalContainerComponent.tsx @@ -41,6 +41,7 @@ export function ConditionalContainerComponent({ style, className, groupedData, // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ + onSave, // ๐Ÿ†• EditModal์˜ handleSave ์ฝœ๋ฐฑ }: ConditionalContainerProps) { console.log("๐ŸŽฏ ConditionalContainerComponent ๋ Œ๋”๋ง!", { isDesignMode, @@ -179,6 +180,7 @@ export function ConditionalContainerComponent({ formData={formData} onFormDataChange={onFormDataChange} groupedData={groupedData} + onSave={onSave} /> ))} @@ -199,6 +201,7 @@ export function ConditionalContainerComponent({ formData={formData} onFormDataChange={onFormDataChange} groupedData={groupedData} + onSave={onSave} /> ) : null ) diff --git a/frontend/lib/registry/components/conditional-container/ConditionalSectionViewer.tsx b/frontend/lib/registry/components/conditional-container/ConditionalSectionViewer.tsx index f77dbcdb..9709b620 100644 --- a/frontend/lib/registry/components/conditional-container/ConditionalSectionViewer.tsx +++ b/frontend/lib/registry/components/conditional-container/ConditionalSectionViewer.tsx @@ -26,6 +26,7 @@ export function ConditionalSectionViewer({ formData, onFormDataChange, groupedData, // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ + onSave, // ๐Ÿ†• EditModal์˜ handleSave ์ฝœ๋ฐฑ }: ConditionalSectionViewerProps) { const { userId, userName, user } = useAuth(); const [isLoading, setIsLoading] = useState(false); @@ -153,17 +154,18 @@ export function ConditionalSectionViewer({ }} > + onSave={onSave} + /> ); })} diff --git a/frontend/lib/registry/components/conditional-container/types.ts b/frontend/lib/registry/components/conditional-container/types.ts index 0cf741b2..bcd701ef 100644 --- a/frontend/lib/registry/components/conditional-container/types.ts +++ b/frontend/lib/registry/components/conditional-container/types.ts @@ -46,6 +46,7 @@ export interface ConditionalContainerProps { formData?: Record; onFormDataChange?: (fieldName: string, value: any) => void; groupedData?: Record[]; // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ (EditModal โ†’ ModalRepeaterTable) + onSave?: () => Promise; // ๐Ÿ†• EditModal์˜ handleSave ์ฝœ๋ฐฑ // ํ™”๋ฉด ํŽธ์ง‘๊ธฐ ๊ด€๋ จ isDesignMode?: boolean; // ๋””์ž์ธ ๋ชจ๋“œ ์—ฌ๋ถ€ @@ -77,5 +78,6 @@ export interface ConditionalSectionViewerProps { formData?: Record; onFormDataChange?: (fieldName: string, value: any) => void; groupedData?: Record[]; // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ + onSave?: () => Promise; // ๐Ÿ†• EditModal์˜ handleSave ์ฝœ๋ฐฑ } diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 3b9b9d9e..ddcf0f18 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -112,6 +112,7 @@ export interface ButtonActionContext { onClose?: () => void; onRefresh?: () => void; onFlowRefresh?: () => void; // ํ”Œ๋กœ์šฐ ์ƒˆ๋กœ๊ณ ์นจ ์ฝœ๋ฐฑ + onSave?: () => Promise; // ๐Ÿ†• EditModal์˜ handleSave ์ฝœ๋ฐฑ // ํ…Œ์ด๋ธ” ์„ ํƒ๋œ ํ–‰ ์ •๋ณด (๋‹ค์ค‘ ์„ ํƒ ์•ก์…˜์šฉ) selectedRows?: any[]; @@ -213,9 +214,23 @@ export class ButtonActionExecutor { * ์ €์žฅ ์•ก์…˜ ์ฒ˜๋ฆฌ (INSERT/UPDATE ์ž๋™ ํŒ๋‹จ - DB ๊ธฐ๋ฐ˜) */ private static async handleSave(config: ButtonActionConfig, context: ButtonActionContext): Promise { - const { formData, originalData, tableName, screenId } = context; + const { formData, originalData, tableName, screenId, onSave } = context; - console.log("๐Ÿ’พ [handleSave] ์ €์žฅ ์‹œ์ž‘:", { formData, tableName, screenId }); + console.log("๐Ÿ’พ [handleSave] ์ €์žฅ ์‹œ์ž‘:", { formData, tableName, screenId, hasOnSave: !!onSave }); + + // ๐Ÿ†• 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 ๋“ฑ์—์„œ ์ตœ์‹  ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘) // context.formData๋ฅผ ์ด๋ฒคํŠธ detail์— ํฌํ•จํ•˜์—ฌ ์ง์ ‘ ์ˆ˜์ • ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•จ