From d8a16278b988d086c825e98de945a9145d532180 Mon Sep 17 00:00:00 2001 From: Sony Mathew Date: Mon, 15 Jun 2026 21:34:19 +0530 Subject: [PATCH 1/6] fix: added ordering capability for sidebar sections folders, teams, channels and labels (CW-7193) (#14609) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description * Added the ability to sort for 4 sub-sections under conversations folders, teams, channels and labels. * the sort options are basically created at, alphabetical and unread counts along with both directions. * for folders we don't have an unread count, so we sort it by only created and alphabetical. * all the sort preferences are stored on the frontend - easiest implementation for now. Fixes # CW-7193 ## Type of change Please delete options that are not relevant. - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? Tested this locally by visually verifying the changes. Also ran the newly added tests for component level changes. Here is the screenshot of the changes: Added the sort option to sub sections in the conversation sidebar: Screenshot 2026-06-02 at 1 58 12 AM The sort options looks like this: Screenshot 2026-06-02 at 1 58 49 AM ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] 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 - [x] 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 --------- Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: iamsivin --- .../dropdown-menu/DropdownMenu.vue | 2 +- .../components-next/sidebar/Sidebar.vue | 95 +++++--- .../sidebar/SidebarCollapsedPopover.vue | 66 ++++-- .../components-next/sidebar/SidebarGroup.vue | 15 +- .../sidebar/SidebarGroupHeader.vue | 10 +- .../sidebar/SidebarGroupSeparator.vue | 94 +++++--- .../sidebar/SidebarSortMenu.vue | 209 ++++++++++++++++++ .../sidebar/SidebarSubGroup.vue | 11 +- .../dashboard/helper/sidebarSort.js | 181 +++++++++++++++ .../helper/specs/sidebarSort.spec.js | 205 +++++++++++++++++ .../dashboard/i18n/locale/en/settings.json | 15 ++ app/javascript/dashboard/store/index.js | 2 + .../store/modules/sidebarSortPreferences.js | 82 +++++++ .../sidebarSortPreferences/actions.spec.js | 106 +++++++++ .../sidebarSortPreferences/getters.spec.js | 31 +++ .../sidebarSortPreferences/mutations.spec.js | 34 +++ 16 files changed, 1071 insertions(+), 87 deletions(-) create mode 100644 app/javascript/dashboard/components-next/sidebar/SidebarSortMenu.vue create mode 100644 app/javascript/dashboard/helper/sidebarSort.js create mode 100644 app/javascript/dashboard/helper/specs/sidebarSort.spec.js create mode 100644 app/javascript/dashboard/store/modules/sidebarSortPreferences.js create mode 100644 app/javascript/dashboard/store/modules/specs/sidebarSortPreferences/actions.spec.js create mode 100644 app/javascript/dashboard/store/modules/specs/sidebarSortPreferences/getters.spec.js create mode 100644 app/javascript/dashboard/store/modules/specs/sidebarSortPreferences/mutations.spec.js diff --git a/app/javascript/dashboard/components-next/dropdown-menu/DropdownMenu.vue b/app/javascript/dashboard/components-next/dropdown-menu/DropdownMenu.vue index 8fa465704..fba446c0d 100644 --- a/app/javascript/dashboard/components-next/dropdown-menu/DropdownMenu.vue +++ b/app/javascript/dashboard/components-next/dropdown-menu/DropdownMenu.vue @@ -162,7 +162,7 @@ onMounted(() => { >

{{ section.title }}

diff --git a/app/javascript/dashboard/components-next/sidebar/Sidebar.vue b/app/javascript/dashboard/components-next/sidebar/Sidebar.vue index ab037618c..15a007a74 100644 --- a/app/javascript/dashboard/components-next/sidebar/Sidebar.vue +++ b/app/javascript/dashboard/components-next/sidebar/Sidebar.vue @@ -21,6 +21,12 @@ import ChannelIcon from 'next/icon/ChannelIcon.vue'; import SidebarAccountSwitcher from './SidebarAccountSwitcher.vue'; import Logo from 'next/icon/Logo.vue'; import ComposeConversation from 'dashboard/components-next/NewConversation/ComposeConversation.vue'; +import { + SIDEBAR_SORT_SECTIONS, + getSidebarSortOptions, + resolveSidebarSort, + sortSidebarItems, +} from 'dashboard/helper/sidebarSort'; const props = defineProps({ isMobileSidebarOpen: { @@ -50,6 +56,7 @@ const { width: windowWidth } = useWindowSize(); const isMobile = computed(() => windowWidth.value < 768); const accountId = useMapGetter('getCurrentAccountId'); +const currentUserId = useMapGetter('getCurrentUserID'); const isFeatureEnabledonAccount = useMapGetter( 'accounts/isFeatureEnabledonAccount' ); @@ -79,6 +86,11 @@ const fetchConversationUnreadCounts = ([currentAccountId, isEnabled]) => { store.dispatch('conversationUnreadCounts/get'); }; +const fetchSidebarSortPreferences = ([currentAccountId, userId]) => { + if (!currentAccountId || !userId) return; + store.dispatch('sidebarSortPreferences/initialize'); +}; + const toggleShortcutModalFn = show => { if (show) { emit('openKeyShortcutModal'); @@ -192,6 +204,9 @@ const contactCustomViews = useMapGetter('customViews/getContactCustomViews'); const conversationCustomViews = useMapGetter( 'customViews/getConversationCustomViews' ); +const getSidebarSectionSort = useMapGetter( + 'sidebarSortPreferences/getSectionSort' +); onMounted(() => { store.dispatch('labels/get'); @@ -207,44 +222,62 @@ watch([accountId, hasConversationUnreadCounts], fetchConversationUnreadCounts, { immediate: true, }); -const normalizeUnreadCount = count => { - const unreadCount = Number(count); - return Number.isFinite(unreadCount) && unreadCount > 0 ? unreadCount : 0; -}; +watch([accountId, currentUserId], fetchSidebarSortPreferences, { + immediate: true, +}); -const sortByUnreadCount = (items, labelKey, unreadCountKey) => - items.slice().sort((a, b) => { - const unreadCountDiff = - normalizeUnreadCount(unreadCountKey(b)) - - normalizeUnreadCount(unreadCountKey(a)); - - if (unreadCountDiff !== 0) return unreadCountDiff; - - return labelKey(a).localeCompare(labelKey(b)); +const getSortOptionsForSection = section => + getSidebarSortOptions(section, { + hasUnreadCounts: hasConversationUnreadCounts.value, }); +const getSortForSection = section => + resolveSidebarSort(section, getSidebarSectionSort.value(section), { + hasUnreadCounts: hasConversationUnreadCounts.value, + }); + +const updateSortPreference = (section, sortBy) => { + store.dispatch('sidebarSortPreferences/setSectionSort', { + section, + sortBy, + }); +}; + +const buildSortConfig = section => ({ + sortOptions: getSortOptionsForSection(section), + activeSort: getSortForSection(section), + onSortChange: sortBy => updateSortPreference(section, sortBy), +}); + +const sortedFolders = computed(() => + sortSidebarItems(conversationCustomViews.value, { + sortBy: getSortForSection(SIDEBAR_SORT_SECTIONS.FOLDERS), + labelKey: view => view.name, + }) +); + const sortedTeams = computed(() => - sortByUnreadCount( - teams.value, - team => team.name, - team => getTeamUnreadCount.value(team.id) - ) + sortSidebarItems(teams.value, { + sortBy: getSortForSection(SIDEBAR_SORT_SECTIONS.TEAMS), + labelKey: team => team.name, + unreadCountKey: team => getTeamUnreadCount.value(team.id), + }) ); const sortedInboxes = computed(() => - sortByUnreadCount( - inboxes.value, - inbox => inbox.name, - inbox => getInboxUnreadCount.value(inbox.id) - ) + sortSidebarItems(inboxes.value, { + sortBy: getSortForSection(SIDEBAR_SORT_SECTIONS.CHANNELS), + labelKey: inbox => inbox.name, + unreadCountKey: inbox => getInboxUnreadCount.value(inbox.id), + }) ); const sortedLabels = computed(() => - sortByUnreadCount( - labels.value, - label => label.title, - label => getLabelUnreadCount.value(label.id) - ) + sortSidebarItems(labels.value, { + sortBy: getSortForSection(SIDEBAR_SORT_SECTIONS.LABELS), + labelKey: label => label.title, + unreadCountKey: label => getLabelUnreadCount.value(label.id), + }) ); const closeMobileSidebar = () => { @@ -331,9 +364,10 @@ const menuItems = computed(() => { label: t('SIDEBAR.CUSTOM_VIEWS_FOLDER'), icon: 'i-lucide-folder', activeOn: ['conversations_through_folders'], + ...buildSortConfig(SIDEBAR_SORT_SECTIONS.FOLDERS), collapsible: true, showTreeLine: true, - children: conversationCustomViews.value.map(view => ({ + children: sortedFolders.value.map(view => ({ name: `${view.name}-${view.id}`, label: view.name, to: accountScopedRoute('folder_conversations', { id: view.id }), @@ -344,6 +378,7 @@ const menuItems = computed(() => { label: t('SIDEBAR.TEAMS'), icon: 'i-lucide-users', activeOn: ['conversations_through_team'], + ...buildSortConfig(SIDEBAR_SORT_SECTIONS.TEAMS), collapsible: true, showTreeLine: true, children: sortedTeams.value.map(team => ({ @@ -358,6 +393,7 @@ const menuItems = computed(() => { label: t('SIDEBAR.CHANNELS'), icon: 'i-lucide-mailbox', activeOn: ['conversation_through_inbox'], + ...buildSortConfig(SIDEBAR_SORT_SECTIONS.CHANNELS), collapsible: true, showTreeLine: true, children: sortedInboxes.value.map(inbox => ({ @@ -380,6 +416,7 @@ const menuItems = computed(() => { label: t('SIDEBAR.LABELS'), icon: 'i-lucide-tag', activeOn: ['conversations_through_label'], + ...buildSortConfig(SIDEBAR_SORT_SECTIONS.LABELS), collapsible: true, showTreeLine: true, children: sortedLabels.value.map(label => ({ diff --git a/app/javascript/dashboard/components-next/sidebar/SidebarCollapsedPopover.vue b/app/javascript/dashboard/components-next/sidebar/SidebarCollapsedPopover.vue index 18e1aea23..2e431eb7e 100644 --- a/app/javascript/dashboard/components-next/sidebar/SidebarCollapsedPopover.vue +++ b/app/javascript/dashboard/components-next/sidebar/SidebarCollapsedPopover.vue @@ -1,11 +1,13 @@ diff --git a/app/javascript/dashboard/components-next/sidebar/SidebarSortMenu.vue b/app/javascript/dashboard/components-next/sidebar/SidebarSortMenu.vue new file mode 100644 index 000000000..c4e29d881 --- /dev/null +++ b/app/javascript/dashboard/components-next/sidebar/SidebarSortMenu.vue @@ -0,0 +1,209 @@ + + + diff --git a/app/javascript/dashboard/components-next/sidebar/SidebarSubGroup.vue b/app/javascript/dashboard/components-next/sidebar/SidebarSubGroup.vue index a9f9c45e1..82005b49a 100644 --- a/app/javascript/dashboard/components-next/sidebar/SidebarSubGroup.vue +++ b/app/javascript/dashboard/components-next/sidebar/SidebarSubGroup.vue @@ -1,5 +1,6 @@