diff --git a/app/javascript/dashboard/components/buttons/ResolveAction.vue b/app/javascript/dashboard/components/buttons/ResolveAction.vue
index 4c18f248c..163328e76 100644
--- a/app/javascript/dashboard/components/buttons/ResolveAction.vue
+++ b/app/javascript/dashboard/components/buttons/ResolveAction.vue
@@ -86,6 +86,7 @@ import {
hasPressedAltAndMKey,
} from 'shared/helpers/KeyboardHelpers';
+import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers';
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
@@ -181,7 +182,7 @@ export default {
onCmdSnoozeConversation(snoozeType) {
this.toggleStatus(
this.STATUS_TYPE.SNOOZED,
- this.snoozeTimes[snoozeType] || null
+ findSnoozeTime(snoozeType) || null
);
},
onCmdOpenConversation() {
diff --git a/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue b/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue
index f9ac4fb2d..98e71c6ec 100644
--- a/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue
+++ b/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue
@@ -57,7 +57,6 @@ import { hasPressedAltAndOKey } from 'shared/helpers/KeyboardHelpers';
import { mapGetters } from 'vuex';
import agentMixin from '../../../mixins/agentMixin.js';
import BackButton from '../BackButton';
-import differenceInHours from 'date-fns/differenceInHours';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import inboxMixin from 'shared/mixins/inboxMixin';
import InboxName from '../InboxName';
@@ -65,6 +64,8 @@ import MoreActions from './MoreActions';
import Thumbnail from '../Thumbnail';
import wootConstants from 'dashboard/constants/globals';
import { conversationListPageURL } from 'dashboard/helper/URLHelper';
+import { conversationReopenTime } from 'dashboard/helper/snoozeHelpers';
+
export default {
components: {
BackButton,
@@ -125,17 +126,9 @@ export default {
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'
+ )} ${conversationReopenTime(snoozedUntil)}`;
}
return this.$t('CONVERSATION.HEADER.SNOOZED_UNTIL_NEXT_REPLY');
},
diff --git a/app/javascript/dashboard/constants/globals.js b/app/javascript/dashboard/constants/globals.js
index e5aaf7814..749774476 100644
--- a/app/javascript/dashboard/constants/globals.js
+++ b/app/javascript/dashboard/constants/globals.js
@@ -30,5 +30,12 @@ export default {
TESTIMONIAL_URL: 'https://testimonials.cdn.chatwoot.com/content.json',
SMALL_SCREEN_BREAKPOINT: 1024,
AVAILABILITY_STATUS_KEYS: ['online', 'busy', 'offline'],
+ SNOOZE_OPTIONS: {
+ UNTIL_NEXT_REPLY: 'until_next_reply',
+ AN_HOUR_FROM_NOW: 'an_hour_from_now',
+ UNTIL_TOMORROW: 'until_tomorrow',
+ UNTIL_NEXT_WEEK: 'until_next_week',
+ UNTIL_NEXT_MONTH: 'until_next_month',
+ },
};
export const DEFAULT_REDIRECT_URL = '/app/';
diff --git a/app/javascript/dashboard/helper/snoozeHelpers.js b/app/javascript/dashboard/helper/snoozeHelpers.js
new file mode 100644
index 000000000..07cb3a20f
--- /dev/null
+++ b/app/javascript/dashboard/helper/snoozeHelpers.js
@@ -0,0 +1,66 @@
+import {
+ getUnixTime,
+ format,
+ add,
+ startOfWeek,
+ addWeeks,
+ startOfMonth,
+ isMonday,
+ isToday,
+ setHours,
+} from 'date-fns';
+import wootConstants from 'dashboard/constants/globals';
+
+const SNOOZE_OPTIONS = wootConstants.SNOOZE_OPTIONS;
+
+export const findStartOfNextWeek = currentDate => {
+ const startOfNextWeek = startOfWeek(addWeeks(currentDate, 1));
+ return isMonday(startOfNextWeek)
+ ? startOfNextWeek
+ : add(startOfNextWeek, {
+ days: (8 - startOfNextWeek.getDay()) % 7,
+ });
+};
+
+export const findStartOfNextMonth = currentDate => {
+ const startOfNextMonth = startOfMonth(add(currentDate, { months: 1 }));
+ return isMonday(startOfNextMonth)
+ ? startOfNextMonth
+ : add(startOfNextMonth, {
+ days: (8 - startOfNextMonth.getDay()) % 7,
+ });
+};
+
+export const findNextDay = currentDate => {
+ return add(currentDate, { days: 1 });
+};
+
+export const setHoursToNine = date => {
+ return setHours(date, 9, 0, 0);
+};
+
+export const findSnoozeTime = (snoozeType, currentDate = new Date()) => {
+ let parsedDate = null;
+ if (snoozeType === SNOOZE_OPTIONS.AN_HOUR_FROM_NOW) {
+ parsedDate = add(currentDate, { hours: 1 });
+ } else if (snoozeType === SNOOZE_OPTIONS.UNTIL_TOMORROW) {
+ parsedDate = setHoursToNine(findNextDay(currentDate));
+ } else if (snoozeType === SNOOZE_OPTIONS.UNTIL_NEXT_WEEK) {
+ parsedDate = setHoursToNine(findStartOfNextWeek(currentDate));
+ } else if (snoozeType === SNOOZE_OPTIONS.UNTIL_NEXT_MONTH) {
+ parsedDate = setHoursToNine(findStartOfNextMonth(currentDate));
+ }
+
+ return parsedDate ? getUnixTime(parsedDate) : null;
+};
+export const conversationReopenTime = snoozedUntil => {
+ if (!snoozedUntil) {
+ return null;
+ }
+ const date = new Date(snoozedUntil);
+
+ if (isToday(date)) {
+ return format(date, 'h.mmaaa');
+ }
+ return snoozedUntil ? format(date, 'd MMM, h.mmaaa') : null;
+};
diff --git a/app/javascript/dashboard/helper/specs/snoozeHelpers.spec.js b/app/javascript/dashboard/helper/specs/snoozeHelpers.spec.js
new file mode 100644
index 000000000..6da35d02c
--- /dev/null
+++ b/app/javascript/dashboard/helper/specs/snoozeHelpers.spec.js
@@ -0,0 +1,105 @@
+import {
+ findSnoozeTime,
+ conversationReopenTime,
+ findStartOfNextWeek,
+ findStartOfNextMonth,
+ findNextDay,
+ setHoursToNine,
+} from '../snoozeHelpers';
+
+describe('#Snooze Helpers', () => {
+ describe('findStartOfNextWeek', () => {
+ it('should return first working day of next week if a date is passed', () => {
+ const today = new Date('06/16/2023');
+ const startOfNextWeek = new Date('06/19/2023');
+ expect(findStartOfNextWeek(today)).toEqual(startOfNextWeek);
+ });
+ it('should return first working day of next week if a date is passed', () => {
+ const today = new Date('06/03/2023');
+ const startOfNextWeek = new Date('06/05/2023');
+ expect(findStartOfNextWeek(today)).toEqual(startOfNextWeek);
+ });
+ });
+
+ describe('findStartOfNextMonth', () => {
+ it('should return first working day of next month if a valid date is passed', () => {
+ const today = new Date('06/21/2023');
+ const startOfNextMonth = new Date('07/03/2023');
+ expect(findStartOfNextMonth(today)).toEqual(startOfNextMonth);
+ });
+ it('should return first working day of next month if a valid date is passed', () => {
+ const today = new Date('02/28/2023');
+ const startOfNextMonth = new Date('03/06/2023');
+ expect(findStartOfNextMonth(today)).toEqual(startOfNextMonth);
+ });
+ });
+
+ describe('setHoursToNine', () => {
+ it('should return date with 9.00AM time', () => {
+ const nextDay = new Date('06/17/2023');
+ nextDay.setHours(9, 0, 0, 0);
+ expect(setHoursToNine(nextDay)).toEqual(nextDay);
+ });
+ });
+
+ describe('findSnoozeTime', () => {
+ it('should return nil if until_next_reply is passed', () => {
+ expect(findSnoozeTime('until_next_reply')).toEqual(null);
+ });
+
+ it('should return next hour time stamp if an_hour_from_now is passed', () => {
+ const nextHour = new Date();
+ nextHour.setHours(nextHour.getHours() + 1);
+ expect(findSnoozeTime('an_hour_from_now')).toBeCloseTo(
+ Math.floor(nextHour.getTime() / 1000)
+ );
+ });
+
+ it('should return next day 9.00AM time stamp until_tomorrow is passed', () => {
+ const today = new Date('06/16/2023');
+ const nextDay = new Date('06/17/2023');
+ nextDay.setHours(9, 0, 0, 0);
+ expect(findSnoozeTime('until_tomorrow', today)).toBeCloseTo(
+ nextDay.getTime() / 1000
+ );
+ });
+
+ it('should return next week monday 9.00AM time stamp if until_next_week is passed', () => {
+ const today = new Date('06/16/2023');
+ const startOfNextWeek = new Date('06/19/2023');
+ startOfNextWeek.setHours(9, 0, 0, 0);
+ expect(findSnoozeTime('until_next_week', today)).toBeCloseTo(
+ startOfNextWeek.getTime() / 1000
+ );
+ });
+
+ it('should return next month 9.00AM time stamp if until_next_month is passed', () => {
+ const today = new Date('06/21/2023');
+ const startOfNextMonth = new Date('07/03/2023');
+ startOfNextMonth.setHours(9, 0, 0, 0);
+ expect(findSnoozeTime('until_next_month', today)).toBeCloseTo(
+ startOfNextMonth.getTime() / 1000
+ );
+ });
+ });
+
+ describe('conversationReopenTime', () => {
+ it('should return nil if snoozedUntil is nil', () => {
+ expect(conversationReopenTime(null)).toEqual(null);
+ });
+
+ it('should return formatted date if snoozedUntil is not nil', () => {
+ expect(conversationReopenTime('2023-06-07T09:00:00.000Z')).toEqual(
+ '7 Jun, 9.00am'
+ );
+ });
+ });
+
+ describe('findNextDay', () => {
+ it('should return next day', () => {
+ const today = new Date('06/16/2023');
+ const nextDay = new Date('06/17/2023');
+ expect(findNextDay(today)).toEqual(nextDay);
+ });
+ });
+});
diff --git a/app/javascript/dashboard/i18n/locale/en/generalSettings.json b/app/javascript/dashboard/i18n/locale/en/generalSettings.json
index f5af885a9..de41110c0 100644
--- a/app/javascript/dashboard/i18n/locale/en/generalSettings.json
+++ b/app/javascript/dashboard/i18n/locale/en/generalSettings.json
@@ -106,6 +106,7 @@
"CHANGE_ASSIGNEE": "Change Assignee",
"CHANGE_PRIORITY": "Change Priority",
"CHANGE_TEAM": "Change Team",
+ "SNOOZE_CONVERSATION": "Snooze Conversation",
"ADD_LABEL": "Add label to the conversation",
"REMOVE_LABEL": "Remove label from the conversation",
"SETTINGS": "Settings"
@@ -141,7 +142,10 @@
"SNOOZE_CONVERSATION": "Snooze Conversation",
"UNTIL_NEXT_REPLY": "Until next reply",
"UNTIL_NEXT_WEEK": "Until next week",
- "UNTIL_TOMORROW": "Until tomorrow"
+ "UNTIL_TOMORROW": "Until tomorrow",
+ "UNTIL_NEXT_MONTH": "Until next month",
+ "AN_HOUR_FROM_NOW": "Until an hour from now",
+ "CUSTOM": "Custom..."
}
},
"DASHBOARD_APPS": {
diff --git a/app/javascript/dashboard/routes/dashboard/commands/CommandBarIcons.js b/app/javascript/dashboard/routes/dashboard/commands/CommandBarIcons.js
index 4a4bd9cf0..97831ae53 100644
--- a/app/javascript/dashboard/routes/dashboard/commands/CommandBarIcons.js
+++ b/app/javascript/dashboard/routes/dashboard/commands/CommandBarIcons.js
@@ -6,8 +6,7 @@ export const ICON_REMOVE_LABEL = ``;
export const ICON_RESOLVE_CONVERSATION = ``;
export const ICON_SEND_TRANSCRIPT = ``;
-export const ICON_SNOOZE_CONVERSATION = ``;
-export const ICON_SNOOZE_UNTIL_NEXT_REPLY = ``;
+export const ICON_SNOOZE_CONVERSATION = ``;
export const ICON_SNOOZE_UNTIL_NEXT_WEEK = ``;
export const ICON_SNOOZE_UNTIL_TOMORRROW = ``;
export const ICON_CONVERSATION_DASHBOARD = ``;
diff --git a/app/javascript/dashboard/routes/dashboard/commands/conversationHotKeys.js b/app/javascript/dashboard/routes/dashboard/commands/conversationHotKeys.js
index 01bd6a2f5..ea1ca868d 100644
--- a/app/javascript/dashboard/routes/dashboard/commands/conversationHotKeys.js
+++ b/app/javascript/dashboard/routes/dashboard/commands/conversationHotKeys.js
@@ -20,9 +20,6 @@ import {
ICON_RESOLVE_CONVERSATION,
ICON_SEND_TRANSCRIPT,
ICON_SNOOZE_CONVERSATION,
- ICON_SNOOZE_UNTIL_NEXT_REPLY,
- ICON_SNOOZE_UNTIL_NEXT_WEEK,
- ICON_SNOOZE_UNTIL_TOMORRROW,
ICON_UNMUTE_CONVERSATION,
ICON_PRIORITY_URGENT,
ICON_PRIORITY_HIGH,
@@ -31,6 +28,8 @@ import {
ICON_PRIORITY_NONE,
} from './CommandBarIcons';
+const SNOOZE_OPTIONS = wootConstants.SNOOZE_OPTIONS;
+
const OPEN_CONVERSATION_ACTIONS = [
{
id: 'resolve_conversation',
@@ -39,32 +38,60 @@ const OPEN_CONVERSATION_ACTIONS = [
icon: ICON_RESOLVE_CONVERSATION,
handler: () => bus.$emit(CMD_RESOLVE_CONVERSATION),
},
+];
+
+const SNOOZE_CONVERSATION_ACTIONS = [
{
id: 'snooze_conversation',
title: 'COMMAND_BAR.COMMANDS.SNOOZE_CONVERSATION',
icon: ICON_SNOOZE_CONVERSATION,
- children: ['until_next_reply', 'until_tomorrow', 'until_next_week'],
+ children: Object.values(SNOOZE_OPTIONS),
},
+
{
- id: 'until_next_reply',
+ id: SNOOZE_OPTIONS.UNTIL_NEXT_REPLY,
title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_REPLY',
parent: 'snooze_conversation',
- icon: ICON_SNOOZE_UNTIL_NEXT_REPLY,
- handler: () => bus.$emit(CMD_SNOOZE_CONVERSATION, 'nextReply'),
+ section: 'COMMAND_BAR.SECTIONS.SNOOZE_CONVERSATION',
+ icon: ICON_SNOOZE_CONVERSATION,
+ handler: () =>
+ bus.$emit(CMD_SNOOZE_CONVERSATION, SNOOZE_OPTIONS.UNTIL_NEXT_REPLY),
},
{
- id: 'until_tomorrow',
+ id: SNOOZE_OPTIONS.AN_HOUR_FROM_NOW,
+ title: 'COMMAND_BAR.COMMANDS.AN_HOUR_FROM_NOW',
+ parent: 'snooze_conversation',
+ section: 'COMMAND_BAR.SECTIONS.SNOOZE_CONVERSATION',
+ icon: ICON_SNOOZE_CONVERSATION,
+ handler: () =>
+ bus.$emit(CMD_SNOOZE_CONVERSATION, SNOOZE_OPTIONS.AN_HOUR_FROM_NOW),
+ },
+ {
+ id: SNOOZE_OPTIONS.UNTIL_TOMORROW,
title: 'COMMAND_BAR.COMMANDS.UNTIL_TOMORROW',
+ section: 'COMMAND_BAR.SECTIONS.SNOOZE_CONVERSATION',
parent: 'snooze_conversation',
- icon: ICON_SNOOZE_UNTIL_TOMORRROW,
- handler: () => bus.$emit(CMD_SNOOZE_CONVERSATION, 'tomorrow'),
+ icon: ICON_SNOOZE_CONVERSATION,
+ handler: () =>
+ bus.$emit(CMD_SNOOZE_CONVERSATION, SNOOZE_OPTIONS.UNTIL_TOMORROW),
},
{
- id: 'until_next_week',
+ id: SNOOZE_OPTIONS.UNTIL_NEXT_WEEK,
title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_WEEK',
+ section: 'COMMAND_BAR.SECTIONS.SNOOZE_CONVERSATION',
parent: 'snooze_conversation',
- icon: ICON_SNOOZE_UNTIL_NEXT_WEEK,
- handler: () => bus.$emit(CMD_SNOOZE_CONVERSATION, 'nextWeek'),
+ icon: ICON_SNOOZE_CONVERSATION,
+ handler: () =>
+ bus.$emit(CMD_SNOOZE_CONVERSATION, SNOOZE_OPTIONS.UNTIL_NEXT_WEEK),
+ },
+ {
+ id: SNOOZE_OPTIONS.UNTIL_NEXT_MONTH,
+ title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_MONTH',
+ section: 'COMMAND_BAR.SECTIONS.SNOOZE_CONVERSATION',
+ parent: 'snooze_conversation',
+ icon: ICON_SNOOZE_CONVERSATION,
+ handler: () =>
+ bus.$emit(CMD_SNOOZE_CONVERSATION, SNOOZE_OPTIONS.UNTIL_NEXT_MONTH),
},
];
@@ -135,6 +162,7 @@ export default {
conversationId() {
return this.currentChat?.id;
},
+
statusActions() {
const isOpen =
this.currentChat?.status === wootConstants.STATUS_TYPE.OPEN;
@@ -145,7 +173,10 @@ export default {
let actions = [];
if (isOpen) {
- actions = OPEN_CONVERSATION_ACTIONS;
+ actions = [
+ ...OPEN_CONVERSATION_ACTIONS,
+ ...SNOOZE_CONVERSATION_ACTIONS,
+ ];
} else if (isResolved || isSnoozed) {
actions = RESOLVED_CONVERSATION_ACTIONS;
}
@@ -296,6 +327,7 @@ export default {
SEND_TRANSCRIPT_ACTION,
]);
},
+
conversationHotKeys() {
if (isAConversationRoute(this.$route.name)) {
return [