feat: Display "Snoozed Until" time on conversation header (#3028)
This commit is contained in:
parent
49ac4a4400
commit
57abdc4d5f
19 changed files with 217 additions and 172 deletions
|
@ -14,6 +14,7 @@
|
||||||
@import 'helper-classes';
|
@import 'helper-classes';
|
||||||
@import 'formulate';
|
@import 'formulate';
|
||||||
@import 'date-picker';
|
@import 'date-picker';
|
||||||
|
@import 'utility-helpers';
|
||||||
|
|
||||||
@import 'foundation-sites/scss/foundation';
|
@import 'foundation-sites/scss/foundation';
|
||||||
@import '~bourbon/core/bourbon';
|
@import '~bourbon/core/bourbon';
|
||||||
|
|
|
@ -42,14 +42,6 @@ $resolve-button-width: 13.2rem;
|
||||||
margin-right: var(--space-normal);
|
margin-right: var(--space-normal);
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
||||||
.user--name {
|
|
||||||
@include margin(0);
|
|
||||||
display: inline-block;
|
|
||||||
font-size: $font-size-medium;
|
|
||||||
line-height: 1.3;
|
|
||||||
text-transform: capitalize;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user--profile__meta {
|
.user--profile__meta {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
@ -59,12 +51,6 @@ $resolve-button-width: 13.2rem;
|
||||||
margin-left: $space-slab;
|
margin-left: $space-slab;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user--profile__button {
|
|
||||||
font-size: $font-size-mini;
|
|
||||||
margin-top: $space-micro;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
35
app/javascript/dashboard/components/widgets/InboxName.vue
Normal file
35
app/javascript/dashboard/components/widgets/InboxName.vue
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<template>
|
||||||
|
<span class="inbox--name">
|
||||||
|
<i :class="computedInboxClass" />
|
||||||
|
{{ inbox.name }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { getInboxClassByType } from 'dashboard/helper/inbox';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
inbox: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
computedInboxClass() {
|
||||||
|
const { phone_number: phoneNumber, channel_type: type } = this.inbox;
|
||||||
|
const classByType = getInboxClassByType(type, phoneNumber);
|
||||||
|
return classByType;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.inbox--name {
|
||||||
|
padding: var(--space-micro) 0;
|
||||||
|
line-height: var(--space-slab);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
background: none;
|
||||||
|
color: var(--s-500);
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -19,11 +19,7 @@
|
||||||
/>
|
/>
|
||||||
<div class="conversation--details columns">
|
<div class="conversation--details columns">
|
||||||
<div class="conversation--metadata">
|
<div class="conversation--metadata">
|
||||||
<span v-if="showInboxName" class="label">
|
<inbox-name v-if="showInboxName" :inbox="inbox" />
|
||||||
<i :class="computedInboxClass" />
|
|
||||||
{{ inboxName }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
<span
|
||||||
v-if="showAssignee && assignee"
|
v-if="showAssignee && assignee"
|
||||||
class="label assignee-label text-truncate"
|
class="label assignee-label text-truncate"
|
||||||
|
@ -72,16 +68,17 @@
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
||||||
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||||
import { getInboxClassByType } from 'dashboard/helper/inbox';
|
|
||||||
import Thumbnail from '../Thumbnail';
|
import Thumbnail from '../Thumbnail';
|
||||||
import conversationMixin from '../../../mixins/conversations';
|
import conversationMixin from '../../../mixins/conversations';
|
||||||
import timeMixin from '../../../mixins/time';
|
import timeMixin from '../../../mixins/time';
|
||||||
import router from '../../../routes';
|
import router from '../../../routes';
|
||||||
import { frontendURL, conversationUrl } from '../../../helper/URLHelper';
|
import { frontendURL, conversationUrl } from '../../../helper/URLHelper';
|
||||||
|
import InboxName from '../InboxName';
|
||||||
import inboxMixin from 'shared/mixins/inboxMixin';
|
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
InboxName,
|
||||||
Thumbnail,
|
Thumbnail,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -192,12 +189,6 @@ export default {
|
||||||
return stateInbox;
|
return stateInbox;
|
||||||
},
|
},
|
||||||
|
|
||||||
computedInboxClass() {
|
|
||||||
const { phone_number: phoneNumber, channel_type: type } = this.inbox;
|
|
||||||
const classByType = getInboxClassByType(type, phoneNumber);
|
|
||||||
return classByType;
|
|
||||||
},
|
|
||||||
|
|
||||||
showInboxName() {
|
showInboxName() {
|
||||||
return (
|
return (
|
||||||
!this.hideInboxName &&
|
!this.hideInboxName &&
|
||||||
|
@ -244,15 +235,6 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.conversation--details .label {
|
|
||||||
padding: var(--space-micro) 0 var(--space-micro) 0;
|
|
||||||
line-height: var(--space-slab);
|
|
||||||
font-weight: var(--font-weight-medium);
|
|
||||||
background: none;
|
|
||||||
color: var(--s-500);
|
|
||||||
font-size: var(--font-size-mini);
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation--details {
|
.conversation--details {
|
||||||
.conversation--user {
|
.conversation--user {
|
||||||
padding-top: var(--space-micro);
|
padding-top: var(--space-micro);
|
||||||
|
@ -276,6 +258,15 @@ export default {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding-right: var(--space-normal);
|
padding-right: var(--space-normal);
|
||||||
|
|
||||||
|
.label {
|
||||||
|
padding: var(--space-micro) 0 var(--space-micro) 0;
|
||||||
|
line-height: var(--space-slab);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
background: none;
|
||||||
|
color: var(--s-500);
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
}
|
||||||
|
|
||||||
.assignee-label {
|
.assignee-label {
|
||||||
max-width: 50%;
|
max-width: 50%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,22 +12,25 @@
|
||||||
<h3 class="user--name text-truncate">
|
<h3 class="user--name text-truncate">
|
||||||
{{ currentContact.name }}
|
{{ currentContact.name }}
|
||||||
</h3>
|
</h3>
|
||||||
|
<div class="conversation--header--actions">
|
||||||
|
<inbox-name :inbox="inbox" class="margin-right-small" />
|
||||||
|
<span
|
||||||
|
v-if="isSnoozed"
|
||||||
|
class="snoozed--display-text margin-right-small"
|
||||||
|
>
|
||||||
|
{{ snoozedDisplayText }}
|
||||||
|
</span>
|
||||||
<woot-button
|
<woot-button
|
||||||
class="user--profile__button"
|
class="user--profile__button margin-right-small"
|
||||||
size="small"
|
size="small"
|
||||||
variant="link"
|
variant="link"
|
||||||
@click="$emit('contact-panel-toggle')"
|
@click="$emit('contact-panel-toggle')"
|
||||||
>
|
>
|
||||||
{{
|
{{ contactPanelToggleText }}
|
||||||
`${
|
|
||||||
isContactPanelOpen
|
|
||||||
? $t('CONVERSATION.HEADER.CLOSE')
|
|
||||||
: $t('CONVERSATION.HEADER.OPEN')
|
|
||||||
} ${$t('CONVERSATION.HEADER.DETAILS')}`
|
|
||||||
}}
|
|
||||||
</woot-button>
|
</woot-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="header-actions-wrap"
|
class="header-actions-wrap"
|
||||||
:class="{ 'has-open-sidebar': isContactPanelOpen }"
|
:class="{ 'has-open-sidebar': isContactPanelOpen }"
|
||||||
|
@ -44,9 +47,13 @@ import agentMixin from '../../../mixins/agentMixin.js';
|
||||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||||
import inboxMixin from 'shared/mixins/inboxMixin';
|
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||||
import { hasPressedAltAndOKey } from 'shared/helpers/KeyboardHelpers';
|
import { hasPressedAltAndOKey } from 'shared/helpers/KeyboardHelpers';
|
||||||
|
import wootConstants from '../../../constants';
|
||||||
|
import differenceInHours from 'date-fns/differenceInHours';
|
||||||
|
import InboxName from '../InboxName';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
InboxName,
|
||||||
MoreActions,
|
MoreActions,
|
||||||
Thumbnail,
|
Thumbnail,
|
||||||
},
|
},
|
||||||
|
@ -61,39 +68,50 @@ export default {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
currentChatAssignee: null,
|
|
||||||
inboxId: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
uiFlags: 'inboxAssignableAgents/getUIFlags',
|
uiFlags: 'inboxAssignableAgents/getUIFlags',
|
||||||
currentChat: 'getSelectedChat',
|
currentChat: 'getSelectedChat',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
chatMetadata() {
|
chatMetadata() {
|
||||||
return this.chat.meta;
|
return this.chat.meta;
|
||||||
},
|
},
|
||||||
|
|
||||||
inbox() {
|
|
||||||
const { inbox_id: inboxId } = this.chat;
|
|
||||||
const stateInbox = this.$store.getters['inboxes/getInbox'](inboxId);
|
|
||||||
return stateInbox;
|
|
||||||
},
|
|
||||||
|
|
||||||
currentContact() {
|
currentContact() {
|
||||||
return this.$store.getters['contacts/getContact'](
|
return this.$store.getters['contacts/getContact'](
|
||||||
this.chat.meta.sender.id
|
this.chat.meta.sender.id
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
isSnoozed() {
|
||||||
|
return this.currentChat.status === wootConstants.STATUS_TYPE.SNOOZED;
|
||||||
},
|
},
|
||||||
mounted() {
|
snoozedDisplayText() {
|
||||||
|
const { snoozed_until: snoozedUntil } = this.currentChat;
|
||||||
|
if (snoozedUntil) {
|
||||||
|
// When the snooze is applied, it schedules the unsnooze event to next day/week 9AM.
|
||||||
|
// By that logic if the time difference is less than or equal to 24 + 9 hours we can consider it tomorrow.
|
||||||
|
const MAX_TIME_DIFFERENCE = 33;
|
||||||
|
const isSnoozedUntilTomorrow =
|
||||||
|
differenceInHours(new Date(snoozedUntil), new Date()) <=
|
||||||
|
MAX_TIME_DIFFERENCE;
|
||||||
|
return this.$t(
|
||||||
|
isSnoozedUntilTomorrow
|
||||||
|
? 'CONVERSATION.HEADER.SNOOZED_UNTIL_TOMORROW'
|
||||||
|
: 'CONVERSATION.HEADER.SNOOZED_UNTIL_NEXT_WEEK'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.$t('CONVERSATION.HEADER.SNOOZED_UNTIL_NEXT_REPLY');
|
||||||
|
},
|
||||||
|
contactPanelToggleText() {
|
||||||
|
return `${
|
||||||
|
this.isContactPanelOpen
|
||||||
|
? this.$t('CONVERSATION.HEADER.CLOSE')
|
||||||
|
: this.$t('CONVERSATION.HEADER.OPEN')
|
||||||
|
} ${this.$t('CONVERSATION.HEADER.DETAILS')}`;
|
||||||
|
},
|
||||||
|
inbox() {
|
||||||
const { inbox_id: inboxId } = this.chat;
|
const { inbox_id: inboxId } = this.chat;
|
||||||
this.inboxId = inboxId;
|
return this.$store.getters['inboxes/getInbox'](inboxId);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -129,4 +147,28 @@ export default {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user--name {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: var(--font-size-medium);
|
||||||
|
line-height: 1.3;
|
||||||
|
margin: 0;
|
||||||
|
text-transform: capitalize;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conversation--header--actions {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
|
||||||
|
.user--profile__button {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snoozed--display-text {
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
color: var(--y-900);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,41 +1,29 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex-container actions--container">
|
<div class="flex-container actions--container">
|
||||||
|
<woot-button
|
||||||
|
v-if="!currentChat.muted"
|
||||||
|
v-tooltip="$t('CONTACT_PANEL.MUTE_CONTACT')"
|
||||||
|
class="hollow secondary actions--button"
|
||||||
|
icon="ion-volume-mute"
|
||||||
|
@click="mute"
|
||||||
|
/>
|
||||||
|
<woot-button
|
||||||
|
v-else
|
||||||
|
v-tooltip.left="$t('CONTACT_PANEL.UNMUTE_CONTACT')"
|
||||||
|
class="hollow secondary actions--button"
|
||||||
|
icon="ion-volume-medium"
|
||||||
|
@click="unmute"
|
||||||
|
/>
|
||||||
|
<woot-button
|
||||||
|
v-tooltip="$t('CONTACT_PANEL.SEND_TRANSCRIPT')"
|
||||||
|
class="hollow secondary actions--button"
|
||||||
|
icon="ion-share"
|
||||||
|
@click="toggleEmailActionsModal"
|
||||||
|
/>
|
||||||
<resolve-action
|
<resolve-action
|
||||||
:conversation-id="currentChat.id"
|
:conversation-id="currentChat.id"
|
||||||
:status="currentChat.status"
|
:status="currentChat.status"
|
||||||
/>
|
/>
|
||||||
<woot-button
|
|
||||||
class="more--button"
|
|
||||||
variant="clear"
|
|
||||||
size="large"
|
|
||||||
color-scheme="secondary"
|
|
||||||
icon="ion-android-more-vertical"
|
|
||||||
@click="toggleConversationActions"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-if="showConversationActions"
|
|
||||||
v-on-clickaway="hideConversationActions"
|
|
||||||
class="dropdown-pane dropdowm--bottom"
|
|
||||||
:class="{ 'dropdown-pane--open': showConversationActions }"
|
|
||||||
>
|
|
||||||
<woot-dropdown-menu>
|
|
||||||
<woot-dropdown-item v-if="!currentChat.muted">
|
|
||||||
<button class="button clear alert " @click="mute">
|
|
||||||
<span>{{ $t('CONTACT_PANEL.MUTE_CONTACT') }}</span>
|
|
||||||
</button>
|
|
||||||
</woot-dropdown-item>
|
|
||||||
<woot-dropdown-item v-else>
|
|
||||||
<button class="button clear alert" @click="unmute">
|
|
||||||
<span>{{ $t('CONTACT_PANEL.UNMUTE_CONTACT') }}</span>
|
|
||||||
</button>
|
|
||||||
</woot-dropdown-item>
|
|
||||||
<woot-dropdown-item>
|
|
||||||
<button class="button clear" @click="toggleEmailActionsModal">
|
|
||||||
{{ $t('CONTACT_PANEL.SEND_TRANSCRIPT') }}
|
|
||||||
</button>
|
|
||||||
</woot-dropdown-item>
|
|
||||||
</woot-dropdown-menu>
|
|
||||||
</div>
|
|
||||||
<email-transcript-modal
|
<email-transcript-modal
|
||||||
v-if="showEmailActionsModal"
|
v-if="showEmailActionsModal"
|
||||||
:show="showEmailActionsModal"
|
:show="showEmailActionsModal"
|
||||||
|
@ -50,13 +38,9 @@ import { mixin as clickaway } from 'vue-clickaway';
|
||||||
import alertMixin from 'shared/mixins/alertMixin';
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
import EmailTranscriptModal from './EmailTranscriptModal';
|
import EmailTranscriptModal from './EmailTranscriptModal';
|
||||||
import ResolveAction from '../../buttons/ResolveAction';
|
import ResolveAction from '../../buttons/ResolveAction';
|
||||||
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
|
|
||||||
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
WootDropdownMenu,
|
|
||||||
WootDropdownItem,
|
|
||||||
EmailTranscriptModal,
|
EmailTranscriptModal,
|
||||||
ResolveAction,
|
ResolveAction,
|
||||||
},
|
},
|
||||||
|
@ -97,7 +81,16 @@ export default {
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import '~dashboard/assets/scss/mixins';
|
.actions--container {
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
font-size: var(--font-size-large);
|
||||||
|
margin-right: var(--space-small);
|
||||||
|
border-color: var(--color-border);
|
||||||
|
color: var(--s-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.more--button {
|
.more--button {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { createLocalVue, mount } from '@vue/test-utils';
|
import { createLocalVue, mount } from '@vue/test-utils';
|
||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
import VueI18n from 'vue-i18n';
|
import VueI18n from 'vue-i18n';
|
||||||
|
import VTooltip from 'v-tooltip';
|
||||||
|
|
||||||
import Button from 'dashboard/components/buttons/Button';
|
import Button from 'dashboard/components/buttons/Button';
|
||||||
import i18n from 'dashboard/i18n';
|
import i18n from 'dashboard/i18n';
|
||||||
|
@ -10,6 +11,7 @@ import MoreActions from '../MoreActions';
|
||||||
const localVue = createLocalVue();
|
const localVue = createLocalVue();
|
||||||
localVue.use(Vuex);
|
localVue.use(Vuex);
|
||||||
localVue.use(VueI18n);
|
localVue.use(VueI18n);
|
||||||
|
localVue.use(VTooltip);
|
||||||
|
|
||||||
localVue.component('woot-button', Button);
|
localVue.component('woot-button', Button);
|
||||||
|
|
||||||
|
@ -63,21 +65,9 @@ describe('MoveActions', () => {
|
||||||
moreActions = mount(MoreActions, { store, localVue, i18n: i18nConfig });
|
moreActions = mount(MoreActions, { store, localVue, i18n: i18nConfig });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('opens the menu when user clicks "more"', async () => {
|
|
||||||
expect(moreActions.find('.dropdown-pane').exists()).toBe(false);
|
|
||||||
|
|
||||||
await moreActions.find('.more--button').trigger('click');
|
|
||||||
|
|
||||||
expect(moreActions.find('.dropdown-pane').exists()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('muting discussion', () => {
|
describe('muting discussion', () => {
|
||||||
it('triggers "muteConversation"', async () => {
|
it('triggers "muteConversation"', async () => {
|
||||||
await moreActions.find('.more--button').trigger('click');
|
await moreActions.find('button:first-child').trigger('click');
|
||||||
|
|
||||||
await moreActions
|
|
||||||
.find('.dropdown-pane button:first-child')
|
|
||||||
.trigger('click');
|
|
||||||
|
|
||||||
expect(muteConversation).toBeCalledWith(
|
expect(muteConversation).toBeCalledWith(
|
||||||
expect.any(Object),
|
expect.any(Object),
|
||||||
|
@ -87,11 +77,7 @@ describe('MoveActions', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows alert', async () => {
|
it('shows alert', async () => {
|
||||||
await moreActions.find('.more--button').trigger('click');
|
await moreActions.find('button:first-child').trigger('click');
|
||||||
|
|
||||||
await moreActions
|
|
||||||
.find('.dropdown-pane button:first-child')
|
|
||||||
.trigger('click');
|
|
||||||
|
|
||||||
expect(window.bus.$emit).toBeCalledWith(
|
expect(window.bus.$emit).toBeCalledWith(
|
||||||
'newToastMessage',
|
'newToastMessage',
|
||||||
|
@ -106,11 +92,7 @@ describe('MoveActions', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('triggers "unmuteConversation"', async () => {
|
it('triggers "unmuteConversation"', async () => {
|
||||||
await moreActions.find('.more--button').trigger('click');
|
await moreActions.find('button:first-child').trigger('click');
|
||||||
|
|
||||||
await moreActions
|
|
||||||
.find('.dropdown-pane button:first-child')
|
|
||||||
.trigger('click');
|
|
||||||
|
|
||||||
expect(unmuteConversation).toBeCalledWith(
|
expect(unmuteConversation).toBeCalledWith(
|
||||||
expect.any(Object),
|
expect.any(Object),
|
||||||
|
@ -120,11 +102,7 @@ describe('MoveActions', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows alert', async () => {
|
it('shows alert', async () => {
|
||||||
await moreActions.find('.more--button').trigger('click');
|
await moreActions.find('button:first-child').trigger('click');
|
||||||
|
|
||||||
await moreActions
|
|
||||||
.find('.dropdown-pane button:first-child')
|
|
||||||
.trigger('click');
|
|
||||||
|
|
||||||
expect(window.bus.$emit).toBeCalledWith(
|
expect(window.bus.$emit).toBeCalledWith(
|
||||||
'newToastMessage',
|
'newToastMessage',
|
||||||
|
|
|
@ -39,7 +39,10 @@
|
||||||
"OPEN_ACTION": "Open",
|
"OPEN_ACTION": "Open",
|
||||||
"OPEN": "More",
|
"OPEN": "More",
|
||||||
"CLOSE": "Close",
|
"CLOSE": "Close",
|
||||||
"DETAILS": "details"
|
"DETAILS": "details",
|
||||||
|
"SNOOZED_UNTIL_TOMORROW": "Snoozed until tomorrow",
|
||||||
|
"SNOOZED_UNTIL_NEXT_WEEK": "Snoozed until next week",
|
||||||
|
"SNOOZED_UNTIL_NEXT_REPLY": "Snoozed until next reply"
|
||||||
},
|
},
|
||||||
"RESOLVE_DROPDOWN": {
|
"RESOLVE_DROPDOWN": {
|
||||||
"MARK_PENDING": "Mark as pending",
|
"MARK_PENDING": "Mark as pending",
|
||||||
|
|
|
@ -91,7 +91,6 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '~dashboard/assets/scss/_utility-helpers.scss';
|
|
||||||
.page-title {
|
.page-title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,9 @@ const actions = {
|
||||||
commit(types.default.SET_LIST_LOADING_STATUS);
|
commit(types.default.SET_LIST_LOADING_STATUS);
|
||||||
try {
|
try {
|
||||||
const response = await ConversationApi.get(params);
|
const response = await ConversationApi.get(params);
|
||||||
const { data } = response.data;
|
const {
|
||||||
const { payload: chatList, meta: metaData } = data;
|
data: { payload: chatList, meta: metaData },
|
||||||
|
} = response.data;
|
||||||
commit(types.default.SET_ALL_CONVERSATION, chatList);
|
commit(types.default.SET_ALL_CONVERSATION, chatList);
|
||||||
dispatch('conversationStats/set', metaData);
|
dispatch('conversationStats/set', metaData);
|
||||||
dispatch('conversationLabels/setBulkConversationLabels', chatList);
|
dispatch('conversationLabels/setBulkConversationLabels', chatList);
|
||||||
|
@ -36,10 +37,7 @@ const actions = {
|
||||||
);
|
);
|
||||||
dispatch(
|
dispatch(
|
||||||
'conversationPage/setCurrentPage',
|
'conversationPage/setCurrentPage',
|
||||||
{
|
{ filter: params.assigneeType, page: params.page },
|
||||||
filter: params.assigneeType,
|
|
||||||
page: params.page,
|
|
||||||
},
|
|
||||||
{ root: true }
|
{ root: true }
|
||||||
);
|
);
|
||||||
if (!chatList.length) {
|
if (!chatList.length) {
|
||||||
|
@ -69,10 +67,7 @@ const actions = {
|
||||||
} = await MessageApi.getPreviousMessages(data);
|
} = await MessageApi.getPreviousMessages(data);
|
||||||
commit(
|
commit(
|
||||||
`conversationMetadata/${types.default.SET_CONVERSATION_METADATA}`,
|
`conversationMetadata/${types.default.SET_CONVERSATION_METADATA}`,
|
||||||
{
|
{ id: data.conversationId, data: meta }
|
||||||
id: data.conversationId,
|
|
||||||
data: meta,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
commit(types.default.SET_PREVIOUS_CONVERSATIONS, {
|
commit(types.default.SET_PREVIOUS_CONVERSATIONS, {
|
||||||
id: data.conversationId,
|
id: data.conversationId,
|
||||||
|
@ -140,14 +135,22 @@ const actions = {
|
||||||
{ conversationId, status, snoozedUntil = null }
|
{ conversationId, status, snoozedUntil = null }
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const response = await ConversationApi.toggleStatus({
|
const {
|
||||||
|
data: {
|
||||||
|
payload: {
|
||||||
|
current_status: updatedStatus,
|
||||||
|
snoozed_until: updatedSnoozedUntil,
|
||||||
|
} = {},
|
||||||
|
} = {},
|
||||||
|
} = await ConversationApi.toggleStatus({
|
||||||
conversationId,
|
conversationId,
|
||||||
status,
|
status,
|
||||||
snoozedUntil,
|
snoozedUntil,
|
||||||
});
|
});
|
||||||
commit(types.default.RESOLVE_CONVERSATION, {
|
commit(types.default.CHANGE_CONVERSATION_STATUS, {
|
||||||
conversationId,
|
conversationId,
|
||||||
status: response.data.payload.current_status,
|
status: updatedStatus,
|
||||||
|
snoozedUntil: updatedSnoozedUntil,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Handle error
|
// Handle error
|
||||||
|
@ -223,11 +226,7 @@ const actions = {
|
||||||
data: { id, agent_last_seen_at: lastSeen },
|
data: { id, agent_last_seen_at: lastSeen },
|
||||||
} = await ConversationApi.markMessageRead(data);
|
} = await ConversationApi.markMessageRead(data);
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() =>
|
() => commit(types.default.MARK_MESSAGE_READ, { id, lastSeen }),
|
||||||
commit(types.default.MARK_MESSAGE_READ, {
|
|
||||||
id,
|
|
||||||
lastSeen,
|
|
||||||
}),
|
|
||||||
4000
|
4000
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -69,9 +69,13 @@ export const mutations = {
|
||||||
Vue.set(chat.meta, 'team', team);
|
Vue.set(chat.meta, 'team', team);
|
||||||
},
|
},
|
||||||
|
|
||||||
[types.default.RESOLVE_CONVERSATION](_state, { conversationId, status }) {
|
[types.default.CHANGE_CONVERSATION_STATUS](
|
||||||
|
_state,
|
||||||
|
{ conversationId, status, snoozedUntil }
|
||||||
|
) {
|
||||||
const conversation =
|
const conversation =
|
||||||
getters.getConversationById(_state)(conversationId) || {};
|
getters.getConversationById(_state)(conversationId) || {};
|
||||||
|
Vue.set(conversation, 'snoozed_until', snoozedUntil);
|
||||||
Vue.set(conversation, 'status', status);
|
Vue.set(conversation, 'status', status);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -217,15 +217,24 @@ describe('#actions', () => {
|
||||||
describe('#toggleStatus', () => {
|
describe('#toggleStatus', () => {
|
||||||
it('sends correct mutations if toggle status is successful', async () => {
|
it('sends correct mutations if toggle status is successful', async () => {
|
||||||
axios.post.mockResolvedValue({
|
axios.post.mockResolvedValue({
|
||||||
data: { payload: { conversation_id: 1, current_status: 'resolved' } },
|
data: {
|
||||||
|
payload: {
|
||||||
|
conversation_id: 1,
|
||||||
|
current_status: 'snoozed',
|
||||||
|
snoozed_until: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
await actions.toggleStatus(
|
await actions.toggleStatus(
|
||||||
{ commit },
|
{ commit },
|
||||||
{ conversationId: 1, status: 'resolved' }
|
{ conversationId: 1, status: 'snoozed' }
|
||||||
);
|
);
|
||||||
expect(commit).toHaveBeenCalledTimes(1);
|
expect(commit).toHaveBeenCalledTimes(1);
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
['RESOLVE_CONVERSATION', { conversationId: 1, status: 'resolved' }],
|
[
|
||||||
|
'CHANGE_CONVERSATION_STATUS',
|
||||||
|
{ conversationId: 1, status: 'snoozed', snoozedUntil: null },
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -161,7 +161,7 @@ describe('#mutations', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#RESOLVE_CONVERSATION', () => {
|
describe('#CHANGE_CONVERSATION_STATUS', () => {
|
||||||
it('updates the conversation status correctly', () => {
|
it('updates the conversation status correctly', () => {
|
||||||
const state = {
|
const state = {
|
||||||
allConversations: [
|
allConversations: [
|
||||||
|
@ -173,7 +173,7 @@ describe('#mutations', () => {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
mutations[types.RESOLVE_CONVERSATION](state, {
|
mutations[types.CHANGE_CONVERSATION_STATUS](state, {
|
||||||
conversationId: '1',
|
conversationId: '1',
|
||||||
status: 'resolved',
|
status: 'resolved',
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,7 +23,7 @@ export default {
|
||||||
SET_CURRENT_CHAT_WINDOW: 'SET_CURRENT_CHAT_WINDOW',
|
SET_CURRENT_CHAT_WINDOW: 'SET_CURRENT_CHAT_WINDOW',
|
||||||
CLEAR_CURRENT_CHAT_WINDOW: 'CLEAR_CURRENT_CHAT_WINDOW',
|
CLEAR_CURRENT_CHAT_WINDOW: 'CLEAR_CURRENT_CHAT_WINDOW',
|
||||||
CLEAR_ALL_MESSAGES: 'CLEAR_ALL_MESSAGES',
|
CLEAR_ALL_MESSAGES: 'CLEAR_ALL_MESSAGES',
|
||||||
RESOLVE_CONVERSATION: 'RESOLVE_CONVERSATION',
|
CHANGE_CONVERSATION_STATUS: 'CHANGE_CONVERSATION_STATUS',
|
||||||
ADD_CONVERSATION: 'ADD_CONVERSATION',
|
ADD_CONVERSATION: 'ADD_CONVERSATION',
|
||||||
UPDATE_CONVERSATION: 'UPDATE_CONVERSATION',
|
UPDATE_CONVERSATION: 'UPDATE_CONVERSATION',
|
||||||
MUTE_CONVERSATION: 'MUTE_CONVERSATION',
|
MUTE_CONVERSATION: 'MUTE_CONVERSATION',
|
||||||
|
|
|
@ -4,12 +4,13 @@ class Conversations::EventDataPresenter < SimpleDelegator
|
||||||
additional_attributes: additional_attributes,
|
additional_attributes: additional_attributes,
|
||||||
can_reply: can_reply?,
|
can_reply: can_reply?,
|
||||||
channel: inbox.try(:channel_type),
|
channel: inbox.try(:channel_type),
|
||||||
|
contact_inbox: contact_inbox,
|
||||||
id: display_id,
|
id: display_id,
|
||||||
inbox_id: inbox_id,
|
inbox_id: inbox_id,
|
||||||
contact_inbox: contact_inbox,
|
|
||||||
messages: push_messages,
|
messages: push_messages,
|
||||||
meta: push_meta,
|
meta: push_meta,
|
||||||
status: status,
|
status: status,
|
||||||
|
snoozed_until: snoozed_until,
|
||||||
unread_count: unread_incoming_messages.count,
|
unread_count: unread_incoming_messages.count,
|
||||||
**push_timestamps
|
**push_timestamps
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ end
|
||||||
|
|
||||||
json.payload do
|
json.payload do
|
||||||
json.success @status
|
json.success @status
|
||||||
json.current_status @conversation.status
|
|
||||||
json.conversation_id @conversation.display_id
|
json.conversation_id @conversation.display_id
|
||||||
|
json.current_status @conversation.status
|
||||||
|
json.snoozed_until @conversation.snoozed_until
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,16 +24,17 @@ else
|
||||||
json.messages conversation.unread_messages.includes([:user, { attachments: [{ file_attachment: [:blob] }] }]).last(10).map(&:push_event_data)
|
json.messages conversation.unread_messages.includes([:user, { attachments: [{ file_attachment: [:blob] }] }]).last(10).map(&:push_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
json.inbox_id conversation.inbox_id
|
json.account_id conversation.account_id
|
||||||
json.status conversation.status
|
json.additional_attributes conversation.additional_attributes
|
||||||
json.muted conversation.muted?
|
|
||||||
json.can_reply conversation.can_reply?
|
|
||||||
json.timestamp conversation.last_activity_at.to_i
|
|
||||||
json.contact_last_seen_at conversation.contact_last_seen_at.to_i
|
|
||||||
json.agent_last_seen_at conversation.agent_last_seen_at.to_i
|
json.agent_last_seen_at conversation.agent_last_seen_at.to_i
|
||||||
json.assignee_last_seen_at conversation.assignee_last_seen_at.to_i
|
json.assignee_last_seen_at conversation.assignee_last_seen_at.to_i
|
||||||
json.unread_count conversation.unread_incoming_messages.count
|
json.can_reply conversation.can_reply?
|
||||||
json.additional_attributes conversation.additional_attributes
|
json.contact_last_seen_at conversation.contact_last_seen_at.to_i
|
||||||
json.custom_attributes conversation.custom_attributes
|
json.custom_attributes conversation.custom_attributes
|
||||||
json.account_id conversation.account_id
|
json.inbox_id conversation.inbox_id
|
||||||
json.labels conversation.label_list
|
json.labels conversation.label_list
|
||||||
|
json.muted conversation.muted?
|
||||||
|
json.snoozed_until conversation.snoozed_until
|
||||||
|
json.status conversation.status
|
||||||
|
json.timestamp conversation.last_activity_at.to_i
|
||||||
|
json.unread_count conversation.unread_incoming_messages.count
|
||||||
|
|
|
@ -354,6 +354,7 @@ RSpec.describe Conversation, type: :model do
|
||||||
timestamp: conversation.last_activity_at.to_i,
|
timestamp: conversation.last_activity_at.to_i,
|
||||||
can_reply: true,
|
can_reply: true,
|
||||||
channel: 'Channel::WebWidget',
|
channel: 'Channel::WebWidget',
|
||||||
|
snoozed_until: conversation.snoozed_until,
|
||||||
contact_last_seen_at: conversation.contact_last_seen_at.to_i,
|
contact_last_seen_at: conversation.contact_last_seen_at.to_i,
|
||||||
agent_last_seen_at: conversation.agent_last_seen_at.to_i,
|
agent_last_seen_at: conversation.agent_last_seen_at.to_i,
|
||||||
unread_count: 0
|
unread_count: 0
|
||||||
|
|
|
@ -22,6 +22,7 @@ RSpec.describe Conversations::EventDataPresenter do
|
||||||
can_reply: conversation.can_reply?,
|
can_reply: conversation.can_reply?,
|
||||||
channel: conversation.inbox.channel_type,
|
channel: conversation.inbox.channel_type,
|
||||||
timestamp: conversation.last_activity_at.to_i,
|
timestamp: conversation.last_activity_at.to_i,
|
||||||
|
snoozed_until: conversation.snoozed_until,
|
||||||
contact_last_seen_at: conversation.contact_last_seen_at.to_i,
|
contact_last_seen_at: conversation.contact_last_seen_at.to_i,
|
||||||
agent_last_seen_at: conversation.agent_last_seen_at.to_i,
|
agent_last_seen_at: conversation.agent_last_seen_at.to_i,
|
||||||
unread_count: 0
|
unread_count: 0
|
||||||
|
|
Loading…
Reference in a new issue