From 204724e34cc48a2415b7fd3f855c1b8041d3cb12 Mon Sep 17 00:00:00 2001 From: Chirag Chhatrala <60499540+chiragchhatrala@users.noreply.github.com> Date: Mon, 28 Apr 2025 22:11:39 +0530 Subject: [PATCH] Add LogicConfirmationModal Component and Integrate Logic Validation (#742) * Add LogicConfirmationModal Component and Integrate Logic Validation - Introduced LogicConfirmationModal component to display validation errors related to form logic. - Integrated logic validation and cleaning functionalities into FormEditor, prompting users when invalid logic is detected. - Added useLogicValidation and useLogicCleaner composables for error checking and logic correction. - Removed deprecated validatePropertiesLogic function to streamline logic handling. These changes enhance user experience by providing clear feedback on form logic issues and ensuring that invalid logic is addressed before form submission. * Refactor Logic Handling in Form Components - Updated LogicConfirmationModal to improve user feedback by changing the title and icon colors, enhancing clarity on incomplete form logic. - Replaced useLogicValidation and useLogicCleaner with a new useFormLogic composable in FormEditor for streamlined logic validation and cleaning processes. - Removed deprecated useLogicCleaner and useLogicValidation files to simplify the codebase and improve maintainability. These changes enhance the user experience by providing clearer messages regarding form logic issues and ensuring that invalid logic is effectively managed before form submission. --------- Co-authored-by: JhumanJ --- .../forms/LogicConfirmationModal.vue | 103 +++++++++++++ .../open/forms/components/FormEditor.vue | 42 +++++- client/composables/forms/useFormLogic.js | 141 ++++++++++++++++++ .../forms/validatePropertiesLogic.js | 14 -- 4 files changed, 283 insertions(+), 17 deletions(-) create mode 100644 client/components/forms/LogicConfirmationModal.vue create mode 100644 client/composables/forms/useFormLogic.js delete mode 100644 client/composables/forms/validatePropertiesLogic.js 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 -}