refactor(captain): simplify useAudienceFilterTypes composable
This commit is contained in:
+53
-57
@@ -5,7 +5,6 @@ import { useContactFilterContext } from 'dashboard/components-next/filter/contac
|
||||
import { useOperators } from 'dashboard/components-next/filter/operators.js';
|
||||
import languages from 'dashboard/components/widgets/conversation/advancedFilterItems/languages.js';
|
||||
|
||||
// Icons for the standard contact attributes, keyed by attribute key.
|
||||
const STANDARD_ICONS = {
|
||||
name: 'i-lucide-user',
|
||||
email: 'i-lucide-mail',
|
||||
@@ -18,11 +17,11 @@ const STANDARD_ICONS = {
|
||||
last_activity_at: 'i-lucide-activity',
|
||||
blocked: 'i-lucide-ban',
|
||||
labels: 'i-lucide-tags',
|
||||
hmac_verified: 'i-lucide-user-check',
|
||||
browser_language: 'i-lucide-globe',
|
||||
conversation_language: 'i-lucide-languages',
|
||||
};
|
||||
|
||||
// Icons for custom attributes, keyed by the attribute's display type.
|
||||
const CUSTOM_TYPE_ICONS = {
|
||||
text: 'i-lucide-type',
|
||||
number: 'i-lucide-hash',
|
||||
@@ -38,11 +37,7 @@ const DEFAULT_ICON = 'i-lucide-tag';
|
||||
|
||||
/**
|
||||
* Filter types for a Captain assistant audience: contact attributes (reused from the contact
|
||||
* segment builder) plus the two conversation language fields. Each option carries an icon, and the
|
||||
* options are split into grouped sections (contact / conversation / custom) via disabled headers,
|
||||
* which FilterSelect renders as non-clickable section titles.
|
||||
*
|
||||
* @returns {{ filterTypes: import('vue').ComputedRef<Array> }}
|
||||
* segment builder) plus conversation-scoped fields, grouped via disabled header options.
|
||||
*/
|
||||
export function useAudienceFilterTypes() {
|
||||
const { t } = useI18n();
|
||||
@@ -50,25 +45,33 @@ export function useAudienceFilterTypes() {
|
||||
const { equalityOperators } = useOperators();
|
||||
const contactAttributes = useMapGetter('attributes/getContactAttributes');
|
||||
|
||||
// Map custom attribute key -> display type, so each custom option gets a type-based icon.
|
||||
const customTypeByKey = computed(() =>
|
||||
(contactAttributes.value || []).reduce((acc, attr) => {
|
||||
acc[attr.attributeKey] = attr.attributeDisplayType;
|
||||
return acc;
|
||||
}, {})
|
||||
Object.fromEntries(
|
||||
(contactAttributes.value || []).map(attr => [
|
||||
attr.attributeKey,
|
||||
attr.attributeDisplayType,
|
||||
])
|
||||
)
|
||||
);
|
||||
|
||||
// Conversation-level attributes: the logged-in (HMAC verified) flag and the language fields.
|
||||
// Languages use a searchable dropdown (same option list as automation rules), not free text.
|
||||
const conversationOption = (key, label, options) => ({
|
||||
attributeKey: key,
|
||||
value: key,
|
||||
attributeName: label,
|
||||
label,
|
||||
icon: STANDARD_ICONS[key],
|
||||
inputType: 'searchSelect',
|
||||
options,
|
||||
dataType: 'text',
|
||||
filterOperators: equalityOperators.value,
|
||||
attributeModel: 'additional',
|
||||
});
|
||||
|
||||
const conversationFilterTypes = computed(() => [
|
||||
{
|
||||
attributeKey: 'hmac_verified',
|
||||
value: 'hmac_verified',
|
||||
attributeName: t('CAPTAIN.ASSISTANTS.FORM.AUDIENCE.LOGGED_IN'),
|
||||
label: t('CAPTAIN.ASSISTANTS.FORM.AUDIENCE.LOGGED_IN'),
|
||||
icon: 'i-lucide-user-check',
|
||||
inputType: 'searchSelect',
|
||||
options: [
|
||||
conversationOption(
|
||||
'hmac_verified',
|
||||
t('CAPTAIN.ASSISTANTS.FORM.AUDIENCE.LOGGED_IN'),
|
||||
[
|
||||
{
|
||||
id: 'true',
|
||||
name: t('CAPTAIN.ASSISTANTS.FORM.AUDIENCE.LOGGED_IN_TRUE'),
|
||||
@@ -77,23 +80,18 @@ export function useAudienceFilterTypes() {
|
||||
id: 'false',
|
||||
name: t('CAPTAIN.ASSISTANTS.FORM.AUDIENCE.LOGGED_IN_FALSE'),
|
||||
},
|
||||
],
|
||||
dataType: 'text',
|
||||
filterOperators: equalityOperators.value,
|
||||
attributeModel: 'additional',
|
||||
},
|
||||
...['browser_language', 'conversation_language'].map(key => ({
|
||||
attributeKey: key,
|
||||
value: key,
|
||||
attributeName: t(`CAPTAIN.ASSISTANTS.FORM.AUDIENCE.${key.toUpperCase()}`),
|
||||
label: t(`CAPTAIN.ASSISTANTS.FORM.AUDIENCE.${key.toUpperCase()}`),
|
||||
icon: STANDARD_ICONS[key],
|
||||
inputType: 'searchSelect',
|
||||
options: languages,
|
||||
dataType: 'text',
|
||||
filterOperators: equalityOperators.value,
|
||||
attributeModel: 'additional',
|
||||
})),
|
||||
]
|
||||
),
|
||||
conversationOption(
|
||||
'browser_language',
|
||||
t('CAPTAIN.ASSISTANTS.FORM.AUDIENCE.BROWSER_LANGUAGE'),
|
||||
languages
|
||||
),
|
||||
conversationOption(
|
||||
'conversation_language',
|
||||
t('CAPTAIN.ASSISTANTS.FORM.AUDIENCE.CONVERSATION_LANGUAGE'),
|
||||
languages
|
||||
),
|
||||
]);
|
||||
|
||||
const header = (id, label) => ({
|
||||
@@ -103,25 +101,23 @@ export function useAudienceFilterTypes() {
|
||||
});
|
||||
|
||||
const filterTypes = computed(() => {
|
||||
const standard = [];
|
||||
const custom = [];
|
||||
const standard = contactFilterTypes.value
|
||||
.filter(type => type.attributeModel !== 'customAttributes')
|
||||
.map(type => ({
|
||||
...type,
|
||||
icon: STANDARD_ICONS[type.attributeKey] || DEFAULT_ICON,
|
||||
}));
|
||||
|
||||
contactFilterTypes.value.forEach(type => {
|
||||
if (type.attributeModel === 'customAttributes') {
|
||||
// Only contact-model custom attributes belong here; never conversation/company ones.
|
||||
if (!(type.attributeKey in customTypeByKey.value)) return;
|
||||
const displayType = customTypeByKey.value[type.attributeKey];
|
||||
custom.push({
|
||||
...type,
|
||||
icon: CUSTOM_TYPE_ICONS[displayType] || DEFAULT_ICON,
|
||||
});
|
||||
} else {
|
||||
standard.push({
|
||||
...type,
|
||||
icon: STANDARD_ICONS[type.attributeKey] || DEFAULT_ICON,
|
||||
});
|
||||
}
|
||||
});
|
||||
// Only contact-model custom attributes belong here; never conversation/company ones.
|
||||
const custom = contactFilterTypes.value
|
||||
.filter(type => type.attributeModel === 'customAttributes')
|
||||
.filter(type => type.attributeKey in customTypeByKey.value)
|
||||
.map(type => ({
|
||||
...type,
|
||||
icon:
|
||||
CUSTOM_TYPE_ICONS[customTypeByKey.value[type.attributeKey]] ||
|
||||
DEFAULT_ICON,
|
||||
}));
|
||||
|
||||
return [
|
||||
header('contact', t('CAPTAIN.ASSISTANTS.FORM.AUDIENCE.GROUP_CONTACT')),
|
||||
|
||||
Reference in New Issue
Block a user