Compare commits

...
5 changed files with 158 additions and 5 deletions
@@ -616,6 +616,8 @@ function updateAssigneeTab(selectedTab) {
resetBulkActions(); resetBulkActions();
emitter.emit('clearSearchInput'); emitter.emit('clearSearchInput');
activeAssigneeTab.value = selectedTab; activeAssigneeTab.value = selectedTab;
// Set active tab in store for tab-scoped caching
store.commit('conversations/SET_ACTIVE_TAB', selectedTab);
if (!currentPage.value) { if (!currentPage.value) {
fetchConversations(); fetchConversations();
} }
@@ -75,7 +75,13 @@ const getters = {
getMineChats: (_state, _, __, rootGetters) => activeFilters => { getMineChats: (_state, _, __, rootGetters) => activeFilters => {
const currentUserID = rootGetters.getCurrentUser?.id; const currentUserID = rootGetters.getCurrentUser?.id;
return _state.allConversations.filter(conversation => { // Use allConversations if there are applied filters, otherwise use tab cache
const hasAppliedFilters = _state.appliedFilters?.length > 0;
const conversations = hasAppliedFilters
? _state.allConversations
: _state.conversationsByTab.me || [];
return conversations.filter(conversation => {
const { assignee } = conversation.meta; const { assignee } = conversation.meta;
const isAssignedToMe = assignee && assignee.id === currentUserID; const isAssignedToMe = assignee && assignee.id === currentUserID;
const shouldFilter = applyPageFilters(conversation, activeFilters); const shouldFilter = applyPageFilters(conversation, activeFilters);
@@ -96,7 +102,13 @@ const getters = {
return hasAppliedFilters ? filterQueryGenerator(_state.appliedFilters) : []; return hasAppliedFilters ? filterQueryGenerator(_state.appliedFilters) : [];
}, },
getUnAssignedChats: _state => activeFilters => { getUnAssignedChats: _state => activeFilters => {
return _state.allConversations.filter(conversation => { // Use allConversations if there are applied filters, otherwise use tab cache
const hasAppliedFilters = _state.appliedFilters?.length > 0;
const conversations = hasAppliedFilters
? _state.allConversations
: _state.conversationsByTab.unassigned || [];
return conversations.filter(conversation => {
const isUnAssigned = !conversation.meta.assignee; const isUnAssigned = !conversation.meta.assignee;
const shouldFilter = applyPageFilters(conversation, activeFilters); const shouldFilter = applyPageFilters(conversation, activeFilters);
return isUnAssigned && shouldFilter; return isUnAssigned && shouldFilter;
@@ -107,10 +119,16 @@ const getters = {
const currentUserId = rootGetters.getCurrentUser.id; const currentUserId = rootGetters.getCurrentUser.id;
const currentAccountId = rootGetters.getCurrentAccountId; const currentAccountId = rootGetters.getCurrentAccountId;
// Use allConversations if there are applied filters, otherwise use tab cache
const hasAppliedFilters = _state.appliedFilters?.length > 0;
const conversations = hasAppliedFilters
? _state.allConversations
: _state.conversationsByTab.all || [];
const permissions = getUserPermissions(currentUser, currentAccountId); const permissions = getUserPermissions(currentUser, currentAccountId);
const userRole = getUserRole(currentUser, currentAccountId); const userRole = getUserRole(currentUser, currentAccountId);
return _state.allConversations.filter(conversation => { return conversations.filter(conversation => {
const shouldFilter = applyPageFilters(conversation, activeFilters); const shouldFilter = applyPageFilters(conversation, activeFilters);
const allowedForRole = applyRoleFilter( const allowedForRole = applyRoleFilter(
conversation, conversation,
@@ -38,6 +38,14 @@ export const isOnFoldersView = ({ route: { name: routeName } }) => {
return FOLDER_ROUTES.includes(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, return null to use allConversations
return null;
};
export const buildConversationList = ( export const buildConversationList = (
context, context,
requestPayload, requestPayload,
@@ -45,7 +53,20 @@ export const buildConversationList = (
filterType filterType
) => { ) => {
const { payload: conversationList, meta: metaData } = responseData; const { payload: conversationList, meta: metaData } = responseData;
context.commit(types.SET_ALL_CONVERSATION, conversationList); const tab = getTabFromFilterType(filterType);
if (tab) {
// Use tab-scoped mutation for basic assignee tabs
context.commit(types.SET_TAB_CONVERSATION, {
conversations: conversationList,
tab,
});
context.commit(types.SET_ACTIVE_TAB, tab);
} else {
// Use allConversations for filtered views (appliedFilters, folders, etc.)
context.commit(types.SET_ALL_CONVERSATION, conversationList);
}
context.dispatch('conversationStats/set', metaData); context.dispatch('conversationStats/set', metaData);
context.dispatch( context.dispatch(
'conversationLabels/setBulkConversationLabels', 'conversationLabels/setBulkConversationLabels',
@@ -8,7 +8,13 @@ import { BUS_EVENTS } from '../../../../shared/constants/busEvents';
import { emitter } from 'shared/helpers/mitt'; import { emitter } from 'shared/helpers/mitt';
const state = { const state = {
allConversations: [], allConversations: [], // Keep for backward compatibility during migration
conversationsByTab: {
me: [],
unassigned: [],
all: [],
},
activeCacheTab: 'all',
attachments: {}, attachments: {},
listLoadingStatus: true, listLoadingStatus: true,
chatStatusFilter: wootConstants.STATUS_TYPE.OPEN, chatStatusFilter: wootConstants.STATUS_TYPE.OPEN,
@@ -54,9 +60,52 @@ export const mutations = {
}); });
_state.allConversations = newAllConversations; _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) { [types.EMPTY_ALL_CONVERSATION](_state) {
_state.allConversations = []; _state.allConversations = [];
_state.selectedChatId = null; _state.selectedChatId = null;
// Also clear all tab caches so UI renders empty state immediately
_state.conversationsByTab = {
me: [],
unassigned: [],
all: [],
};
},
[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) { [types.SET_ALL_MESSAGES_LOADED](_state) {
const [chat] = getSelectedChatConversation(_state); const [chat] = getSelectedChatConversation(_state);
@@ -202,12 +251,38 @@ export const mutations = {
[types.ADD_CONVERSATION](_state, conversation) { [types.ADD_CONVERSATION](_state, conversation) {
_state.allConversations.push(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) { [types.DELETE_CONVERSATION](_state, conversationId) {
_state.allConversations = _state.allConversations.filter( _state.allConversations = _state.allConversations.filter(
c => c.id !== conversationId 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) { [types.UPDATE_CONVERSATION](_state, conversation) {
@@ -231,6 +306,28 @@ export const mutations = {
} else { } else {
_state.allConversations.push(conversation); _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) { [types.SET_LIST_LOADING_STATUS](_state) {
@@ -253,10 +350,22 @@ export const mutations = {
}, },
[types.CHANGE_CHAT_STATUS_FILTER](_state, data) { [types.CHANGE_CHAT_STATUS_FILTER](_state, data) {
_state.chatStatusFilter = 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) { [types.CHANGE_CHAT_SORT_FILTER](_state, data) {
_state.chatSortFilter = data; _state.chatSortFilter = data;
// Clear all tab caches since sort filter is global
_state.conversationsByTab = {
me: [],
unassigned: [],
all: [],
};
}, },
// Update assignee on action cable message // Update assignee on action cable message
@@ -10,7 +10,10 @@ export default {
// Chat List // Chat List
RECEIVE_CHAT_LIST: 'RECEIVE_CHAT_LIST', RECEIVE_CHAT_LIST: 'RECEIVE_CHAT_LIST',
SET_ALL_CONVERSATION: 'SET_ALL_CONVERSATION', SET_ALL_CONVERSATION: 'SET_ALL_CONVERSATION',
SET_TAB_CONVERSATION: 'SET_TAB_CONVERSATION',
EMPTY_ALL_CONVERSATION: 'EMPTY_ALL_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', SET_CONV_TAB_META: 'SET_CONV_TAB_META',
CLEAR_LIST_LOADING_STATUS: 'CLEAR_LIST_LOADING_STATUS', CLEAR_LIST_LOADING_STATUS: 'CLEAR_LIST_LOADING_STATUS',
SET_LIST_LOADING_STATUS: 'SET_LIST_LOADING_STATUS', SET_LIST_LOADING_STATUS: 'SET_LIST_LOADING_STATUS',