diff --git a/frontend/components/dataflow/ConnectionSetupModal.tsx b/frontend/components/dataflow/ConnectionSetupModal.tsx index 7baac19c..b8d37f8a 100644 --- a/frontend/components/dataflow/ConnectionSetupModal.tsx +++ b/frontend/components/dataflow/ConnectionSetupModal.tsx @@ -343,6 +343,37 @@ export const ConnectionSetupModal: React.FC = ({ break; case "data-save": settings = dataSaveSettings; + + // INSERT가 아닌 액션 타입에 대한 실행조건 필수 검증 + for (const action of dataSaveSettings.actions) { + if (action.actionType !== "insert") { + if (!action.conditions || action.conditions.length === 0) { + toast.error( + `${action.actionType.toUpperCase()} 액션은 실행조건이 필수입니다. '${action.name}' 액션에 실행조건을 추가해주세요.`, + ); + return; + } + + // 실제 조건이 있는지 확인 (group-start, group-end만 있는 경우 제외) + const hasValidConditions = action.conditions.some((condition) => { + if (condition.type !== "condition") return false; + if (!condition.field || !condition.operator) return false; + + // value가 null, undefined, 빈 문자열이면 유효하지 않음 + const value = condition.value; + if (value === null || value === undefined || value === "") return false; + + return true; + }); + + if (!hasValidConditions) { + toast.error( + `${action.actionType.toUpperCase()} 액션은 완전한 실행조건이 필요합니다. '${action.name}' 액션에 필드, 연산자, 값을 모두 설정해주세요.`, + ); + return; + } + } + } break; case "external-call": // 외부 호출은 plan에 저장 @@ -508,9 +539,21 @@ export const ConnectionSetupModal: React.FC = ({ case "data-save": // 데이터 저장: 액션과 필드 매핑이 완성되어야 함 const hasActions = dataSaveSettings.actions.length > 0; - const allActionsHaveMappings = dataSaveSettings.actions.every((action) => action.fieldMappings.length > 0); - const allMappingsComplete = dataSaveSettings.actions.every((action) => - action.fieldMappings.every((mapping) => { + + // DELETE 액션은 필드 매핑이 필요 없음 + const allActionsHaveMappings = dataSaveSettings.actions.every((action) => { + if (action.actionType === "delete") { + return true; // DELETE는 필드 매핑 불필요 + } + return action.fieldMappings.length > 0; + }); + + const allMappingsComplete = dataSaveSettings.actions.every((action) => { + if (action.actionType === "delete") { + return true; // DELETE는 필드 매핑 검증 생략 + } + + return action.fieldMappings.every((mapping) => { // 타겟은 항상 필요 if (!mapping.targetTable || !mapping.targetField) return false; @@ -525,9 +568,36 @@ export const ConnectionSetupModal: React.FC = ({ // FROM 테이블이 있으면 소스 매핑 완성 또는 기본값 필요 return hasSource || hasDefault; - }), - ); - return !hasActions || !allActionsHaveMappings || !allMappingsComplete; + }); + }); + + // INSERT가 아닌 액션 타입에 대한 실행조건 필수 검증 + const allRequiredConditionsMet = dataSaveSettings.actions.every((action) => { + if (action.actionType === "insert") { + return true; // INSERT는 조건 불필요 + } + + // INSERT가 아닌 액션은 유효한 조건이 있어야 함 + if (!action.conditions || action.conditions.length === 0) { + return false; + } + + // 실제 조건이 있는지 확인 (group-start, group-end만 있는 경우 제외) + const hasValidConditions = action.conditions.some((condition) => { + if (condition.type !== "condition") return false; + if (!condition.field || !condition.operator) return false; + + // value가 null, undefined, 빈 문자열이면 유효하지 않음 + const value = condition.value; + if (value === null || value === undefined || value === "") return false; + + return true; + }); + + return hasValidConditions; + }); + + return !hasActions || !allActionsHaveMappings || !allMappingsComplete || !allRequiredConditionsMet; case "external-call": // 외부 호출: 설정 ID와 메시지가 있어야 함 diff --git a/frontend/components/dataflow/connection/ActionConditionsSection.tsx b/frontend/components/dataflow/connection/ActionConditionsSection.tsx index 9e40cade..ff498dc0 100644 --- a/frontend/components/dataflow/connection/ActionConditionsSection.tsx +++ b/frontend/components/dataflow/connection/ActionConditionsSection.tsx @@ -32,6 +32,22 @@ export const ActionConditionsSection: React.FC = ( }) => { const { addActionGroupStart, addActionGroupEnd, getActionCurrentGroupLevel } = useActionConditionHelpers(); + // INSERT가 아닌 액션 타입인지 확인 + const isConditionRequired = action.actionType !== "insert"; + + // 유효한 조건이 있는지 확인 (group-start, group-end만 있는 경우 제외) + const hasValidConditions = + action.conditions?.some((condition) => { + if (condition.type !== "condition") return false; + if (!condition.field || !condition.operator) return false; + + // value가 null, undefined, 빈 문자열이면 유효하지 않음 + const value = condition.value; + if (value === null || value === undefined || value === "") return false; + + return true; + }) || false; + const addActionCondition = () => { const newActions = [...settings.actions]; if (!newActions[actionIndex].conditions) { @@ -65,14 +81,28 @@ export const ActionConditionsSection: React.FC = ( return (
- +
- 🔍 이 액션의 실행 조건 (선택사항) + 🔍 이 액션의 실행 조건 + {isConditionRequired ? ( + 필수 + ) : ( + (선택사항) + )} {action.conditions && action.conditions.length > 0 && ( {action.conditions.length}개 )} + {isConditionRequired && !hasValidConditions && ( + ⚠️ 조건 필요 + )}
{action.conditions && action.conditions.length > 0 && (
+ + {/* 조건이 없을 때 안내 메시지 */} + {(!action.conditions || action.conditions.length === 0) && ( +
+ {isConditionRequired ? ( +
+ ⚠️ +
+
실행조건이 필요합니다
+
+ {action.actionType.toUpperCase()} 액션은 언제 실행될지 결정하는 조건이 반드시 필요합니다. +
+ 필드, 연산자, 값을 모두 입력해야 합니다. +
+ 예: user_name = '관리자우저' AND status = 'active' +
+
+
+ ) : ( +
조건이 설정되지 않았습니다. INSERT 액션은 조건 없이도 실행 가능합니다.
+ )} +
+ )} + {action.conditions && action.conditions.length > 0 && (
{action.conditions.map((condition, condIndex) => ( diff --git a/frontend/components/dataflow/connection/ActionFieldMappings.tsx b/frontend/components/dataflow/connection/ActionFieldMappings.tsx index 1a4f19da..d75d804b 100644 --- a/frontend/components/dataflow/connection/ActionFieldMappings.tsx +++ b/frontend/components/dataflow/connection/ActionFieldMappings.tsx @@ -54,7 +54,10 @@ export const ActionFieldMappings: React.FC = ({ return (
- +
+ + (필수) +
))} + + {/* 필드 매핑이 없을 때 안내 메시지 */} + {action.fieldMappings.length === 0 && ( +
+
+ ⚠️ +
+
필드 매핑이 필요합니다
+
+ {action.actionType.toUpperCase()} 액션은 어떤 데이터를 어떻게 처리할지 결정하는 필드 매핑이 + 필요합니다. +
+
+
+
+ )}
); diff --git a/frontend/components/dataflow/connection/DataSaveSettings.tsx b/frontend/components/dataflow/connection/DataSaveSettings.tsx index 15047f6a..b9483e8a 100644 --- a/frontend/components/dataflow/connection/DataSaveSettings.tsx +++ b/frontend/components/dataflow/connection/DataSaveSettings.tsx @@ -135,25 +135,52 @@ export const DataSaveSettings: React.FC = ({ toTableName={toTableName} /> - {/* 데이터 분할 설정 */} - + {/* 데이터 분할 설정 - DELETE 액션은 제외 */} + {action.actionType !== "delete" && ( + + )} - {/* 필드 매핑 */} - + {/* 필드 매핑 - DELETE 액션은 제외 */} + {action.actionType !== "delete" && ( + + )} + + {/* DELETE 액션일 때 안내 메시지 */} + {action.actionType === "delete" && ( +
+
+
+ ℹ️ +
+
DELETE 액션 정보
+
+ DELETE 액션은 실행조건만 필요합니다. +
+ • 데이터 분할 설정: 불필요 (삭제 작업에는 분할이 의미 없음) +
+ • 필드 매핑: 불필요 (조건에 맞는 데이터를 통째로 삭제) +
+ 위에서 설정한 실행조건에 맞는 모든 데이터가 삭제됩니다. +
+
+
+
+
+ )} ))}