From 8606aa1310af3ffadfa40603e60a00f6ccdb8cca Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Tue, 2 Sep 2025 12:35:18 +0530 Subject: [PATCH] feat: Ability to filter contact based on labels (#12343) # Pull Request Template ## Description This PR add the ability to filter contact based on labels. Fixes https://linear.app/chatwoot/issue/CW-4001/feat-ability-to-filter-contact-based-on-labels ## Type of change - [x] New feature (non-breaking change which adds functionality ## How Has This Been Tested? ### Loom video https://www.loom.com/share/f3d58d0fcee844b7817325a9a19929d3?sid=075b9448-7e6d-4180-af3c-9466fbf2138b ## 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 - [ ] 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 --- .../ContactsHeader/ContactListHeaderWrapper.vue | 2 ++ .../filter/ActiveFilterPreview.vue | 14 +++++++++----- .../components-next/filter/contactProvider.js | 15 +++++++++++++++ .../components-next/filter/helper/filterHelper.js | 1 + .../dashboard/i18n/locale/en/contactFilters.json | 3 ++- .../contacts/contactFilterItems/index.js | 13 +++++++++++++ 6 files changed, 42 insertions(+), 6 deletions(-) diff --git a/app/javascript/dashboard/components-next/Contacts/ContactsHeader/ContactListHeaderWrapper.vue b/app/javascript/dashboard/components-next/Contacts/ContactsHeader/ContactListHeaderWrapper.vue index ea5c63ebc..0b4880264 100644 --- a/app/javascript/dashboard/components-next/Contacts/ContactsHeader/ContactListHeaderWrapper.vue +++ b/app/javascript/dashboard/components-next/Contacts/ContactsHeader/ContactListHeaderWrapper.vue @@ -62,6 +62,7 @@ const segmentsQuery = ref({}); const appliedFilters = useMapGetter('contacts/getAppliedContactFiltersV4'); const contactAttributes = useMapGetter('attributes/getContactAttributes'); +const labels = useMapGetter('labels/getLabels'); const hasActiveSegments = computed( () => props.activeSegment && props.segmentsId !== 0 ); @@ -215,6 +216,7 @@ const setParamsForEditSegmentModal = () => { countries, filterTypes: contactFilterItems, allCustomAttributes: useSnakeCase(contactAttributes.value), + labels: labels.value || [], }; }; diff --git a/app/javascript/dashboard/components-next/filter/ActiveFilterPreview.vue b/app/javascript/dashboard/components-next/filter/ActiveFilterPreview.vue index 959d95ccc..38b3d72db 100644 --- a/app/javascript/dashboard/components-next/filter/ActiveFilterPreview.vue +++ b/app/javascript/dashboard/components-next/filter/ActiveFilterPreview.vue @@ -34,14 +34,17 @@ const formatOperatorLabel = operator => { }; const formatFilterValue = value => { + // Case 1: null, undefined, empty string if (!value) return ''; + + // Case 2: array → map each item, use name if present, else the item itself if (Array.isArray(value)) { - return value.join(', '); + return value.map(item => item?.name ?? item).join(', '); } - if (typeof value === 'object' && value.name) { - return value.name; - } - return value; + + // Case 3: object with a "name" property → return name + // Case 4: primitive (string, number, etc.) → return as is + return value?.name ?? value; }; @@ -66,6 +69,7 @@ const formatFilterValue = value => {