From 0740d4762f8c32a01df08daeff03cfa92536a602 Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Wed, 26 Feb 2020 21:15:01 +0530 Subject: [PATCH] Enhancement: Paginate conversation calls in tabs (#560) * Use conversationPage module for pagination * Load more conversations * Reset list if conversation status is changed * Add specs to conversationPage * Reset filter when page is re-mounted * Update text * Update text --- app/finders/conversation_finder.rb | 17 +-- .../dashboard/api/inbox/conversation.js | 5 +- .../dashboard/assets/scss/_mixins.scss | 1 - .../scss/widgets/_conversation-view.scss | 26 +++- .../dashboard/components/ChatList.vue | 123 ++++++++++-------- .../components/buttons/ResolveButton.vue | 20 ++- .../components/widgets/ChatTypeTabs.vue | 25 ++-- .../widgets/conversation/ChatFilter.vue | 11 +- app/javascript/dashboard/constants.js | 12 +- .../dashboard/i18n/locale/en/chatlist.json | 18 +-- .../i18n/locale/en/conversation.json | 1 + .../conversation/ConversationView.vue | 24 ++-- app/javascript/dashboard/routes/index.spec.js | 12 +- app/javascript/dashboard/store/index.js | 4 +- .../store/modules/conversationPage.js | 70 ++++++++++ .../store/modules/conversations/actions.js | 17 ++- .../store/modules/conversations/index.js | 4 +- .../specs/conversationPage/actions.spec.js | 33 +++++ .../specs/conversationPage/getters.spec.js | 29 +++++ .../specs/conversationPage/mutations.spec.js | 61 +++++++++ .../dashboard/store/mutation-types.js | 5 + .../conversations/event_data_presenter.rb | 2 +- .../conversations/index.json.jbuilder | 2 +- .../api/v1/conversations/index.json.jbuilder | 2 +- .../conversations/toggle_status.json.jbuilder | 2 +- spec/finders/conversation_finder_spec.rb | 6 +- spec/models/conversation_spec.rb | 2 +- .../event_data_presenter_spec.rb | 2 +- 28 files changed, 395 insertions(+), 141 deletions(-) create mode 100644 app/javascript/dashboard/store/modules/conversationPage.js create mode 100644 app/javascript/dashboard/store/modules/specs/conversationPage/actions.spec.js create mode 100644 app/javascript/dashboard/store/modules/specs/conversationPage/getters.spec.js create mode 100644 app/javascript/dashboard/store/modules/specs/conversationPage/mutations.spec.js diff --git a/app/finders/conversation_finder.rb b/app/finders/conversation_finder.rb index f4e114bf4..fcc0daf98 100644 --- a/app/finders/conversation_finder.rb +++ b/app/finders/conversation_finder.rb @@ -1,23 +1,18 @@ class ConversationFinder attr_reader :current_user, :current_account, :params - ASSIGNEE_TYPES = { me: 0, unassigned: 1, all: 2 }.freeze - - ASSIGNEE_TYPES_BY_ID = ASSIGNEE_TYPES.invert - ASSIGNEE_TYPES_BY_ID.default = :me - DEFAULT_STATUS = 'open'.freeze # assumptions # inbox_id if not given, take from all conversations, else specific to inbox - # assignee_type if not given, take 'me' + # assignee_type if not given, take 'all' # conversation_status if not given, take 'open' # response of this class will be of type # {conversations: [array of conversations], count: {open: count, resolved: count}} # params - # assignee_type_id, inbox_id, :status + # assignee_type, inbox_id, :status def initialize(current_user, params) @current_user = current_user @@ -62,7 +57,7 @@ class ConversationFinder end def set_assignee_type - @assignee_type_id = ASSIGNEE_TYPES[ASSIGNEE_TYPES_BY_ID[params[:assignee_type_id].to_i]] + @assignee_type = params[:assignee_type] end def find_all_conversations @@ -72,12 +67,10 @@ class ConversationFinder end def filter_by_assignee_type - if @assignee_type_id == ASSIGNEE_TYPES[:me] + if @assignee_type == 'me' @conversations = @conversations.assigned_to(current_user) - elsif @assignee_type_id == ASSIGNEE_TYPES[:unassigned] + elsif @assignee_type == 'unassigned' @conversations = @conversations.unassigned - elsif @assignee_type_id == ASSIGNEE_TYPES[:all] - @conversations end @conversations end diff --git a/app/javascript/dashboard/api/inbox/conversation.js b/app/javascript/dashboard/api/inbox/conversation.js index b444d0e17..6a86cff7f 100644 --- a/app/javascript/dashboard/api/inbox/conversation.js +++ b/app/javascript/dashboard/api/inbox/conversation.js @@ -6,12 +6,13 @@ class ConversationApi extends ApiClient { super('conversations'); } - get({ inboxId, status, assigneeType }) { + get({ inboxId, status, assigneeType, page }) { return axios.get(this.url, { params: { inbox_id: inboxId, status, - assignee_type_id: assigneeType, + assignee_type: assigneeType, + page, }, }); } diff --git a/app/javascript/dashboard/assets/scss/_mixins.scss b/app/javascript/dashboard/assets/scss/_mixins.scss index 7b64b5119..e4efee5ca 100644 --- a/app/javascript/dashboard/assets/scss/_mixins.scss +++ b/app/javascript/dashboard/assets/scss/_mixins.scss @@ -129,7 +129,6 @@ $spinner-before-border-color: rgba(255, 255, 255, 0.7); } @mixin scroll-on-hover() { - transition: all .4s $ease-in-out-cubic; overflow: hidden; &:hover { diff --git a/app/javascript/dashboard/assets/scss/widgets/_conversation-view.scss b/app/javascript/dashboard/assets/scss/widgets/_conversation-view.scss index a97d47479..cf625423b 100644 --- a/app/javascript/dashboard/assets/scss/widgets/_conversation-view.scss +++ b/app/javascript/dashboard/assets/scss/widgets/_conversation-view.scss @@ -82,6 +82,27 @@ @include flex; flex-direction: column; + .load-more-conversations { + color: $color-woot; + cursor: pointer; + font-size: $font-size-small; + padding: $space-normal; + + &:hover { + background: $color-background; + } + } + + .end-of-list-text { + font-style: italic; + padding: $space-normal; + } + + .conversations-list { + @include flex-weight(1); + @include scroll-on-hover; + } + .chat-list__top { @include flex; @include padding($space-normal $zero $space-small $zero); @@ -108,10 +129,7 @@ } } - .conversations-list { - @include flex-weight(1); - @include scroll-on-hover; - } + .content-box { text-align: center; diff --git a/app/javascript/dashboard/components/ChatList.vue b/app/javascript/dashboard/components/ChatList.vue index 6f69857f7..83e1766b5 100644 --- a/app/javascript/dashboard/components/ChatList.vue +++ b/app/javascript/dashboard/components/ChatList.vue @@ -3,40 +3,52 @@

- {{ inbox.name || pageTitle }} + {{ inbox.name || $t('CHAT_LIST.TAB_HEADING') }}

- +
-

+

{{ $t('CHAT_LIST.LIST.404') }}

-
- -
- - +
- + +
+ +
+ +
+ {{ $t('CHAT_LIST.LOAD_MORE_CONVERSATIONS') }} +
+ +

+ {{ $t('CHAT_LIST.EOF') }} +

+
@@ -59,11 +71,11 @@ export default { ChatFilter, }, mixins: [timeMixin, conversationMixin], - props: ['conversationInbox', 'pageTitle'], + props: ['conversationInbox'], data() { return { - activeAssigneeTab: 0, - activeStatus: 0, + activeAssigneeTab: wootConstants.ASSIGNEE_TYPE.ME, + activeStatus: wootConstants.STATUS_TYPE.OPEN, }; }, computed: { @@ -78,66 +90,69 @@ export default { convStats: 'getConvTabStats', }), assigneeTabItems() { - return this.$t('CHAT_LIST.ASSIGNEE_TYPE_TABS').map((item, index) => ({ - id: index, + return this.$t('CHAT_LIST.ASSIGNEE_TYPE_TABS').map(item => ({ + key: item.KEY, name: item.NAME, - count: this.convStats[item.KEY] || 0, + count: this.convStats[item.COUNT_KEY] || 0, })); }, inbox() { return this.$store.getters['inboxes/getInbox'](this.activeInbox); }, - getToggleStatus() { - if (this.toggleType) { - return 'Open'; - } - return 'Resolved'; + currentPage() { + return this.$store.getters['conversationPage/getCurrentPage']( + this.activeAssigneeTab + ); + }, + hasCurrentPageEndReached() { + return this.$store.getters['conversationPage/getHasEndReached']( + this.activeAssigneeTab + ); + }, + }, + watch: { + conversationInbox() { + this.resetAndFetchData(); }, }, mounted() { - this.$watch('$store.state.route', () => { - if (this.$store.state.route.name !== 'inbox_conversation') { - this.$store.dispatch('emptyAllConversations'); - this.fetchData(); - } - }); - - this.$store.dispatch('emptyAllConversations'); - this.fetchData(); + this.$store.dispatch('setChatFilter', this.activeStatus); + this.resetAndFetchData(); this.$store.dispatch('agents/get'); }, methods: { - fetchData() { - if (this.chatLists.length === 0) { - this.fetchConversations(); - } + resetAndFetchData() { + this.$store.dispatch('conversationPage/reset'); + this.$store.dispatch('emptyAllConversations'); + this.fetchConversations(); }, fetchConversations() { this.$store.dispatch('fetchAllConversations', { inboxId: this.conversationInbox ? this.conversationInbox : undefined, assigneeType: this.activeAssigneeTab, - status: this.activeStatus ? 'resolved' : 'open', + status: this.activeStatus, + page: this.currentPage + 1, }); }, - getDataForTab(index) { - if (this.activeAssigneeTab !== index) { - this.activeAssigneeTab = index; - this.fetchConversations(); + updateAssigneeTab(selectedTab) { + if (this.activeAssigneeTab !== selectedTab) { + this.activeAssigneeTab = selectedTab; + if (!this.currentPage) { + this.fetchConversations(); + } } }, - getDataForStatusTab(index) { + updateStatusType(index) { if (this.activeStatus !== index) { this.activeStatus = index; - this.fetchConversations(); + this.resetAndFetchData(); } }, getChatsForTab() { let copyList = []; - if (this.activeAssigneeTab === wootConstants.ASSIGNEE_TYPE_SLUG.MINE) { + if (this.activeAssigneeTab === 'me') { copyList = this.mineChatsList.slice(); - } else if ( - this.activeAssigneeTab === wootConstants.ASSIGNEE_TYPE_SLUG.UNASSIGNED - ) { + } else if (this.activeAssigneeTab === 'unassigned') { copyList = this.unAssignedChatsList.slice(); } else { copyList = this.allChatList.slice(); diff --git a/app/javascript/dashboard/components/buttons/ResolveButton.vue b/app/javascript/dashboard/components/buttons/ResolveButton.vue index d8ca582cb..9b1181426 100644 --- a/app/javascript/dashboard/components/buttons/ResolveButton.vue +++ b/app/javascript/dashboard/components/buttons/ResolveButton.vue @@ -16,8 +16,12 @@ /* global bus */ import { mapGetters } from 'vuex'; import Spinner from 'shared/components/Spinner'; +import wootConstants from '../../constants'; export default { + components: { + Spinner, + }, props: ['conversationId'], data() { return { @@ -29,19 +33,23 @@ export default { currentChat: 'getSelectedChat', }), currentStatus() { - const ButtonName = this.currentChat.status === 0 ? 'Resolve' : 'Reopen'; + const ButtonName = + this.currentChat.status === wootConstants.STATUS_TYPE.OPEN + ? this.$t('CONVERSATION.HEADER.RESOLVE_ACTION') + : this.$t('CONVERSATION.HEADER.REOPEN_ACTION'); return ButtonName; }, buttonClass() { - return this.currentChat.status === 0 ? 'success' : 'warning'; + return this.currentChat.status === wootConstants.STATUS_TYPE.OPEN + ? 'success' + : 'warning'; }, buttonIconClass() { - return this.currentChat.status === 0 ? 'ion-checkmark' : 'ion-refresh'; + return this.currentChat.status === wootConstants.STATUS_TYPE.OPEN + ? 'ion-checkmark' + : 'ion-refresh'; }, }, - components: { - Spinner, - }, methods: { toggleStatus() { this.isLoading = true; diff --git a/app/javascript/dashboard/components/widgets/ChatTypeTabs.vue b/app/javascript/dashboard/components/widgets/ChatTypeTabs.vue index 3615543c6..0ed5e542b 100644 --- a/app/javascript/dashboard/components/widgets/ChatTypeTabs.vue +++ b/app/javascript/dashboard/components/widgets/ChatTypeTabs.vue @@ -1,15 +1,15 @@