From aae21dfbb86735e1b47d6cee4d39369ed795b097 Mon Sep 17 00:00:00 2001 From: Pranav Date: Tue, 14 Jul 2026 17:53:50 -0700 Subject: [PATCH] refactor(captain): simplify useAudienceFilterTypes composable --- .../audience/useAudienceFilterTypes.js | 110 +++++++++--------- 1 file changed, 53 insertions(+), 57 deletions(-) diff --git a/app/javascript/dashboard/components-next/captain/pageComponents/assistant/settings/audience/useAudienceFilterTypes.js b/app/javascript/dashboard/components-next/captain/pageComponents/assistant/settings/audience/useAudienceFilterTypes.js index 4ba27814c..ad5c5046a 100644 --- a/app/javascript/dashboard/components-next/captain/pageComponents/assistant/settings/audience/useAudienceFilterTypes.js +++ b/app/javascript/dashboard/components-next/captain/pageComponents/assistant/settings/audience/useAudienceFilterTypes.js @@ -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 }} + * 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')),