diff --git a/backend-node/src/services/nodeFlowExecutionService.ts b/backend-node/src/services/nodeFlowExecutionService.ts index 24bbb88f..baa1f02c 100644 --- a/backend-node/src/services/nodeFlowExecutionService.ts +++ b/backend-node/src/services/nodeFlowExecutionService.ts @@ -2282,6 +2282,7 @@ export class NodeFlowExecutionService { UPDATE ${targetTable} SET ${setClauses.join(", ")} WHERE ${updateWhereConditions} + RETURNING * `; logger.info(`๐Ÿ”„ UPDATE ์‹คํ–‰:`, { @@ -2292,8 +2293,14 @@ export class NodeFlowExecutionService { values: updateValues, }); - await txClient.query(updateSql, updateValues); + const updateResult = await txClient.query(updateSql, updateValues); updatedCount++; + + // ๐Ÿ†• UPDATE ๊ฒฐ๊ณผ๋ฅผ ์ž…๋ ฅ ๋ฐ์ดํ„ฐ์— ๋ณ‘ํ•ฉ (๋‹ค์Œ ๋…ธ๋“œ์—์„œ id ๋“ฑ ์‚ฌ์šฉ ๊ฐ€๋Šฅ) + if (updateResult.rows && updateResult.rows[0]) { + Object.assign(data, updateResult.rows[0]); + logger.info(` ๐Ÿ“ฆ UPDATE ๊ฒฐ๊ณผ ๋ณ‘ํ•ฉ: id=${updateResult.rows[0].id}`); + } } else { // 3-B. ์—†์œผ๋ฉด INSERT const columns: string[] = []; @@ -2340,6 +2347,7 @@ export class NodeFlowExecutionService { const insertSql = ` INSERT INTO ${targetTable} (${columns.join(", ")}) VALUES (${placeholders}) + RETURNING * `; logger.info(`โž• INSERT ์‹คํ–‰:`, { @@ -2348,8 +2356,14 @@ export class NodeFlowExecutionService { conflictKeyValues, }); - await txClient.query(insertSql, values); + const insertResult = await txClient.query(insertSql, values); insertedCount++; + + // ๐Ÿ†• INSERT ๊ฒฐ๊ณผ๋ฅผ ์ž…๋ ฅ ๋ฐ์ดํ„ฐ์— ๋ณ‘ํ•ฉ (๋‹ค์Œ ๋…ธ๋“œ์—์„œ id ๋“ฑ ์‚ฌ์šฉ ๊ฐ€๋Šฅ) + if (insertResult.rows && insertResult.rows[0]) { + Object.assign(data, insertResult.rows[0]); + logger.info(` ๐Ÿ“ฆ INSERT ๊ฒฐ๊ณผ ๋ณ‘ํ•ฉ: id=${insertResult.rows[0].id}`); + } } } @@ -2357,11 +2371,10 @@ export class NodeFlowExecutionService { `โœ… UPSERT ์™„๋ฃŒ (๋‚ด๋ถ€ DB): ${targetTable}, INSERT ${insertedCount}๊ฑด, UPDATE ${updatedCount}๊ฑด` ); - return { - insertedCount, - updatedCount, - totalCount: insertedCount + updatedCount, - }; + // ๐Ÿ”ฅ ๋‹ค์Œ ๋…ธ๋“œ์— ์ „๋‹ฌํ•  ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜ + // dataArray์—๋Š” Object.assign์œผ๋กœ UPSERT ๊ฒฐ๊ณผ(id ๋“ฑ)๊ฐ€ ์ด๋ฏธ ๋ณ‘ํ•ฉ๋˜์–ด ์žˆ์Œ + // ์นด์šดํŠธ ์ •๋ณด๋„ ํ•จ๊ป˜ ๋ฐ˜ํ™˜ํ•˜์—ฌ ๊ธฐ์กด ํ˜ธํ™˜์„ฑ ์œ ์ง€ + return dataArray; }; // ๐Ÿ”ฅ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ „๋‹ฌ๋˜์—ˆ์œผ๋ฉด ์‚ฌ์šฉ, ์•„๋‹ˆ๋ฉด ๋…๋ฆฝ ํŠธ๋žœ์žญ์…˜ ์ƒ์„ฑ diff --git a/frontend/components/screen-embedding/EmbeddedScreen.tsx b/frontend/components/screen-embedding/EmbeddedScreen.tsx index 3bfb7a77..0b32830e 100644 --- a/frontend/components/screen-embedding/EmbeddedScreen.tsx +++ b/frontend/components/screen-embedding/EmbeddedScreen.tsx @@ -27,13 +27,14 @@ interface EmbeddedScreenProps { onSelectionChanged?: (selectedRows: any[]) => void; position?: SplitPanelPosition; // ๋ถ„ํ•  ํŒจ๋„ ๋‚ด ์œ„์น˜ (left/right) initialFormData?: Record; // ๐Ÿ†• ์ˆ˜์ • ๋ชจ๋“œ์—์„œ ์ „๋‹ฌ๋˜๋Š” ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ + groupedData?: Record[]; // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ (์ˆ˜์ • ๋ชจ๋“œ์—์„œ ์›๋ณธ ๋ฐ์ดํ„ฐ ์ถ”์ ์šฉ) } /** * ์ž„๋ฒ ๋“œ๋œ ํ™”๋ฉด ์ปดํฌ๋„ŒํŠธ */ export const EmbeddedScreen = forwardRef( - ({ embedding, onSelectionChanged, position, initialFormData }, ref) => { + ({ embedding, onSelectionChanged, position, initialFormData, groupedData }, ref) => { const [layout, setLayout] = useState([]); const [selectedRows, setSelectedRows] = useState([]); const [loading, setLoading] = useState(true); @@ -430,6 +431,8 @@ export const EmbeddedScreen = forwardRef ); diff --git a/frontend/components/screen-embedding/ScreenSplitPanel.tsx b/frontend/components/screen-embedding/ScreenSplitPanel.tsx index f457e851..2f30a4ec 100644 --- a/frontend/components/screen-embedding/ScreenSplitPanel.tsx +++ b/frontend/components/screen-embedding/ScreenSplitPanel.tsx @@ -17,13 +17,14 @@ interface ScreenSplitPanelProps { screenId?: number; config?: any; // ์„ค์ • ํŒจ๋„์—์„œ ์˜ค๋Š” config (leftScreenId, rightScreenId, splitRatio, resizable) initialFormData?: Record; // ๐Ÿ†• ์ˆ˜์ • ๋ชจ๋“œ์—์„œ ์ „๋‹ฌ๋˜๋Š” ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ + groupedData?: Record[]; // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ (์ˆ˜์ • ๋ชจ๋“œ์—์„œ ์›๋ณธ ๋ฐ์ดํ„ฐ ์ถ”์ ์šฉ) } /** * ๋ถ„ํ•  ํŒจ๋„ ์ปดํฌ๋„ŒํŠธ * ์ˆœ์ˆ˜ํ•˜๊ฒŒ ํ™”๋ฉด ๋ถ„ํ•  ๊ธฐ๋Šฅ๋งŒ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. */ -export function ScreenSplitPanel({ screenId, config, initialFormData }: ScreenSplitPanelProps) { +export function ScreenSplitPanel({ screenId, config, initialFormData, groupedData }: ScreenSplitPanelProps) { // config์—์„œ splitRatio ์ถ”์ถœ (๊ธฐ๋ณธ๊ฐ’ 50) const configSplitRatio = config?.splitRatio ?? 50; @@ -117,7 +118,7 @@ export function ScreenSplitPanel({ screenId, config, initialFormData }: ScreenSp {/* ์ขŒ์ธก ํŒจ๋„ */}
{hasLeftScreen ? ( - + ) : (

์ขŒ์ธก ํ™”๋ฉด์„ ์„ ํƒํ•˜์„ธ์š”

@@ -157,7 +158,7 @@ export function ScreenSplitPanel({ screenId, config, initialFormData }: ScreenSp {/* ์šฐ์ธก ํŒจ๋„ */}
{hasRightScreen ? ( - + ) : (

์šฐ์ธก ํ™”๋ฉด์„ ์„ ํƒํ•˜์„ธ์š”

diff --git a/frontend/components/screen/config-panels/ButtonConfigPanel.tsx b/frontend/components/screen/config-panels/ButtonConfigPanel.tsx index 417ea4ff..a97d78b3 100644 --- a/frontend/components/screen/config-panels/ButtonConfigPanel.tsx +++ b/frontend/components/screen/config-panels/ButtonConfigPanel.tsx @@ -60,6 +60,7 @@ export const ButtonConfigPanel: React.FC = ({ editModalTitle: String(config.action?.editModalTitle || ""), editModalDescription: String(config.action?.editModalDescription || ""), targetUrl: String(config.action?.targetUrl || ""), + groupByColumn: String(config.action?.groupByColumns?.[0] || ""), }); const [screens, setScreens] = useState([]); @@ -97,6 +98,11 @@ export const ButtonConfigPanel: React.FC = ({ const [modalTargetColumns, setModalTargetColumns] = useState>([]); const [modalSourcePopoverOpen, setModalSourcePopoverOpen] = useState>({}); const [modalTargetPopoverOpen, setModalTargetPopoverOpen] = useState>({}); + + // ๐Ÿ†• ๊ทธ๋ฃนํ™” ์ปฌ๋Ÿผ ์„ ํƒ์šฉ ์ƒํƒœ + const [currentTableColumns, setCurrentTableColumns] = useState>([]); + const [groupByColumnOpen, setGroupByColumnOpen] = useState(false); + const [groupByColumnSearch, setGroupByColumnSearch] = useState(""); const [modalSourceSearch, setModalSourceSearch] = useState>({}); const [modalTargetSearch, setModalTargetSearch] = useState>({}); @@ -130,6 +136,7 @@ export const ButtonConfigPanel: React.FC = ({ editModalTitle: String(latestAction.editModalTitle || ""), editModalDescription: String(latestAction.editModalDescription || ""), targetUrl: String(latestAction.targetUrl || ""), + groupByColumn: String(latestAction.groupByColumns?.[0] || ""), }); // ๐Ÿ†• ์ œ๋ชฉ ๋ธ”๋ก ์ดˆ๊ธฐํ™” @@ -327,6 +334,35 @@ export const ButtonConfigPanel: React.FC = ({ loadColumns(); }, [config.action?.dataTransfer?.sourceTable, config.action?.dataTransfer?.targetTable]); + // ๐Ÿ†• ํ˜„์žฌ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋กœ๋“œ (๊ทธ๋ฃนํ™” ์ปฌ๋Ÿผ ์„ ํƒ์šฉ) + useEffect(() => { + if (!currentTableName) return; + + const loadCurrentTableColumns = async () => { + try { + const response = await apiClient.get(`/table-management/tables/${currentTableName}/columns`); + if (response.data.success) { + let columnData = response.data.data; + if (!Array.isArray(columnData) && columnData?.columns) columnData = columnData.columns; + if (!Array.isArray(columnData) && columnData?.data) columnData = columnData.data; + + if (Array.isArray(columnData)) { + const columns = columnData.map((col: any) => ({ + name: col.name || col.columnName, + label: col.displayName || col.label || col.columnLabel || col.name || col.columnName, + })); + setCurrentTableColumns(columns); + console.log(`โœ… ํ˜„์žฌ ํ…Œ์ด๋ธ” ${currentTableName} ์ปฌ๋Ÿผ ๋กœ๋“œ ์„ฑ๊ณต:`, columns.length, "๊ฐœ"); + } + } + } catch (error) { + console.error("ํ˜„์žฌ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋กœ๋“œ ์‹คํŒจ:", error); + } + }; + + loadCurrentTableColumns(); + }, [currentTableName]); + // ๐Ÿ†• openModalWithData ์†Œ์Šค/ํƒ€๊ฒŸ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋กœ๋“œ useEffect(() => { const actionType = config.action?.type; @@ -1529,6 +1565,106 @@ export const ButtonConfigPanel: React.FC = ({
)} + +
+ + + + + + +
+
+ + setGroupByColumnSearch(e.target.value)} + className="border-0 p-0 focus-visible:ring-0" + /> +
+
+ {currentTableColumns.length === 0 ? ( +
+ {currentTableName ? "์ปฌ๋Ÿผ์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘..." : "ํ…Œ์ด๋ธ”์ด ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค"} +
+ ) : ( + <> + {/* ์„ ํƒ ํ•ด์ œ ์˜ต์…˜ */} +
{ + setLocalInputs((prev) => ({ ...prev, groupByColumn: "" })); + onUpdateProperty("componentConfig.action.groupByColumns", undefined); + setGroupByColumnOpen(false); + setGroupByColumnSearch(""); + }} + > + + ์„ ํƒ ์•ˆ ํ•จ +
+ {/* ์ปฌ๋Ÿผ ๋ชฉ๋ก */} + {currentTableColumns + .filter((col) => { + if (!groupByColumnSearch) return true; + const search = groupByColumnSearch.toLowerCase(); + return ( + col.name.toLowerCase().includes(search) || + col.label.toLowerCase().includes(search) + ); + }) + .map((col) => ( +
{ + setLocalInputs((prev) => ({ ...prev, groupByColumn: col.name })); + onUpdateProperty("componentConfig.action.groupByColumns", [col.name]); + setGroupByColumnOpen(false); + setGroupByColumnSearch(""); + }} + > + +
+ {col.name} + {col.label !== col.name && ( + {col.label} + )} +
+
+ ))} + + )} +
+
+
+
+

+ ์—ฌ๋Ÿฌ ํ–‰์„ ํ•˜๋‚˜์˜ ๊ทธ๋ฃน์œผ๋กœ ๋ฌถ์–ด์„œ ์ˆ˜์ •ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค +

+
)} diff --git a/frontend/lib/registry/components/screen-split-panel/ScreenSplitPanelRenderer.tsx b/frontend/lib/registry/components/screen-split-panel/ScreenSplitPanelRenderer.tsx index 5dc1830c..adeb9e20 100644 --- a/frontend/lib/registry/components/screen-split-panel/ScreenSplitPanelRenderer.tsx +++ b/frontend/lib/registry/components/screen-split-panel/ScreenSplitPanelRenderer.tsx @@ -66,7 +66,7 @@ class ScreenSplitPanelRenderer extends AutoRegisteringComponentRenderer { }; render() { - const { component, style = {}, componentConfig, config, screenId, formData } = this.props as any; + const { component, style = {}, componentConfig, config, screenId, formData, groupedData } = this.props as any; // componentConfig ๋˜๋Š” config ๋˜๋Š” component.componentConfig ์‚ฌ์šฉ const finalConfig = componentConfig || config || component?.componentConfig || {}; @@ -77,6 +77,7 @@ class ScreenSplitPanelRenderer extends AutoRegisteringComponentRenderer { screenId={screenId || finalConfig.screenId} config={finalConfig} initialFormData={formData} // ๐Ÿ†• ์ˆ˜์ • ๋ฐ์ดํ„ฐ ์ „๋‹ฌ + groupedData={groupedData} // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์ „๋‹ฌ (์ˆ˜์ • ๋ชจ๋“œ์—์„œ ์›๋ณธ ๋ฐ์ดํ„ฐ ์ถ”์ ์šฉ) />
); diff --git a/frontend/lib/registry/components/table-list/TableListComponent.tsx b/frontend/lib/registry/components/table-list/TableListComponent.tsx index 389dd92d..74cea859 100644 --- a/frontend/lib/registry/components/table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/table-list/TableListComponent.tsx @@ -2392,6 +2392,44 @@ export const TableListComponent: React.FC = ({ fetchLabels(); }, [columnUniqueValues, categoryLabelCache]); + // ๐Ÿ†• ๋ฐ์ดํ„ฐ์—์„œ CATEGORY_ ์ฝ”๋“œ๋ฅผ ์ฐพ์•„ ๋ผ๋ฒจ ๋ฏธ๋ฆฌ ๋กœ๋“œ (ํ…Œ์ด๋ธ” ์…€ ๋ Œ๋”๋ง์šฉ) + useEffect(() => { + if (data.length === 0) return; + + const categoryCodesToFetch = new Set(); + + // ๋ชจ๋“  ๋ฐ์ดํ„ฐ ํ–‰์—์„œ CATEGORY_ ์ฝ”๋“œ ์ˆ˜์ง‘ + data.forEach((row) => { + Object.entries(row).forEach(([key, value]) => { + if (value && typeof value === "string") { + // ์ฝค๋งˆ๋กœ ๊ตฌ๋ถ„๋œ ๋‹ค์ค‘ ๊ฐ’๋„ ์ฒ˜๋ฆฌ + const codes = value.split(",").map((v) => v.trim()); + codes.forEach((code) => { + if (code.startsWith("CATEGORY_") && !categoryLabelCache[code]) { + categoryCodesToFetch.add(code); + } + }); + } + }); + }); + + if (categoryCodesToFetch.size === 0) return; + + // API๋กœ ๋ผ๋ฒจ ์กฐํšŒ + const fetchLabels = async () => { + try { + const response = await getCategoryLabelsByCodes(Array.from(categoryCodesToFetch)); + if (response.success && response.data && Object.keys(response.data).length > 0) { + setCategoryLabelCache((prev) => ({ ...prev, ...response.data })); + } + } catch (error) { + console.error("CATEGORY_ ๋ผ๋ฒจ ์กฐํšŒ ์‹คํŒจ:", error); + } + }; + + fetchLabels(); + }, [data, categoryLabelCache]); + // ๐Ÿ†• ํ—ค๋” ํ•„ํ„ฐ ํ† ๊ธ€ const toggleHeaderFilter = useCallback((columnName: string, value: string) => { setHeaderFilters((prev) => { @@ -4548,10 +4586,36 @@ export const TableListComponent: React.FC = ({ case "boolean": return value ? "์˜ˆ" : "์•„๋‹ˆ์˜ค"; default: - return String(value); + // ๐Ÿ†• CATEGORY_ ์ฝ”๋“œ ์ž๋™ ๋ณ€ํ™˜ (inputType์ด category๊ฐ€ ์•„๋‹ˆ์–ด๋„) + const strValue = String(value); + if (strValue.startsWith("CATEGORY_")) { + // rowData์—์„œ _label ํ•„๋“œ ์ฐพ๊ธฐ + if (rowData) { + const labelFieldCandidates = [ + `${column.columnName}_label`, + `${column.columnName}_name`, + `${column.columnName}_value_label`, + ]; + for (const labelField of labelFieldCandidates) { + if (rowData[labelField] && rowData[labelField] !== "") { + return String(rowData[labelField]); + } + } + } + // categoryMappings์—์„œ ์ฐพ๊ธฐ + const mapping = categoryMappings[column.columnName]; + if (mapping && mapping[strValue]) { + return mapping[strValue].label; + } + // categoryLabelCache์—์„œ ์ฐพ๊ธฐ (ํ•„ํ„ฐ์šฉ ์บ์‹œ) + if (categoryLabelCache[strValue]) { + return categoryLabelCache[strValue]; + } + } + return strValue; } }, - [columnMeta, joinedColumnMeta, optimizedConvertCode, categoryMappings], + [columnMeta, joinedColumnMeta, optimizedConvertCode, categoryMappings, categoryLabelCache], ); // ======================================== diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 6daf17e9..4c9d638b 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -304,6 +304,9 @@ export interface ButtonActionContext { selectedLeftData?: Record; refreshRightPanel?: () => void; }; + + // ๐Ÿ†• ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ (์ €์žฅ ํ›„ ์ œ์–ด ์‹คํ–‰ ์‹œ ํ”Œ๋กœ์šฐ์— ์ „๋‹ฌ) + savedData?: any; } /** @@ -1036,10 +1039,11 @@ export class ButtonActionExecutor { } // ๐Ÿ†• ๊ณตํ†ต ํ•„๋“œ ๋ณ‘ํ•ฉ + ์‚ฌ์šฉ์ž ์ •๋ณด ์ถ”๊ฐ€ - // ๊ณตํ†ต ํ•„๋“œ๋ฅผ ๋จผ์ € ๋„ฃ๊ณ , ๊ฐœ๋ณ„ ํ•ญ๋ชฉ ๋ฐ์ดํ„ฐ๋กœ ๋ฎ์–ด์”€ (๊ฐœ๋ณ„ ํ•ญ๋ชฉ์ด ์šฐ์„ ) + // ๊ฐœ๋ณ„ ํ•ญ๋ชฉ ๋ฐ์ดํ„ฐ๋ฅผ ๋จผ์ € ๋„ฃ๊ณ , ๊ณตํ†ต ํ•„๋“œ๋กœ ๋ฎ์–ด์”€ (๊ณตํ†ต ํ•„๋“œ๊ฐ€ ์šฐ์„ ) + // ์ด์œ : ์‚ฌ์šฉ์ž๊ฐ€ ๊ณตํ†ต ํ•„๋“œ(์ถœ๊ณ ์ƒํƒœ ๋“ฑ)๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด ๋ชจ๋“  ํ•ญ๋ชฉ์— ์ ์šฉ๋˜์–ด์•ผ ํ•จ const dataWithMeta: Record = { - ...commonFields, // ๋ฒ”์šฉ ํผ ๋ชจ๋‹ฌ์˜ ๊ณตํ†ต ํ•„๋“œ (order_no, manager_id ๋“ฑ) ...dataToSave, // RepeaterFieldGroup์˜ ๊ฐœ๋ณ„ ํ•ญ๋ชฉ ๋ฐ์ดํ„ฐ + ...commonFields, // ๋ฒ”์šฉ ํผ ๋ชจ๋‹ฌ์˜ ๊ณตํ†ต ํ•„๋“œ (outbound_status ๋“ฑ) - ๊ณตํ†ต ํ•„๋“œ๊ฐ€ ์šฐ์„ ! created_by: context.userId, updated_by: context.userId, company_code: context.companyCode, @@ -1251,7 +1255,49 @@ export class ButtonActionExecutor { // ๐Ÿ”ฅ ์ €์žฅ ์„ฑ๊ณต ํ›„ ์—ฐ๊ฒฐ๋œ ์ œ์–ด ์‹คํ–‰ (dataflowTiming์ด 'after'์ธ ๊ฒฝ์šฐ) if (config.enableDataflowControl && config.dataflowConfig) { console.log("๐ŸŽฏ ์ €์žฅ ํ›„ ์ œ์–ด ์‹คํ–‰ ์‹œ์ž‘:", config.dataflowConfig); - await this.executeAfterSaveControl(config, context); + + // ํ…Œ์ด๋ธ” ์„น์…˜ ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ (comp_๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ•„๋“œ์— JSON ๋ฐฐ์—ด์ด ์žˆ๋Š” ๊ฒฝ์šฐ) + // ์ž…๊ณ  ํ™”๋ฉด ๋“ฑ์—์„œ ํ’ˆ๋ชฉ ๋ชฉ๋ก์ด comp_xxx ํ•„๋“œ์— JSON ๋ฌธ์ž์—ด๋กœ ์ €์žฅ๋จ + const formData: Record = (saveResult.data || context.formData || {}) as Record; + let parsedSectionData: any[] = []; + + // comp_๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ•„๋“œ์—์„œ ํ…Œ์ด๋ธ” ์„น์…˜ ๋ฐ์ดํ„ฐ ์ฐพ๊ธฐ + const compFieldKey = Object.keys(formData).find(key => + key.startsWith("comp_") && typeof formData[key] === "string" + ); + + if (compFieldKey) { + try { + const sectionData = JSON.parse(formData[compFieldKey]); + if (Array.isArray(sectionData) && sectionData.length > 0) { + // ๊ณตํ†ต ํ•„๋“œ์™€ ์„น์…˜ ๋ฐ์ดํ„ฐ ๋ณ‘ํ•ฉ + parsedSectionData = sectionData.map((item: any) => { + // ์„น์…˜ ๋ฐ์ดํ„ฐ์—์„œ ๋ถˆํ•„์š”ํ•œ ๋‚ด๋ถ€ ํ•„๋“œ ์ œ๊ฑฐ + const { _isNewItem, _targetTable, _existingRecord, ...cleanItem } = item; + // ๊ณตํ†ต ํ•„๋“œ(comp_ ํ•„๋“œ ์ œ์™ธ) + ์„น์…˜ ์•„์ดํ…œ ๋ณ‘ํ•ฉ + const commonFields: Record = {}; + Object.keys(formData).forEach(key => { + if (!key.startsWith("comp_") && !key.endsWith("_numberingRuleId")) { + commonFields[key] = formData[key]; + } + }); + return { ...commonFields, ...cleanItem }; + }); + console.log(`๐Ÿ“ฆ [handleSave] ํ…Œ์ด๋ธ” ์„น์…˜ ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ ์™„๋ฃŒ: ${parsedSectionData.length}๊ฑด`, parsedSectionData[0]); + } + } catch (parseError) { + console.warn("โš ๏ธ [handleSave] ํ…Œ์ด๋ธ” ์„น์…˜ ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ ์‹คํŒจ:", parseError); + } + } + + // ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ context์— ์ถ”๊ฐ€ํ•˜์—ฌ ํ”Œ๋กœ์šฐ์— ์ „๋‹ฌ + const contextWithSavedData = { + ...context, + savedData: formData, + // ํŒŒ์‹ฑ๋œ ์„น์…˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด selectedRowsData๋กœ ์ „๋‹ฌ + selectedRowsData: parsedSectionData.length > 0 ? parsedSectionData : context.selectedRowsData, + }; + await this.executeAfterSaveControl(config, contextWithSavedData); } } else { throw new Error("์ €์žฅ์— ํ•„์š”ํ•œ ์ •๋ณด๊ฐ€ ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค. (ํ…Œ์ด๋ธ”๋ช… ๋˜๋Š” ํ™”๋ฉดID ๋ˆ„๋ฝ)"); @@ -3643,8 +3689,20 @@ export class ButtonActionExecutor { // ๋…ธ๋“œ ํ”Œ๋กœ์šฐ ์‹คํ–‰ API const { executeNodeFlow } = await import("@/lib/api/nodeFlows"); - // ๋ฐ์ดํ„ฐ ์†Œ์Šค ์ค€๋น„ - const sourceData: any = context.formData || {}; + // ๋ฐ์ดํ„ฐ ์†Œ์Šค ์ค€๋น„: context-data ๋ชจ๋“œ๋Š” ๋ฐฐ์—ด์„ ๊ธฐ๋Œ€ํ•จ + // ์šฐ์„ ์ˆœ์œ„: selectedRowsData > savedData > formData + // - selectedRowsData: ํ…Œ์ด๋ธ” ์„น์…˜์—์„œ ์ €์žฅ๋œ ํ•˜์œ„ ํ•ญ๋ชฉ๋“ค (item_code, inbound_qty ๋“ฑ ํฌํ•จ) + // - savedData: ์ €์žฅ API ์‘๋‹ต ๋ฐ์ดํ„ฐ + // - formData: ํผ์— ์ž…๋ ฅ๋œ ๋ฐ์ดํ„ฐ + let sourceData: any[]; + if (context.selectedRowsData && context.selectedRowsData.length > 0) { + sourceData = context.selectedRowsData; + console.log("๐Ÿ“ฆ [๋‹ค์ค‘์ œ์–ด] selectedRowsData ์‚ฌ์šฉ:", sourceData.length, "๊ฑด"); + } else { + const savedData = context.savedData || context.formData || {}; + sourceData = Array.isArray(savedData) ? savedData : [savedData]; + console.log("๐Ÿ“ฆ [๋‹ค์ค‘์ œ์–ด] savedData/formData ์‚ฌ์šฉ:", sourceData.length, "๊ฑด"); + } let allSuccess = true; const results: Array<{ flowId: number; flowName: string; success: boolean; message?: string }> = []; @@ -3751,8 +3809,20 @@ export class ButtonActionExecutor { // ๋…ธ๋“œ ํ”Œ๋กœ์šฐ ์‹คํ–‰ API ํ˜ธ์ถœ const { executeNodeFlow } = await import("@/lib/api/nodeFlows"); - // ๋ฐ์ดํ„ฐ ์†Œ์Šค ์ค€๋น„ - const sourceData: any = context.formData || {}; + // ๋ฐ์ดํ„ฐ ์†Œ์Šค ์ค€๋น„: context-data ๋ชจ๋“œ๋Š” ๋ฐฐ์—ด์„ ๊ธฐ๋Œ€ํ•จ + // ์šฐ์„ ์ˆœ์œ„: selectedRowsData > savedData > formData + // - selectedRowsData: ํ…Œ์ด๋ธ” ์„น์…˜์—์„œ ์ €์žฅ๋œ ํ•˜์œ„ ํ•ญ๋ชฉ๋“ค (item_code, inbound_qty ๋“ฑ ํฌํ•จ) + // - savedData: ์ €์žฅ API ์‘๋‹ต ๋ฐ์ดํ„ฐ + // - formData: ํผ์— ์ž…๋ ฅ๋œ ๋ฐ์ดํ„ฐ + let sourceData: any[]; + if (context.selectedRowsData && context.selectedRowsData.length > 0) { + sourceData = context.selectedRowsData; + console.log("๐Ÿ“ฆ [๋‹จ์ผ์ œ์–ด] selectedRowsData ์‚ฌ์šฉ:", sourceData.length, "๊ฑด"); + } else { + const savedData = context.savedData || context.formData || {}; + sourceData = Array.isArray(savedData) ? savedData : [savedData]; + console.log("๐Ÿ“ฆ [๋‹จ์ผ์ œ์–ด] savedData/formData ์‚ฌ์šฉ:", sourceData.length, "๊ฑด"); + } // repeat-screen-modal ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ๋ณ‘ํ•ฉ const repeatScreenModalKeys = Object.keys(context.formData || {}).filter((key) => @@ -3765,7 +3835,8 @@ export class ButtonActionExecutor { console.log("๐Ÿ“ฆ ๋…ธ๋“œ ํ”Œ๋กœ์šฐ์— ์ „๋‹ฌํ•  ๋ฐ์ดํ„ฐ:", { flowId, dataSourceType: controlDataSource, - sourceData, + sourceDataCount: sourceData.length, + sourceDataSample: sourceData[0], }); const result = await executeNodeFlow(flowId, {