feat(edit-modal): 저장 완료 후 제어로직(노드 플로우) 자동 실행 기능 추가
- EditModal에서 INSERT/UPDATE/그룹 저장 완료 후 제어로직 자동 실행 - loadSaveButtonConfig(): 모달 내부 저장 버튼의 제어로직 설정 조회 - findSaveButtonInComponents(): 재귀적으로 저장 버튼 탐색 (conditional-container 내부 포함) - buttonActions.ts: openEditModal 이벤트에 buttonConfig, buttonContext 전달 - executeAfterSaveControl()을 public으로 변경하여 외부 호출 가능 - 제어로직 실행 오류 시 저장 성공 유지, 경고 토스트만 표시
This commit is contained in:
@@ -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<EditModalProps> = ({ className }) => {
|
||||
const { user } = useAuth();
|
||||
const [modalState, setModalState] = useState<EditModalState>({
|
||||
@@ -44,6 +88,9 @@ export const EditModal: React.FC<EditModalProps> = ({ 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<EditModalProps> = ({ 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<EditModalProps> = ({ className }) => {
|
||||
onSave,
|
||||
groupByColumns, // 🆕 그룹핑 컬럼
|
||||
tableName, // 🆕 테이블명
|
||||
buttonConfig, // 🆕 버튼 설정
|
||||
buttonContext, // 🆕 버튼 컨텍스트
|
||||
saveButtonConfig, // 🆕 모달 내부 저장 버튼의 제어로직 설정
|
||||
});
|
||||
|
||||
// 편집 데이터로 폼 데이터 초기화
|
||||
@@ -581,6 +709,46 @@ export const EditModal: React.FC<EditModalProps> = ({ 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<EditModalProps> = ({ 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<EditModalProps> = ({ 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 || "수정에 실패했습니다.");
|
||||
|
||||
@@ -376,6 +376,7 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||
// 🔥 제어관리 설정 추가 (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<ButtonPrimaryComponentProps> = ({
|
||||
...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)의 높이를 따라감
|
||||
|
||||
@@ -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<void> {
|
||||
|
||||
Reference in New Issue
Block a user