diff --git a/client/components/forms/LogicConfirmationModal.vue b/client/components/forms/LogicConfirmationModal.vue new file mode 100644 index 00000000..8174f2cd --- /dev/null +++ b/client/components/forms/LogicConfirmationModal.vue @@ -0,0 +1,103 @@ + + + \ No newline at end of file diff --git a/client/components/open/forms/components/FormEditor.vue b/client/components/open/forms/components/FormEditor.vue index 7ad0e10b..4bb77f64 100644 --- a/client/components/open/forms/components/FormEditor.vue +++ b/client/components/open/forms/components/FormEditor.vue @@ -84,6 +84,14 @@ :validation-error-response="validationErrorResponse" @close="showFormErrorModal = false" /> + + +
0) { + this.showLogicConfirmationModal = true + return + } + + this.proceedWithSave() + }, + proceedWithSave() { + if (this.logicErrors.length > 0) { + // Clean invalid logic before saving using the comprehensive validator + const { validatePropertiesLogic } = useFormLogic() + this.form.properties = validatePropertiesLogic(this.form.properties) + } + if (this.isGuest) { this.saveFormGuest() } else if (this.isEdit) { @@ -247,6 +276,13 @@ export default { this.saveFormCreate() } }, + handleLogicConfirmationCancel() { + this.showLogicConfirmationModal = false + }, + handleLogicConfirmationConfirm() { + this.showLogicConfirmationModal = false + this.proceedWithSave() + }, saveFormEdit() { if (this.updateFormLoading) return diff --git a/client/composables/forms/useFormLogic.js b/client/composables/forms/useFormLogic.js new file mode 100644 index 00000000..cdfa32af --- /dev/null +++ b/client/composables/forms/useFormLogic.js @@ -0,0 +1,141 @@ +import FormPropertyLogicRule from '~/lib/forms/FormPropertyLogicRule.js' + +export const useFormLogic = () => { + const validateCondition = (condition, properties, fieldId, errors, parentIndex = null) => { + if (condition.operatorIdentifier) { + // This is a group condition (and/or) + if (!Array.isArray(condition.children)) { + errors.push({ + fieldId, + error: 'INVALID_CONDITION_GROUP', + message: 'Condition group must have children array' + }) + return + } + + condition.children.forEach((childCondition, index) => { + validateCondition(childCondition, properties, fieldId, errors, parentIndex || index) + }) + } else { + // This is a leaf condition + const referencedField = properties.find(p => p.id === condition.value?.property_meta?.id) + if (!referencedField) { + errors.push({ + fieldId, + ruleIndex: parentIndex, + error: 'INVALID_FIELD_REFERENCE', + referencedFieldId: condition.value?.property_meta?.id, + message: `Referenced field ${condition.value?.property_meta?.id} no longer exists` + }) + } + } + } + + const getLogicErrors = (properties) => { + const errors = [] + + properties.forEach((field) => { + // Skip if field has no logic configured at all + if (!field.logic) return + + // Skip if field has default empty logic (null conditions and empty actions) + if (field.logic.conditions === null && (!field.logic.actions || field.logic.actions.length === 0)) return + + // Now validate if logic is configured but missing required parts + if (!field.logic.conditions) { + errors.push({ + fieldId: field.id, + fieldName: field.name, + error: 'MISSING_CONDITIONS', + message: 'No conditions specified' + }) + } + + if (!field.logic.actions || field.logic.actions.length === 0) { + errors.push({ + fieldId: field.id, + fieldName: field.name, + error: 'MISSING_ACTIONS', + message: 'No actions specified' + }) + } + + // Validate conditions structure and field references + if (field.logic.conditions) { + validateCondition(field.logic.conditions, properties, field.id, errors) + } + + // Apply comprehensive validation from FormPropertyLogicRule + const logicRule = new FormPropertyLogicRule(field) + if (!logicRule.isValid()) { + errors.push({ + fieldId: field.id, + fieldName: field.name, + error: 'INVALID_LOGIC_RULE', + message: logicRule.isConditionCorrect ? + 'Invalid action configuration' : + 'Invalid condition configuration' + }) + } + }) + + return errors + } + + const cleanInvalidLogic = (properties) => { + // Create a deep copy to avoid mutating the original + const cleanedProperties = JSON.parse(JSON.stringify(properties)) + + // Get all validation errors + const errors = getLogicErrors(cleanedProperties) + + // Group errors by fieldId for efficient processing + const errorsByField = errors.reduce((acc, error) => { + if (!acc[error.fieldId]) { + acc[error.fieldId] = [] + } + acc[error.fieldId].push(error) + return acc + }, {}) + + // Clean invalid logic for each field + cleanedProperties.forEach((field) => { + const fieldErrors = errorsByField[field.id] + + // If field has any errors, reset its logic + if (fieldErrors?.length > 0) { + field.logic = { + conditions: null, + actions: [], + } + } + }) + + return cleanedProperties + } + + // Direct implementation of the previous validatePropertiesLogic function + const validatePropertiesLogic = (properties) => { + // Create a deep copy to avoid mutating the original + const validatedProperties = JSON.parse(JSON.stringify(properties)) + + validatedProperties.forEach((field) => { + const isValid = new FormPropertyLogicRule(field).isValid() + if (!isValid) { + field.logic = { + conditions: null, + actions: [], + } + } + }) + + return validatedProperties + } + + return { + validateCondition, + getLogicErrors, + cleanInvalidLogic, + validatePropertiesLogic + } +} \ No newline at end of file diff --git a/client/composables/forms/validatePropertiesLogic.js b/client/composables/forms/validatePropertiesLogic.js deleted file mode 100644 index d9b6d625..00000000 --- a/client/composables/forms/validatePropertiesLogic.js +++ /dev/null @@ -1,14 +0,0 @@ -import FormPropertyLogicRule from "~/lib/forms/FormPropertyLogicRule.js" - -export const validatePropertiesLogic = (properties) => { - properties.forEach((field) => { - const isValid = new FormPropertyLogicRule(field).isValid() - if (!isValid) { - field.logic = { - conditions: null, - actions: [], - } - } - }) - return properties -}