From 99f17891d0875d3c321f60d6787d236ec74f8dcc Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Wed, 3 Dec 2025 15:46:16 +0530 Subject: [PATCH] feat: Updated conversation list UI (#12971) --- .../dropdown-menu/DropdownMenu.vue | 44 +- .../filter/ActiveFilterPreview.vue | 34 +- .../filter/ConversationFilter.vue | 138 +++-- .../components-next/filter/SaveCustomView.vue | 6 +- .../filter/inputs/MultiSelect.vue | 6 +- .../dashboard/components/ChatList.vue | 573 +++--------------- .../dashboard/components/ChatListFilters.vue | 308 ++++++++++ .../dashboard/components/ChatListHeader.vue | 65 +- .../dashboard/components/ConversationItem.vue | 154 ++++- .../dashboard/components/ConversationList.vue | 13 +- .../dashboard/components/ui/Tabs/TabsItem.vue | 10 +- .../components/widgets/ChatTypeTabs.vue | 4 +- .../ConversationActiveFiltersPreview.vue | 151 +++++ .../conversation/ConversationBasicFilter.vue | 10 +- .../conversationBulkActions/AgentSelector.vue | 254 -------- .../BulkAgentActions.vue | 195 ++++++ .../BulkLabelActions.vue | 151 +++++ .../BulkTeamActions.vue | 164 +++++ .../BulkUpdateActions.vue | 109 ++++ .../conversationBulkActions/Index.vue | 401 ++++-------- .../conversationBulkActions/LabelActions.vue | 141 ----- .../conversationBulkActions/TeamActions.vue | 145 ----- .../conversationBulkActions/UpdateActions.vue | 109 ---- .../composables/chatlist/useBulkActions.js | 12 +- .../i18n/locale/en/advancedFilters.json | 8 +- .../dashboard/i18n/locale/en/bulkActions.json | 10 +- .../dashboard/i18n/locale/en/chatlist.json | 2 +- .../components/ContactsBulkActionBar.vue | 59 +- .../conversation/search/SwitchLayout.vue | 12 +- theme/icons.js | 10 + 30 files changed, 1670 insertions(+), 1628 deletions(-) create mode 100644 app/javascript/dashboard/components/ChatListFilters.vue create mode 100644 app/javascript/dashboard/components/widgets/conversation/ConversationActiveFiltersPreview.vue delete mode 100644 app/javascript/dashboard/components/widgets/conversation/conversationBulkActions/AgentSelector.vue create mode 100644 app/javascript/dashboard/components/widgets/conversation/conversationBulkActions/BulkAgentActions.vue create mode 100644 app/javascript/dashboard/components/widgets/conversation/conversationBulkActions/BulkLabelActions.vue create mode 100644 app/javascript/dashboard/components/widgets/conversation/conversationBulkActions/BulkTeamActions.vue create mode 100644 app/javascript/dashboard/components/widgets/conversation/conversationBulkActions/BulkUpdateActions.vue delete mode 100644 app/javascript/dashboard/components/widgets/conversation/conversationBulkActions/LabelActions.vue delete mode 100644 app/javascript/dashboard/components/widgets/conversation/conversationBulkActions/TeamActions.vue delete mode 100644 app/javascript/dashboard/components/widgets/conversation/conversationBulkActions/UpdateActions.vue diff --git a/app/javascript/dashboard/components-next/dropdown-menu/DropdownMenu.vue b/app/javascript/dashboard/components-next/dropdown-menu/DropdownMenu.vue index 6276dfec6..b79f3a0d2 100644 --- a/app/javascript/dashboard/components-next/dropdown-menu/DropdownMenu.vue +++ b/app/javascript/dashboard/components-next/dropdown-menu/DropdownMenu.vue @@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n'; import Icon from 'dashboard/components-next/icon/Icon.vue'; import Avatar from 'dashboard/components-next/avatar/Avatar.vue'; +import Spinner from 'dashboard/components-next/spinner/Spinner.vue'; const props = defineProps({ menuItems: { @@ -37,9 +38,17 @@ const props = defineProps({ type: String, default: '', }, + disableLocalFiltering: { + type: Boolean, + default: false, + }, + isLoading: { + type: Boolean, + default: false, + }, }); -const emit = defineEmits(['action']); +const emit = defineEmits(['action', 'search']); const { t } = useI18n(); @@ -57,6 +66,7 @@ const flattenedMenuItems = computed(() => { }); const filteredMenuItems = computed(() => { + if (props.disableLocalFiltering) return props.menuItems; if (!searchQuery.value) return flattenedMenuItems.value; return flattenedMenuItems.value.filter(item => @@ -69,7 +79,7 @@ const filteredMenuSections = computed(() => { return []; } - if (!searchQuery.value) { + if (props.disableLocalFiltering || !searchQuery.value) { return props.menuSections; } @@ -89,6 +99,12 @@ const filteredMenuSections = computed(() => { .filter(section => section.items.length > 0); }); +const handleSearchInput = event => { + if (props.disableLocalFiltering) { + emit('search', event.target.value); + } +}; + const handleAction = item => { const { action, value, ...rest } = item; emit('action', { action, value, ...rest }); @@ -118,7 +134,7 @@ onMounted(() => { >
@@ -130,6 +146,7 @@ onMounted(() => { searchPlaceholder || t('DROPDOWN_MENU.SEARCH_PLACEHOLDER') " class="reset-base w-full h-8 py-2 pl-10 pr-2 text-sm focus:outline-none border-none rounded-lg bg-n-alpha-black2 dark:bg-n-solid-1 text-n-slate-12" + @input="handleSearchInput" />
@@ -141,10 +158,23 @@ onMounted(() => { >

{{ section.title }}

+
+ +
+
+ {{ section.emptyState }} +
{
{ : t('DROPDOWN_MENU.EMPTY_STATE') }}
+ diff --git a/app/javascript/dashboard/components-next/filter/ActiveFilterPreview.vue b/app/javascript/dashboard/components-next/filter/ActiveFilterPreview.vue index 38b3d72db..125db2722 100644 --- a/app/javascript/dashboard/components-next/filter/ActiveFilterPreview.vue +++ b/app/javascript/dashboard/components-next/filter/ActiveFilterPreview.vue @@ -39,38 +39,45 @@ const formatFilterValue = value => { // Case 2: array → map each item, use name if present, else the item itself if (Array.isArray(value)) { + // Empty array + if (value.length === 0) return ''; return value.map(item => item?.name ?? item).join(', '); } - // Case 3: object with a "name" property → return name - // Case 4: primitive (string, number, etc.) → return as is + // Case 3: empty object + if (typeof value === 'object' && Object.keys(value).length === 0) { + return ''; + } + + // Case 4: object with a "name" property → return name + // Case 5: primitive (string, number, etc.) → return as is return value?.name ?? value; }; diff --git a/app/javascript/dashboard/components-next/filter/ConversationFilter.vue b/app/javascript/dashboard/components-next/filter/ConversationFilter.vue index 79897d944..30b92fd41 100644 --- a/app/javascript/dashboard/components-next/filter/ConversationFilter.vue +++ b/app/javascript/dashboard/components-next/filter/ConversationFilter.vue @@ -1,12 +1,14 @@ + + diff --git a/app/javascript/dashboard/components/ChatListHeader.vue b/app/javascript/dashboard/components/ChatListHeader.vue index a5f6490d7..50baf5dcb 100644 --- a/app/javascript/dashboard/components/ChatListHeader.vue +++ b/app/javascript/dashboard/components/ChatListHeader.vue @@ -18,13 +18,7 @@ const props = defineProps({ isListLoading: { type: Boolean, required: true }, }); -const emit = defineEmits([ - 'addFolders', - 'deleteFolders', - 'resetFilters', - 'basicFilterChange', - 'filtersModal', -]); +const emit = defineEmits(['basicFilterChange', 'filtersModal']); const { uiSettings, updateUISettings } = useUISettings(); @@ -56,12 +50,7 @@ const toggleConversationLayout = () => {