diff --git a/app/javascript/dashboard/api/inbox/conversation.js b/app/javascript/dashboard/api/inbox/conversation.js index 159e77f0e..b520156a4 100644 --- a/app/javascript/dashboard/api/inbox/conversation.js +++ b/app/javascript/dashboard/api/inbox/conversation.js @@ -33,12 +33,17 @@ class ConversationApi extends ApiClient { } assignAgent({ conversationId, agentId }) { - axios.post( + return axios.post( `${this.url}/${conversationId}/assignments?assignee_id=${agentId}`, {} ); } + assignTeam({ conversationId, teamId }) { + const params = { team_id: teamId }; + return axios.post(`${this.url}/${conversationId}/assignments`, params); + } + markMessageRead({ id }) { return axios.post(`${this.url}/${id}/update_last_seen`); } diff --git a/app/javascript/dashboard/api/specs/inbox/conversation.spec.js b/app/javascript/dashboard/api/specs/inbox/conversation.spec.js index 31be5e87e..6d4cdeec4 100644 --- a/app/javascript/dashboard/api/specs/inbox/conversation.spec.js +++ b/app/javascript/dashboard/api/specs/inbox/conversation.spec.js @@ -11,6 +11,7 @@ describe('#ConversationAPI', () => { expect(conversationAPI).toHaveProperty('delete'); expect(conversationAPI).toHaveProperty('toggleStatus'); expect(conversationAPI).toHaveProperty('assignAgent'); + expect(conversationAPI).toHaveProperty('assignTeam'); expect(conversationAPI).toHaveProperty('markMessageRead'); expect(conversationAPI).toHaveProperty('toggleTyping'); expect(conversationAPI).toHaveProperty('mute'); diff --git a/app/javascript/dashboard/assets/scss/plugins/_multiselect.scss b/app/javascript/dashboard/assets/scss/plugins/_multiselect.scss index b7a27dff6..36df6af14 100644 --- a/app/javascript/dashboard/assets/scss/plugins/_multiselect.scss +++ b/app/javascript/dashboard/assets/scss/plugins/_multiselect.scss @@ -16,7 +16,7 @@ margin-bottom: var(--space-normal); .multiselect--active { - > .multiselect__tags { + >.multiselect__tags { border-color: $color-woot; } } @@ -124,6 +124,7 @@ } .sidebar-labels-wrap { + &.has-edited, &:hover { .multiselect { @@ -132,16 +133,48 @@ } .multiselect { - > .multiselect__select { + >.multiselect__select { visibility: hidden; } - > .multiselect__tags { + >.multiselect__tags { border-color: transparent; } - &.multiselect--active > .multiselect__tags { + &.multiselect--active>.multiselect__tags { border-color: $color-woot; } } } + + +.multiselect-wrap--small { + $multiselect-height: 3.8rem; + + .multiselect__tags, + .multiselect__input, + .multiselect { + background: var(--white); + font-size: var(--font-size-small); + height: $multiselect-height; + min-height: $multiselect-height; + } + + .multiselect__input { + height: $multiselect-height - $space-micro; + min-height: $multiselect-height - $space-micro; + } + + .multiselect__single { + font-size: var(--font-size-small); + padding: var(--space-small) 0; + } + + .multiselect__placeholder { + padding: var(--space-small) 0; + } + + .multiselect__select { + min-height: $multiselect-height; + } +} diff --git a/app/javascript/dashboard/i18n/locale/en/conversation.json b/app/javascript/dashboard/i18n/locale/en/conversation.json index bf7c4550a..4fccad865 100644 --- a/app/javascript/dashboard/i18n/locale/en/conversation.json +++ b/app/javascript/dashboard/i18n/locale/en/conversation.json @@ -51,6 +51,7 @@ "VISIBLE_TO_AGENTS": "Private Note: Only visible to you and your team", "CHANGE_STATUS": "Conversation status changed", "CHANGE_AGENT": "Conversation Assignee changed", + "CHANGE_TEAM": "Conversation team changed", "SENT_BY": "Sent by:", "ASSIGNMENT": { "SELECT_AGENT": "Select Agent", @@ -98,5 +99,14 @@ "DESCRIPTION": "Labels provide an easier way to categorize your conversation. Create some labels like #support-enquiry, #billing-question etc., so that you can use them in a conversation later.", "NEW_LINK": "Click here to create tags" } + }, + "CONVERSATION_SIDEBAR": { + "DETAILS_TITLE": "Conversations Details", + "ASSIGNEE_LABEL": "Assigned Agent", + "TEAM_LABEL": "Assigned Team", + "SELECT": { + "PLACEHOLDER": "None" + } } + } diff --git a/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue b/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue index ccf65f329..9939b80dd 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue +++ b/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue @@ -4,6 +4,43 @@ +
+

+ {{ $t('CONVERSATION_SIDEBAR.DETAILS_TITLE') }} +

+
+ + +
+
+ + +
+
import { mapGetters } from 'vuex'; +import alertMixin from 'shared/mixins/alertMixin'; import ContactConversations from './ContactConversations.vue'; import ContactDetailsItem from './ContactDetailsItem.vue'; @@ -83,6 +121,7 @@ export default { ContactInfo, ConversationLabels, }, + mixins: [alertMixin], props: { conversationId: { type: [Number, String], @@ -96,6 +135,8 @@ export default { computed: { ...mapGetters({ currentChat: 'getSelectedChat', + agents: 'agents/getVerifiedAgents', + teams: 'teams/getTeams', }), currentConversationMetaData() { return this.$store.getters[ @@ -159,6 +200,46 @@ export default { contact() { return this.$store.getters['contacts/getContact'](this.contactId); }, + agentsList() { + return [{ id: 0, name: 'None' }, ...this.agents]; + }, + teamsList() { + return [{ id: 0, name: 'None' }, ...this.teams]; + }, + assignedAgent: { + get() { + return this.currentChat.meta.assignee; + }, + set(agent) { + const agentId = agent ? agent.id : 0; + this.$store.dispatch('setCurrentChatAssignee', agent); + this.$store + .dispatch('assignAgent', { + conversationId: this.currentChat.id, + agentId, + }) + .then(() => { + this.showAlert(this.$t('CONVERSATION.CHANGE_AGENT')); + }); + }, + }, + assignedTeam: { + get() { + return this.currentChat.meta.team; + }, + set(team) { + const teamId = team ? team.id : 0; + this.$store.dispatch('setCurrentChatTeam', team); + this.$store + .dispatch('assignTeam', { + conversationId: this.currentChat.id, + teamId, + }) + .then(() => { + this.showAlert(this.$t('CONVERSATION.CHANGE_TEAM')); + }); + }, + }, }, watch: { conversationId(newConversationId, prevConversationId) { @@ -191,12 +272,10 @@ export default { diff --git a/app/javascript/dashboard/store/modules/conversations/actions.js b/app/javascript/dashboard/store/modules/conversations/actions.js index 88ca6d710..8e4e20954 100644 --- a/app/javascript/dashboard/store/modules/conversations/actions.js +++ b/app/javascript/dashboard/store/modules/conversations/actions.js @@ -103,18 +103,38 @@ const actions = { } }, - assignAgent: async ({ commit }, { conversationId, agentId }) => { + assignAgent: async ({ dispatch }, { conversationId, agentId }) => { try { const response = await ConversationApi.assignAgent({ conversationId, agentId, }); - commit(types.default.ASSIGN_AGENT, response.data); + dispatch('setCurrentChatAssignee', response.data); } catch (error) { // Handle error } }, + setCurrentChatAssignee({ commit }, assignee) { + commit(types.default.ASSIGN_AGENT, assignee); + }, + + assignTeam: async ({ dispatch }, { conversationId, teamId }) => { + try { + const response = await ConversationApi.assignTeam({ + conversationId, + teamId, + }); + dispatch('setCurrentChatTeam', response.data); + } catch (error) { + // Handle error + } + }, + + setCurrentChatTeam({ commit }, team) { + commit(types.default.ASSIGN_TEAM, team); + }, + toggleStatus: async ({ commit }, data) => { try { const response = await ConversationApi.toggleStatus(data); diff --git a/app/javascript/dashboard/store/modules/conversations/index.js b/app/javascript/dashboard/store/modules/conversations/index.js index ae9945bf3..d9721b4fc 100644 --- a/app/javascript/dashboard/store/modules/conversations/index.js +++ b/app/javascript/dashboard/store/modules/conversations/index.js @@ -61,7 +61,12 @@ export const mutations = { [types.default.ASSIGN_AGENT](_state, assignee) { const [chat] = getSelectedChatConversation(_state); - chat.meta.assignee = assignee; + Vue.set(chat.meta, 'assignee', assignee); + }, + + [types.default.ASSIGN_TEAM](_state, team) { + const [chat] = getSelectedChatConversation(_state); + Vue.set(chat.meta, 'team', team); }, [types.default.RESOLVE_CONVERSATION](_state, status) { @@ -145,7 +150,7 @@ export const mutations = { // Update assignee on action cable message [types.default.UPDATE_ASSIGNEE](_state, payload) { const [chat] = _state.allConversations.filter(c => c.id === payload.id); - chat.meta.assignee = payload.assignee; + Vue.set(chat.meta, 'assignee', payload.assignee); }, [types.default.UPDATE_CONVERSATION_CONTACT]( diff --git a/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js b/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js index 666f5fa4b..a4e59f5c3 100644 --- a/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js +++ b/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js @@ -189,4 +189,52 @@ describe('#actions', () => { expect(commit.mock.calls).toEqual([]); }); }); + + describe('#assignAgent', () => { + it('sends correct mutations if assignment is successful', async () => { + axios.post.mockResolvedValue({ + data: { id: 1, name: 'User' }, + }); + await actions.assignAgent({ commit }, { conversationId: 1, agentId: 1 }); + expect(commit).toHaveBeenCalledTimes(0); + expect(commit.mock.calls).toEqual([]); + }); + }); + + describe('#setCurrentChatAssignee', () => { + it('sends correct mutations if assignment is successful', async () => { + axios.post.mockResolvedValue({ + data: { id: 1, name: 'User' }, + }); + await actions.setCurrentChatAssignee({ commit }, { id: 1, name: 'User' }); + expect(commit).toHaveBeenCalledTimes(1); + expect(commit.mock.calls).toEqual([ + ['ASSIGN_AGENT', { id: 1, name: 'User' }], + ]); + }); + }); + + describe('#assignTeam', () => { + it('sends correct mutations if assignment is successful', async () => { + axios.post.mockResolvedValue({ + data: { id: 1, name: 'Team' }, + }); + await actions.assignTeam({ commit }, { conversationId: 1, teamId: 1 }); + expect(commit).toHaveBeenCalledTimes(0); + expect(commit.mock.calls).toEqual([]); + }); + }); + + describe('#setCurrentChatTeam', () => { + it('sends correct mutations if assignment is successful', async () => { + axios.post.mockResolvedValue({ + data: { id: 1, name: 'Team' }, + }); + await actions.setCurrentChatTeam({ commit }, { id: 1, name: 'Team' }); + expect(commit).toHaveBeenCalledTimes(1); + expect(commit.mock.calls).toEqual([ + ['ASSIGN_TEAM', { id: 1, name: 'Team' }], + ]); + }); + }); }); diff --git a/app/javascript/dashboard/store/mutation-types.js b/app/javascript/dashboard/store/mutation-types.js index 433c4c6b0..355058cba 100755 --- a/app/javascript/dashboard/store/mutation-types.js +++ b/app/javascript/dashboard/store/mutation-types.js @@ -28,6 +28,7 @@ export default { MUTE_CONVERSATION: 'MUTE_CONVERSATION', UNMUTE_CONVERSATION: 'UNMUTE_CONVERSATION', ASSIGN_AGENT: 'ASSIGN_AGENT', + ASSIGN_TEAM: 'ASSIGN_TEAM', SET_CHAT_META: 'SET_CHAT_META', ADD_MESSAGE: 'ADD_MESSAGE', ADD_PENDING_MESSAGE: 'ADD_PENDING_MESSAGE', diff --git a/app/views/api/v1/conversations/partials/_conversation.json.jbuilder b/app/views/api/v1/conversations/partials/_conversation.json.jbuilder index 50afe3b44..12879b3d5 100644 --- a/app/views/api/v1/conversations/partials/_conversation.json.jbuilder +++ b/app/views/api/v1/conversations/partials/_conversation.json.jbuilder @@ -8,6 +8,11 @@ json.meta do json.partial! 'api/v1/models/agent.json.jbuilder', resource: conversation.assignee end end + if conversation.team.present? + json.team do + json.partial! 'api/v1/models/team.json.jbuilder', resource: conversation.team + end + end end json.id conversation.display_id