diff --git a/frontend/components/screen/EditModal.tsx b/frontend/components/screen/EditModal.tsx index 294bca7f..5a123b3f 100644 --- a/frontend/components/screen/EditModal.tsx +++ b/frontend/components/screen/EditModal.tsx @@ -26,12 +26,56 @@ interface EditModalState { onSave?: () => void; groupByColumns?: string[]; // ๐Ÿ†• ๊ทธ๋ฃนํ•‘ ์ปฌ๋Ÿผ (์˜ˆ: ["order_no"]) tableName?: string; // ๐Ÿ†• ํ…Œ์ด๋ธ”๋ช… (๊ทธ๋ฃน ์กฐํšŒ์šฉ) + buttonConfig?: any; // ๐Ÿ†• ๋ฒ„ํŠผ ์„ค์ • (์ œ์–ด๋กœ์ง ์‹คํ–‰์šฉ) + buttonContext?: any; // ๐Ÿ†• ๋ฒ„ํŠผ ์ปจํ…์ŠคํŠธ (screenId, userId ๋“ฑ) + saveButtonConfig?: { + enableDataflowControl?: boolean; + dataflowConfig?: any; + dataflowTiming?: string; + }; // ๐Ÿ†• ๋ชจ๋‹ฌ ๋‚ด๋ถ€ ์ €์žฅ ๋ฒ„ํŠผ์˜ ์ œ์–ด๋กœ์ง ์„ค์ • } interface EditModalProps { className?: string; } +/** + * ๋ชจ๋‹ฌ ๋‚ด๋ถ€์—์„œ ์ €์žฅ ๋ฒ„ํŠผ ์ฐพ๊ธฐ (์žฌ๊ท€์ ์œผ๋กœ ํƒ์ƒ‰) + * action.type์ด "save"์ธ button-primary ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ฐพ์Œ + */ +const findSaveButtonInComponents = (components: any[]): any | null => { + if (!components || !Array.isArray(components)) return null; + + for (const comp of components) { + // button-primary์ด๊ณ  action.type์ด save์ธ ๊ฒฝ์šฐ + if ( + comp.componentType === "button-primary" && + comp.componentConfig?.action?.type === "save" + ) { + return comp; + } + + // conditional-container์˜ sections ๋‚ด๋ถ€ ํƒ์ƒ‰ + if (comp.componentType === "conditional-container" && comp.componentConfig?.sections) { + for (const section of comp.componentConfig.sections) { + if (section.screenId) { + // ์กฐ๊ฑด๋ถ€ ์ปจํ…Œ์ด๋„ˆ์˜ ๋‚ด๋ถ€ ํ™”๋ฉด์€ ๋ณ„๋„๋กœ ๋กœ๋“œํ•ด์•ผ ํ•จ + // ์—ฌ๊ธฐ์„œ๋Š” null ๋ฐ˜ํ™˜ํ•˜๊ณ , loadSaveButtonConfig์—์„œ ์ฒ˜๋ฆฌ + continue; + } + } + } + + // ์ž์‹ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ์œผ๋ฉด ์žฌ๊ท€ ํƒ์ƒ‰ + if (comp.children && Array.isArray(comp.children)) { + const found = findSaveButtonInComponents(comp.children); + if (found) return found; + } + } + + return null; +}; + export const EditModal: React.FC = ({ className }) => { const { user } = useAuth(); const [modalState, setModalState] = useState({ @@ -44,6 +88,9 @@ export const EditModal: React.FC = ({ className }) => { onSave: undefined, groupByColumns: undefined, tableName: undefined, + buttonConfig: undefined, + buttonContext: undefined, + saveButtonConfig: undefined, }); const [screenData, setScreenData] = useState<{ @@ -115,10 +162,88 @@ export const EditModal: React.FC = ({ className }) => { }; }; + // ๐Ÿ†• ๋ชจ๋‹ฌ ๋‚ด๋ถ€ ์ €์žฅ ๋ฒ„ํŠผ์˜ ์ œ์–ด๋กœ์ง ์„ค์ • ์กฐํšŒ + const loadSaveButtonConfig = async (targetScreenId: number): Promise<{ + enableDataflowControl?: boolean; + dataflowConfig?: any; + dataflowTiming?: string; + } | null> => { + try { + // 1. ๋Œ€์ƒ ํ™”๋ฉด์˜ ๋ ˆ์ด์•„์›ƒ ์กฐํšŒ + const layoutData = await screenApi.getLayout(targetScreenId); + + if (!layoutData?.components) { + console.log("[EditModal] ๋ ˆ์ด์•„์›ƒ ์ปดํฌ๋„ŒํŠธ ์—†์Œ:", targetScreenId); + return null; + } + + // 2. ์ €์žฅ ๋ฒ„ํŠผ ์ฐพ๊ธฐ + let saveButton = findSaveButtonInComponents(layoutData.components); + + // 3. conditional-container๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ๋‚ด๋ถ€ ํ™”๋ฉด๋„ ํƒ์ƒ‰ + if (!saveButton) { + for (const comp of layoutData.components) { + if (comp.componentType === "conditional-container" && comp.componentConfig?.sections) { + for (const section of comp.componentConfig.sections) { + if (section.screenId) { + try { + const innerLayoutData = await screenApi.getLayout(section.screenId); + saveButton = findSaveButtonInComponents(innerLayoutData?.components || []); + if (saveButton) { + console.log("[EditModal] ์กฐ๊ฑด๋ถ€ ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์—์„œ ์ €์žฅ ๋ฒ„ํŠผ ๋ฐœ๊ฒฌ:", { + sectionScreenId: section.screenId, + sectionLabel: section.label, + }); + break; + } + } catch (innerError) { + console.warn("[EditModal] ๋‚ด๋ถ€ ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ์กฐํšŒ ์‹คํŒจ:", section.screenId); + } + } + } + if (saveButton) break; + } + } + } + + if (!saveButton) { + console.log("[EditModal] ์ €์žฅ ๋ฒ„ํŠผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ:", targetScreenId); + return null; + } + + // 4. webTypeConfig์—์„œ ์ œ์–ด๋กœ์ง ์„ค์ • ์ถ”์ถœ + const webTypeConfig = saveButton.webTypeConfig; + if (webTypeConfig?.enableDataflowControl) { + const config = { + enableDataflowControl: webTypeConfig.enableDataflowControl, + dataflowConfig: webTypeConfig.dataflowConfig, + dataflowTiming: webTypeConfig.dataflowConfig?.flowConfig?.executionTiming || "after", + }; + console.log("[EditModal] ์ €์žฅ ๋ฒ„ํŠผ ์ œ์–ด๋กœ์ง ์„ค์ • ๋ฐœ๊ฒฌ:", config); + return config; + } + + console.log("[EditModal] ์ €์žฅ ๋ฒ„ํŠผ์— ์ œ์–ด๋กœ์ง ์„ค์ • ์—†์Œ"); + return null; + } catch (error) { + console.warn("[EditModal] ์ €์žฅ ๋ฒ„ํŠผ ์„ค์ • ์กฐํšŒ ์‹คํŒจ:", error); + return null; + } + }; + // ์ „์—ญ ๋ชจ๋‹ฌ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ useEffect(() => { - const handleOpenEditModal = (event: CustomEvent) => { - const { screenId, title, description, modalSize, editData, onSave, groupByColumns, tableName, isCreateMode } = event.detail; + const handleOpenEditModal = async (event: CustomEvent) => { + const { screenId, title, description, modalSize, editData, onSave, groupByColumns, tableName, isCreateMode, buttonConfig, buttonContext } = event.detail; + + // ๐Ÿ†• ๋ชจ๋‹ฌ ๋‚ด๋ถ€ ์ €์žฅ ๋ฒ„ํŠผ์˜ ์ œ์–ด๋กœ์ง ์„ค์ • ์กฐํšŒ + let saveButtonConfig: EditModalState["saveButtonConfig"] = undefined; + if (screenId) { + const config = await loadSaveButtonConfig(screenId); + if (config) { + saveButtonConfig = config; + } + } setModalState({ isOpen: true, @@ -130,6 +255,9 @@ export const EditModal: React.FC = ({ className }) => { onSave, groupByColumns, // ๐Ÿ†• ๊ทธ๋ฃนํ•‘ ์ปฌ๋Ÿผ tableName, // ๐Ÿ†• ํ…Œ์ด๋ธ”๋ช… + buttonConfig, // ๐Ÿ†• ๋ฒ„ํŠผ ์„ค์ • + buttonContext, // ๐Ÿ†• ๋ฒ„ํŠผ ์ปจํ…์ŠคํŠธ + saveButtonConfig, // ๐Ÿ†• ๋ชจ๋‹ฌ ๋‚ด๋ถ€ ์ €์žฅ ๋ฒ„ํŠผ์˜ ์ œ์–ด๋กœ์ง ์„ค์ • }); // ํŽธ์ง‘ ๋ฐ์ดํ„ฐ๋กœ ํผ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” @@ -581,6 +709,46 @@ export const EditModal: React.FC = ({ className }) => { } } + // ๐Ÿ†• ์ €์žฅ ํ›„ ์ œ์–ด๋กœ์ง ์‹คํ–‰ (๋ฒ„ํŠผ์˜ After ํƒ€์ด๋ฐ ์ œ์–ด) + // ์šฐ์„ ์ˆœ์œ„: ๋ชจ๋‹ฌ ๋‚ด๋ถ€ ์ €์žฅ ๋ฒ„ํŠผ ์„ค์ •(saveButtonConfig) > ์ˆ˜์ • ๋ฒ„ํŠผ์—์„œ ์ „๋‹ฌ๋ฐ›์€ ์„ค์ •(buttonConfig) + try { + const controlConfig = modalState.saveButtonConfig || modalState.buttonConfig; + + console.log("[EditModal] ๊ทธ๋ฃน ์ €์žฅ ์™„๋ฃŒ ํ›„ ์ œ์–ด๋กœ์ง ์‹คํ–‰ ์‹œ๋„", { + hasSaveButtonConfig: !!modalState.saveButtonConfig, + hasButtonConfig: !!modalState.buttonConfig, + controlConfig, + }); + + if (controlConfig?.enableDataflowControl && controlConfig?.dataflowTiming === "after") { + console.log("๐ŸŽฏ [EditModal] ์ €์žฅ ํ›„ ์ œ์–ด๋กœ์ง ๋ฐœ๊ฒฌ:", controlConfig.dataflowConfig); + + // buttonActions์˜ executeAfterSaveControl ๋™์  import + const { ButtonActionExecutor } = await import("@/lib/utils/buttonActions"); + + // ์ œ์–ด๋กœ์ง ์‹คํ–‰ + await ButtonActionExecutor.executeAfterSaveControl( + controlConfig, + { + formData: modalState.editData, + screenId: modalState.buttonContext?.screenId || modalState.screenId, + tableName: modalState.buttonContext?.tableName || screenData?.screenInfo?.tableName, + userId: user?.userId, + companyCode: user?.companyCode, + onRefresh: modalState.onSave, + } + ); + + console.log("โœ… [EditModal] ์ œ์–ด๋กœ์ง ์‹คํ–‰ ์™„๋ฃŒ"); + } else { + console.log("โ„น๏ธ [EditModal] ์ €์žฅ ํ›„ ์‹คํ–‰ํ•  ์ œ์–ด๋กœ์ง ์—†์Œ"); + } + } catch (controlError) { + console.error("โŒ [EditModal] ์ œ์–ด๋กœ์ง ์‹คํ–‰ ์˜ค๋ฅ˜:", controlError); + // ์ œ์–ด๋กœ์ง ์˜ค๋ฅ˜๋Š” ์ €์žฅ ์„ฑ๊ณต์„ ๋ฐฉํ•ดํ•˜์ง€ ์•Š์Œ (๊ฒฝ๊ณ ๋งŒ ํ‘œ์‹œ) + toast.warning("์ €์žฅ์€ ์™„๋ฃŒ๋˜์—ˆ์œผ๋‚˜ ์—ฐ๊ฒฐ๋œ ์ œ์–ด ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."); + } + handleClose(); } else { toast.info("๋ณ€๊ฒฝ๋œ ๋‚ด์šฉ์ด ์—†์Šต๋‹ˆ๋‹ค."); @@ -615,6 +783,37 @@ export const EditModal: React.FC = ({ className }) => { } } + // ๐Ÿ†• ์ €์žฅ ํ›„ ์ œ์–ด๋กœ์ง ์‹คํ–‰ (๋ฒ„ํŠผ์˜ After ํƒ€์ด๋ฐ ์ œ์–ด) + // ์šฐ์„ ์ˆœ์œ„: ๋ชจ๋‹ฌ ๋‚ด๋ถ€ ์ €์žฅ ๋ฒ„ํŠผ ์„ค์ •(saveButtonConfig) > ์ˆ˜์ • ๋ฒ„ํŠผ์—์„œ ์ „๋‹ฌ๋ฐ›์€ ์„ค์ •(buttonConfig) + try { + const controlConfig = modalState.saveButtonConfig || modalState.buttonConfig; + + console.log("[EditModal] INSERT ์™„๋ฃŒ ํ›„ ์ œ์–ด๋กœ์ง ์‹คํ–‰ ์‹œ๋„", { controlConfig }); + + if (controlConfig?.enableDataflowControl && controlConfig?.dataflowTiming === "after") { + console.log("๐ŸŽฏ [EditModal] ์ €์žฅ ํ›„ ์ œ์–ด๋กœ์ง ๋ฐœ๊ฒฌ:", controlConfig.dataflowConfig); + + const { ButtonActionExecutor } = await import("@/lib/utils/buttonActions"); + + await ButtonActionExecutor.executeAfterSaveControl( + controlConfig, + { + formData, + screenId: modalState.buttonContext?.screenId || modalState.screenId, + tableName: modalState.buttonContext?.tableName || screenData?.screenInfo?.tableName, + userId: user?.userId, + companyCode: user?.companyCode, + onRefresh: modalState.onSave, + } + ); + + console.log("โœ… [EditModal] ์ œ์–ด๋กœ์ง ์‹คํ–‰ ์™„๋ฃŒ"); + } + } catch (controlError) { + console.error("โŒ [EditModal] ์ œ์–ด๋กœ์ง ์‹คํ–‰ ์˜ค๋ฅ˜:", controlError); + toast.warning("์ €์žฅ์€ ์™„๋ฃŒ๋˜์—ˆ์œผ๋‚˜ ์—ฐ๊ฒฐ๋œ ์ œ์–ด ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."); + } + handleClose(); } else { throw new Error(response.message || "์ƒ์„ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."); @@ -657,6 +856,37 @@ export const EditModal: React.FC = ({ className }) => { } } + // ๐Ÿ†• ์ €์žฅ ํ›„ ์ œ์–ด๋กœ์ง ์‹คํ–‰ (๋ฒ„ํŠผ์˜ After ํƒ€์ด๋ฐ ์ œ์–ด) + // ์šฐ์„ ์ˆœ์œ„: ๋ชจ๋‹ฌ ๋‚ด๋ถ€ ์ €์žฅ ๋ฒ„ํŠผ ์„ค์ •(saveButtonConfig) > ์ˆ˜์ • ๋ฒ„ํŠผ์—์„œ ์ „๋‹ฌ๋ฐ›์€ ์„ค์ •(buttonConfig) + try { + const controlConfig = modalState.saveButtonConfig || modalState.buttonConfig; + + console.log("[EditModal] UPDATE ์™„๋ฃŒ ํ›„ ์ œ์–ด๋กœ์ง ์‹คํ–‰ ์‹œ๋„", { controlConfig }); + + if (controlConfig?.enableDataflowControl && controlConfig?.dataflowTiming === "after") { + console.log("๐ŸŽฏ [EditModal] ์ €์žฅ ํ›„ ์ œ์–ด๋กœ์ง ๋ฐœ๊ฒฌ:", controlConfig.dataflowConfig); + + const { ButtonActionExecutor } = await import("@/lib/utils/buttonActions"); + + await ButtonActionExecutor.executeAfterSaveControl( + controlConfig, + { + formData, + screenId: modalState.buttonContext?.screenId || modalState.screenId, + tableName: modalState.buttonContext?.tableName || screenData?.screenInfo?.tableName, + userId: user?.userId, + companyCode: user?.companyCode, + onRefresh: modalState.onSave, + } + ); + + console.log("โœ… [EditModal] ์ œ์–ด๋กœ์ง ์‹คํ–‰ ์™„๋ฃŒ"); + } + } catch (controlError) { + console.error("โŒ [EditModal] ์ œ์–ด๋กœ์ง ์‹คํ–‰ ์˜ค๋ฅ˜:", controlError); + toast.warning("์ €์žฅ์€ ์™„๋ฃŒ๋˜์—ˆ์œผ๋‚˜ ์—ฐ๊ฒฐ๋œ ์ œ์–ด ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."); + } + handleClose(); } else { throw new Error(response.message || "์ˆ˜์ •์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."); diff --git a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx index 1942d268..4b88c565 100644 --- a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx +++ b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx @@ -376,6 +376,7 @@ export const ButtonPrimaryComponent: React.FC = ({ // ๐Ÿ”ฅ ์ œ์–ด๊ด€๋ฆฌ ์„ค์ • ์ถ”๊ฐ€ (webTypeConfig์—์„œ ๊ฐ€์ ธ์˜ด) enableDataflowControl: component.webTypeConfig?.enableDataflowControl, dataflowConfig: component.webTypeConfig?.dataflowConfig, + dataflowTiming: component.webTypeConfig?.dataflowTiming, }; } else if (componentConfig.action && typeof componentConfig.action === "object") { // ๐Ÿ”ฅ ์ด๋ฏธ ๊ฐ์ฒด์ธ ๊ฒฝ์šฐ์—๋„ ์ œ์–ด๊ด€๋ฆฌ ์„ค์ • ์ถ”๊ฐ€ @@ -383,8 +384,19 @@ export const ButtonPrimaryComponent: React.FC = ({ ...componentConfig.action, enableDataflowControl: component.webTypeConfig?.enableDataflowControl, dataflowConfig: component.webTypeConfig?.dataflowConfig, + dataflowTiming: component.webTypeConfig?.dataflowTiming, }; } + + // ๐Ÿ” ๋””๋ฒ„๊น…: processedConfig.action ํ™•์ธ + console.log("[ButtonPrimaryComponent] processedConfig.action ์ƒ์„ฑ ์™„๋ฃŒ", { + actionType: processedConfig.action?.type, + enableDataflowControl: processedConfig.action?.enableDataflowControl, + dataflowTiming: processedConfig.action?.dataflowTiming, + dataflowConfig: processedConfig.action?.dataflowConfig, + webTypeConfigRaw: component.webTypeConfig, + componentText: component.text, + }); // ์Šคํƒ€์ผ ๊ณ„์‚ฐ // height: 100%๋กœ ๋ถ€๋ชจ(RealtimePreviewDynamic์˜ ๋‚ด๋ถ€ div)์˜ ๋†’์ด๋ฅผ ๋”ฐ๋ผ๊ฐ diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 1ced2836..ede92868 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -2086,6 +2086,8 @@ export class ButtonActionExecutor { editData: rowData, groupByColumns: groupByColumns.length > 0 ? groupByColumns : undefined, // ๐Ÿ†• ๊ทธ๋ฃนํ•‘ ์ปฌ๋Ÿผ ์ „๋‹ฌ tableName: context.tableName, // ๐Ÿ†• ํ…Œ์ด๋ธ”๋ช… ์ „๋‹ฌ + buttonConfig: config, // ๐Ÿ†• ๋ฒ„ํŠผ ์„ค์ • ์ „๋‹ฌ (์ œ์–ด๋กœ์ง ์‹คํ–‰์šฉ) + buttonContext: context, // ๐Ÿ†• ๋ฒ„ํŠผ ์ปจํ…์ŠคํŠธ ์ „๋‹ฌ (screenId, userId ๋“ฑ) onSave: () => { context.onRefresh?.(); }, @@ -2621,8 +2623,9 @@ export class ButtonActionExecutor { /** * ์ €์žฅ ํ›„ ์ œ์–ด ์‹คํ–‰ (After Timing) + * EditModal ๋“ฑ ์™ธ๋ถ€์—์„œ๋„ ํ˜ธ์ถœ ๊ฐ€๋Šฅํ•˜๋„๋ก public์œผ๋กœ ๋ณ€๊ฒฝ */ - private static async executeAfterSaveControl( + public static async executeAfterSaveControl( config: ButtonActionConfig, context: ButtonActionContext, ): Promise {