From 229f56d6e3422979ef52bcd2d9093df8aeb8303c Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Tue, 17 Feb 2026 16:40:12 +0530 Subject: [PATCH] chore: Remove `vue-multiselect` and migrate to next components (#13506) # Pull Request Template ## Description This PR includes: 1. Removes multiselect usage from the Merge Contact modal (Conversation sidebar) and replaces it with the existing component used on the Contact Details page. 2. Replaces legacy form and multiselect elements in Add and Edit automations flows with next components.**(Also check Macros)** 3. Replace multiselect with ComboBox in contact form country field. 4. Replace multiselect with TagInput in create/edit attribute form. 5. Replace multiselect with TagInput for agent selection in inbox creation. 6. Replace multiselect with ComboBox in Facebook channel page selection ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? **Screenshots** 1. **Merge modal** image image 2. **Automations** image image image 3. **Country field** image 4. **Add/Edit attribute form** image image image 5. **Agent selection in inbox creation** image image 7. **Facebook channel page selection** image image image ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Shivam Mishra --- .../ContactListHeaderWrapper.vue | 3 +- .../components/EmailOptions.vue | 2 + .../components-next/combobox/ComboBox.vue | 9 +- .../components-next/dialog/Dialog.vue | 22 +- .../dropdown-menu/base/DropdownSection.vue | 9 +- .../components-next/filter/ConditionRow.vue | 16 +- .../filter/inputs/FilterSelect.vue | 27 +- .../filter/inputs/MultiSelect.vue | 8 +- .../filter/inputs/SingleSelect.vue | 16 +- .../components-next/taginput/TagInput.vue | 59 ++- .../helper/spec/tagInputHelper.spec.js | 65 +++ .../taginput/helper/tagInputHelper.js | 14 +- .../widgets/AutomationActionInput.vue | 264 ++++------- .../AutomationActionTeamMessageInput.vue | 58 +-- .../widgets/AutomationFileInput.vue | 2 +- .../components/widgets/FilterInput/Index.vue | 305 ------------- .../dashboard/composables/useAutomation.js | 16 +- .../dashboard/helper/automationHelper.js | 8 +- .../modules/contact/ContactMergeModal.vue | 162 +++---- .../components/ContactDropdownItem.vue | 87 ---- .../contact/components/MergeContact.vue | 268 ++++-------- .../conversation/contact/ContactForm.vue | 53 ++- .../conversation/contact/ContactInfo.vue | 13 +- .../settings/attributes/AddAttribute.vue | 86 ++-- .../settings/attributes/EditAttribute.vue | 78 ++-- .../settings/automation/AddAutomationRule.vue | 381 +++------------- .../automation/AutomationRuleForm.vue | 414 ++++++++++++++++++ .../automation/EditAutomationRule.vue | 401 +++-------------- .../dashboard/settings/automation/Index.vue | 43 +- .../dashboard/settings/inbox/AddAgents.vue | 71 ++- .../settings/inbox/channels/Facebook.vue | 44 +- 31 files changed, 1209 insertions(+), 1795 deletions(-) delete mode 100644 app/javascript/dashboard/components/widgets/FilterInput/Index.vue delete mode 100644 app/javascript/dashboard/modules/contact/components/ContactDropdownItem.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/automation/AutomationRuleForm.vue diff --git a/app/javascript/dashboard/components-next/Contacts/ContactsHeader/ContactListHeaderWrapper.vue b/app/javascript/dashboard/components-next/Contacts/ContactsHeader/ContactListHeaderWrapper.vue index 0b4880264..3d4b5c8a5 100644 --- a/app/javascript/dashboard/components-next/Contacts/ContactsHeader/ContactListHeaderWrapper.vue +++ b/app/javascript/dashboard/components-next/Contacts/ContactsHeader/ContactListHeaderWrapper.vue @@ -68,8 +68,7 @@ const hasActiveSegments = computed( ); const activeSegmentName = computed(() => props.activeSegment?.name); -const openCreateNewContactDialog = async () => { - await createNewContactDialogRef.value?.contactsFormRef.resetValidation(); +const openCreateNewContactDialog = () => { createNewContactDialogRef.value?.dialogRef.open(); }; const openContactImportDialog = () => diff --git a/app/javascript/dashboard/components-next/NewConversation/components/EmailOptions.vue b/app/javascript/dashboard/components-next/NewConversation/components/EmailOptions.vue index ac7e8d659..4e0c71adb 100644 --- a/app/javascript/dashboard/components-next/NewConversation/components/EmailOptions.vue +++ b/app/javascript/dashboard/components-next/NewConversation/components/EmailOptions.vue @@ -95,6 +95,7 @@ const inputClass = computed(() => { :show-dropdown="showCcEmailsDropdown" :is-loading="isLoading" type="email" + allow-create class="flex-1 min-h-7" @focus="emit('updateDropdown', 'cc', true)" @input="emit('searchCcEmails', $event)" @@ -127,6 +128,7 @@ const inputClass = computed(() => { :show-dropdown="showBccEmailsDropdown" :is-loading="isLoading" type="email" + allow-create class="flex-1 min-h-7" focus-on-mount @focus="emit('updateDropdown', 'bcc', true)" diff --git a/app/javascript/dashboard/components-next/combobox/ComboBox.vue b/app/javascript/dashboard/components-next/combobox/ComboBox.vue index d50134e88..58d9e634d 100644 --- a/app/javascript/dashboard/components-next/combobox/ComboBox.vue +++ b/app/javascript/dashboard/components-next/combobox/ComboBox.vue @@ -56,8 +56,13 @@ const selectedLabel = computed(() => { }); const selectOption = option => { - selectedValue.value = option.value; - emit('update:modelValue', option.value); + if (selectedValue.value === option.value) { + selectedValue.value = ''; + emit('update:modelValue', ''); + } else { + selectedValue.value = option.value; + emit('update:modelValue', option.value); + } open.value = false; search.value = ''; }; diff --git a/app/javascript/dashboard/components-next/dialog/Dialog.vue b/app/javascript/dashboard/components-next/dialog/Dialog.vue index 78ca5206b..8a5439554 100644 --- a/app/javascript/dashboard/components-next/dialog/Dialog.vue +++ b/app/javascript/dashboard/components-next/dialog/Dialog.vue @@ -53,6 +53,11 @@ const props = defineProps({ default: 'lg', validator: value => ['3xl', '2xl', 'xl', 'lg', 'md', 'sm'].includes(value), }, + position: { + type: String, + default: 'center', + validator: value => ['center', 'top'].includes(value), + }, }); const emit = defineEmits(['confirm', 'close']); @@ -61,6 +66,7 @@ const { t } = useI18n(); const dialogRef = ref(null); const dialogContentRef = ref(null); +const isOpen = ref(false); const maxWidthClass = computed(() => { const classesMap = { @@ -75,13 +81,19 @@ const maxWidthClass = computed(() => { return classesMap[props.width] ?? 'max-w-md'; }); +const positionClass = computed(() => + props.position === 'top' ? 'dialog-position-top' : '' +); + const open = () => { + isOpen.value = true; dialogRef.value?.showModal(); }; const close = () => { emit('close'); dialogRef.value?.close(); + isOpen.value = false; }; const confirm = () => { @@ -98,6 +110,7 @@ defineExpose({ open, close }); class="w-full transition-all duration-300 ease-in-out shadow-xl rounded-xl" :class="[ maxWidthClass, + positionClass, overflowYAuto ? 'overflow-y-auto' : 'overflow-visible', ]" @close="close" @@ -105,7 +118,7 @@ defineExpose({ open, close });
@@ -119,7 +132,7 @@ defineExpose({ open, close });

- +
diff --git a/app/javascript/dashboard/components-next/dropdown-menu/base/DropdownSection.vue b/app/javascript/dashboard/components-next/dropdown-menu/base/DropdownSection.vue index 54872c440..5fc63b85c 100644 --- a/app/javascript/dashboard/components-next/dropdown-menu/base/DropdownSection.vue +++ b/app/javascript/dashboard/components-next/dropdown-menu/base/DropdownSection.vue @@ -4,6 +4,10 @@ defineProps({ type: String, default: '', }, + height: { + type: String, + default: 'max-h-96', + }, }); @@ -15,7 +19,10 @@ defineProps({ > {{ title }}
-
    +
    diff --git a/app/javascript/dashboard/components-next/filter/ConditionRow.vue b/app/javascript/dashboard/components-next/filter/ConditionRow.vue index c74879a58..510ddbde3 100644 --- a/app/javascript/dashboard/components-next/filter/ConditionRow.vue +++ b/app/javascript/dashboard/components-next/filter/ConditionRow.vue @@ -50,12 +50,12 @@ const currentFilter = computed(() => ); const getOperator = (filter, selectedOperator) => { - const operatorFromOptions = filter.filterOperators.find( + const operatorFromOptions = filter?.filterOperators?.find( operator => operator.value === selectedOperator ); if (!operatorFromOptions) { - return filter.filterOperators[0]; + return filter?.filterOperators?.[0]; } return operatorFromOptions; @@ -138,7 +138,11 @@ const validate = () => { return !validationError.value; }; -defineExpose({ validate }); +const resetValidation = () => { + showErrors.value = false; +}; + +defineExpose({ validate, resetValidation });