Compare commits

...
Author SHA1 Message Date
Muhsin Keloth 70fd1e27dc chore: add @chatwoot/ninja-keys 2024-02-21 11:57:22 +05:30
Sivin VargheseandGitHub a2f9cd6e38 Merge branch 'feat/CW-3059' into feat/CW-3067-snooze 2024-02-21 08:28:31 +05:30
Sivin VargheseandGitHub 758c4bd361 Merge branch 'develop' into feat/CW-3059 2024-02-21 08:28:23 +05:30
Sivin VargheseandGitHub 4a48569ec4 Merge branch 'feat/CW-3059' into feat/CW-3067-snooze 2024-02-20 21:52:13 +05:30
Sivin VargheseandGitHub 20c609a615 Merge branch 'develop' into feat/CW-3059 2024-02-20 21:50:58 +05:30
Sivin VargheseandGitHub 010998d541 Merge branch 'feat/CW-3059' into feat/CW-3067-snooze 2024-02-20 09:22:42 +05:30
Sivin VargheseandGitHub 4b88ae906d Merge branch 'develop' into feat/CW-3059 2024-02-19 18:29:51 +05:30
Sivin VargheseandGitHub c1ce9fe215 Merge branch 'feat/CW-3059' into feat/CW-3067-snooze 2024-02-19 11:25:49 +05:30
iamsivin 94b86a1244 chore: Review fix 2024-02-19 11:25:14 +05:30
Sivin VargheseandGitHub ac947eedf5 Merge branch 'feat/CW-3059' into feat/CW-3067-snooze 2024-02-18 22:51:08 +05:30
Muhsin KelothandGitHub 6fb3e96bc9 Merge branch 'develop' into feat/CW-3059 2024-02-17 13:59:43 +05:30
Sivin VargheseandGitHub 72f64836a5 Merge branch 'feat/CW-3059' into feat/CW-3067-snooze 2024-02-16 23:47:53 +05:30
Sivin VargheseandGitHub 33d93310d2 Merge branch 'develop' into feat/CW-3059 2024-02-16 23:47:39 +05:30
Muhsin KelothandGitHub 9ec4ec94ed Merge branch 'develop' into feat/CW-3059 2024-02-16 17:12:21 +05:30
Sivin VargheseandGitHub 04e829e639 Merge branch 'feat/CW-3059' into feat/CW-3067-snooze 2024-02-16 10:55:12 +05:30
Sivin VargheseandGitHub 69182f748b Merge branch 'develop' into feat/CW-3059 2024-02-16 10:54:46 +05:30
Sivin VargheseandGitHub b6244c870c Merge branch 'feat/CW-3059' into feat/CW-3067-snooze 2024-02-15 06:00:22 +05:30
Sivin VargheseandGitHub 9d063b730b Merge branch 'develop' into feat/CW-3059 2024-02-15 05:59:55 +05:30
Sivin VargheseandGitHub 4146da5a6d Merge branch 'feat/CW-3059' into feat/CW-3067-snooze 2024-02-14 16:44:20 +05:30
Sivin VargheseandGitHub b53730bb73 Merge branch 'develop' into feat/CW-3059 2024-02-14 16:44:08 +05:30
Sivin VargheseandGitHub 75b57d6a0a Merge branch 'feat/CW-3059' into feat/CW-3067-snooze 2024-02-14 09:34:24 +05:30
iamsivin e3cd3283a0 chore: Minor fix 2024-02-14 09:31:32 +05:30
iamsivin 554395b6c9 feat: Snooze inbox item from context menu 2024-02-13 22:33:59 +05:30
iamsivin 8b2f656372 chore: Clean up 2024-02-13 22:32:43 +05:30
Sivin VargheseandGitHub b4686ec07a Merge branch 'develop' into feat/CW-3059 2024-02-13 19:07:48 +05:30
Sivin VargheseandGitHub 4d995b16f0 Merge branch 'develop' into feat/CW-3059 2024-02-13 16:54:01 +05:30
iamsivin 8de4911fb3 chore: Route review fix 2024-02-13 16:53:41 +05:30
iamsivin a870d1c8a2 chore: Minor UI fixes 2024-02-13 14:15:18 +05:30
iamsivin ab0ab9b032 chore: Fix small screens 2024-02-13 13:49:58 +05:30
iamsivin ac4ce529bf chore: Minor fix 2024-02-13 13:06:06 +05:30
iamsivin f19474c681 chore: Minor fix 2024-02-13 13:05:23 +05:30
Sivin VargheseandGitHub 7359f10be2 Delete app/javascript/shared/mixins/specs/resizeListenerMixin.spec.js 2024-02-13 13:04:36 +05:30
Sivin VargheseandGitHub 778dbaf812 Delete app/javascript/shared/mixins/resizeListenerMixin.js 2024-02-13 13:04:21 +05:30
Sivin VargheseandGitHub faa7a5f585 Merge branch 'develop' into feat/CW-3059 2024-02-13 10:25:59 +05:30
Sivin VargheseandGitHub eaddb253a5 Merge branch 'develop' into feat/CW-3059 2024-02-13 08:55:12 +05:30
iamsivin 8d200b8a99 chore: Review fix 2024-02-13 08:54:56 +05:30
Sivin VargheseandGitHub 34aef51064 Merge branch 'develop' into feat/CW-3059 2024-02-13 06:34:46 +05:30
iamsivin 139b0d8a4c chore: Remove loader 2024-02-12 22:42:27 +05:30
Sivin VargheseandGitHub 440e357c84 Merge branch 'develop' into feat/CW-3059 2024-02-12 20:56:33 +05:30
Sivin VargheseandGitHub 58959d42f5 Merge branch 'develop' into feat/CW-3059 2024-02-12 18:03:22 +05:30
iamsivin 01264d45c2 chore: Clean up 2024-02-12 13:18:53 +05:30
Sivin VargheseandGitHub 8548f6e00b Merge branch 'develop' into feat/CW-3059 2024-02-12 13:07:13 +05:30
iamsivin 0763cc477f chore: Add snooze 2024-02-12 13:05:47 +05:30
Sivin VargheseandGitHub 2fc5c6f236 Merge branch 'develop' into feat/CW-3059 2024-02-12 08:29:40 +05:30
iamsivin 5935a4ff6c chore: Fix codeclimate 2024-02-09 16:18:25 +05:30
iamsivin 43de19f685 chore: Inbox view enhancements 2024-02-09 15:40:12 +05:30
21 changed files with 451 additions and 330 deletions
@@ -8,7 +8,7 @@
:chat="currentChat" :chat="currentChat"
:is-inbox-view="isInboxView" :is-inbox-view="isInboxView"
:is-contact-panel-open="isContactPanelOpen" :is-contact-panel-open="isContactPanelOpen"
:show-back-button="isOnExpandedLayout" :show-back-button="isOnExpandedLayout && !isInboxView"
@contact-panel-toggle="onToggleContactPanel" @contact-panel-toggle="onToggleContactPanel"
/> />
<woot-tabs <woot-tabs
@@ -35,7 +35,10 @@
:is-contact-panel-open="isContactPanelOpen" :is-contact-panel-open="isContactPanelOpen"
@contact-panel-toggle="onToggleContactPanel" @contact-panel-toggle="onToggleContactPanel"
/> />
<empty-state v-else :is-on-expanded-layout="isOnExpandedLayout" /> <empty-state
v-if="!currentChat.id && !isInboxView"
:is-on-expanded-layout="isOnExpandedLayout"
/>
<div <div
v-show="showContactPanel" v-show="showContactPanel"
class="conversation-sidebar-wrap basis-full sm:basis-[17.5rem] md:basis-[18.75rem] lg:basis-[19.375rem] xl:basis-[20.625rem] 2xl:basis-[25rem] rtl:border-r border-slate-50 dark:border-slate-700 h-auto overflow-auto z-10 flex-shrink-0 flex-grow-0" class="conversation-sidebar-wrap basis-full sm:basis-[17.5rem] md:basis-[18.75rem] lg:basis-[19.375rem] xl:basis-[20.625rem] 2xl:basis-[25rem] rtl:border-r border-slate-50 dark:border-slate-700 h-auto overflow-auto z-10 flex-shrink-0 flex-grow-0"
@@ -3,11 +3,10 @@
class="bg-white dark:bg-slate-900 flex justify-between items-center py-2 px-4 border-b border-slate-50 dark:border-slate-800/50 flex-col md:flex-row" class="bg-white dark:bg-slate-900 flex justify-between items-center py-2 px-4 border-b border-slate-50 dark:border-slate-800/50 flex-col md:flex-row"
> >
<div <div
class="flex-1 w-full min-w-0 flex flex-col md:flex-row items-center justify-center" class="flex-1 w-full min-w-0 flex flex-col items-center justify-center gap-4"
:class="isInboxView ? 'sm:flex-row' : 'md:flex-row'"
> >
<div <div class="flex justify-start items-center min-w-0 w-fit max-w-full">
class="flex justify-start items-center mr-4 rtl:mr-0 rtl:ml-4 min-w-0 w-[inherit]"
>
<back-button <back-button
v-if="showBackButton" v-if="showBackButton"
:back-url="backButtonUrl" :back-url="backButtonUrl"
@@ -20,9 +19,11 @@
:status="currentContact.availability_status" :status="currentContact.availability_status"
/> />
<div <div
class="items-start flex flex-col ml-2 rtl:ml-0 rtl:mr-2 min-w-0 w-[inherit] overflow-hidden" class="items-start flex flex-col ml-2 rtl:ml-0 rtl:mr-2 min-w-0 w-fit overflow-hidden"
> >
<div class="flex items-center flex-row gap-1 m-0 p-0 w-[inherit]"> <div
class="flex items-center flex-row gap-1 m-0 p-0 w-fit max-w-full"
>
<woot-button <woot-button
variant="link" variant="link"
color-scheme="secondary" color-scheme="secondary"
@@ -66,7 +67,7 @@
</div> </div>
</div> </div>
<div <div
class="header-actions-wrap items-center flex flex-row flex-grow justify-end mt-3 lg:mt-0 rtl:relative rtl:left-6" class="header-actions-wrap items-center flex flex-row flex-grow justify-end mt-3 lg:mt-0"
:class="{ 'justify-end': isContactPanelOpen }" :class="{ 'justify-end': isContactPanelOpen }"
> >
<more-actions :conversation-id="currentChat.id" /> <more-actions :conversation-id="currentChat.id" />
@@ -87,7 +88,6 @@ import Thumbnail from '../Thumbnail.vue';
import wootConstants from 'dashboard/constants/globals'; import wootConstants from 'dashboard/constants/globals';
import { conversationListPageURL } from 'dashboard/helper/URLHelper'; import { conversationListPageURL } from 'dashboard/helper/URLHelper';
import { snoozedReopenTime } from 'dashboard/helper/snoozeHelpers'; import { snoozedReopenTime } from 'dashboard/helper/snoozeHelpers';
import { frontendURL } from 'dashboard/helper/URLHelper';
export default { export default {
components: { components: {
@@ -128,9 +128,6 @@ export default {
params: { accountId, inbox_id: inboxId, label, teamId }, params: { accountId, inbox_id: inboxId, label, teamId },
name, name,
} = this.$route; } = this.$route;
if (this.isInboxView) {
return frontendURL(`accounts/${accountId}/inbox`);
}
return conversationListPageURL({ return conversationListPageURL({
accountId, accountId,
inboxId, inboxId,
@@ -4,7 +4,6 @@
"TITLE": "Inbox", "TITLE": "Inbox",
"DISPLAY_DROPDOWN": "Display", "DISPLAY_DROPDOWN": "Display",
"LOADING": "Fetching notifications", "LOADING": "Fetching notifications",
"EOF": "All notifications loaded 🎉",
"404": "There are no active notifications in this group.", "404": "There are no active notifications in this group.",
"NO_NOTIFICATIONS": "No notifications", "NO_NOTIFICATIONS": "No notifications",
"NOTE": "Notifications from all subscribed inboxes", "NOTE": "Notifications from all subscribed inboxes",
@@ -14,7 +13,8 @@
}, },
"ACTION_HEADER": { "ACTION_HEADER": {
"SNOOZE": "Snooze notification", "SNOOZE": "Snooze notification",
"DELETE": "Delete notification" "DELETE": "Delete notification",
"BACK": "Back"
}, },
"TYPES": { "TYPES": {
"CONVERSATION_MENTION": "You have been mentioned in a conversation", "CONVERSATION_MENTION": "You have been mentioned in a conversation",
@@ -18,3 +18,4 @@ export const CMD_AI_ASSIST = 'CMD_AI_ASSIST';
// Inbox Commands (Notifications) // Inbox Commands (Notifications)
export const CMD_SNOOZE_NOTIFICATION = 'CMD_SNOOZE_NOTIFICATION'; export const CMD_SNOOZE_NOTIFICATION = 'CMD_SNOOZE_NOTIFICATION';
export const CMD_TOGGLE_SNOOZE_NOTIFICATION = 'CMD_TOGGLE_SNOOZE_NOTIFICATION';
@@ -6,11 +6,12 @@
hideBreadcrumbs hideBreadcrumbs
:placeholder="placeholder" :placeholder="placeholder"
@selected="onSelected" @selected="onSelected"
@closed="onClosed"
/> />
</template> </template>
<script> <script>
import 'ninja-keys'; import '@chatwoot/ninja-keys';
import conversationHotKeysMixin from './conversationHotKeys'; import conversationHotKeysMixin from './conversationHotKeys';
import inboxHotKeysMixin from './inboxHotKeys'; import inboxHotKeysMixin from './inboxHotKeys';
import goToCommandHotKeys from './goToCommandHotKeys'; import goToCommandHotKeys from './goToCommandHotKeys';
@@ -32,6 +33,11 @@ export default {
appearanceHotKeys, appearanceHotKeys,
goToCommandHotKeys, goToCommandHotKeys,
], ],
data() {
return {
modalObserver: null,
};
},
computed: { computed: {
placeholder() { placeholder() {
return this.$t('COMMAND_BAR.SEARCH_PLACEHOLDER'); return this.$t('COMMAND_BAR.SEARCH_PLACEHOLDER');
@@ -55,6 +61,10 @@ export default {
routeName() { routeName() {
this.setCommandbarData(); this.setCommandbarData();
}, },
hotKeys() {
// This is to update the command bar data when the hot keys are updated dynamically
this.setCommandbarData();
},
}, },
mounted() { mounted() {
this.setCommandbarData(); this.setCommandbarData();
@@ -72,6 +82,9 @@ export default {
}); });
this.setCommandbarData(); this.setCommandbarData();
}, },
onClosed() {
this.showSnoozeNotificationItems = false;
},
}, },
}; };
</script> </script>
@@ -1,6 +1,9 @@
import wootConstants from 'dashboard/constants/globals'; 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 { ICON_SNOOZE_NOTIFICATION } from './CommandBarIcons';
import { isAInboxViewRoute } from 'dashboard/helper/routeHelpers'; import { isAInboxViewRoute } from 'dashboard/helper/routeHelpers';
@@ -61,14 +64,28 @@ const INBOX_SNOOZE_EVENTS = [
}, },
]; ];
export default { export default {
data() {
return {
showSnoozeNotificationItems: false,
};
},
computed: { computed: {
inboxHotKeys() { inboxHotKeys() {
if (isAInboxViewRoute(this.$route.name)) { if (
isAInboxViewRoute(this.$route.name) ||
this.showSnoozeNotificationItems
) {
return this.prepareActions(INBOX_SNOOZE_EVENTS); return this.prepareActions(INBOX_SNOOZE_EVENTS);
} }
return []; return [];
}, },
}, },
mounted() {
bus.$on(CMD_TOGGLE_SNOOZE_NOTIFICATION, this.toggleSnoozeOptions);
},
destroyed() {
bus.$off(CMD_TOGGLE_SNOOZE_NOTIFICATION, this.toggleSnoozeOptions);
},
methods: { methods: {
prepareActions(actions) { prepareActions(actions) {
return actions.map(action => ({ return actions.map(action => ({
@@ -77,5 +94,9 @@ export default {
section: this.$t(action.section), section: this.$t(action.section),
})); }));
}, },
toggleSnoozeOptions() {
// Used to show/hide snooze notification items in cmd bar dynamically
this.showSnoozeNotificationItems = true;
},
}, },
}; };
@@ -1,28 +1,9 @@
/* eslint arrow-body-style: 0 */ /* eslint arrow-body-style: 0 */
import { frontendURL } from '../../../helper/URLHelper'; import { frontendURL } from '../../../helper/URLHelper';
const ConversationView = () => import('./ConversationView'); const ConversationView = () => import('./ConversationView');
const InboxView = () => import('../inbox/InboxView.vue');
export default { export default {
routes: [ routes: [
{
path: frontendURL('accounts/:accountId/inbox-view'),
name: 'inbox_view',
roles: ['administrator', 'agent'],
component: InboxView,
props: () => {
return { inboxId: 0 };
},
},
{
path: frontendURL('accounts/:accountId/inbox-view/:conversation_id'),
name: 'inbox_view_conversation',
roles: ['administrator', 'agent'],
component: InboxView,
props: route => {
return { inboxId: 0, conversationId: route.params.conversation_id };
},
},
{ {
path: frontendURL('accounts/:accountId/dashboard'), path: frontendURL('accounts/:accountId/dashboard'),
name: 'home', name: 'home',
@@ -3,6 +3,7 @@ import conversation from './conversation/conversation.routes';
import { routes as searchRoutes } from '../../modules/search/search.routes'; import { routes as searchRoutes } from '../../modules/search/search.routes';
import { routes as contactRoutes } from './contacts/routes'; import { routes as contactRoutes } from './contacts/routes';
import { routes as notificationRoutes } from './notifications/routes'; import { routes as notificationRoutes } from './notifications/routes';
import { routes as inboxRoutes } from './inbox/routes';
import { frontendURL } from '../../helper/URLHelper'; import { frontendURL } from '../../helper/URLHelper';
import helpcenterRoutes from './helpcenter/helpcenter.routes'; import helpcenterRoutes from './helpcenter/helpcenter.routes';
@@ -16,6 +17,7 @@ export default {
path: frontendURL('accounts/:account_id'), path: frontendURL('accounts/:account_id'),
component: AppContainer, component: AppContainer,
children: [ children: [
...inboxRoutes,
...conversation.routes, ...conversation.routes,
...settings.routes, ...settings.routes,
...contactRoutes, ...contactRoutes,
@@ -0,0 +1,153 @@
<template>
<div class="flex flex-col h-full w-full md:w-[calc(100%-360px)]">
<inbox-item-header
:total-length="totalNotifications"
:current-index="activeNotificationPosition"
:active-notification="activeNotification"
@next="onClickNext"
@prev="onClickPrev"
/>
<conversation-box
class="h-[calc(100%-56px)]"
is-inbox-view
:inbox-id="inboxId"
:is-contact-panel-open="isContactPanelOpen"
:is-on-expanded-layout="false"
@contact-panel-toggle="onToggleContactPanel"
/>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import InboxItemHeader from './components/InboxItemHeader.vue';
import ConversationBox from 'dashboard/components/widgets/conversation/ConversationBox.vue';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import { BUS_EVENTS } from 'shared/constants/busEvents';
import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
export default {
components: {
InboxItemHeader,
ConversationBox,
},
mixins: [uiSettingsMixin],
computed: {
...mapGetters({
notifications: 'notifications/getNotifications',
activeNotificationById: 'notifications/getActiveNotificationById',
currentChat: 'getSelectedChat',
conversationById: 'getConversationById',
uiFlags: 'notifications/getUIFlags',
}),
inboxId() {
return Number(this.$route.params.inboxId);
},
notificationId() {
return Number(this.$route.params.notification_id);
},
activeNotification() {
return this.activeNotificationById(this.notificationId);
},
conversationId() {
return this.activeNotification?.primary_actor?.id;
},
totalNotifications() {
return this.notifications?.length ?? 0;
},
activeNotificationPosition() {
const notificationIndex = this.notifications.findIndex(
n => n.id === this.notificationId
);
return notificationIndex >= 0 ? notificationIndex + 1 : 0;
},
isContactPanelOpen() {
if (this.currentChat.id) {
const { is_contact_sidebar_open: isContactSidebarOpen } =
this.uiSettings;
return isContactSidebarOpen;
}
return false;
},
},
watch: {
conversationId: {
immediate: true,
handler(newVal, oldVal) {
if (newVal !== oldVal) {
this.fetchConversationById();
}
},
},
},
mounted() {
this.$store.dispatch('agents/get');
},
methods: {
async fetchConversationById() {
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(conversation = null) {
const selectedConversation = conversation || this.findConversation();
if (!selectedConversation) return;
this.$store
.dispatch('setActiveChat', { data: selectedConversation })
.then(() => {
bus.$emit(BUS_EVENTS.SCROLL_TO_MESSAGE);
});
},
findConversation() {
return this.conversationById(this.conversationId);
},
navigateToConversation(activeIndex, direction) {
const indexOffset = direction === 'next' ? 0 : -2;
const targetNotification = this.notifications[activeIndex + indexOffset];
if (targetNotification) {
const {
id,
primary_actor_id: primaryActorId,
primary_actor_type: primaryActorType,
primary_actor: { inbox_id: inboxId, meta: { unreadCount } = {} },
notification_type: notificationType,
} = targetNotification;
this.$track(INBOX_EVENTS.OPEN_CONVERSATION_VIA_INBOX, {
notificationType,
});
this.$store.dispatch('notifications/read', {
id,
primaryActorId,
primaryActorType,
unreadCount,
});
this.$router.push({
name: 'inbox_view_conversation',
params: { inboxId, notification_id: id },
});
}
},
onClickNext() {
this.navigateToConversation(this.activeNotificationPosition, 'next');
},
onClickPrev() {
this.navigateToConversation(this.activeNotificationPosition, 'prev');
},
onToggleContactPanel() {
this.updateUISettings({
is_contact_sidebar_open: !this.isContactPanelOpen,
});
},
},
};
</script>
@@ -0,0 +1,28 @@
<template>
<div
class="text-center bg-slate-25 dark:bg-slate-800 justify-center w-full h-full hidden md:flex items-center"
>
<span v-if="uiFlags.isFetching" class="spinner mt-4 mb-4" />
<div v-else class="flex flex-col items-center gap-2">
<fluent-icon
icon="mail-inbox"
size="40"
class="text-slate-600 dark:text-slate-400"
/>
<span class="text-slate-500 text-sm font-medium dark:text-slate-300">
{{ $t('INBOX.LIST.NOTE') }}
</span>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters({
uiFlags: 'notifications/getUIFlags',
}),
},
};
</script>
@@ -1,9 +1,13 @@
<template> <template>
<div <div
class="flex flex-col h-full w-full ltr:border-r border-slate-50 dark:border-slate-800/50" class="flex-col h-full w-full md:min-w-[360px] md:max-w-[360px] ltr:border-r border-slate-50 dark:border-slate-800/50"
:class="isOnExpandedLayout ? '' : 'min-w-[360px] max-w-[360px]'" :class="!notificationId ? 'flex' : 'hidden md:flex'"
> >
<inbox-list-header @filter="onFilterChange" /> <inbox-list-header
:is-context-menu-open="isInboxContextMenuOpen"
@filter="onFilterChange"
@redirect="redirectToInbox"
/>
<div <div
ref="notificationList" ref="notificationList"
class="flex flex-col w-full h-[calc(100%-56px)] overflow-x-hidden overflow-y-auto" class="flex flex-col w-full h-[calc(100%-56px)] overflow-x-hidden overflow-y-auto"
@@ -15,6 +19,9 @@
@mark-notification-as-read="markNotificationAsRead" @mark-notification-as-read="markNotificationAsRead"
@mark-notification-as-unread="markNotificationAsUnRead" @mark-notification-as-unread="markNotificationAsUnRead"
@delete-notification="deleteNotification" @delete-notification="deleteNotification"
@snooze-notification="openSnoozeNotificationModal"
@context-menu-open="isInboxContextMenuOpen = true"
@context-menu-close="isInboxContextMenuOpen = false"
/> />
<div v-if="uiFlags.isFetching" class="text-center"> <div v-if="uiFlags.isFetching" class="text-center">
<span class="spinner mt-4 mb-4" /> <span class="spinner mt-4 mb-4" />
@@ -25,18 +32,21 @@
> >
{{ $t('INBOX.LIST.NO_NOTIFICATIONS') }} {{ $t('INBOX.LIST.NO_NOTIFICATIONS') }}
</p> </p>
<p
v-if="showEndOfListMessage"
class="text-center text-slate-400 dark:text-slate-400 p-4"
>
{{ $t('INBOX.LIST.EOF') }}
</p>
<intersection-observer <intersection-observer
v-if="!showEndOfList && !uiFlags.isFetching" v-if="!showEndOfList && !uiFlags.isFetching"
:options="infiniteLoaderOptions" :options="infiniteLoaderOptions"
@observed="loadMoreNotifications" @observed="loadMoreNotifications"
/> />
</div> </div>
<woot-modal
:show.sync="showCustomSnoozeModal"
:on-close="hideCustomSnoozeModal"
>
<custom-snooze-modal
@close="hideCustomSnoozeModal"
@choose-time="scheduleCustomSnooze"
/>
</woot-modal>
</div> </div>
</template> </template>
@@ -46,26 +56,25 @@ import wootConstants from 'dashboard/constants/globals';
import InboxCard from './components/InboxCard.vue'; import InboxCard from './components/InboxCard.vue';
import InboxListHeader from './components/InboxListHeader.vue'; import InboxListHeader from './components/InboxListHeader.vue';
import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events'; 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 IntersectionObserver from 'dashboard/components/IntersectionObserver.vue';
import alertMixin from 'shared/mixins/alertMixin'; import alertMixin from 'shared/mixins/alertMixin';
import uiSettingsMixin from 'dashboard/mixins/uiSettings'; import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue';
export default { export default {
components: { components: {
InboxCard, InboxCard,
InboxListHeader, InboxListHeader,
IntersectionObserver, IntersectionObserver,
CustomSnoozeModal,
}, },
mixins: [alertMixin, uiSettingsMixin], mixins: [alertMixin, uiSettingsMixin],
props: {
conversationId: {
type: [String, Number],
default: 0,
},
isOnExpandedLayout: {
type: Boolean,
default: false,
},
},
data() { data() {
return { return {
infiniteLoaderOptions: { infiniteLoaderOptions: {
@@ -76,6 +85,9 @@ export default {
status: '', status: '',
type: '', type: '',
sortOrder: wootConstants.INBOX_SORT_BY.NEWEST, sortOrder: wootConstants.INBOX_SORT_BY.NEWEST,
isInboxContextMenuOpen: false,
showCustomSnoozeModal: false,
notificationIdToSnooze: null,
}; };
}, },
computed: { computed: {
@@ -85,6 +97,9 @@ export default {
uiFlags: 'notifications/getUIFlags', uiFlags: 'notifications/getUIFlags',
notification: 'notifications/getFilteredNotifications', notification: 'notifications/getFilteredNotifications',
}), }),
notificationId() {
return Number(this.$route.params.notification_id);
},
inboxFilters() { inboxFilters() {
return { return {
page: this.page, page: this.page,
@@ -102,13 +117,14 @@ export default {
showEmptyState() { showEmptyState() {
return !this.uiFlags.isFetching && !this.notifications.length; return !this.uiFlags.isFetching && !this.notifications.length;
}, },
showEndOfListMessage() {
return this.showEndOfList && this.notifications.length;
},
}, },
mounted() { mounted() {
this.setSavedFilter(); this.setSavedFilter();
this.fetchNotifications(); this.fetchNotifications();
bus.$on(CMD_SNOOZE_NOTIFICATION, this.onCmdSnoozeNotification);
},
destroyed() {
bus.$off(CMD_SNOOZE_NOTIFICATION, this.onCmdSnoozeNotification);
}, },
methods: { methods: {
fetchNotifications() { fetchNotifications() {
@@ -119,17 +135,15 @@ export default {
this.$store.dispatch('notifications/index', filter); this.$store.dispatch('notifications/index', filter);
}, },
redirectToInbox() { redirectToInbox() {
if (!this.conversationId) return; if (!this.notificationId) return;
if (this.$route.name === 'inbox_view') return; if (this.$route.name === 'inbox_view') return;
this.$router.push({ name: 'inbox_view' }); this.$router.push({ name: 'inbox_view' });
}, },
loadMoreNotifications() { loadMoreNotifications() {
if (this.uiFlags.isAllNotificationsLoaded) return; if (this.uiFlags.isAllNotificationsLoaded) return;
this.$store.dispatch('notifications/index', { this.$store.dispatch('notifications/index', {
...this.inboxFilters,
page: this.page + 1, page: this.page + 1,
status: this.status,
type: this.type,
sortOrder: this.sortOrder,
}); });
this.page += 1; this.page += 1;
}, },
@@ -195,6 +209,47 @@ export default {
this.type = type; this.type = type;
this.sortOrder = sortBy || wootConstants.INBOX_SORT_BY.NEWEST; 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);
}
},
}, },
}; };
</script> </script>
@@ -1,144 +1,25 @@
<template> <template>
<section class="flex w-full h-full bg-white dark:bg-slate-900"> <section class="flex w-full h-full bg-white dark:bg-slate-900">
<inbox-list <router-view name="default" />
v-show="showConversationList" <router-view name="detailView" />
:conversation-id="conversationId"
:is-on-expanded-layout="isOnExpandedLayout"
/>
<div
v-if="showInboxMessageView"
class="flex flex-col h-full"
:class="isOnExpandedLayout ? 'w-full' : 'w-[calc(100%-360px)]'"
>
<inbox-item-header
:total-length="totalNotifications"
:current-index="activeNotificationIndex"
:active-notification="activeNotification"
@next="onClickNext"
@prev="onClickPrev"
/>
<conversation-box
class="h-[calc(100%-56px)]"
is-inbox-view
:inbox-id="inboxId"
:is-contact-panel-open="isContactPanelOpen"
:is-on-expanded-layout="isOnExpandedLayout"
@contact-panel-toggle="onToggleContactPanel"
/>
</div>
<div
v-if="!showInboxMessageView && !isOnExpandedLayout"
class="text-center bg-slate-25 dark:bg-slate-800 justify-center w-full h-full flex items-center"
>
<span v-if="uiFlags.isFetching" class="spinner mt-4 mb-4" />
<div v-else class="flex flex-col items-center gap-2">
<fluent-icon
icon="mail-inbox"
size="40"
class="text-slate-600 dark:text-slate-400"
/>
<span class="text-slate-500 text-sm font-medium dark:text-slate-300">
{{ $t('INBOX.LIST.NOTE') }}
</span>
</div>
</div>
</section> </section>
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import InboxList from './InboxList.vue';
import InboxItemHeader from './components/InboxItemHeader.vue';
import ConversationBox from 'dashboard/components/widgets/conversation/ConversationBox.vue';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import wootConstants from 'dashboard/constants/globals';
import { FEATURE_FLAGS } from 'dashboard/featureFlags'; import { FEATURE_FLAGS } from 'dashboard/featureFlags';
import { BUS_EVENTS } from 'shared/constants/busEvents';
import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
export default { export default {
components: {
InboxList,
InboxItemHeader,
ConversationBox,
},
mixins: [uiSettingsMixin],
props: {
inboxId: {
type: [String, Number],
default: 0,
},
conversationId: {
type: [String, Number],
default: 0,
},
},
computed: { computed: {
...mapGetters({ ...mapGetters({
currentAccountId: 'getCurrentAccountId', currentAccountId: 'getCurrentAccountId',
notifications: 'notifications/getNotifications',
currentChat: 'getSelectedChat',
allConversation: 'getAllConversations',
uiFlags: 'notifications/getUIFlags',
}), }),
activeNotification() {
return this.notifications.find(
n => n.primary_actor.id === Number(this.conversationId)
);
},
isInboxViewEnabled() { isInboxViewEnabled() {
return this.$store.getters['accounts/isFeatureEnabledGlobally']( return this.$store.getters['accounts/isFeatureEnabledGlobally'](
this.currentAccountId, this.currentAccountId,
FEATURE_FLAGS.INBOX_VIEW FEATURE_FLAGS.INBOX_VIEW
); );
}, },
showConversationList() {
return this.isOnExpandedLayout ? !this.conversationId : true;
},
isFetchingInitialData() {
return this.uiFlags.isFetching && !this.notifications.length;
},
showInboxMessageView() {
return (
Boolean(this.conversationId) &&
Boolean(this.currentChat.id) &&
!this.isFetchingInitialData
);
},
totalNotifications() {
return this.notifications?.length ?? 0;
},
activeNotificationIndex() {
const conversationId = Number(this.conversationId);
const notificationIndex = this.notifications.findIndex(
n => n.primary_actor.id === conversationId
);
return notificationIndex >= 0 ? notificationIndex + 1 : 0;
},
isOnExpandedLayout() {
const {
LAYOUT_TYPES: { CONDENSED },
} = wootConstants;
const { conversation_display_type: conversationDisplayType = CONDENSED } =
this.uiSettings;
return conversationDisplayType !== CONDENSED;
},
isContactPanelOpen() {
if (this.currentChat.id) {
const { is_contact_sidebar_open: isContactSidebarOpen } =
this.uiSettings;
return isContactSidebarOpen;
}
return false;
},
},
watch: {
conversationId: {
immediate: true,
handler() {
this.fetchConversationById();
},
},
}, },
mounted() { mounted() {
// Open inbox view if inbox view feature is enabled, else redirect to dashboard // Open inbox view if inbox view feature is enabled, else redirect to dashboard
@@ -148,70 +29,6 @@ export default {
name: 'home', name: 'home',
}); });
} }
this.$store.dispatch('agents/get');
},
methods: {
async fetchConversationById() {
if (!this.conversationId) return;
const chat = this.findConversation();
if (!chat) {
await this.$store.dispatch('getConversation', this.conversationId);
}
this.setActiveChat();
},
setActiveChat() {
const selectedConversation = this.findConversation();
if (!selectedConversation) return;
this.$store
.dispatch('setActiveChat', { data: selectedConversation })
.then(() => {
bus.$emit(BUS_EVENTS.SCROLL_TO_MESSAGE);
});
},
findConversation() {
const conversationId = Number(this.conversationId);
return this.allConversation.find(c => c.id === conversationId);
},
navigateToConversation(activeIndex, direction) {
const indexOffset = direction === 'next' ? 0 : -2;
const targetNotification = this.notifications[activeIndex + indexOffset];
if (targetNotification) {
const {
id,
primary_actor_id: primaryActorId,
primary_actor_type: primaryActorType,
primary_actor: { id: conversationId, meta: { unreadCount } = {} },
notification_type: notificationType,
} = targetNotification;
this.$track(INBOX_EVENTS.OPEN_CONVERSATION_VIA_INBOX, {
notificationType,
});
this.$store.dispatch('notifications/read', {
id,
primaryActorId,
primaryActorType,
unreadCount,
});
this.$router.push({
name: 'inbox_view_conversation',
params: { conversation_id: conversationId },
});
}
},
onClickNext() {
this.navigateToConversation(this.activeNotificationIndex, 'next');
},
onClickPrev() {
this.navigateToConversation(this.activeNotificationIndex, 'prev');
},
onToggleContactPanel() {
this.updateUISettings({
is_contact_sidebar_open: !this.isContactPanelOpen,
});
},
}, },
}; };
</script> </script>
@@ -89,6 +89,7 @@ export default {
return { return {
isContextMenuOpen: false, isContextMenuOpen: false,
contextMenuPosition: { x: null, y: null }, contextMenuPosition: { x: null, y: null },
activeNotificationId: null,
}; };
}, },
computed: { computed: {
@@ -96,7 +97,9 @@ export default {
return this.notificationItem?.primary_actor; return this.notificationItem?.primary_actor;
}, },
isInboxCardActive() { isInboxCardActive() {
return this.$route.params.conversation_id === this.primaryActor?.id; return (
Number(this.activeNotificationId) === Number(this.notificationItem?.id)
);
}, },
inbox() { inbox() {
return this.$store.getters['inboxes/getInbox']( return this.$store.getters['inboxes/getInbox'](
@@ -109,6 +112,9 @@ export default {
meta() { meta() {
return this.primaryActor?.meta; return this.primaryActor?.meta;
}, },
isNotSnoozed() {
return !this.notificationItem?.snoozed_until;
},
assigneeMeta() { assigneeMeta() {
return this.meta?.assignee; return this.meta?.assignee;
}, },
@@ -131,6 +137,12 @@ export default {
}, },
]; ];
if (this.isNotSnoozed) {
items.push({
key: 'snooze',
label: this.$t('INBOX.MENU_ITEM.SNOOZE'),
});
}
if (!this.isUnread) { if (!this.isUnread) {
items.push({ items.push({
key: 'mark_as_unread', key: 'mark_as_unread',
@@ -157,6 +169,14 @@ export default {
return ''; return '';
}, },
}, },
watch: {
'$route.params.notification_id': {
immediate: true,
handler(newVal) {
this.activeNotificationId = newVal;
},
},
},
unmounted() { unmounted() {
this.closeContextMenu(); this.closeContextMenu();
}, },
@@ -166,11 +186,11 @@ export default {
id, id,
primary_actor_id: primaryActorId, primary_actor_id: primaryActorId,
primary_actor_type: primaryActorType, primary_actor_type: primaryActorType,
primary_actor: { id: conversationId, inbox_id: inboxId }, primary_actor: { inbox_id: inboxId },
notification_type: notificationType, notification_type: notificationType,
} = notification; } = notification;
if (this.$route.params.conversation_id !== conversationId) { if (this.$route.params.notification_id !== id) {
this.$track(INBOX_EVENTS.OPEN_CONVERSATION_VIA_INBOX, { this.$track(INBOX_EVENTS.OPEN_CONVERSATION_VIA_INBOX, {
notificationType, notificationType,
}); });
@@ -184,13 +204,14 @@ export default {
this.$router.push({ this.$router.push({
name: 'inbox_view_conversation', name: 'inbox_view_conversation',
params: { inboxId, conversation_id: conversationId }, params: { inboxId, notification_id: id },
}); });
} }
}, },
closeContextMenu() { closeContextMenu() {
this.isContextMenuOpen = false; this.isContextMenuOpen = false;
this.contextMenuPosition = { x: null, y: null }; this.contextMenuPosition = { x: null, y: null };
this.$emit('context-menu-close');
}, },
openContextMenu(e) { openContextMenu(e) {
this.closeContextMenu(); this.closeContextMenu();
@@ -200,6 +221,7 @@ export default {
y: e.pageY || e.clientY, y: e.pageY || e.clientY,
}; };
this.isContextMenuOpen = true; this.isContextMenuOpen = true;
this.$emit('context-menu-open');
}, },
handleAction(key) { handleAction(key) {
switch (key) { switch (key) {
@@ -212,6 +234,9 @@ export default {
case 'delete': case 'delete':
this.$emit('delete-notification', this.notificationItem); this.$emit('delete-notification', this.notificationItem);
break; break;
case 'snooze':
this.$emit('snooze-notification', this.notificationItem);
break;
default: default:
} }
}, },
@@ -2,6 +2,16 @@
<div <div
class="flex gap-2 py-2 ltr:pl-4 rtl:pl-2 h-14 ltr:pr-2 rtl:pr-4 rtl:border-r justify-between items-center w-full border-b border-slate-50 dark:border-slate-800/50" class="flex gap-2 py-2 ltr:pl-4 rtl:pl-2 h-14 ltr:pr-2 rtl:pr-4 rtl:border-r justify-between items-center w-full border-b border-slate-50 dark:border-slate-800/50"
> >
<woot-button
variant="clear link"
class="flex md:hidden !pt-1 !pb-1 rounded-md ltr:pr-1 rtl:pl-1 !no-underline"
size="medium"
color-scheme="primary"
icon="chevron-left"
@click="onClickGoToInboxList"
>
{{ $t('INBOX.ACTION_HEADER.BACK') }}
</woot-button>
<pagination-button <pagination-button
v-if="totalLength > 1" v-if="totalLength > 1"
:total-length="totalLength" :total-length="totalLength"
@@ -12,10 +22,12 @@
<div v-else /> <div v-else />
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<woot-button <woot-button
v-if="!isNotificationAlreadySnoozed"
variant="hollow" variant="hollow"
size="small" size="small"
color-scheme="secondary" color-scheme="secondary"
icon="snooze" icon="snooze"
class="[&>span]:hidden md:[&>span]:inline-flex"
@click="openSnoozeNotificationModal" @click="openSnoozeNotificationModal"
> >
{{ $t('INBOX.ACTION_HEADER.SNOOZE') }} {{ $t('INBOX.ACTION_HEADER.SNOOZE') }}
@@ -25,37 +37,23 @@
size="small" size="small"
color-scheme="secondary" color-scheme="secondary"
variant="hollow" variant="hollow"
class="[&>span]:hidden md:[&>span]:inline-flex"
@click="deleteNotification" @click="deleteNotification"
> >
{{ $t('INBOX.ACTION_HEADER.DELETE') }} {{ $t('INBOX.ACTION_HEADER.DELETE') }}
</woot-button> </woot-button>
</div> </div>
<woot-modal
:show.sync="showCustomSnoozeModal"
:on-close="hideCustomSnoozeModal"
>
<custom-snooze-modal
@close="hideCustomSnoozeModal"
@choose-time="scheduleCustomSnooze"
/>
</woot-modal>
</div> </div>
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { getUnixTime } from 'date-fns';
import { CMD_SNOOZE_NOTIFICATION } from 'dashboard/routes/dashboard/commands/commandBarBusEvents';
import wootConstants from 'dashboard/constants/globals';
import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers';
import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events'; import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
import PaginationButton from './PaginationButton.vue'; import PaginationButton from './PaginationButton.vue';
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue';
import alertMixin from 'shared/mixins/alertMixin'; import alertMixin from 'shared/mixins/alertMixin';
export default { export default {
components: { components: {
PaginationButton, PaginationButton,
CustomSnoozeModal,
}, },
mixins: [alertMixin], mixins: [alertMixin],
props: { props: {
@@ -72,55 +70,19 @@ export default {
default: null, default: null,
}, },
}, },
data() {
return {
showCustomSnoozeModal: false,
};
},
computed: { computed: {
...mapGetters({ ...mapGetters({
meta: 'notifications/getMeta', meta: 'notifications/getMeta',
}), }),
}, isNotificationAlreadySnoozed() {
mounted() { return this.activeNotification?.snoozed_until;
bus.$on(CMD_SNOOZE_NOTIFICATION, this.onCmdSnoozeNotification); },
},
destroyed() {
bus.$off(CMD_SNOOZE_NOTIFICATION, this.onCmdSnoozeNotification);
}, },
methods: { methods: {
openSnoozeNotificationModal() { openSnoozeNotificationModal() {
const ninja = document.querySelector('ninja-keys'); const ninja = document.querySelector('ninja-keys');
ninja.open({ parent: 'snooze_notification' }); ninja.open({ parent: 'snooze_notification' });
}, },
hideCustomSnoozeModal() {
this.showCustomSnoozeModal = false;
},
snoozeNotification(snoozedUntil) {
this.$store
.dispatch('notifications/snooze', {
id: this.activeNotification?.id,
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);
}
},
deleteNotification() { deleteNotification() {
this.$track(INBOX_EVENTS.DELETE_NOTIFICATION); this.$track(INBOX_EVENTS.DELETE_NOTIFICATION);
this.$store this.$store
@@ -140,6 +102,9 @@ export default {
onClickPrev() { onClickPrev() {
this.$emit('prev'); this.$emit('prev');
}, },
onClickGoToInboxList() {
this.$router.push({ name: 'inbox_view' });
},
}, },
}; };
</script> </script>
@@ -49,7 +49,7 @@
<inbox-option-menu <inbox-option-menu
v-if="showInboxOptionMenu" v-if="showInboxOptionMenu"
v-on-clickaway="openInboxOptionsMenu" v-on-clickaway="openInboxOptionsMenu"
class="absolute top-9" class="absolute top-9 ltr:right-0 ltr:md:right-[unset] rtl:left-0 rtl:md:left-[unset]"
@option-click="onInboxOptionMenuClick" @option-click="onInboxOptionMenuClick"
/> />
</div> </div>
@@ -69,12 +69,29 @@ export default {
InboxDisplayMenu, InboxDisplayMenu,
}, },
mixins: [clickaway, alertMixin], mixins: [clickaway, alertMixin],
props: {
isContextMenuOpen: {
type: Boolean,
default: false,
},
},
data() { data() {
return { return {
showInboxDisplayMenu: false, showInboxDisplayMenu: false,
showInboxOptionMenu: false, showInboxOptionMenu: false,
}; };
}, },
watch: {
isContextMenuOpen: {
handler(val) {
if (val) {
this.showInboxDisplayMenu = false;
this.showInboxOptionMenu = false;
}
},
immediate: true,
},
},
methods: { methods: {
markAllRead() { markAllRead() {
this.$track(INBOX_EVENTS.MARK_ALL_NOTIFICATIONS_AS_READ); this.$track(INBOX_EVENTS.MARK_ALL_NOTIFICATIONS_AS_READ);
@@ -100,21 +117,19 @@ export default {
}, },
onInboxOptionMenuClick(key) { onInboxOptionMenuClick(key) {
this.showInboxOptionMenu = false; this.showInboxOptionMenu = false;
if (key === 'mark_all_read') { const actions = {
this.markAllRead(); mark_all_read: () => this.markAllRead(),
} delete_all: () => this.deleteAll(),
if (key === 'delete_all') { delete_all_read: () => this.deleteAllRead(),
this.deleteAll(); };
} const action = actions[key];
if (key === 'delete_all_read') { if (action) action();
this.deleteAllRead(); this.$emit('redirect');
}
}, },
onFilterChange(option) { onFilterChange(option) {
this.$emit('filter', option); this.$emit('filter', option);
this.showInboxDisplayMenu = false; this.showInboxDisplayMenu = false;
if (this.$route.name === 'inbox_view') return; this.$emit('redirect');
this.$router.push({ name: 'inbox_view' });
}, },
}, },
}; };
@@ -19,7 +19,7 @@
/> />
</div> </div>
<div class="flex items-center gap-1 whitespace-nowrap"> <div class="flex items-center gap-1 whitespace-nowrap">
<span class="text-sm font-medium text-gray-600"> <span class="text-sm font-medium text-gray-600 tabular-nums">
{{ totalLength <= 1 ? '1' : currentIndex }} {{ totalLength <= 1 ? '1' : currentIndex }}
</span> </span>
<span <span
@@ -28,7 +28,7 @@
> >
/ /
</span> </span>
<span v-if="totalLength > 1" class="text-sm text-slate-400"> <span v-if="totalLength > 1" class="text-sm text-slate-400 tabular-nums">
{{ totalLength }} {{ totalLength }}
</span> </span>
</div> </div>
@@ -0,0 +1,32 @@
import { frontendURL } from 'dashboard/helper/URLHelper';
const InboxView = () => import('./InboxView.vue');
const InboxListView = () => import('./InboxList.vue');
const InboxDetailView = () => import('./InboxDetail.vue');
const InboxEmptyStateView = () => import('./InboxEmptyState.vue');
export const routes = [
{
path: frontendURL('accounts/:accountId/inbox-view'),
component: InboxView,
children: [
{
path: '',
name: 'inbox_view',
components: {
default: InboxListView,
detailView: InboxEmptyStateView,
},
roles: ['administrator', 'agent'],
},
{
path: ':notification_id',
name: 'inbox_view_conversation',
components: {
default: InboxListView,
detailView: InboxDetailView,
},
roles: ['administrator', 'agent'],
},
],
},
];
@@ -11,6 +11,9 @@ export const getters = {
); );
return sortedNotifications; return sortedNotifications;
}, },
getActiveNotificationById: $state => id => {
return $state.records[id] || {};
},
getUIFlags($state) { getUIFlags($state) {
return $state.uiFlags; return $state.uiFlags;
}, },
@@ -44,6 +44,16 @@ describe('#getters', () => {
]); ]);
}); });
it('getActiveNotificationById', () => {
const state = {
records: {
1: { id: 1 },
},
};
expect(getters.getActiveNotificationById(state)(1)).toEqual({ id: 1 });
expect(getters.getActiveNotificationById(state)(2)).toEqual({});
});
it('getUIFlags', () => { it('getUIFlags', () => {
const state = { const state = {
uiFlags: { uiFlags: {
+1 -1
View File
@@ -67,7 +67,7 @@
"markdown-it": "^13.0.1", "markdown-it": "^13.0.1",
"markdown-it-link-attributes": "^4.0.1", "markdown-it-link-attributes": "^4.0.1",
"md5": "^2.3.0", "md5": "^2.3.0",
"ninja-keys": "^1.2.2", "@chatwoot/ninja-keys": "1.2.3",
"opus-recorder": "^8.0.5", "opus-recorder": "^8.0.5",
"postcss": "^8.4.31", "postcss": "^8.4.31",
"postcss-loader": "^4.2.0", "postcss-loader": "^4.2.0",
+9 -9
View File
@@ -3156,6 +3156,15 @@
"@braid/vue-formulate-i18n" "^1.16.0" "@braid/vue-formulate-i18n" "^1.16.0"
is-plain-object "^3.0.1" is-plain-object "^3.0.1"
"@chatwoot/ninja-keys@1.2.3":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@chatwoot/ninja-keys/-/ninja-keys-1.2.3.tgz#3c3f2b505f091ef4707fd1da39bb2ec6d12e7824"
integrity sha512-xM8d9P5ikDMZm2WbaCTk/TW5HFauylrU3cJ75fq5je6ixKwyhl/0kZbVN/vbbZN4+AUX/OaSIn6IJbtCgIF67g==
dependencies:
"@material/mwc-icon" "0.25.3"
hotkeys-js "3.8.7"
lit "2.2.6"
"@chatwoot/prosemirror-schema@1.0.5": "@chatwoot/prosemirror-schema@1.0.5":
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/@chatwoot/prosemirror-schema/-/prosemirror-schema-1.0.5.tgz#d6053692beae59d466ac0b04128fa157f59eb176" resolved "https://registry.yarnpkg.com/@chatwoot/prosemirror-schema/-/prosemirror-schema-1.0.5.tgz#d6053692beae59d466ac0b04128fa157f59eb176"
@@ -15077,15 +15086,6 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
ninja-keys@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/ninja-keys/-/ninja-keys-1.2.2.tgz#c1e1ec1a98aee3a977ee77157ac4aa865348be88"
integrity sha512-ylo8jzKowi3XBHkgHRjBJaKQkl32WRLr7kRiA0ajiku11vHRDJ2xANtTScR5C7XlDwKEOYvUPesCKacUeeLAYw==
dependencies:
"@material/mwc-icon" "0.25.3"
hotkeys-js "3.8.7"
lit "2.2.6"
no-case@^3.0.4: no-case@^3.0.4:
version "3.0.4" version "3.0.4"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"