diff --git a/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx b/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx index 48c58392..85e43ce9 100644 --- a/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx +++ b/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx @@ -99,6 +99,123 @@ export function RepeatScreenModalComponent({ contentRowId: string; } | null>(null); + // ๐Ÿ†• v3.9: beforeFormSave ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ - ButtonPrimary ์ €์žฅ ์‹œ externalTableData๋ฅผ formData์— ๋ณ‘ํ•ฉ + useEffect(() => { + const handleBeforeFormSave = (event: Event) => { + if (!(event instanceof CustomEvent) || !event.detail?.formData) return; + + console.log("[RepeatScreenModal] beforeFormSave ์ด๋ฒคํŠธ ์ˆ˜์‹ "); + + // ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ์—์„œ dirty ํ–‰๋งŒ ์ถ”์ถœํ•˜์—ฌ ์ €์žฅ ๋ฐ์ดํ„ฐ ์ค€๋น„ + const saveDataByTable: Record = {}; + + for (const [key, rows] of Object.entries(externalTableData)) { + // contentRow ์ฐพ๊ธฐ + const contentRow = contentRows.find((r) => key.includes(r.id)); + if (!contentRow?.tableDataSource?.enabled) continue; + + const targetTable = contentRow.tableCrud?.targetTable || contentRow.tableDataSource.sourceTable; + + // dirty ํ–‰๋งŒ ํ•„ํ„ฐ๋ง (์‚ญ์ œ๋œ ํ–‰ ์ œ์™ธ) + const dirtyRows = rows.filter((row) => row._isDirty && !row._isDeleted); + + if (dirtyRows.length === 0) continue; + + // ์ €์žฅํ•  ํ•„๋“œ๋งŒ ์ถ”์ถœ + const editableFields = (contentRow.tableColumns || []) + .filter((col) => col.editable) + .map((col) => col.field); + + const joinKeys = (contentRow.tableDataSource.joinConditions || []) + .map((cond) => cond.sourceKey); + + const allowedFields = [...new Set([...editableFields, ...joinKeys])]; + + if (!saveDataByTable[targetTable]) { + saveDataByTable[targetTable] = []; + } + + for (const row of dirtyRows) { + const saveData: Record = {}; + + // ํ—ˆ์šฉ๋œ ํ•„๋“œ๋งŒ ํฌํ•จ + for (const field of allowedFields) { + if (row[field] !== undefined) { + saveData[field] = row[field]; + } + } + + // _isNew ํ”Œ๋ž˜๊ทธ ์œ ์ง€ + saveData._isNew = row._isNew; + saveData._targetTable = targetTable; + + // ๊ธฐ์กด ๋ ˆ์ฝ”๋“œ์˜ ๊ฒฝ์šฐ id ํฌํ•จ + if (!row._isNew && row._originalData?.id) { + saveData.id = row._originalData.id; + } + + saveDataByTable[targetTable].push(saveData); + } + } + + // formData์— ํ…Œ์ด๋ธ”๋ณ„ ์ €์žฅ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€ + for (const [tableName, rows] of Object.entries(saveDataByTable)) { + const fieldKey = `_repeatScreenModal_${tableName}`; + event.detail.formData[fieldKey] = rows; + console.log(`[RepeatScreenModal] beforeFormSave - ${tableName} ์ €์žฅ ๋ฐ์ดํ„ฐ:`, rows); + } + + // ๐Ÿ†• v3.9: ์ง‘๊ณ„ ์ €์žฅ ์„ค์ • ์ •๋ณด๋„ formData์— ์ถ”๊ฐ€ + if (grouping?.aggregations && groupedCardsData.length > 0) { + const aggregationSaveConfigs: Array<{ + resultField: string; + aggregatedValue: number; + targetTable: string; + targetColumn: string; + joinKey: { sourceField: string; targetField: string }; + sourceValue: any; // ์กฐ์ธ ํ‚ค ๊ฐ’ + }> = []; + + for (const card of groupedCardsData) { + for (const agg of grouping.aggregations) { + if (agg.saveConfig?.enabled) { + const { saveConfig, resultField } = agg; + const { targetTable, targetColumn, joinKey } = saveConfig; + + if (!targetTable || !targetColumn || !joinKey?.sourceField || !joinKey?.targetField) { + continue; + } + + const aggregatedValue = card._aggregations?.[resultField] ?? 0; + const sourceValue = card._representativeData?.[joinKey.sourceField]; + + if (sourceValue !== undefined) { + aggregationSaveConfigs.push({ + resultField, + aggregatedValue, + targetTable, + targetColumn, + joinKey, + sourceValue, + }); + } + } + } + } + + if (aggregationSaveConfigs.length > 0) { + event.detail.formData._repeatScreenModal_aggregations = aggregationSaveConfigs; + console.log("[RepeatScreenModal] beforeFormSave - ์ง‘๊ณ„ ์ €์žฅ ์„ค์ •:", aggregationSaveConfigs); + } + } + }; + + window.addEventListener("beforeFormSave", handleBeforeFormSave as EventListener); + return () => { + window.removeEventListener("beforeFormSave", handleBeforeFormSave as EventListener); + }; + }, [externalTableData, contentRows, grouping, groupedCardsData]); + // ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ useEffect(() => { const loadInitialData = async () => { @@ -795,16 +912,91 @@ export function RepeatScreenModalComponent({ const result = await saveTableAreaData(cardId, contentRowId, contentRow); if (result.success) { console.log("[RepeatScreenModal] ํ…Œ์ด๋ธ” ์˜์—ญ ์ €์žฅ ์„ฑ๊ณต:", result); - // ์„ฑ๊ณต ์•Œ๋ฆผ (ํ•„์š” ์‹œ toast ์ถ”๊ฐ€) + + // ๐Ÿ†• v3.9: ์ง‘๊ณ„ ์ €์žฅ ์„ค์ •์ด ์žˆ๋Š” ๊ฒฝ์šฐ ์—ฐ๊ด€ ํ…Œ์ด๋ธ” ๋™๊ธฐํ™” + const card = groupedCardsData.find((c) => c._cardId === cardId); + if (card && grouping?.aggregations) { + await saveAggregationsToRelatedTables(card, contentRowId); + } } else { console.error("[RepeatScreenModal] ํ…Œ์ด๋ธ” ์˜์—ญ ์ €์žฅ ์‹คํŒจ:", result.message); - // ์‹คํŒจ ์•Œ๋ฆผ (ํ•„์š” ์‹œ toast ์ถ”๊ฐ€) } } finally { setIsSaving(false); } }; + // ๐Ÿ†• v3.9: ์ง‘๊ณ„ ๊ฒฐ๊ณผ๋ฅผ ์—ฐ๊ด€ ํ…Œ์ด๋ธ”์— ์ €์žฅ + const saveAggregationsToRelatedTables = async (card: GroupedCardData, contentRowId: string) => { + if (!grouping?.aggregations) return; + + const savePromises: Promise[] = []; + + for (const agg of grouping.aggregations) { + const saveConfig = agg.saveConfig; + + // ์ €์žฅ ์„ค์ •์ด ์—†๊ฑฐ๋‚˜ ๋น„ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ ์Šคํ‚ต + if (!saveConfig?.enabled) continue; + + // ์ž๋™ ์ €์žฅ์ด ์•„๋‹Œ ๊ฒฝ์šฐ, ๋ ˆ์ด์•„์›ƒ์— ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธ ํ•„์š” + // (ํ˜„์žฌ๋Š” ์ž๋™ ์ €์žฅ๊ณผ ๋™์ผํ•˜๊ฒŒ ์ฒ˜๋ฆฌ - ์ถ”ํ›„ ๋ ˆ์ด์•„์›ƒ ์—ฐ๊ฒฐ ์ฒดํฌ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ) + + // ์ง‘๊ณ„ ๊ฒฐ๊ณผ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ + const aggregatedValue = card._aggregations[agg.resultField]; + + if (aggregatedValue === undefined) { + console.warn(`[RepeatScreenModal] ์ง‘๊ณ„ ๊ฒฐ๊ณผ ์—†์Œ: ${agg.resultField}`); + continue; + } + + // ์กฐ์ธ ํ‚ค๋กœ ๋Œ€์ƒ ๋ ˆ์ฝ”๋“œ ์‹๋ณ„ + const sourceKeyValue = card._representativeData[saveConfig.joinKey.sourceField]; + + if (!sourceKeyValue) { + console.warn(`[RepeatScreenModal] ์กฐ์ธ ํ‚ค ๊ฐ’ ์—†์Œ: ${saveConfig.joinKey.sourceField}`); + continue; + } + + console.log(`[RepeatScreenModal] ์ง‘๊ณ„ ์ €์žฅ ์‹œ์ž‘:`, { + aggregation: agg.resultField, + value: aggregatedValue, + targetTable: saveConfig.targetTable, + targetColumn: saveConfig.targetColumn, + joinKey: `${saveConfig.joinKey.sourceField}=${sourceKeyValue} -> ${saveConfig.joinKey.targetField}`, + }); + + // UPDATE API ํ˜ธ์ถœ + const updatePayload = { + originalData: { [saveConfig.joinKey.targetField]: sourceKeyValue }, + updatedData: { + [saveConfig.targetColumn]: aggregatedValue, + [saveConfig.joinKey.targetField]: sourceKeyValue, + }, + }; + + savePromises.push( + apiClient.put(`/table-management/tables/${saveConfig.targetTable}/edit`, updatePayload) + .then((res) => { + console.log(`[RepeatScreenModal] ์ง‘๊ณ„ ์ €์žฅ ์„ฑ๊ณต: ${agg.resultField} -> ${saveConfig.targetTable}.${saveConfig.targetColumn}`); + return res; + }) + .catch((err) => { + console.error(`[RepeatScreenModal] ์ง‘๊ณ„ ์ €์žฅ ์‹คํŒจ: ${agg.resultField}`, err.response?.data || err.message); + throw err; + }) + ); + } + + if (savePromises.length > 0) { + try { + await Promise.all(savePromises); + console.log(`[RepeatScreenModal] ๋ชจ๋“  ์ง‘๊ณ„ ์ €์žฅ ์™„๋ฃŒ: ${savePromises.length}๊ฑด`); + } catch (error) { + console.error("[RepeatScreenModal] ์ผ๋ถ€ ์ง‘๊ณ„ ์ €์žฅ ์‹คํŒจ:", error); + } + } + }; + // ๐Ÿ†• v3.1: ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ํ–‰ ์‚ญ์ œ ์š”์ฒญ const handleDeleteExternalRowRequest = (cardId: string, rowId: string, contentRowId: string, contentRow: CardContentRowConfig) => { if (contentRow.tableCrud?.deleteConfirm?.enabled !== false) { @@ -1002,8 +1194,11 @@ export function RepeatScreenModalComponent({ }); } + // ์•ˆ์ •์ ์ธ _cardId ์ƒ์„ฑ (Date.now() ๋Œ€์‹  groupKey ์‚ฌ์šฉ) + // groupKey๊ฐ€ ์—†์œผ๋ฉด ๋Œ€ํ‘œ ๋ฐ์ดํ„ฐ์˜ id ์‚ฌ์šฉ + const stableId = groupKey || representativeData.id || cardIndex; result.push({ - _cardId: `grouped-card-${cardIndex}-${Date.now()}`, + _cardId: `grouped-card-${cardIndex}-${stableId}`, _groupKey: groupKey, _groupField: groupByField || "", _aggregations: aggregations, diff --git a/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalConfigPanel.tsx b/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalConfigPanel.tsx index 54949627..44ee5ce6 100644 --- a/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalConfigPanel.tsx +++ b/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalConfigPanel.tsx @@ -766,7 +766,7 @@ function AggregationConfigItem({ const currentSourceType = agg.sourceType || "column"; return ( -
+
+ + {/* ๐Ÿ†• v3.9: ์ €์žฅ ์„ค์ • */} + +
+ ); +} + +// ๐Ÿ†• v3.9: ์ง‘๊ณ„ ์ €์žฅ ์„ค์ • ์„น์…˜ +function AggregationSaveConfigSection({ + agg, + sourceTable, + allTables, + onUpdate, +}: { + agg: AggregationConfig; + sourceTable: string; + allTables: { tableName: string; displayName?: string }[]; + onUpdate: (updates: Partial) => void; +}) { + const saveConfig = agg.saveConfig || { enabled: false, autoSave: false, targetTable: "", targetColumn: "", joinKey: { sourceField: "", targetField: "" } }; + + const updateSaveConfig = (updates: Partial) => { + onUpdate({ + saveConfig: { + ...saveConfig, + ...updates, + }, + }); + }; + + return ( +
+
+ + updateSaveConfig({ enabled: checked })} + className="scale-[0.6]" + /> +
+ + {saveConfig.enabled && ( +
+ {/* ์ž๋™ ์ €์žฅ ์˜ต์…˜ */} +
+
+ +

+ ๋ ˆ์ด์•„์›ƒ์— ์—†์–ด๋„ ์ €์žฅ +

+
+ updateSaveConfig({ autoSave: checked })} + className="scale-[0.6]" + /> +
+ + {/* ๋Œ€์ƒ ํ…Œ์ด๋ธ” */} +
+ + +
+ + {/* ๋Œ€์ƒ ์ปฌ๋Ÿผ */} +
+ + updateSaveConfig({ targetColumn: value })} + placeholder="์ปฌ๋Ÿผ ์„ ํƒ" + /> +
+ + {/* ์กฐ์ธ ํ‚ค ์„ค์ • */} +
+ +
+
+ ์นด๋“œ ํ‚ค (ํ˜„์žฌ ์นด๋“œ ๋ฐ์ดํ„ฐ) + + updateSaveConfig({ + joinKey: { ...saveConfig.joinKey, sourceField: value }, + }) + } + placeholder="์นด๋“œ ํ‚ค ์„ ํƒ" + /> +
+
+ โ†“ +
+
+ ๋Œ€์ƒ ํ‚ค (์—…๋ฐ์ดํŠธํ•  ๋ ˆ์ฝ”๋“œ ์‹๋ณ„) + + updateSaveConfig({ + joinKey: { ...saveConfig.joinKey, targetField: value }, + }) + } + placeholder="๋Œ€์ƒ ํ‚ค ์„ ํƒ" + /> +
+
+
+ + {/* ์„ค์ • ์š”์•ฝ */} + {saveConfig.targetTable && saveConfig.targetColumn && ( +
+
+ ์ €์žฅ ๊ฒฝ๋กœ: + {saveConfig.autoSave && ( + + ์ž๋™ + + )} +
+
+ {saveConfig.targetTable}.{saveConfig.targetColumn} +
+ {saveConfig.joinKey?.sourceField && saveConfig.joinKey?.targetField && ( +
+ ์กฐ์ธ: {saveConfig.joinKey.sourceField} โ†’ {saveConfig.joinKey.targetField} +
+ )} +
+ )} +
+ )}
); } diff --git a/frontend/lib/registry/components/repeat-screen-modal/types.ts b/frontend/lib/registry/components/repeat-screen-modal/types.ts index 7226503e..2191818f 100644 --- a/frontend/lib/registry/components/repeat-screen-modal/types.ts +++ b/frontend/lib/registry/components/repeat-screen-modal/types.ts @@ -265,6 +265,7 @@ export interface ChainedJoinConfig { /** * ์ง‘๊ณ„ ์„ค์ • * ๐Ÿ†• v3.2: ๋‹ค์ค‘ ํ…Œ์ด๋ธ” ๋ฐ ๊ฐ€์ƒ ์ง‘๊ณ„(formula) ์ง€์› + * ๐Ÿ†• v3.9: ์—ฐ๊ด€ ํ…Œ์ด๋ธ” ์ €์žฅ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ */ export interface AggregationConfig { // === ์ง‘๊ณ„ ์†Œ์Šค ํƒ€์ž… === @@ -287,6 +288,28 @@ export interface AggregationConfig { // === ๊ณตํ†ต === resultField: string; // ๊ฒฐ๊ณผ ํ•„๋“œ๋ช… (์˜ˆ: "total_balance_qty") label: string; // ํ‘œ์‹œ ๋ผ๋ฒจ (์˜ˆ: "์ด์ˆ˜์ฃผ์ž”๋Ÿ‰") + + // === ๐Ÿ†• v3.9: ์ €์žฅ ์„ค์ • === + saveConfig?: AggregationSaveConfig; // ์—ฐ๊ด€ ํ…Œ์ด๋ธ” ์ €์žฅ ์„ค์ • +} + +/** + * ๐Ÿ†• v3.9: ์ง‘๊ณ„ ๊ฒฐ๊ณผ ์ €์žฅ ์„ค์ • + * ์ง‘๊ณ„๋œ ๊ฐ’์„ ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์— ๋™๊ธฐํ™” + */ +export interface AggregationSaveConfig { + enabled: boolean; // ์ €์žฅ ํ™œ์„ฑํ™” ์—ฌ๋ถ€ + autoSave: boolean; // ์ž๋™ ์ €์žฅ (๋ ˆ์ด์•„์›ƒ์— ์—†์–ด๋„ ์ €์žฅ) + + // ์ €์žฅ ๋Œ€์ƒ + targetTable: string; // ์ €์žฅํ•  ํ…Œ์ด๋ธ” (์˜ˆ: "sales_order_mng") + targetColumn: string; // ์ €์žฅํ•  ์ปฌ๋Ÿผ (์˜ˆ: "plan_qty_total") + + // ์กฐ์ธ ํ‚ค (์–ด๋–ค ๋ ˆ์ฝ”๋“œ๋ฅผ ์—…๋ฐ์ดํŠธํ• ์ง€) + joinKey: { + sourceField: string; // ํ˜„์žฌ ์นด๋“œ์˜ ์กฐ์ธ ํ‚ค (์˜ˆ: "id" ๋˜๋Š” "sales_order_id") + targetField: string; // ๋Œ€์ƒ ํ…Œ์ด๋ธ”์˜ ํ‚ค (์˜ˆ: "id") + }; } /** diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 5be55b65..235946ce 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -5,6 +5,7 @@ import { toast } from "sonner"; import { screenApi } from "@/lib/api/screen"; import { DynamicFormApi } from "@/lib/api/dynamicForm"; import { ImprovedButtonActionExecutor } from "@/lib/utils/improvedButtonActionExecutor"; +import { apiClient } from "@/lib/api/client"; import type { ExtendedControlContext } from "@/types/control-management"; /** @@ -663,11 +664,122 @@ export class ButtonActionExecutor { } } - saveResult = await DynamicFormApi.saveFormData({ - screenId, - tableName, - data: dataWithUserInfo, - }); + // ๐Ÿ†• v3.9: RepeatScreenModal์˜ ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์ €์žฅ ์ฒ˜๋ฆฌ + const repeatScreenModalKeys = Object.keys(context.formData).filter((key) => + key.startsWith("_repeatScreenModal_") && key !== "_repeatScreenModal_aggregations" + ); + + // RepeatScreenModal ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ํ•ด๋‹น ํ…Œ์ด๋ธ”์— ๋Œ€ํ•œ ๋ฉ”์ธ ์ €์žฅ์€ ๊ฑด๋„ˆ๋œ€ + const repeatScreenModalTables = repeatScreenModalKeys.map((key) => key.replace("_repeatScreenModal_", "")); + const shouldSkipMainSave = repeatScreenModalTables.includes(tableName); + + if (shouldSkipMainSave) { + console.log(`โญ๏ธ [handleSave] ${tableName} ๋ฉ”์ธ ์ €์žฅ ๊ฑด๋„ˆ๋œ€ (RepeatScreenModal์—์„œ ์ฒ˜๋ฆฌ)`); + saveResult = { success: true, message: "RepeatScreenModal์—์„œ ์ฒ˜๋ฆฌ" }; + } else { + saveResult = await DynamicFormApi.saveFormData({ + screenId, + tableName, + data: dataWithUserInfo, + }); + } + + if (repeatScreenModalKeys.length > 0) { + console.log("๐Ÿ“ฆ [handleSave] RepeatScreenModal ๋ฐ์ดํ„ฐ ์ €์žฅ ์‹œ์ž‘:", repeatScreenModalKeys); + + // ๐Ÿ†• formData์—์„œ ์ฑ„๋ฒˆ ๊ทœ์น™์œผ๋กœ ์ƒ์„ฑ๋œ ๊ฐ’๋“ค ์ถ”์ถœ (์˜ˆ: shipment_plan_no) + const numberingFields: Record = {}; + for (const [fieldKey, value] of Object.entries(context.formData)) { + // _numberingRuleId๋กœ ๋๋‚˜๋Š” ํ‚ค๊ฐ€ ์žˆ์œผ๋ฉด ํ•ด๋‹น ํ•„๋“œ๋Š” ์ฑ„๋ฒˆ ๊ทœ์น™ ๊ฐ’ + if (context.formData[`${fieldKey}_numberingRuleId`]) { + numberingFields[fieldKey] = value; + } + } + console.log("๐Ÿ“ฆ [handleSave] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ•„๋“œ:", numberingFields); + + for (const key of repeatScreenModalKeys) { + const targetTable = key.replace("_repeatScreenModal_", ""); + const rows = context.formData[key] as any[]; + + if (!Array.isArray(rows) || rows.length === 0) continue; + + console.log(`๐Ÿ“ฆ [handleSave] ${targetTable} ํ…Œ์ด๋ธ” ์ €์žฅ:`, rows); + + for (const row of rows) { + const { _isNew, _targetTable, id, ...dataToSave } = row; + + // ์‚ฌ์šฉ์ž ์ •๋ณด ์ถ”๊ฐ€ + ์ฑ„๋ฒˆ ๊ทœ์น™ ๊ฐ’ ๋ณ‘ํ•ฉ + const dataWithMeta = { + ...dataToSave, + ...numberingFields, // ์ฑ„๋ฒˆ ๊ทœ์น™ ๊ฐ’ (shipment_plan_no ๋“ฑ) + created_by: context.userId, + updated_by: context.userId, + company_code: context.companyCode, + }; + + try { + if (_isNew) { + // INSERT + console.log(`๐Ÿ“ [handleSave] ${targetTable} INSERT:`, dataWithMeta); + const insertResult = await apiClient.post( + `/table-management/tables/${targetTable}/add`, + dataWithMeta + ); + console.log(`โœ… [handleSave] ${targetTable} INSERT ์™„๋ฃŒ:`, insertResult.data); + } else if (id) { + // UPDATE + const originalData = { id }; + const updatedData = { ...dataWithMeta, id }; + console.log(`๐Ÿ“ [handleSave] ${targetTable} UPDATE:`, { originalData, updatedData }); + const updateResult = await apiClient.put( + `/table-management/tables/${targetTable}/edit`, + { originalData, updatedData } + ); + console.log(`โœ… [handleSave] ${targetTable} UPDATE ์™„๋ฃŒ:`, updateResult.data); + } + } catch (error: any) { + console.error(`โŒ [handleSave] ${targetTable} ์ €์žฅ ์‹คํŒจ:`, error.response?.data || error.message); + // ๊ฐœ๋ณ„ ์‹คํŒจ๋Š” ์ „์ฒด ์ €์žฅ์„ ์ค‘๋‹จํ•˜์ง€ ์•Š์Œ + } + } + } + } + + // ๐Ÿ†• v3.9: RepeatScreenModal ์ง‘๊ณ„ ์ €์žฅ ์ฒ˜๋ฆฌ + const aggregationConfigs = context.formData._repeatScreenModal_aggregations as Array<{ + resultField: string; + aggregatedValue: number; + targetTable: string; + targetColumn: string; + joinKey: { sourceField: string; targetField: string }; + sourceValue: any; + }>; + + if (aggregationConfigs && aggregationConfigs.length > 0) { + console.log("๐Ÿ“Š [handleSave] ์ง‘๊ณ„ ์ €์žฅ ์‹œ์ž‘:", aggregationConfigs); + + for (const config of aggregationConfigs) { + const { targetTable, targetColumn, joinKey, aggregatedValue, sourceValue } = config; + + try { + const originalData = { [joinKey.targetField]: sourceValue }; + const updatedData = { + [targetColumn]: aggregatedValue, + [joinKey.targetField]: sourceValue, + }; + + console.log(`๐Ÿ“Š [handleSave] ${targetTable}.${targetColumn} = ${aggregatedValue} (์กฐ์ธ: ${joinKey.sourceField} = ${sourceValue})`); + + const updateResult = await apiClient.put( + `/table-management/tables/${targetTable}/edit`, + { originalData, updatedData } + ); + console.log(`โœ… [handleSave] ${targetTable} ์ง‘๊ณ„ ์ €์žฅ ์™„๋ฃŒ:`, updateResult.data); + } catch (error: any) { + console.error(`โŒ [handleSave] ${targetTable} ์ง‘๊ณ„ ์ €์žฅ ์‹คํŒจ:`, error.response?.data || error.message); + } + } + } } if (!saveResult.success) {