From d06b66319ec228f999c413e16d0f3270013e060d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 19 Sep 2025 13:52:24 +0530 Subject: [PATCH] feat: add independent cache for each tab --- .../dashboard/components/ChatList.vue | 2 + .../store/modules/conversations/getters.js | 9 +- .../conversations/helpers/actionHelpers.js | 19 ++++ .../store/modules/conversations/index.js | 105 +++++++++++++++++- .../dashboard/store/mutation-types.js | 3 + 5 files changed, 134 insertions(+), 4 deletions(-) diff --git a/app/javascript/dashboard/components/ChatList.vue b/app/javascript/dashboard/components/ChatList.vue index 3727a16c3..295e87600 100644 --- a/app/javascript/dashboard/components/ChatList.vue +++ b/app/javascript/dashboard/components/ChatList.vue @@ -616,6 +616,8 @@ function updateAssigneeTab(selectedTab) { resetBulkActions(); emitter.emit('clearSearchInput'); activeAssigneeTab.value = selectedTab; + // Set active tab in store for tab-scoped caching + store.commit('conversations/SET_ACTIVE_TAB', selectedTab); if (!currentPage.value) { fetchConversations(); } diff --git a/app/javascript/dashboard/store/modules/conversations/getters.js b/app/javascript/dashboard/store/modules/conversations/getters.js index 9f5744fbb..b2337968c 100644 --- a/app/javascript/dashboard/store/modules/conversations/getters.js +++ b/app/javascript/dashboard/store/modules/conversations/getters.js @@ -74,8 +74,9 @@ const getters = { }, getMineChats: (_state, _, __, rootGetters) => activeFilters => { const currentUserID = rootGetters.getCurrentUser?.id; + const tabConversations = _state.conversationsByTab.me || []; - return _state.allConversations.filter(conversation => { + return tabConversations.filter(conversation => { const { assignee } = conversation.meta; const isAssignedToMe = assignee && assignee.id === currentUserID; const shouldFilter = applyPageFilters(conversation, activeFilters); @@ -96,7 +97,8 @@ const getters = { return hasAppliedFilters ? filterQueryGenerator(_state.appliedFilters) : []; }, getUnAssignedChats: _state => activeFilters => { - return _state.allConversations.filter(conversation => { + const tabConversations = _state.conversationsByTab.unassigned || []; + return tabConversations.filter(conversation => { const isUnAssigned = !conversation.meta.assignee; const shouldFilter = applyPageFilters(conversation, activeFilters); return isUnAssigned && shouldFilter; @@ -106,11 +108,12 @@ const getters = { const currentUser = rootGetters.getCurrentUser; const currentUserId = rootGetters.getCurrentUser.id; const currentAccountId = rootGetters.getCurrentAccountId; + const tabConversations = _state.conversationsByTab.all || []; const permissions = getUserPermissions(currentUser, currentAccountId); const userRole = getUserRole(currentUser, currentAccountId); - return _state.allConversations.filter(conversation => { + return tabConversations.filter(conversation => { const shouldFilter = applyPageFilters(conversation, activeFilters); const allowedForRole = applyRoleFilter( conversation, diff --git a/app/javascript/dashboard/store/modules/conversations/helpers/actionHelpers.js b/app/javascript/dashboard/store/modules/conversations/helpers/actionHelpers.js index f1c594b26..cbd5f18e8 100644 --- a/app/javascript/dashboard/store/modules/conversations/helpers/actionHelpers.js +++ b/app/javascript/dashboard/store/modules/conversations/helpers/actionHelpers.js @@ -38,6 +38,14 @@ export const isOnFoldersView = ({ route: { name: routeName } }) => { return FOLDER_ROUTES.includes(routeName); }; +const getTabFromFilterType = filterType => { + if (filterType === 'me') return 'me'; + if (filterType === 'unassigned') return 'unassigned'; + if (filterType === 'all') return 'all'; + // For appliedFilters and other cases, default to 'all' + return 'all'; +}; + export const buildConversationList = ( context, requestPayload, @@ -45,7 +53,18 @@ export const buildConversationList = ( filterType ) => { const { payload: conversationList, meta: metaData } = responseData; + const tab = getTabFromFilterType(filterType); + + // Use tab-scoped mutation + context.commit(types.SET_TAB_CONVERSATION, { + conversations: conversationList, + tab, + }); + context.commit(types.SET_ACTIVE_TAB, tab); + + // Keep legacy mutation for backward compatibility during migration context.commit(types.SET_ALL_CONVERSATION, conversationList); + context.dispatch('conversationStats/set', metaData); context.dispatch( 'conversationLabels/setBulkConversationLabels', diff --git a/app/javascript/dashboard/store/modules/conversations/index.js b/app/javascript/dashboard/store/modules/conversations/index.js index 2ee6fd061..112b7e2bb 100644 --- a/app/javascript/dashboard/store/modules/conversations/index.js +++ b/app/javascript/dashboard/store/modules/conversations/index.js @@ -8,7 +8,13 @@ import { BUS_EVENTS } from '../../../../shared/constants/busEvents'; import { emitter } from 'shared/helpers/mitt'; const state = { - allConversations: [], + allConversations: [], // Keep for backward compatibility during migration + conversationsByTab: { + me: [], + unassigned: [], + all: [], + }, + activeCacheTab: 'all', attachments: {}, listLoadingStatus: true, chatStatusFilter: wootConstants.STATUS_TYPE.OPEN, @@ -54,10 +60,47 @@ export const mutations = { }); _state.allConversations = newAllConversations; }, + [types.SET_TAB_CONVERSATION](_state, { conversations, tab }) { + if (!_state.conversationsByTab[tab]) { + _state.conversationsByTab[tab] = []; + } + + const targetCache = [..._state.conversationsByTab[tab]]; + const newConversations = []; + + conversations.forEach(conversation => { + const indexInCache = targetCache.findIndex(c => c.id === conversation.id); + if (indexInCache < 0) { + newConversations.push(conversation); + } else if (conversation.id !== _state.selectedChatId) { + targetCache[indexInCache] = conversation; + } else { + // Preserve messages, attachments, dataFetched, allMessagesLoaded for selected chat + const existingConversation = targetCache[indexInCache]; + targetCache[indexInCache] = { + ...conversation, + allMessagesLoaded: existingConversation.allMessagesLoaded, + messages: existingConversation.messages, + dataFetched: existingConversation.dataFetched, + }; + } + }); + + // Maintain server order by appending new conversations + _state.conversationsByTab[tab] = [...targetCache, ...newConversations]; + }, [types.EMPTY_ALL_CONVERSATION](_state) { _state.allConversations = []; _state.selectedChatId = null; }, + [types.EMPTY_TAB_CONVERSATION](_state, tab) { + if (_state.conversationsByTab[tab]) { + _state.conversationsByTab[tab] = []; + } + }, + [types.SET_ACTIVE_TAB](_state, tab) { + _state.activeCacheTab = tab; + }, [types.SET_ALL_MESSAGES_LOADED](_state) { const [chat] = getSelectedChatConversation(_state); chat.allMessagesLoaded = true; @@ -202,12 +245,38 @@ export const mutations = { [types.ADD_CONVERSATION](_state, conversation) { _state.allConversations.push(conversation); + + // Add to appropriate tab caches based on conversation properties + const { meta: { assignee } = {} } = conversation; + + // Add to 'all' tab + if (!_state.conversationsByTab.all.find(c => c.id === conversation.id)) { + _state.conversationsByTab.all.unshift(conversation); + } + + // Add to 'me' or 'unassigned' tab based on assignee + if (assignee) { + if (!_state.conversationsByTab.me.find(c => c.id === conversation.id)) { + _state.conversationsByTab.me.unshift(conversation); + } + } else if ( + !_state.conversationsByTab.unassigned.find(c => c.id === conversation.id) + ) { + _state.conversationsByTab.unassigned.unshift(conversation); + } }, [types.DELETE_CONVERSATION](_state, conversationId) { _state.allConversations = _state.allConversations.filter( c => c.id !== conversationId ); + + // Also remove from all tab caches + Object.keys(_state.conversationsByTab).forEach(tab => { + _state.conversationsByTab[tab] = _state.conversationsByTab[tab].filter( + c => c.id !== conversationId + ); + }); }, [types.UPDATE_CONVERSATION](_state, conversation) { @@ -231,6 +300,28 @@ export const mutations = { } else { _state.allConversations.push(conversation); } + + // Also update conversation in all tab caches + Object.keys(_state.conversationsByTab).forEach(tab => { + const tabIndex = _state.conversationsByTab[tab].findIndex( + c => c.id === conversation.id + ); + if (tabIndex > -1) { + const selectedTabConversation = + _state.conversationsByTab[tab][tabIndex]; + + // ignore out of order events + if (conversation.updated_at < selectedTabConversation.updated_at) { + return; + } + + const { messages, ...updates } = conversation; + _state.conversationsByTab[tab][tabIndex] = { + ...selectedTabConversation, + ...updates, + }; + } + }); }, [types.SET_LIST_LOADING_STATUS](_state) { @@ -253,10 +344,22 @@ export const mutations = { }, [types.CHANGE_CHAT_STATUS_FILTER](_state, data) { _state.chatStatusFilter = data; + // Clear all tab caches since status filter is global + _state.conversationsByTab = { + me: [], + unassigned: [], + all: [], + }; }, [types.CHANGE_CHAT_SORT_FILTER](_state, data) { _state.chatSortFilter = data; + // Clear all tab caches since sort filter is global + _state.conversationsByTab = { + me: [], + unassigned: [], + all: [], + }; }, // Update assignee on action cable message diff --git a/app/javascript/dashboard/store/mutation-types.js b/app/javascript/dashboard/store/mutation-types.js index 4f361e140..670f73a00 100644 --- a/app/javascript/dashboard/store/mutation-types.js +++ b/app/javascript/dashboard/store/mutation-types.js @@ -10,7 +10,10 @@ export default { // Chat List RECEIVE_CHAT_LIST: 'RECEIVE_CHAT_LIST', SET_ALL_CONVERSATION: 'SET_ALL_CONVERSATION', + SET_TAB_CONVERSATION: 'SET_TAB_CONVERSATION', EMPTY_ALL_CONVERSATION: 'EMPTY_ALL_CONVERSATION', + EMPTY_TAB_CONVERSATION: 'EMPTY_TAB_CONVERSATION', + SET_ACTIVE_TAB: 'SET_ACTIVE_TAB', SET_CONV_TAB_META: 'SET_CONV_TAB_META', CLEAR_LIST_LOADING_STATUS: 'CLEAR_LIST_LOADING_STATUS', SET_LIST_LOADING_STATUS: 'SET_LIST_LOADING_STATUS',