mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-29 18:22:53 +00:00
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
This commit is contained in:
@@ -62,6 +62,7 @@ const segmentsQuery = ref({});
|
|||||||
|
|
||||||
const appliedFilters = useMapGetter('contacts/getAppliedContactFiltersV4');
|
const appliedFilters = useMapGetter('contacts/getAppliedContactFiltersV4');
|
||||||
const contactAttributes = useMapGetter('attributes/getContactAttributes');
|
const contactAttributes = useMapGetter('attributes/getContactAttributes');
|
||||||
|
const labels = useMapGetter('labels/getLabels');
|
||||||
const hasActiveSegments = computed(
|
const hasActiveSegments = computed(
|
||||||
() => props.activeSegment && props.segmentsId !== 0
|
() => props.activeSegment && props.segmentsId !== 0
|
||||||
);
|
);
|
||||||
@@ -215,6 +216,7 @@ const setParamsForEditSegmentModal = () => {
|
|||||||
countries,
|
countries,
|
||||||
filterTypes: contactFilterItems,
|
filterTypes: contactFilterItems,
|
||||||
allCustomAttributes: useSnakeCase(contactAttributes.value),
|
allCustomAttributes: useSnakeCase(contactAttributes.value),
|
||||||
|
labels: labels.value || [],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -34,14 +34,17 @@ const formatOperatorLabel = operator => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const formatFilterValue = value => {
|
const formatFilterValue = value => {
|
||||||
|
// Case 1: null, undefined, empty string
|
||||||
if (!value) return '';
|
if (!value) return '';
|
||||||
|
|
||||||
|
// Case 2: array → map each item, use name if present, else the item itself
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return value.join(', ');
|
return value.map(item => item?.name ?? item).join(', ');
|
||||||
}
|
}
|
||||||
if (typeof value === 'object' && value.name) {
|
|
||||||
return value.name;
|
// Case 3: object with a "name" property → return name
|
||||||
}
|
// Case 4: primitive (string, number, etc.) → return as is
|
||||||
return value;
|
return value?.name ?? value;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -66,6 +69,7 @@ const formatFilterValue = value => {
|
|||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="filter.values"
|
v-if="filter.values"
|
||||||
|
:title="formatFilterValue(filter.values)"
|
||||||
class="lowercase truncate text-n-slate-12"
|
class="lowercase truncate text-n-slate-12"
|
||||||
:class="{
|
:class="{
|
||||||
'first-letter:capitalize': shouldCapitalizeFirstLetter(
|
'first-letter:capitalize': shouldCapitalizeFirstLetter(
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export function useContactFilterContext() {
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const contactAttributes = useMapGetter('attributes/getContactAttributes');
|
const contactAttributes = useMapGetter('attributes/getContactAttributes');
|
||||||
|
const labels = useMapGetter('labels/getLabels');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
equalityOperators,
|
equalityOperators,
|
||||||
@@ -184,6 +185,20 @@ export function useContactFilterContext() {
|
|||||||
filterOperators: equalityOperators.value,
|
filterOperators: equalityOperators.value,
|
||||||
attributeModel: 'standard',
|
attributeModel: 'standard',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
attributeKey: CONTACT_ATTRIBUTES.LABELS,
|
||||||
|
value: CONTACT_ATTRIBUTES.LABELS,
|
||||||
|
attributeName: t('CONTACTS_FILTER.ATTRIBUTES.LABELS'),
|
||||||
|
label: t('CONTACTS_FILTER.ATTRIBUTES.LABELS'),
|
||||||
|
inputType: 'multiSelect',
|
||||||
|
options: labels.value?.map(label => ({
|
||||||
|
id: label.title,
|
||||||
|
name: label.title,
|
||||||
|
})),
|
||||||
|
dataType: 'text',
|
||||||
|
filterOperators: equalityOperators.value,
|
||||||
|
attributeModel: 'standard',
|
||||||
|
},
|
||||||
...customFilterTypes.value,
|
...customFilterTypes.value,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export const CONTACT_ATTRIBUTES = {
|
|||||||
LAST_ACTIVITY_AT: 'last_activity_at',
|
LAST_ACTIVITY_AT: 'last_activity_at',
|
||||||
REFERER: 'referer',
|
REFERER: 'referer',
|
||||||
BLOCKED: 'blocked',
|
BLOCKED: 'blocked',
|
||||||
|
LABELS: 'labels',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -48,7 +48,8 @@
|
|||||||
"CREATED_AT": "Created At",
|
"CREATED_AT": "Created At",
|
||||||
"LAST_ACTIVITY": "Last Activity",
|
"LAST_ACTIVITY": "Last Activity",
|
||||||
"REFERER_LINK": "Referrer link",
|
"REFERER_LINK": "Referrer link",
|
||||||
"BLOCKED": "Blocked"
|
"BLOCKED": "Blocked",
|
||||||
|
"LABELS": "Labels"
|
||||||
},
|
},
|
||||||
"GROUPS": {
|
"GROUPS": {
|
||||||
"STANDARD_FILTERS": "Standard Filters",
|
"STANDARD_FILTERS": "Standard Filters",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
OPERATOR_TYPES_1,
|
OPERATOR_TYPES_1,
|
||||||
|
OPERATOR_TYPES_2,
|
||||||
OPERATOR_TYPES_3,
|
OPERATOR_TYPES_3,
|
||||||
OPERATOR_TYPES_5,
|
OPERATOR_TYPES_5,
|
||||||
} from 'dashboard/components/widgets/FilterInput/FilterOperatorTypes.js';
|
} from 'dashboard/components/widgets/FilterInput/FilterOperatorTypes.js';
|
||||||
@@ -84,6 +85,14 @@ const filterTypes = [
|
|||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
attributeModel: 'standard',
|
attributeModel: 'standard',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
attributeKey: 'labels',
|
||||||
|
attributeI18nKey: 'LABELS',
|
||||||
|
inputType: 'multi_select',
|
||||||
|
dataType: 'text',
|
||||||
|
filterOperators: OPERATOR_TYPES_2,
|
||||||
|
attributeModel: 'standard',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const filterAttributeGroups = [
|
export const filterAttributeGroups = [
|
||||||
@@ -127,6 +136,10 @@ export const filterAttributeGroups = [
|
|||||||
key: 'blocked',
|
key: 'blocked',
|
||||||
i18nKey: 'BLOCKED',
|
i18nKey: 'BLOCKED',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'labels',
|
||||||
|
i18nKey: 'LABELS',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user