diff --git a/app/javascript/dashboard/components/ChatList.vue b/app/javascript/dashboard/components/ChatList.vue index d1ac4945f..b0e256087 100644 --- a/app/javascript/dashboard/components/ChatList.vue +++ b/app/javascript/dashboard/components/ChatList.vue @@ -97,7 +97,11 @@ @update-conversations="onUpdateConversations" @assign-labels="onAssignLabels" /> -
+
@@ -217,6 +225,7 @@ export default { showDeleteFoldersModal: false, selectedConversations: [], selectedInboxes: [], + isContextMenuOpen: false, }; }, computed: { @@ -584,11 +593,12 @@ export default { this.resetBulkActions(); } }, - async onAssignAgent(agent) { + // Same method used in context menu, conversationId being passed from there. + async onAssignAgent(agent, conversationId = null) { try { await this.$store.dispatch('bulkActions/process', { type: 'Conversation', - ids: this.selectedConversations, + ids: conversationId || this.selectedConversations, fields: { assignee_id: agent.id, }, @@ -599,11 +609,12 @@ export default { this.showAlert(this.$t('BULK_ACTION.ASSIGN_FAILED')); } }, - async onAssignLabels(labels) { + // Same method used in context menu, conversationId being passed from there. + async onAssignLabels(labels, conversationId = null) { try { await this.$store.dispatch('bulkActions/process', { type: 'Conversation', - ids: this.selectedConversations, + ids: conversationId || this.selectedConversations, labels: { add: labels, }, @@ -629,12 +640,27 @@ export default { this.showAlert(this.$t('BULK_ACTION.UPDATE.UPDATE_FAILED')); } }, + toggleConversationStatus(conversationId, status, snoozedUntil) { + this.$store + .dispatch('toggleStatus', { + conversationId, + status, + snoozedUntil, + }) + .then(() => { + this.showAlert(this.$t('CONVERSATION.CHANGE_STATUS')); + this.isLoading = false; + }); + }, allSelectedConversationsStatus(status) { if (!this.selectedConversations.length) return false; return this.selectedConversations.every(item => { return this.$store.getters.getConversationById(item).status === status; }); }, + onContextMenuToggle(state) { + this.isContextMenuOpen = state; + }, }, }; @@ -647,6 +673,13 @@ export default { margin-bottom: var(--space-normal); } +.conversations-list { + // Prevent the list from scrolling if the submenu is opened + &.is-context-menu-open { + overflow: hidden !important; + } +} + .conversations-list-wrap { flex-shrink: 0; width: 34rem; diff --git a/app/javascript/dashboard/components/buttons/ResolveAction.vue b/app/javascript/dashboard/components/buttons/ResolveAction.vue index 810172ae5..afcdc0b1d 100644 --- a/app/javascript/dashboard/components/buttons/ResolveAction.vue +++ b/app/javascript/dashboard/components/buttons/ResolveAction.vue @@ -113,6 +113,7 @@ import { mapGetters } from 'vuex'; import { mixin as clickaway } from 'vue-clickaway'; import alertMixin from 'shared/mixins/alertMixin'; +import snoozeTimesMixin from 'dashboard/mixins/conversation/snoozeTimesMixin.js'; import eventListenerMixins from 'shared/mixins/eventListenerMixins'; import { hasPressedAltAndEKey, @@ -126,13 +127,6 @@ import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue'; import WootDropdownDivider from 'shared/components/ui/dropdown/DropdownDivider'; import wootConstants from '../../constants'; -import { - getUnixTime, - addHours, - addWeeks, - startOfTomorrow, - startOfWeek, -} from 'date-fns'; import { CMD_REOPEN_CONVERSATION, CMD_RESOLVE_CONVERSATION, @@ -146,7 +140,7 @@ export default { WootDropdownSubMenu, WootDropdownDivider, }, - mixins: [clickaway, alertMixin, eventListenerMixins], + mixins: [clickaway, alertMixin, eventListenerMixins, snoozeTimesMixin], props: { conversationId: { type: [String, Number], required: true } }, data() { return { @@ -178,16 +172,6 @@ export default { showAdditionalActions() { return !this.isPending && !this.isSnoozed; }, - snoozeTimes() { - return { - // tomorrow = 9AM next day - tomorrow: getUnixTime(addHours(startOfTomorrow(), 9)), - // next week = 9AM Monday, next week - nextWeek: getUnixTime( - addHours(startOfWeek(addWeeks(new Date(), 1), { weekStartsOn: 1 }), 9) - ), - }; - }, }, mounted() { bus.$on(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation); diff --git a/app/javascript/dashboard/components/index.js b/app/javascript/dashboard/components/index.js index d2614421c..5e58aa049 100644 --- a/app/javascript/dashboard/components/index.js +++ b/app/javascript/dashboard/components/index.js @@ -22,6 +22,7 @@ import Tabs from './ui/Tabs/Tabs'; import TabsItem from './ui/Tabs/TabsItem'; import Thumbnail from './widgets/Thumbnail.vue'; import ConfirmModal from './widgets/modal/ConfirmationModal.vue'; +import ContextMenu from './ui/ContextMenu.vue'; const WootUIKit = { AvatarUploader, @@ -47,6 +48,7 @@ const WootUIKit = { TabsItem, Thumbnail, ConfirmModal, + ContextMenu, install(Vue) { const keys = Object.keys(this); keys.pop(); // remove 'install' from keys diff --git a/app/javascript/dashboard/components/ui/ContextMenu.vue b/app/javascript/dashboard/components/ui/ContextMenu.vue new file mode 100644 index 000000000..b968cda19 --- /dev/null +++ b/app/javascript/dashboard/components/ui/ContextMenu.vue @@ -0,0 +1,53 @@ + + + diff --git a/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue b/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue index 7b90ef6eb..b3c36928c 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue @@ -10,6 +10,7 @@ @mouseenter="onCardHover" @mouseleave="onCardLeave" @click="cardClick(chat)" + @contextmenu="openContextMenu($event)" >
+ + +
diff --git a/app/javascript/dashboard/components/widgets/conversation/contextMenu/Index.vue b/app/javascript/dashboard/components/widgets/conversation/contextMenu/Index.vue new file mode 100644 index 000000000..32a62544d --- /dev/null +++ b/app/javascript/dashboard/components/widgets/conversation/contextMenu/Index.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/app/javascript/dashboard/components/widgets/conversation/contextMenu/agentLoadingPlaceholder.vue b/app/javascript/dashboard/components/widgets/conversation/contextMenu/agentLoadingPlaceholder.vue new file mode 100644 index 000000000..975d57fdc --- /dev/null +++ b/app/javascript/dashboard/components/widgets/conversation/contextMenu/agentLoadingPlaceholder.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/app/javascript/dashboard/components/widgets/conversation/contextMenu/menuItem.vue b/app/javascript/dashboard/components/widgets/conversation/contextMenu/menuItem.vue new file mode 100644 index 000000000..eb4a71c6f --- /dev/null +++ b/app/javascript/dashboard/components/widgets/conversation/contextMenu/menuItem.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/app/javascript/dashboard/components/widgets/conversation/contextMenu/menuItemWithSubmenu.vue b/app/javascript/dashboard/components/widgets/conversation/contextMenu/menuItemWithSubmenu.vue new file mode 100644 index 000000000..08922bc37 --- /dev/null +++ b/app/javascript/dashboard/components/widgets/conversation/contextMenu/menuItemWithSubmenu.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/app/javascript/dashboard/i18n/locale/en/conversation.json b/app/javascript/dashboard/i18n/locale/en/conversation.json index c38fb16a4..eee623df3 100644 --- a/app/javascript/dashboard/i18n/locale/en/conversation.json +++ b/app/javascript/dashboard/i18n/locale/en/conversation.json @@ -58,6 +58,20 @@ "NEXT_WEEK": "Next week" } }, + "CARD_CONTEXT_MENU": { + "PENDING": "Mark as pending", + "RESOLVED": "Mark as resolved", + "REOPEN": "Reopen conversation", + "SNOOZE": { + "TITLE": "Snooze", + "NEXT_REPLY": "Until next reply", + "TOMORROW": "Until tomorrow", + "NEXT_WEEK": "Until next week" + }, + "ASSIGN_AGENT": "Assign agent", + "ASSIGN_LABEL": "Assign label", + "AGENTS_LOADING": "Loading agents..." + }, "FOOTER": { "MESSAGE_SIGN_TOOLTIP": "Message signature", "ENABLE_SIGN_TOOLTIP": "Enable signature", @@ -100,7 +114,11 @@ }, "VISIBLE_TO_AGENTS": "Private Note: Only visible to you and your team", "CHANGE_STATUS": "Conversation status changed", + "CHANGE_STATUS_FAILED": "Conversation status change failed", "CHANGE_AGENT": "Conversation Assignee changed", + "CHANGE_AGENT_FAILED": "Assignee change failed", + "ASSIGN_LABEL_SUCCESFUL": "Label assigned successfully", + "ASSIGN_LABEL_FAILED": "Label assignment failed", "CHANGE_TEAM": "Conversation team changed", "FILE_SIZE_LIMIT": "File exceeds the {MAXIMUM_FILE_UPLOAD_SIZE} attachment limit", "MESSAGE_ERROR": "Unable to send this message, please try again later", diff --git a/app/javascript/dashboard/mixins/conversation/snoozeTimesMixin.js b/app/javascript/dashboard/mixins/conversation/snoozeTimesMixin.js new file mode 100644 index 000000000..a7eb1f90f --- /dev/null +++ b/app/javascript/dashboard/mixins/conversation/snoozeTimesMixin.js @@ -0,0 +1,22 @@ +import { + getUnixTime, + addHours, + addWeeks, + startOfTomorrow, + startOfWeek, +} from 'date-fns'; + +export default { + computed: { + snoozeTimes() { + return { + // tomorrow = 9AM next day + tomorrow: getUnixTime(addHours(startOfTomorrow(), 9)), + // next week = 9AM Monday, next week + nextWeek: getUnixTime( + addHours(startOfWeek(addWeeks(new Date(), 1), { weekStartsOn: 1 }), 9) + ), + }; + }, + }, +}; diff --git a/app/javascript/shared/assets/stylesheets/shadows.scss b/app/javascript/shared/assets/stylesheets/shadows.scss index 6b11a427b..86a26bf43 100644 --- a/app/javascript/shared/assets/stylesheets/shadows.scss +++ b/app/javascript/shared/assets/stylesheets/shadows.scss @@ -13,4 +13,6 @@ 0 0.4rem 1.2rem rgb(0 0 0 / 7%); --shadow-bulk-action-container: 6px 3px 22px 9px rgb(181 181 181 / 25%); + --shadow-context-menu: rgb(22 23 24 / 35%) 0px 10px 38px -10px, + rgb(22 23 24 / 20%) 0px 10px 20px -15px; } diff --git a/app/javascript/shared/components/FluentIcon/dashboard-icons.json b/app/javascript/shared/components/FluentIcon/dashboard-icons.json index 08a452f88..384bdd896 100644 --- a/app/javascript/shared/components/FluentIcon/dashboard-icons.json +++ b/app/javascript/shared/components/FluentIcon/dashboard-icons.json @@ -119,6 +119,7 @@ "settings-outline": "M12.012 2.25c.734.008 1.465.093 2.182.253a.75.75 0 0 1 .582.649l.17 1.527a1.384 1.384 0 0 0 1.927 1.116l1.401-.615a.75.75 0 0 1 .85.174 9.792 9.792 0 0 1 2.204 3.792.75.75 0 0 1-.271.825l-1.242.916a1.381 1.381 0 0 0 0 2.226l1.243.915a.75.75 0 0 1 .272.826 9.797 9.797 0 0 1-2.204 3.792.75.75 0 0 1-.848.175l-1.407-.617a1.38 1.38 0 0 0-1.926 1.114l-.169 1.526a.75.75 0 0 1-.572.647 9.518 9.518 0 0 1-4.406 0 .75.75 0 0 1-.572-.647l-.168-1.524a1.382 1.382 0 0 0-1.926-1.11l-1.406.616a.75.75 0 0 1-.849-.175 9.798 9.798 0 0 1-2.204-3.796.75.75 0 0 1 .272-.826l1.243-.916a1.38 1.38 0 0 0 0-2.226l-1.243-.914a.75.75 0 0 1-.271-.826 9.793 9.793 0 0 1 2.204-3.792.75.75 0 0 1 .85-.174l1.4.615a1.387 1.387 0 0 0 1.93-1.118l.17-1.526a.75.75 0 0 1 .583-.65c.717-.159 1.45-.243 2.201-.252Zm0 1.5a9.135 9.135 0 0 0-1.354.117l-.109.977A2.886 2.886 0 0 1 6.525 7.17l-.898-.394a8.293 8.293 0 0 0-1.348 2.317l.798.587a2.881 2.881 0 0 1 0 4.643l-.799.588c.32.842.776 1.626 1.348 2.322l.905-.397a2.882 2.882 0 0 1 4.017 2.318l.11.984c.889.15 1.798.15 2.687 0l.11-.984a2.881 2.881 0 0 1 4.018-2.322l.905.396a8.296 8.296 0 0 0 1.347-2.318l-.798-.588a2.881 2.881 0 0 1 0-4.643l.796-.587a8.293 8.293 0 0 0-1.348-2.317l-.896.393a2.884 2.884 0 0 1-4.023-2.324l-.11-.976a8.988 8.988 0 0 0-1.333-.117ZM12 8.25a3.75 3.75 0 1 1 0 7.5 3.75 3.75 0 0 1 0-7.5Zm0 1.5a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5Z", "share-outline": "M6.747 4h3.464a.75.75 0 0 1 .102 1.493l-.102.007H6.747a2.25 2.25 0 0 0-2.245 2.096l-.005.154v9.5a2.25 2.25 0 0 0 2.096 2.245l.154.005h9.5a2.25 2.25 0 0 0 2.245-2.096l.005-.154v-.498a.75.75 0 0 1 1.494-.101l.006.101v.498a3.75 3.75 0 0 1-3.55 3.745l-.2.005h-9.5a3.75 3.75 0 0 1-3.745-3.55l-.005-.2v-9.5a3.75 3.75 0 0 1 3.55-3.745l.2-.005h3.464-3.464ZM14.5 6.52V3.75a.75.75 0 0 1 1.187-.61l.082.069 5.994 5.75c.28.268.306.7.077.997l-.077.085-5.994 5.752a.75.75 0 0 1-1.262-.434l-.007-.107v-2.725l-.344.03c-2.4.25-4.7 1.33-6.914 3.26-.52.453-1.323.025-1.237-.658.664-5.32 3.446-8.252 8.195-8.62l.3-.02V3.75v2.77ZM16 5.509V7.25a.75.75 0 0 1-.75.75c-3.874 0-6.274 1.676-7.312 5.157l-.079.279.352-.237C10.45 11.737 12.798 11 15.251 11a.75.75 0 0 1 .743.648l.007.102v1.743L20.16 9.5l-4.16-3.991Z", "signature-outline": "M14.75 16.5c1.308 0 1.818.582 2.205 1.874l.068.237c.183.658.292.854.513.946.259.106.431.091.703-.048l.147-.083c.053-.031.11-.068.176-.111l.663-.452c.616-.405 1.17-.672 1.843-.84a.75.75 0 0 1 .364 1.454 4.03 4.03 0 0 0-1.146.49l-.298.19-.48.329a5.45 5.45 0 0 1-.583.357c-.643.33-1.27.385-1.96.1-.746-.306-1.046-.78-1.327-1.721l-.156-.542c-.181-.59-.305-.68-.732-.68-.31 0-.63.155-1.069.523l-.184.16-.921.876c-1.408 1.324-2.609 1.966-4.328 1.966-1.686 0-3.144-.254-4.368-.768l2.947-.805c.447.049.921.073 1.421.073 1.183 0 2.032-.415 3.087-1.362l.258-.239.532-.511c.236-.227.414-.39.592-.54.684-.573 1.305-.873 2.033-.873Zm4.28-13.53a3.579 3.579 0 0 1 0 5.06l-.288.289c1.151 1.401 1.11 2.886.039 3.96l-2.001 2.002a.75.75 0 0 1-1.06-1.062l1.999-1.999c.485-.486.54-1.09-.04-1.838l-8.617 8.617a2.25 2.25 0 0 1-1 .58l-5.115 1.394a.75.75 0 0 1-.92-.92l1.394-5.116a2.25 2.25 0 0 1 .58-1L13.97 2.97a3.578 3.578 0 0 1 5.061 0Zm-4 1.06L5.062 14a.75.75 0 0 0-.193.332l-1.05 3.85 3.85-1.05A.75.75 0 0 0 8 16.938l9.969-9.969a2.078 2.078 0 1 0-2.94-2.939Z", + "snooze-outline": "M12 3.5c-3.104 0-6 2.432-6 6.25v4.153L4.682 17h14.67l-1.354-3.093V11.75a.75.75 0 0 1 1.5 0v1.843l1.381 3.156a1.25 1.25 0 0 1-1.145 1.751H15a3.002 3.002 0 0 1-6.003 0H4.305a1.25 1.25 0 0 1-1.15-1.739l1.344-3.164V9.75C4.5 5.068 8.103 2 12 2c.86 0 1.705.15 2.5.432a.75.75 0 0 1-.502 1.413A5.964 5.964 0 0 0 12 3.5ZM12 20c.828 0 1.5-.671 1.501-1.5h-3.003c0 .829.673 1.5 1.502 1.5Zm3.25-13h-2.5l-.101.007A.75.75 0 0 0 12.75 8.5h1.043l-1.653 2.314l-.055.09A.75.75 0 0 0 12.75 12h2.5l.102-.007a.75.75 0 0 0-.102-1.493h-1.042l1.653-2.314l.055-.09A.75.75 0 0 0 15.25 7Zm6-5h-3.5l-.101.007A.75.75 0 0 0 17.75 3.5h2.134l-2.766 4.347l-.05.09A.75.75 0 0 0 17.75 9h3.5l.102-.007A.75.75 0 0 0 21.25 7.5h-2.133l2.766-4.347l.05-.09A.75.75 0 0 0 21.25 2Z", "sound-source-outline": "M3.5 12a8.5 8.5 0 1 1 14.762 5.748l.992 1.135A9.966 9.966 0 0 0 22 12c0-5.523-4.477-10-10-10S2 6.477 2 12a9.966 9.966 0 0 0 2.746 6.883l.993-1.134A8.47 8.47 0 0 1 3.5 12Z M19.25 12.125a7.098 7.098 0 0 1-1.783 4.715l-.998-1.14a5.625 5.625 0 1 0-8.806-.15l-1.004 1.146a7.125 7.125 0 1 1 12.59-4.571Z M16.25 12a4.23 4.23 0 0 1-.821 2.511l-1.026-1.172a2.75 2.75 0 1 0-4.806 0L8.571 14.51A4.25 4.25 0 1 1 16.25 12Z M12.564 12.756a.75.75 0 0 0-1.128 0l-7 8A.75.75 0 0 0 5 22h14a.75.75 0 0 0 .564-1.244l-7-8Zm4.783 7.744H6.653L12 14.389l5.347 6.111Z", "speaker-1-outline": "M14.704 3.442c.191.226.296.512.296.808v15.502a1.25 1.25 0 0 1-2.058.954L7.975 16.5H4.25A2.25 2.25 0 0 1 2 14.25v-4.5A2.25 2.25 0 0 1 4.25 7.5h3.725l4.968-4.204a1.25 1.25 0 0 1 1.761.147ZM13.5 4.79 8.525 9H4.25a.75.75 0 0 0-.75.75v4.5c0 .415.336.75.75.75h4.275l4.975 4.213V4.79Zm3.604 3.851a.75.75 0 0 1 1.03.25c.574.94.862 1.992.862 3.14 0 1.149-.288 2.201-.862 3.141a.75.75 0 1 1-1.28-.781c.428-.702.642-1.483.642-2.36 0-.876-.214-1.657-.642-2.359a.75.75 0 0 1 .25-1.03Z", "speaker-mute-outline": "M12.92 3.316c.806-.717 2.08-.145 2.08.934v15.496c0 1.078-1.274 1.65-2.08.934l-4.492-3.994a.75.75 0 0 0-.498-.19H4.25A2.25 2.25 0 0 1 2 14.247V9.75a2.25 2.25 0 0 1 2.25-2.25h3.68a.75.75 0 0 0 .498-.19l4.491-3.993Zm.58 1.49L9.425 8.43A2.25 2.25 0 0 1 7.93 9H4.25a.75.75 0 0 0-.75.75v4.497c0 .415.336.75.75.75h3.68a2.25 2.25 0 0 1 1.495.57l4.075 3.623V4.807ZM16.22 9.22a.75.75 0 0 1 1.06 0L19 10.94l1.72-1.72a.75.75 0 1 1 1.06 1.06L20.06 12l1.72 1.72a.75.75 0 1 1-1.06 1.06L19 13.06l-1.72 1.72a.75.75 0 1 1-1.06-1.06L17.94 12l-1.72-1.72a.75.75 0 0 1 0-1.06Z",