diff --git a/frontend/components/screen/EditModal.tsx b/frontend/components/screen/EditModal.tsx index 1b54d3b9..1969f562 100644 --- a/frontend/components/screen/EditModal.tsx +++ b/frontend/components/screen/EditModal.tsx @@ -976,6 +976,19 @@ export const EditModal: React.FC = ({ className }) => { const groupedDataProp = groupData.length > 0 ? groupData : undefined; + // ๐Ÿ†• UniversalFormModal์ด ์žˆ๋Š”์ง€ ํ™•์ธ (์ž์ฒด ์ €์žฅ ๋กœ์ง ์‚ฌ์šฉ) + // ์ตœ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ ๋˜๋Š” ์กฐ๊ฑด๋ถ€ ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€ ํ™”๋ฉด์— universal-form-modal์ด ์žˆ๋Š”์ง€ ํ™•์ธ + const hasUniversalFormModal = screenData.components.some( + (c) => { + // ์ตœ์ƒ์œ„์— universal-form-modal์ด ์žˆ๋Š” ๊ฒฝ์šฐ + if (c.componentType === "universal-form-modal") return true; + // ์กฐ๊ฑด๋ถ€ ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์— universal-form-modal์ด ์žˆ๋Š” ๊ฒฝ์šฐ + // (์กฐ๊ฑด๋ถ€ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์žˆ์œผ๋ฉด ๋‚ด๋ถ€ ํ™”๋ฉด์—์„œ universal-form-modal์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๊ฐ€์ •) + if (c.componentType === "conditional-container") return true; + return false; + } + ); + // ๐Ÿ”‘ ์ฒจ๋ถ€ํŒŒ์ผ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ–‰(๋ ˆ์ฝ”๋“œ) ๋‹จ์œ„๋กœ ํŒŒ์ผ์„ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋„๋ก tableName ์ถ”๊ฐ€ const enrichedFormData = { ...(groupData.length > 0 ? groupData[0] : formData), @@ -1024,7 +1037,9 @@ export const EditModal: React.FC = ({ className }) => { id: modalState.screenId!, tableName: screenData.screenInfo?.tableName, }} - onSave={handleSave} + // ๐Ÿ†• UniversalFormModal์ด ์žˆ์œผ๋ฉด onSave ์ „๋‹ฌ ์•ˆ ํ•จ (์ž์ฒด ์ €์žฅ ๋กœ์ง ์‚ฌ์šฉ) + // ModalRepeaterTable๋งŒ ์žˆ์œผ๋ฉด ๊ธฐ์กด๋Œ€๋กœ onSave ์ „๋‹ฌ (ํ˜ธํ™˜์„ฑ ์œ ์ง€) + onSave={hasUniversalFormModal ? undefined : handleSave} isInModal={true} // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ๋ฅผ ModalRepeaterTable์— ์ „๋‹ฌ groupedData={groupedDataProp} diff --git a/frontend/lib/registry/components/conditional-container/ConditionalSectionViewer.tsx b/frontend/lib/registry/components/conditional-container/ConditionalSectionViewer.tsx index d5686f6c..59c82421 100644 --- a/frontend/lib/registry/components/conditional-container/ConditionalSectionViewer.tsx +++ b/frontend/lib/registry/components/conditional-container/ConditionalSectionViewer.tsx @@ -150,46 +150,54 @@ export function ConditionalSectionViewer({ /* ์‹คํ–‰ ๋ชจ๋“œ: ์‹ค์ œ ํ™”๋ฉด ๋ Œ๋”๋ง */
{/* ํ™”๋ฉด ํฌ๊ธฐ๋งŒํผ์˜ ์ ˆ๋Œ€ ์œ„์น˜ ์บ”๋ฒ„์Šค */} -
- {components.map((component) => { - const { position = { x: 0, y: 0, z: 1 }, size = { width: 200, height: 40 } } = component; - - return ( -
- -
- ); - })} -
+ {/* UniversalFormModal์ด ์žˆ์œผ๋ฉด onSave ์ „๋‹ฌํ•˜์ง€ ์•Š์Œ (์ž์ฒด ์ €์žฅ ๋กœ์ง ์‚ฌ์šฉ) */} + {(() => { + const hasUniversalFormModal = components.some( + (c) => c.componentType === "universal-form-modal" + ); + return ( +
+ {components.map((component) => { + const { position = { x: 0, y: 0, z: 1 }, size = { width: 200, height: 40 } } = component; + + return ( +
+ +
+ ); + })} +
+ ); + })()}
)} diff --git a/frontend/lib/registry/components/universal-form-modal/TableSectionRenderer.tsx b/frontend/lib/registry/components/universal-form-modal/TableSectionRenderer.tsx index 1d1298cd..224459f0 100644 --- a/frontend/lib/registry/components/universal-form-modal/TableSectionRenderer.tsx +++ b/frontend/lib/registry/components/universal-form-modal/TableSectionRenderer.tsx @@ -339,6 +339,27 @@ export function TableSectionRenderer({ // ๋‚ ์งœ ์ผ๊ด„ ์ ์šฉ ์™„๋ฃŒ ํ”Œ๋ž˜๊ทธ (์ปฌ๋Ÿผ๋ณ„๋กœ ํ•œ ๋ฒˆ๋งŒ ์ ์šฉ) const [batchAppliedFields, setBatchAppliedFields] = useState>(new Set()); + // ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์™„๋ฃŒ ํ”Œ๋ž˜๊ทธ (๋ฌดํ•œ ๋ฃจํ”„ ๋ฐฉ์ง€) + const initialDataLoadedRef = React.useRef(false); + + // formData์—์„œ ์ดˆ๊ธฐ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ๋กœ๋“œ (์ˆ˜์ • ๋ชจ๋“œ์—์„œ _groupedData ํ‘œ์‹œ) + useEffect(() => { + // ์ด๋ฏธ ์ดˆ๊ธฐํ™”๋˜์—ˆ์œผ๋ฉด ์Šคํ‚ต + if (initialDataLoadedRef.current) return; + + const tableSectionKey = `_tableSection_${sectionId}`; + const initialData = formData[tableSectionKey]; + + if (Array.isArray(initialData) && initialData.length > 0) { + console.log("[TableSectionRenderer] ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ:", { + sectionId, + itemCount: initialData.length, + }); + setTableData(initialData); + initialDataLoadedRef.current = true; + } + }, [sectionId, formData]); + // RepeaterColumnConfig๋กœ ๋ณ€ํ™˜ const columns: RepeaterColumnConfig[] = (tableConfig.columns || []).map(convertToRepeaterColumn); diff --git a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx index 76f28d3f..5230e0fb 100644 --- a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx +++ b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx @@ -195,6 +195,10 @@ export function UniversalFormModalComponent({ // ๋กœ๋”ฉ ์ƒํƒœ const [saving, setSaving] = useState(false); + // ๐Ÿ†• ์ˆ˜์ • ๋ชจ๋“œ: ์›๋ณธ ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ (INSERT/UPDATE/DELETE ์ถ”์ ์šฉ) + const [originalGroupedData, setOriginalGroupedData] = useState([]); + const groupedDataInitializedRef = useRef(false); + // ์‚ญ์ œ ํ™•์ธ ๋‹ค์ด์–ผ๋กœ๊ทธ const [deleteDialog, setDeleteDialog] = useState<{ open: boolean; @@ -304,6 +308,12 @@ export function UniversalFormModalComponent({ console.log(`[UniversalFormModal] ๋ฐ˜๋ณต ์„น์…˜ ๋ณ‘ํ•ฉ: ${sectionKey}`, items); } } + + // ๐Ÿ†• ์ˆ˜์ • ๋ชจ๋“œ: ์›๋ณธ ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์ „๋‹ฌ (UPDATE/DELETE ์ถ”์ ์šฉ) + if (originalGroupedData.length > 0) { + event.detail.formData._originalGroupedData = originalGroupedData; + console.log(`[UniversalFormModal] ์›๋ณธ ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ๋ณ‘ํ•ฉ: ${originalGroupedData.length}๊ฐœ`); + } }; window.addEventListener("beforeFormSave", handleBeforeFormSave as EventListener); @@ -311,7 +321,37 @@ export function UniversalFormModalComponent({ return () => { window.removeEventListener("beforeFormSave", handleBeforeFormSave as EventListener); }; - }, [formData, repeatSections, config.sections]); + }, [formData, repeatSections, config.sections, originalGroupedData]); + + // ๐Ÿ†• ์ˆ˜์ • ๋ชจ๋“œ: _groupedData๊ฐ€ ์žˆ์œผ๋ฉด ํ…Œ์ด๋ธ” ์„น์…˜ ์ดˆ๊ธฐํ™” + useEffect(() => { + if (!_groupedData || _groupedData.length === 0) return; + if (groupedDataInitializedRef.current) return; // ์ด๋ฏธ ์ดˆ๊ธฐํ™”๋จ + + // ํ…Œ์ด๋ธ” ํƒ€์ž… ์„น์…˜ ์ฐพ๊ธฐ + const tableSection = config.sections.find((s) => s.type === "table"); + if (!tableSection) { + console.log("[UniversalFormModal] ํ…Œ์ด๋ธ” ์„น์…˜ ์—†์Œ - _groupedData ๋ฌด์‹œ"); + return; + } + + console.log("[UniversalFormModal] ์ˆ˜์ • ๋ชจ๋“œ - ํ…Œ์ด๋ธ” ์„น์…˜ ์ดˆ๊ธฐํ™”:", { + sectionId: tableSection.id, + itemCount: _groupedData.length, + }); + + // ์›๋ณธ ๋ฐ์ดํ„ฐ ์ €์žฅ (์ˆ˜์ •/์‚ญ์ œ ์ถ”์ ์šฉ) + setOriginalGroupedData(JSON.parse(JSON.stringify(_groupedData))); + + // ํ…Œ์ด๋ธ” ์„น์…˜ ๋ฐ์ดํ„ฐ ์„ค์ • + const tableSectionKey = `_tableSection_${tableSection.id}`; + setFormData((prev) => ({ + ...prev, + [tableSectionKey]: _groupedData, + })); + + groupedDataInitializedRef.current = true; + }, [_groupedData, config.sections]); // ํ•„๋“œ ๋ ˆ๋ฒจ linkedFieldGroup ๋ฐ์ดํ„ฐ ๋กœ๋“œ useEffect(() => { diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index d2adf4cd..de98028a 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -1490,6 +1490,7 @@ export class ButtonActionExecutor { /** * ๐Ÿ†• Universal Form Modal ํ…Œ์ด๋ธ” ์„น์…˜ ๋ณ‘ํ•ฉ ์ €์žฅ ์ฒ˜๋ฆฌ * ๋ฒ”์šฉ_ํผ_๋ชจ๋‹ฌ ๋‚ด๋ถ€์˜ ๊ณตํ†ต ํ•„๋“œ + _tableSection_ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ‘ํ•ฉํ•˜์—ฌ ํ’ˆ๋ชฉ๋ณ„๋กœ ์ €์žฅ + * ์ˆ˜์ • ๋ชจ๋“œ: INSERT/UPDATE/DELETE ์ง€์› */ private static async handleUniversalFormModalTableSectionSave( config: ButtonActionConfig, @@ -1518,6 +1519,10 @@ export class ButtonActionExecutor { const tableSectionData: Record = {}; const commonFieldsData: Record = {}; + // ๐Ÿ†• ์›๋ณธ ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์ถ”์ถœ (์ˆ˜์ • ๋ชจ๋“œ์—์„œ UPDATE/DELETE ์ถ”์ ์šฉ) + // modalData ๋‚ด๋ถ€ ๋˜๋Š” ์ตœ์ƒ์œ„ formData์—์„œ ์ฐพ์Œ + const originalGroupedData: any[] = modalData._originalGroupedData || formData._originalGroupedData || []; + for (const [key, value] of Object.entries(modalData)) { if (key.startsWith("_tableSection_")) { const sectionId = key.replace("_tableSection_", ""); @@ -1532,11 +1537,13 @@ export class ButtonActionExecutor { commonFields: Object.keys(commonFieldsData), tableSections: Object.keys(tableSectionData), tableSectionCounts: Object.entries(tableSectionData).map(([k, v]) => ({ [k]: v.length })), + originalGroupedDataCount: originalGroupedData.length, + isEditMode: originalGroupedData.length > 0, }); - // ํ…Œ์ด๋ธ” ์„น์…˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์Œ + // ํ…Œ์ด๋ธ” ์„น์…˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๊ณ  ์›๋ณธ ๋ฐ์ดํ„ฐ๋„ ์—†์œผ๋ฉด ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์Œ const hasTableSectionData = Object.values(tableSectionData).some((arr) => arr.length > 0); - if (!hasTableSectionData) { + if (!hasTableSectionData && originalGroupedData.length === 0) { console.log("โš ๏ธ [handleUniversalFormModalTableSectionSave] ํ…Œ์ด๋ธ” ์„น์…˜ ๋ฐ์ดํ„ฐ ์—†์Œ - ์ผ๋ฐ˜ ์ €์žฅ์œผ๋กœ ์ „ํ™˜"); return { handled: false, success: false }; } @@ -1554,14 +1561,17 @@ export class ButtonActionExecutor { company_code: context.companyCode || "", }; - let totalSaved = 0; + let insertedCount = 0; + let updatedCount = 0; + let deletedCount = 0; - // ๊ฐ ํ…Œ์ด๋ธ” ์„น์…˜์˜ ํ’ˆ๋ชฉ๋ณ„๋กœ ์ €์žฅ - for (const [sectionId, items] of Object.entries(tableSectionData)) { - console.log(`๐Ÿ”„ [handleUniversalFormModalTableSectionSave] ์„น์…˜ ${sectionId} ์ €์žฅ ์‹œ์ž‘: ${items.length}๊ฐœ ํ’ˆ๋ชฉ`); + // ๊ฐ ํ…Œ์ด๋ธ” ์„น์…˜ ์ฒ˜๋ฆฌ + for (const [sectionId, currentItems] of Object.entries(tableSectionData)) { + console.log(`๐Ÿ”„ [handleUniversalFormModalTableSectionSave] ์„น์…˜ ${sectionId} ์ฒ˜๋ฆฌ ์‹œ์ž‘: ${currentItems.length}๊ฐœ ํ’ˆ๋ชฉ`); - for (const item of items) { - // ๊ณตํ†ต ํ•„๋“œ + ํ’ˆ๋ชฉ ๋ฐ์ดํ„ฐ ๋ณ‘ํ•ฉ + // 1๏ธโƒฃ ์‹ ๊ทœ ํ’ˆ๋ชฉ INSERT (id๊ฐ€ ์—†๋Š” ํ•ญ๋ชฉ) + const newItems = currentItems.filter((item) => !item.id); + for (const item of newItems) { const rowToSave = { ...commonFieldsData, ...item, ...userInfo }; // ๋‚ด๋ถ€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ œ๊ฑฐ @@ -1571,9 +1581,8 @@ export class ButtonActionExecutor { } }); - console.log("๐Ÿ“ [handleUniversalFormModalTableSectionSave] ์ €์žฅํ•  ํ–‰:", rowToSave); + console.log("โž• [INSERT] ์‹ ๊ทœ ํ’ˆ๋ชฉ:", rowToSave); - // INSERT ์‹คํ–‰ const saveResult = await DynamicFormApi.saveFormData({ screenId: screenId!, tableName: tableName!, @@ -1581,19 +1590,100 @@ export class ButtonActionExecutor { }); if (!saveResult.success) { - throw new Error(saveResult.message || "ํ’ˆ๋ชฉ ์ €์žฅ ์‹คํŒจ"); + throw new Error(saveResult.message || "์‹ ๊ทœ ํ’ˆ๋ชฉ ์ €์žฅ ์‹คํŒจ"); } - totalSaved++; + insertedCount++; + } + + // 2๏ธโƒฃ ๊ธฐ์กด ํ’ˆ๋ชฉ UPDATE (id๊ฐ€ ์žˆ๋Š” ํ•ญ๋ชฉ, ๋ณ€๊ฒฝ๋œ ๊ฒฝ์šฐ๋งŒ) + const existingItems = currentItems.filter((item) => item.id); + for (const item of existingItems) { + const originalItem = originalGroupedData.find((orig) => orig.id === item.id); + + if (!originalItem) { + console.warn(`โš ๏ธ [UPDATE] ์›๋ณธ ๋ฐ์ดํ„ฐ ์—†์Œ - INSERT๋กœ ์ฒ˜๋ฆฌ: id=${item.id}`); + // ์›๋ณธ์ด ์—†์œผ๋ฉด ์‹ ๊ทœ๋กœ ์ฒ˜๋ฆฌ + const rowToSave = { ...commonFieldsData, ...item, ...userInfo }; + Object.keys(rowToSave).forEach((key) => { + if (key.startsWith("_")) { + delete rowToSave[key]; + } + }); + delete rowToSave.id; // id ์ œ๊ฑฐํ•˜์—ฌ INSERT + + const saveResult = await DynamicFormApi.saveFormData({ + screenId: screenId!, + tableName: tableName!, + data: rowToSave, + }); + + if (!saveResult.success) { + throw new Error(saveResult.message || "ํ’ˆ๋ชฉ ์ €์žฅ ์‹คํŒจ"); + } + + insertedCount++; + continue; + } + + // ๋ณ€๊ฒฝ ์‚ฌํ•ญ ํ™•์ธ (๊ณตํ†ต ํ•„๋“œ ํฌํ•จ) + const currentDataWithCommon = { ...commonFieldsData, ...item }; + const hasChanges = this.checkForChanges(originalItem, currentDataWithCommon); + + if (hasChanges) { + console.log(`๐Ÿ”„ [UPDATE] ํ’ˆ๋ชฉ ์ˆ˜์ •: id=${item.id}`); + + // ๋ณ€๊ฒฝ๋œ ํ•„๋“œ๋งŒ ์ถ”์ถœํ•˜์—ฌ ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ + const updateResult = await DynamicFormApi.updateFormDataPartial( + item.id, + originalItem, + currentDataWithCommon, + tableName!, + ); + + if (!updateResult.success) { + throw new Error(updateResult.message || "ํ’ˆ๋ชฉ ์ˆ˜์ • ์‹คํŒจ"); + } + + updatedCount++; + } else { + console.log(`โญ๏ธ [SKIP] ๋ณ€๊ฒฝ ์—†์Œ: id=${item.id}`); + } + } + + // 3๏ธโƒฃ ์‚ญ์ œ๋œ ํ’ˆ๋ชฉ DELETE (์›๋ณธ์—๋Š” ์žˆ์ง€๋งŒ ํ˜„์žฌ์—๋Š” ์—†๋Š” ํ•ญ๋ชฉ) + const currentIds = new Set(currentItems.map((item) => item.id).filter(Boolean)); + const deletedItems = originalGroupedData.filter((orig) => orig.id && !currentIds.has(orig.id)); + + for (const deletedItem of deletedItems) { + console.log(`๐Ÿ—‘๏ธ [DELETE] ํ’ˆ๋ชฉ ์‚ญ์ œ: id=${deletedItem.id}`); + + const deleteResult = await DynamicFormApi.deleteFormDataFromTable(tableName!, deletedItem.id); + + if (!deleteResult.success) { + throw new Error(deleteResult.message || "ํ’ˆ๋ชฉ ์‚ญ์ œ ์‹คํŒจ"); + } + + deletedCount++; } } - console.log(`โœ… [handleUniversalFormModalTableSectionSave] ์ด ${totalSaved}๊ฐœ ํ–‰ ์ €์žฅ ์™„๋ฃŒ`); - toast.success(`${totalSaved}๊ฐœ ํ•ญ๋ชฉ์ด ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`); + // ๊ฒฐ๊ณผ ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ + const resultParts: string[] = []; + if (insertedCount > 0) resultParts.push(`${insertedCount}๊ฐœ ์ถ”๊ฐ€`); + if (updatedCount > 0) resultParts.push(`${updatedCount}๊ฐœ ์ˆ˜์ •`); + if (deletedCount > 0) resultParts.push(`${deletedCount}๊ฐœ ์‚ญ์ œ`); + + const resultMessage = resultParts.length > 0 ? resultParts.join(", ") : "๋ณ€๊ฒฝ ์‚ฌํ•ญ ์—†์Œ"; + + console.log(`โœ… [handleUniversalFormModalTableSectionSave] ์™„๋ฃŒ: ${resultMessage}`); + toast.success(`์ €์žฅ ์™„๋ฃŒ: ${resultMessage}`); // ์ €์žฅ ์„ฑ๊ณต ์ด๋ฒคํŠธ ๋ฐœ์ƒ window.dispatchEvent(new CustomEvent("saveSuccess")); window.dispatchEvent(new CustomEvent("refreshTable")); + // EditModal ๋‹ซ๊ธฐ ์ด๋ฒคํŠธ ๋ฐœ์ƒ + window.dispatchEvent(new CustomEvent("closeEditModal")); return { handled: true, success: true }; } catch (error: any) { @@ -1603,6 +1693,38 @@ export class ButtonActionExecutor { } } + /** + * ๋‘ ๊ฐ์ฒด ๊ฐ„ ๋ณ€๊ฒฝ ์‚ฌํ•ญ ํ™•์ธ + */ + private static checkForChanges(original: Record, current: Record): boolean { + // ๋น„๊ตํ•  ํ•„๋“œ ๋ชฉ๋ก (๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ œ์™ธ) + const fieldsToCompare = new Set([ + ...Object.keys(original).filter((k) => !k.startsWith("_")), + ...Object.keys(current).filter((k) => !k.startsWith("_")), + ]); + + for (const field of fieldsToCompare) { + // ์‹œ์Šคํ…œ ํ•„๋“œ๋Š” ๋น„๊ต์—์„œ ์ œ์™ธ + if (["created_date", "updated_date", "created_by", "updated_by", "writer"].includes(field)) { + continue; + } + + const originalValue = original[field]; + const currentValue = current[field]; + + // null/undefined ํ†ต์ผ ์ฒ˜๋ฆฌ + const normalizedOriginal = originalValue === null || originalValue === undefined ? "" : String(originalValue); + const normalizedCurrent = currentValue === null || currentValue === undefined ? "" : String(currentValue); + + if (normalizedOriginal !== normalizedCurrent) { + console.log(` ๐Ÿ“ ๋ณ€๊ฒฝ ๊ฐ์ง€: ${field} = "${normalizedOriginal}" โ†’ "${normalizedCurrent}"`); + return true; + } + } + + return false; + } + /** * ๐Ÿ†• ๋ฐฐ์น˜ ์ €์žฅ ์•ก์…˜ ์ฒ˜๋ฆฌ (SelectedItemsDetailInput์šฉ - ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ) * ItemData[] โ†’ ๊ฐ ํ’ˆ๋ชฉ์˜ details ๋ฐฐ์—ด์„ ๊ฐœ๋ณ„ ๋ ˆ์ฝ”๋“œ๋กœ ์ €์žฅ