From 4574f4203bcdf2d8683a1361f92d9e0afc9b295d Mon Sep 17 00:00:00 2001 From: iamsivin Date: Wed, 21 Feb 2024 12:58:24 +0530 Subject: [PATCH] feat: Snooze notification from context menu --- .../dashboard/commands/commandBarBusEvents.js | 1 + .../routes/dashboard/commands/commandbar.vue | 15 +++- .../routes/dashboard/commands/inboxHotKeys.js | 25 +++++- .../conversation/conversation.routes.js | 4 +- .../routes/dashboard/inbox/InboxList.vue | 78 ++++++++++++++++++- .../routes/dashboard/inbox/InboxView.vue | 44 ++++++----- .../dashboard/inbox/components/InboxCard.vue | 34 +++++++- .../inbox/components/InboxItemHeader.vue | 53 +------------ .../inbox/components/InboxListHeader.vue | 38 ++++++--- .../store/modules/notifications/getters.js | 3 + .../specs/notifications/getters.spec.js | 10 +++ package.json | 2 +- yarn.lock | 18 ++--- 13 files changed, 221 insertions(+), 104 deletions(-) diff --git a/app/javascript/dashboard/routes/dashboard/commands/commandBarBusEvents.js b/app/javascript/dashboard/routes/dashboard/commands/commandBarBusEvents.js index 86e417ad7..7d4f8c9bc 100644 --- a/app/javascript/dashboard/routes/dashboard/commands/commandBarBusEvents.js +++ b/app/javascript/dashboard/routes/dashboard/commands/commandBarBusEvents.js @@ -18,3 +18,4 @@ export const CMD_AI_ASSIST = 'CMD_AI_ASSIST'; // Inbox Commands (Notifications) export const CMD_SNOOZE_NOTIFICATION = 'CMD_SNOOZE_NOTIFICATION'; +export const CMD_TOGGLE_SNOOZE_NOTIFICATION = 'CMD_TOGGLE_SNOOZE_NOTIFICATION'; diff --git a/app/javascript/dashboard/routes/dashboard/commands/commandbar.vue b/app/javascript/dashboard/routes/dashboard/commands/commandbar.vue index 45b807fb3..658517e66 100644 --- a/app/javascript/dashboard/routes/dashboard/commands/commandbar.vue +++ b/app/javascript/dashboard/routes/dashboard/commands/commandbar.vue @@ -6,11 +6,12 @@ hideBreadcrumbs :placeholder="placeholder" @selected="onSelected" + @closed="onClosed" /> diff --git a/app/javascript/dashboard/routes/dashboard/commands/inboxHotKeys.js b/app/javascript/dashboard/routes/dashboard/commands/inboxHotKeys.js index e8f92a9f6..af513a6be 100644 --- a/app/javascript/dashboard/routes/dashboard/commands/inboxHotKeys.js +++ b/app/javascript/dashboard/routes/dashboard/commands/inboxHotKeys.js @@ -1,6 +1,9 @@ import wootConstants from 'dashboard/constants/globals'; -import { CMD_SNOOZE_NOTIFICATION } from './commandBarBusEvents'; +import { + CMD_SNOOZE_NOTIFICATION, + CMD_TOGGLE_SNOOZE_NOTIFICATION, +} from './commandBarBusEvents'; import { ICON_SNOOZE_NOTIFICATION } from './CommandBarIcons'; import { isAInboxViewRoute } from 'dashboard/helper/routeHelpers'; @@ -61,14 +64,28 @@ const INBOX_SNOOZE_EVENTS = [ }, ]; export default { + data() { + return { + showSnoozeNotificationItems: false, + }; + }, computed: { inboxHotKeys() { - if (isAInboxViewRoute(this.$route.name)) { + if ( + isAInboxViewRoute(this.$route.name) || + this.showSnoozeNotificationItems + ) { return this.prepareActions(INBOX_SNOOZE_EVENTS); } return []; }, }, + mounted() { + bus.$on(CMD_TOGGLE_SNOOZE_NOTIFICATION, this.toggleSnoozeOptions); + }, + destroyed() { + bus.$off(CMD_TOGGLE_SNOOZE_NOTIFICATION, this.toggleSnoozeOptions); + }, methods: { prepareActions(actions) { return actions.map(action => ({ @@ -77,5 +94,9 @@ export default { section: this.$t(action.section), })); }, + toggleSnoozeOptions() { + // Used to show/hide snooze notification items in cmd bar dynamically + this.showSnoozeNotificationItems = true; + }, }, }; diff --git a/app/javascript/dashboard/routes/dashboard/conversation/conversation.routes.js b/app/javascript/dashboard/routes/dashboard/conversation/conversation.routes.js index 63a32d72b..2411acdbf 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/conversation.routes.js +++ b/app/javascript/dashboard/routes/dashboard/conversation/conversation.routes.js @@ -15,12 +15,12 @@ export default { }, }, { - path: frontendURL('accounts/:accountId/inbox-view/:conversation_id'), + path: frontendURL('accounts/:accountId/inbox-view/:notification_id'), name: 'inbox_view_conversation', roles: ['administrator', 'agent'], component: InboxView, props: route => { - return { inboxId: 0, conversationId: route.params.conversation_id }; + return { inboxId: 0, notificationId: route.params.notification_id }; }, }, { diff --git a/app/javascript/dashboard/routes/dashboard/inbox/InboxList.vue b/app/javascript/dashboard/routes/dashboard/inbox/InboxList.vue index bb0f2e7df..5aa9045ab 100644 --- a/app/javascript/dashboard/routes/dashboard/inbox/InboxList.vue +++ b/app/javascript/dashboard/routes/dashboard/inbox/InboxList.vue @@ -3,7 +3,11 @@ class="flex flex-col h-full w-full ltr:border-r border-slate-50 dark:border-slate-800/50" :class="isOnExpandedLayout ? '' : 'min-w-[360px] max-w-[360px]'" > - +
@@ -37,6 +44,15 @@ @observed="loadMoreNotifications" />
+ + +
@@ -46,18 +62,26 @@ import wootConstants from 'dashboard/constants/globals'; import InboxCard from './components/InboxCard.vue'; import InboxListHeader from './components/InboxListHeader.vue'; import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events'; +import { + CMD_TOGGLE_SNOOZE_NOTIFICATION, + CMD_SNOOZE_NOTIFICATION, +} from 'dashboard/routes/dashboard/commands/commandBarBusEvents'; +import { getUnixTime } from 'date-fns'; +import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers'; import IntersectionObserver from 'dashboard/components/IntersectionObserver.vue'; import alertMixin from 'shared/mixins/alertMixin'; import uiSettingsMixin from 'dashboard/mixins/uiSettings'; +import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue'; export default { components: { InboxCard, InboxListHeader, IntersectionObserver, + CustomSnoozeModal, }, mixins: [alertMixin, uiSettingsMixin], props: { - conversationId: { + notificationId: { type: [String, Number], default: 0, }, @@ -76,6 +100,9 @@ export default { status: '', type: '', sortOrder: wootConstants.INBOX_SORT_BY.NEWEST, + isInboxContextMenuOpen: false, + showCustomSnoozeModal: false, + notificationIdToSnooze: null, }; }, computed: { @@ -109,6 +136,10 @@ export default { mounted() { this.setSavedFilter(); this.fetchNotifications(); + bus.$on(CMD_SNOOZE_NOTIFICATION, this.onCmdSnoozeNotification); + }, + destroyed() { + bus.$off(CMD_SNOOZE_NOTIFICATION, this.onCmdSnoozeNotification); }, methods: { fetchNotifications() { @@ -119,7 +150,7 @@ export default { this.$store.dispatch('notifications/index', filter); }, redirectToInbox() { - if (!this.conversationId) return; + if (!this.notificationId) return; if (this.$route.name === 'inbox_view') return; this.$router.push({ name: 'inbox_view' }); }, @@ -195,6 +226,47 @@ export default { this.type = type; this.sortOrder = sortBy || wootConstants.INBOX_SORT_BY.NEWEST; }, + openSnoozeNotificationModal(notificationItem) { + this.notificationIdToSnooze = notificationItem.id; + // There is an bus event to toggle snooze modal, because + // We need to show the snooze notification items in the command bar Dynamically + // If cmd + k is pressed from inbox view (In list screen) and search snooze notification items then we no need to show, + // snooze notification because we don't get notification id. + // Only show when the snooze button is clicked from the notification item card context menu + if (!this.notificationId) bus.$emit(CMD_TOGGLE_SNOOZE_NOTIFICATION); + this.$nextTick(() => { + const ninja = document.querySelector('ninja-keys'); + ninja.open({ parent: 'snooze_notification' }); + }); + }, + hideCustomSnoozeModal() { + this.showCustomSnoozeModal = false; + }, + snoozeNotification(snoozedUntil) { + this.$store + .dispatch('notifications/snooze', { + id: this.notificationId || this.notificationIdToSnooze, + snoozedUntil, + }) + .then(() => { + this.showAlert(this.$t('INBOX.ALERTS.SNOOZE')); + }); + }, + onCmdSnoozeNotification(snoozeType) { + if (snoozeType === wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME) { + this.showCustomSnoozeModal = true; + } else { + const snoozedUntil = findSnoozeTime(snoozeType) || null; + this.snoozeNotification(snoozedUntil); + } + }, + scheduleCustomSnooze(customSnoozeTime) { + this.showCustomSnoozeModal = false; + if (customSnoozeTime) { + const snoozedUntil = getUnixTime(customSnoozeTime) || null; + this.snoozeNotification(snoozedUntil); + } + }, }, }; diff --git a/app/javascript/dashboard/routes/dashboard/inbox/InboxView.vue b/app/javascript/dashboard/routes/dashboard/inbox/InboxView.vue index edc36da15..2edd788d0 100644 --- a/app/javascript/dashboard/routes/dashboard/inbox/InboxView.vue +++ b/app/javascript/dashboard/routes/dashboard/inbox/InboxView.vue @@ -2,7 +2,7 @@
n.primary_actor.id === Number(this.conversationId) - ); + return this.activeNotificationById(this.notificationId); + }, + conversationId() { + return this.activeNotification?.primary_actor?.id; }, isInboxViewEnabled() { return this.$store.getters['accounts/isFeatureEnabledGlobally']( @@ -99,11 +101,7 @@ export default { return this.uiFlags.isFetching && !this.notifications.length; }, showInboxMessageView() { - return ( - Boolean(this.conversationId) && - Boolean(this.currentChat.id) && - !this.isFetchingInitialData - ); + return Boolean(this.conversationId) && !this.isFetchingInitialData; }, totalNotifications() { return this.notifications?.length ?? 0; @@ -135,8 +133,10 @@ export default { watch: { conversationId: { immediate: true, - handler() { - this.fetchConversationById(); + handler(newVal, oldVal) { + if (newVal !== oldVal) { + this.fetchConversationById(); + } }, }, }, @@ -152,11 +152,14 @@ export default { }, methods: { async fetchConversationById() { - if (!this.conversationId) return; - const chat = this.findConversation(); - if (!chat) { - await this.$store.dispatch('getConversation', this.conversationId); + if (!this.notificationId || !this.conversationId) return; + this.$store.dispatch('clearSelectedState'); + const existingChat = this.findConversation(); + if (existingChat) { + this.setActiveChat(existingChat); + return; } + await this.$store.dispatch('getConversation', this.conversationId); this.setActiveChat(); }, setActiveChat() { @@ -169,8 +172,7 @@ export default { }); }, findConversation() { - const conversationId = Number(this.conversationId); - return this.allConversation.find(c => c.id === conversationId); + return this.conversationById(this.conversationId); }, navigateToConversation(activeIndex, direction) { const indexOffset = direction === 'next' ? 0 : -2; @@ -180,7 +182,7 @@ export default { id, primary_actor_id: primaryActorId, primary_actor_type: primaryActorType, - primary_actor: { id: conversationId, meta: { unreadCount } = {} }, + primary_actor: { meta: { unreadCount } = {} }, notification_type: notificationType, } = targetNotification; @@ -197,7 +199,7 @@ export default { this.$router.push({ name: 'inbox_view_conversation', - params: { conversation_id: conversationId }, + params: { notification_id: id }, }); } }, diff --git a/app/javascript/dashboard/routes/dashboard/inbox/components/InboxCard.vue b/app/javascript/dashboard/routes/dashboard/inbox/components/InboxCard.vue index 8e100f7b5..3c17f0ace 100644 --- a/app/javascript/dashboard/routes/dashboard/inbox/components/InboxCard.vue +++ b/app/javascript/dashboard/routes/dashboard/inbox/components/InboxCard.vue @@ -89,6 +89,7 @@ export default { return { isContextMenuOpen: false, contextMenuPosition: { x: null, y: null }, + activeNotificationId: null, }; }, computed: { @@ -96,7 +97,9 @@ export default { return this.notificationItem?.primary_actor; }, isInboxCardActive() { - return this.$route.params.conversation_id === this.primaryActor?.id; + return ( + Number(this.activeNotificationId) === Number(this.notificationItem?.id) + ); }, inbox() { return this.$store.getters['inboxes/getInbox']( @@ -109,6 +112,9 @@ export default { meta() { return this.primaryActor?.meta; }, + isNotSnoozed() { + return !this.notificationItem?.snoozed_until; + }, assigneeMeta() { return this.meta?.assignee; }, @@ -131,6 +137,13 @@ export default { }, ]; + if (this.isNotSnoozed) { + items.push({ + key: 'snooze', + label: this.$t('INBOX.MENU_ITEM.SNOOZE'), + }); + } + if (!this.isUnread) { items.push({ key: 'mark_as_unread', @@ -157,6 +170,14 @@ export default { return ''; }, }, + watch: { + '$route.params.notification_id': { + immediate: true, + handler(newVal) { + this.activeNotificationId = newVal; + }, + }, + }, unmounted() { this.closeContextMenu(); }, @@ -166,11 +187,11 @@ export default { id, primary_actor_id: primaryActorId, primary_actor_type: primaryActorType, - primary_actor: { id: conversationId, inbox_id: inboxId }, + primary_actor: { inbox_id: inboxId }, notification_type: notificationType, } = notification; - if (this.$route.params.conversation_id !== conversationId) { + if (this.$route.params.notification_id !== id) { this.$track(INBOX_EVENTS.OPEN_CONVERSATION_VIA_INBOX, { notificationType, }); @@ -184,13 +205,14 @@ export default { this.$router.push({ name: 'inbox_view_conversation', - params: { inboxId, conversation_id: conversationId }, + params: { inboxId, notification_id: id }, }); } }, closeContextMenu() { this.isContextMenuOpen = false; this.contextMenuPosition = { x: null, y: null }; + this.$emit('context-menu-close'); }, openContextMenu(e) { this.closeContextMenu(); @@ -200,6 +222,7 @@ export default { y: e.pageY || e.clientY, }; this.isContextMenuOpen = true; + this.$emit('context-menu-open'); }, handleAction(key) { switch (key) { @@ -212,6 +235,9 @@ export default { case 'delete': this.$emit('delete-notification', this.notificationItem); break; + case 'snooze': + this.$emit('snooze-notification', this.notificationItem); + break; default: } }, diff --git a/app/javascript/dashboard/routes/dashboard/inbox/components/InboxItemHeader.vue b/app/javascript/dashboard/routes/dashboard/inbox/components/InboxItemHeader.vue index 99b642cbf..ca8777f10 100644 --- a/app/javascript/dashboard/routes/dashboard/inbox/components/InboxItemHeader.vue +++ b/app/javascript/dashboard/routes/dashboard/inbox/components/InboxItemHeader.vue @@ -12,6 +12,7 @@
- - -