diff --git a/app/controllers/api/v1/accounts/contacts_controller.rb b/app/controllers/api/v1/accounts/contacts_controller.rb index 0d8e0ed93..0e024b3d8 100644 --- a/app/controllers/api/v1/accounts/contacts_controller.rb +++ b/app/controllers/api/v1/accounts/contacts_controller.rb @@ -68,6 +68,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController @contacts = fetch_contacts(contacts) rescue CustomExceptions::CustomFilter::InvalidAttribute, CustomExceptions::CustomFilter::InvalidOperator, + CustomExceptions::CustomFilter::InvalidQueryOperator, CustomExceptions::CustomFilter::InvalidValue => e render_could_not_create_error(e.message) end diff --git a/app/controllers/api/v1/accounts/conversations_controller.rb b/app/controllers/api/v1/accounts/conversations_controller.rb index 2aedf1928..2cd5281ff 100644 --- a/app/controllers/api/v1/accounts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/conversations_controller.rb @@ -46,6 +46,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro @conversations_count = result[:count] rescue CustomExceptions::CustomFilter::InvalidAttribute, CustomExceptions::CustomFilter::InvalidOperator, + CustomExceptions::CustomFilter::InvalidQueryOperator, CustomExceptions::CustomFilter::InvalidValue => e render_could_not_create_error(e.message) end diff --git a/app/helpers/filter_helper.rb b/app/helpers/filter_helper.rb index bce2de5ea..9b5cac684 100644 --- a/app/helpers/filter_helper.rb +++ b/app/helpers/filter_helper.rb @@ -81,4 +81,12 @@ module FilterHelper def default_filter(query_hash, filter_operator_value) "#{filter_config[:table_name]}.#{query_hash[:attribute_key]} #{filter_operator_value} #{query_hash[:query_operator]}" end + + def validate_single_condition(condition) + return if condition['query_operator'].nil? + return if condition['query_operator'].empty? + + operator = condition['query_operator'].upcase + raise CustomExceptions::CustomFilter::InvalidQueryOperator.new({}) unless %w[AND OR].include?(operator) + end end diff --git a/app/javascript/dashboard/components-next/Contacts/ContactLabels/ContactLabels.vue b/app/javascript/dashboard/components-next/Contacts/ContactLabels/ContactLabels.vue index 255c1b113..af04de9a7 100644 --- a/app/javascript/dashboard/components-next/Contacts/ContactLabels/ContactLabels.vue +++ b/app/javascript/dashboard/components-next/Contacts/ContactLabels/ContactLabels.vue @@ -18,6 +18,11 @@ const route = useRoute(); const showDropdown = ref(false); +// Store the currently hovered label's ID +// Using JS state management instead of CSS :hover / group hover +// This will solve the flickering issue when hovering over the last label item +const hoveredLabel = ref(null); + const allLabels = useMapGetter('labels/getLabels'); const contactLabels = useMapGetter('contactLabels/getContactLabels'); @@ -37,7 +42,7 @@ const labelMenuItems = computed(() => { isSelected: savedLabels.value.some( savedLabel => savedLabel.id === label.id ), - action: 'addLabel', + action: 'contactLabel', })) .toSorted((a, b) => Number(a.isSelected) - Number(b.isSelected)); }); @@ -49,7 +54,7 @@ const fetchLabels = async contactId => { store.dispatch('contactLabels/get', contactId); }; -const handleLabelAction = async ({ action, value }) => { +const handleLabelAction = async ({ value }) => { try { // Get current label titles const currentLabels = savedLabels.value.map(label => label.title); @@ -59,16 +64,15 @@ const handleLabelAction = async ({ action, value }) => { if (!selectedLabel) return; let updatedLabels; - if (action === 'addLabel') { - // If label is already selected, remove it (toggle behavior) - if (currentLabels.includes(selectedLabel.title)) { - updatedLabels = currentLabels.filter( - labelTitle => labelTitle !== selectedLabel.title - ); - } else { - // Add the new label - updatedLabels = [...currentLabels, selectedLabel.title]; - } + + // If label is already selected, remove it (toggle behavior) + if (currentLabels.includes(selectedLabel.title)) { + updatedLabels = currentLabels.filter( + labelTitle => labelTitle !== selectedLabel.title + ); + } else { + // Add the new label + updatedLabels = [...currentLabels, selectedLabel.title]; } await store.dispatch('contactLabels/update', { @@ -82,6 +86,10 @@ const handleLabelAction = async ({ action, value }) => { } }; +const handleRemoveLabel = labelId => { + return handleLabelAction({ value: labelId }); +}; + watch( () => props.contactId, (newVal, oldVal) => { @@ -95,11 +103,31 @@ onMounted(() => { fetchLabels(route.params.contactId); } }); + +const handleMouseLeave = () => { + // Reset hover state when mouse leaves the container + // This ensures all labels return to their default state + hoveredLabel.value = null; +}; + +const handleLabelHover = labelId => { + // Added this to prevent flickering on when showing remove button on hover + // If the label item is at end of the line, it will show the remove button + // when hovering over the last label item + hoveredLabel.value = labelId; +};