Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73abdfc1b7 | ||
|
|
b7454c5d87 | ||
|
|
7b9ebaf90c | ||
|
|
d3ad267227 | ||
|
|
2762da5636 | ||
|
|
1fed175e65 |
@@ -1,6 +1,8 @@
|
||||
<script>
|
||||
import DatePicker from 'vue-datepicker-next';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
import { getUnixTime } from 'date-fns';
|
||||
import { saveLastCustomSnoozeTime } from 'dashboard/helper/customSnoozeStorage';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -25,7 +27,13 @@ export default {
|
||||
this.$emit('close');
|
||||
},
|
||||
chooseTime() {
|
||||
this.$emit('chooseTime', this.snoozeTime);
|
||||
if (this.snoozeTime) {
|
||||
const unixTimestamp = getUnixTime(this.snoozeTime);
|
||||
// Save the custom time to localStorage for future use
|
||||
saveLastCustomSnoozeTime(unixTimestamp);
|
||||
// The saveLastCustomSnoozeTime function will emit the event automatically
|
||||
this.$emit('chooseTime', this.snoozeTime);
|
||||
}
|
||||
},
|
||||
disabledDate(date) {
|
||||
// Disable all the previous dates
|
||||
|
||||
+12
@@ -1,6 +1,7 @@
|
||||
<script>
|
||||
import { getUnixTime } from 'date-fns';
|
||||
import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers';
|
||||
import { getLastCustomSnoozeTime } from 'dashboard/helper/customSnoozeStorage';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import {
|
||||
@@ -100,6 +101,17 @@ export default {
|
||||
onCmdSnoozeConversation(snoozeType) {
|
||||
if (snoozeType === wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME) {
|
||||
this.showCustomTimeSnoozeModal = true;
|
||||
} else if (
|
||||
snoozeType === wootConstants.SNOOZE_OPTIONS.UNTIL_LAST_CUSTOM_TIME
|
||||
) {
|
||||
// Use the saved custom time
|
||||
const lastCustomTime = getLastCustomSnoozeTime();
|
||||
if (lastCustomTime) {
|
||||
this.updateConversations('snoozed', lastCustomTime);
|
||||
} else {
|
||||
// Fallback to showing custom modal if no saved time exists
|
||||
this.showCustomTimeSnoozeModal = true;
|
||||
}
|
||||
} else {
|
||||
this.updateConversations('snoozed', findSnoozeTime(snoozeType) || null);
|
||||
}
|
||||
|
||||
@@ -66,8 +66,9 @@ export function useBulkActionsHotKeys() {
|
||||
const prepareActions = actions => {
|
||||
return actions.map(action => ({
|
||||
...action,
|
||||
title: t(action.title),
|
||||
section: t(action.section),
|
||||
title: typeof action.title === 'string' ? t(action.title) : action.title,
|
||||
section:
|
||||
typeof action.section === 'string' ? t(action.section) : action.section,
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { computed } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStore, useMapGetter } from 'dashboard/composables/store';
|
||||
import { useRoute } from 'vue-router';
|
||||
@@ -31,22 +31,25 @@ import {
|
||||
|
||||
import {
|
||||
OPEN_CONVERSATION_ACTIONS,
|
||||
SNOOZE_CONVERSATION_ACTIONS,
|
||||
RESOLVED_CONVERSATION_ACTIONS,
|
||||
SEND_TRANSCRIPT_ACTION,
|
||||
UNMUTE_ACTION,
|
||||
MUTE_ACTION,
|
||||
createSnoozeHandlers,
|
||||
} from 'dashboard/helper/commandbar/actions';
|
||||
import {
|
||||
isAConversationRoute,
|
||||
isAInboxViewRoute,
|
||||
} from 'dashboard/helper/routeHelpers';
|
||||
import { CMD_SNOOZE_CONVERSATION } from 'dashboard/helper/commandbar/events';
|
||||
import { ICON_SNOOZE_CONVERSATION } from 'dashboard/helper/commandbar/icons';
|
||||
|
||||
const prepareActions = (actions, t) => {
|
||||
return actions.map(action => ({
|
||||
...action,
|
||||
title: t(action.title),
|
||||
section: t(action.section),
|
||||
title: typeof action.title === 'string' ? t(action.title) : action.title,
|
||||
section:
|
||||
typeof action.section === 'string' ? t(action.section) : action.section,
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -144,6 +147,14 @@ export function useConversationHotKeys() {
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
|
||||
// Create a reactive trigger for localStorage changes
|
||||
const localStorageTrigger = ref(0);
|
||||
|
||||
// Watch for localStorage changes (we'll trigger this manually when needed)
|
||||
const forceSnoozeActionsUpdate = () => {
|
||||
localStorageTrigger.value += 1;
|
||||
};
|
||||
|
||||
const {
|
||||
activeLabels,
|
||||
inactiveLabels,
|
||||
@@ -197,6 +208,28 @@ export function useConversationHotKeys() {
|
||||
});
|
||||
};
|
||||
|
||||
// Create reactive snooze conversation actions that update when localStorage changes
|
||||
const snoozeConversationActions = computed(() => {
|
||||
// This computed will re-run when localStorageTrigger changes
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
localStorageTrigger.value; // Access to make it reactive
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'snooze_conversation',
|
||||
title: 'COMMAND_BAR.COMMANDS.SNOOZE_CONVERSATION',
|
||||
section: 'COMMAND_BAR.SECTIONS.CONVERSATION',
|
||||
icon: ICON_SNOOZE_CONVERSATION,
|
||||
children: Object.values(wootConstants.SNOOZE_OPTIONS),
|
||||
},
|
||||
...createSnoozeHandlers(
|
||||
CMD_SNOOZE_CONVERSATION,
|
||||
'snooze_conversation',
|
||||
'COMMAND_BAR.SECTIONS.SNOOZE_CONVERSATION'
|
||||
),
|
||||
];
|
||||
});
|
||||
|
||||
const statusActions = computed(() => {
|
||||
const isOpen = currentChat.value?.status === wootConstants.STATUS_TYPE.OPEN;
|
||||
const isSnoozed =
|
||||
@@ -206,7 +239,10 @@ export function useConversationHotKeys() {
|
||||
|
||||
let actions = [];
|
||||
if (isOpen) {
|
||||
actions = [...OPEN_CONVERSATION_ACTIONS, ...SNOOZE_CONVERSATION_ACTIONS];
|
||||
actions = [
|
||||
...OPEN_CONVERSATION_ACTIONS,
|
||||
...snoozeConversationActions.value,
|
||||
];
|
||||
} else if (isResolved || isSnoozed) {
|
||||
actions = RESOLVED_CONVERSATION_ACTIONS;
|
||||
}
|
||||
@@ -394,7 +430,7 @@ export function useConversationHotKeys() {
|
||||
|
||||
const conversationHotKeys = computed(() => {
|
||||
if (shouldShowSnoozeOption.value) {
|
||||
return prepareActions(SNOOZE_CONVERSATION_ACTIONS, t);
|
||||
return prepareActions(snoozeConversationActions.value, t);
|
||||
}
|
||||
if (isConversationOrInboxRoute.value) {
|
||||
return getDefaultConversationHotKeys.value;
|
||||
@@ -404,5 +440,6 @@ export function useConversationHotKeys() {
|
||||
|
||||
return {
|
||||
conversationHotKeys,
|
||||
forceSnoozeActionsUpdate,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ export default {
|
||||
UNTIL_TOMORROW: 'until_tomorrow',
|
||||
UNTIL_NEXT_WEEK: 'until_next_week',
|
||||
UNTIL_NEXT_MONTH: 'until_next_month',
|
||||
UNTIL_LAST_CUSTOM_TIME: 'until_last_custom_time',
|
||||
UNTIL_CUSTOM_TIME: 'until_custom_time',
|
||||
},
|
||||
EXAMPLE_URL: 'example.com',
|
||||
|
||||
@@ -32,14 +32,97 @@ export const OPEN_CONVERSATION_ACTIONS = [
|
||||
];
|
||||
|
||||
export const createSnoozeHandlers = (busEventName, parentId, section) => {
|
||||
return Object.values(SNOOZE_OPTIONS).map(option => ({
|
||||
id: option,
|
||||
title: `COMMAND_BAR.COMMANDS.${option.toUpperCase()}`,
|
||||
parent: parentId,
|
||||
section: section,
|
||||
icon: ICON_SNOOZE_CONVERSATION,
|
||||
handler: () => emitter.emit(busEventName, option),
|
||||
}));
|
||||
// Import the helper functions inline to avoid import issues
|
||||
const LAST_CUSTOM_SNOOZE_KEY = 'chatwoot_last_custom_snooze_time';
|
||||
|
||||
const getLastCustomSnoozeTime = () => {
|
||||
try {
|
||||
const stored = localStorage.getItem(LAST_CUSTOM_SNOOZE_KEY);
|
||||
if (!stored) return null;
|
||||
|
||||
const data = JSON.parse(stored);
|
||||
const now = Date.now();
|
||||
const nowUnix = Math.floor(now / 1000);
|
||||
const savedAt = data.savedAt;
|
||||
const sevenDaysInMs = 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
// Check if the saved time has already passed OR if it was saved more than 7 days ago
|
||||
const timeHasPassed = data.timestamp <= nowUnix;
|
||||
const tooOld = now - savedAt > sevenDaysInMs;
|
||||
|
||||
if (timeHasPassed || tooOld) {
|
||||
localStorage.removeItem(LAST_CUSTOM_SNOOZE_KEY);
|
||||
return null;
|
||||
}
|
||||
|
||||
return data.timestamp;
|
||||
} catch (error) {
|
||||
localStorage.removeItem(LAST_CUSTOM_SNOOZE_KEY);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const formatLastCustomSnoozeTime = () => {
|
||||
const timestamp = getLastCustomSnoozeTime();
|
||||
if (!timestamp) return null;
|
||||
|
||||
try {
|
||||
const date = new Date(timestamp * 1000);
|
||||
|
||||
// Format as "Sat, 23 Aug, 8.16pm"
|
||||
const dayOfWeek = date.toLocaleDateString('en-US', { weekday: 'short' });
|
||||
const day = date.getDate();
|
||||
const month = date.toLocaleDateString('en-US', { month: 'short' });
|
||||
|
||||
// Format time as 8.16pm (with dots instead of colons)
|
||||
let hours = date.getHours();
|
||||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||
const ampm = hours >= 12 ? 'pm' : 'am';
|
||||
|
||||
// Convert to 12-hour format
|
||||
hours %= 12;
|
||||
if (hours === 0) hours = 12; // 12am/12pm instead of 0am/0pm
|
||||
|
||||
const timeString = `${hours}.${minutes}${ampm}`;
|
||||
|
||||
return `${dayOfWeek}, ${day} ${month}, ${timeString}`;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const hasLastCustomSnoozeTime = () => {
|
||||
return getLastCustomSnoozeTime() !== null;
|
||||
};
|
||||
|
||||
const availableOptions = Object.values(SNOOZE_OPTIONS);
|
||||
const snoozeOptions = availableOptions.filter(option => {
|
||||
if (option === SNOOZE_OPTIONS.UNTIL_LAST_CUSTOM_TIME) {
|
||||
return hasLastCustomSnoozeTime();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return snoozeOptions.map(option => {
|
||||
let title = `COMMAND_BAR.COMMANDS.${option.toUpperCase()}`;
|
||||
|
||||
// For the last custom time option, show the formatted time instead of using translation
|
||||
if (option === SNOOZE_OPTIONS.UNTIL_LAST_CUSTOM_TIME) {
|
||||
const formattedTime = formatLastCustomSnoozeTime();
|
||||
if (formattedTime) {
|
||||
title = `Last snoozed ${formattedTime}`;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: option,
|
||||
title: title,
|
||||
parent: parentId,
|
||||
section: section,
|
||||
icon: ICON_SNOOZE_CONVERSATION,
|
||||
handler: () => emitter.emit(busEventName, option),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const SNOOZE_CONVERSATION_ACTIONS = [
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
const LAST_CUSTOM_SNOOZE_KEY = 'chatwoot_last_custom_snooze_time';
|
||||
|
||||
export const saveLastCustomSnoozeTime = snoozedUntil => {
|
||||
if (!snoozedUntil) return;
|
||||
|
||||
try {
|
||||
localStorage.setItem(
|
||||
LAST_CUSTOM_SNOOZE_KEY,
|
||||
JSON.stringify({
|
||||
timestamp: snoozedUntil,
|
||||
savedAt: Date.now(),
|
||||
})
|
||||
);
|
||||
// Emit event to update command bar
|
||||
if (typeof window !== 'undefined' && window.emitter) {
|
||||
window.emitter.emit('CUSTOM_SNOOZE_TIME_CHANGED');
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to save custom snooze time:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const getLastCustomSnoozeTime = () => {
|
||||
try {
|
||||
const stored = localStorage.getItem(LAST_CUSTOM_SNOOZE_KEY);
|
||||
if (!stored) return null;
|
||||
|
||||
const data = JSON.parse(stored);
|
||||
const now = Date.now();
|
||||
const nowUnix = Math.floor(now / 1000);
|
||||
const savedAt = data.savedAt;
|
||||
const sevenDaysInMs = 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
// Check if the saved time has already passed OR if it was saved more than 7 days ago
|
||||
const timeHasPassed = data.timestamp <= nowUnix;
|
||||
const tooOld = now - savedAt > sevenDaysInMs;
|
||||
|
||||
if (timeHasPassed || tooOld) {
|
||||
localStorage.removeItem(LAST_CUSTOM_SNOOZE_KEY);
|
||||
// Emit event when data is cleared due to expiration
|
||||
if (typeof window !== 'undefined' && window.emitter) {
|
||||
window.emitter.emit('CUSTOM_SNOOZE_TIME_CHANGED');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return data.timestamp;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to get custom snooze time:', error);
|
||||
localStorage.removeItem(LAST_CUSTOM_SNOOZE_KEY);
|
||||
// Emit event when data is cleared due to error
|
||||
if (typeof window !== 'undefined' && window.emitter) {
|
||||
window.emitter.emit('CUSTOM_SNOOZE_TIME_CHANGED');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const hasLastCustomSnoozeTime = () => {
|
||||
return getLastCustomSnoozeTime() !== null;
|
||||
};
|
||||
|
||||
export const formatLastCustomSnoozeTime = () => {
|
||||
const timestamp = getLastCustomSnoozeTime();
|
||||
if (!timestamp) return null;
|
||||
|
||||
try {
|
||||
const date = new Date(timestamp * 1000); // Convert Unix timestamp to milliseconds
|
||||
|
||||
// Format as "Sat, 23 Aug, 8.16pm"
|
||||
const dayOfWeek = date.toLocaleDateString('en-US', { weekday: 'short' });
|
||||
const day = date.getDate();
|
||||
const month = date.toLocaleDateString('en-US', { month: 'short' });
|
||||
|
||||
// Format time as 8.16pm (with dots instead of colons)
|
||||
let hours = date.getHours();
|
||||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||
const ampm = hours >= 12 ? 'pm' : 'am';
|
||||
|
||||
// Convert to 12-hour format
|
||||
hours %= 12;
|
||||
if (hours === 0) hours = 12; // 12am/12pm instead of 0am/0pm
|
||||
|
||||
const timeString = `${hours}.${minutes}${ampm}`;
|
||||
|
||||
return `${dayOfWeek}, ${day} ${month}, ${timeString}`;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
setSeconds,
|
||||
} from 'date-fns';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import { getLastCustomSnoozeTime } from './customSnoozeStorage';
|
||||
|
||||
const SNOOZE_OPTIONS = wootConstants.SNOOZE_OPTIONS;
|
||||
|
||||
@@ -51,6 +52,9 @@ export const findSnoozeTime = (snoozeType, currentDate = new Date()) => {
|
||||
parsedDate = setHoursToNine(findStartOfNextWeek(currentDate));
|
||||
} else if (snoozeType === SNOOZE_OPTIONS.UNTIL_NEXT_MONTH) {
|
||||
parsedDate = setHoursToNine(findStartOfNextMonth(currentDate));
|
||||
} else if (snoozeType === SNOOZE_OPTIONS.UNTIL_LAST_CUSTOM_TIME) {
|
||||
// Return the stored timestamp directly (already in Unix format)
|
||||
return getLastCustomSnoozeTime();
|
||||
}
|
||||
|
||||
return parsedDate ? getUnixTime(parsedDate) : null;
|
||||
|
||||
@@ -233,6 +233,7 @@
|
||||
"UNTIL_TOMORROW": "Until tomorrow",
|
||||
"UNTIL_NEXT_MONTH": "Until next month",
|
||||
"AN_HOUR_FROM_NOW": "Until an hour from now",
|
||||
"UNTIL_LAST_CUSTOM_TIME": "Last snoozed {time}",
|
||||
"UNTIL_CUSTOM_TIME": "Custom...",
|
||||
"CHANGE_APPEARANCE": "Change Appearance",
|
||||
"LIGHT_MODE": "Light",
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import { useEmitter } from 'dashboard/composables/emitter';
|
||||
import { getUnixTime } from 'date-fns';
|
||||
import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers';
|
||||
import { getLastCustomSnoozeTime } from 'dashboard/helper/customSnoozeStorage';
|
||||
import { CMD_SNOOZE_CONVERSATION } from 'dashboard/helper/commandbar/events';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue';
|
||||
@@ -31,6 +32,17 @@ const toggleStatus = async (status, snoozedUntil) => {
|
||||
const onCmdSnoozeConversation = snoozeType => {
|
||||
if (snoozeType === wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME) {
|
||||
showCustomSnoozeModal.value = true;
|
||||
} else if (
|
||||
snoozeType === wootConstants.SNOOZE_OPTIONS.UNTIL_LAST_CUSTOM_TIME
|
||||
) {
|
||||
// Use the saved custom time
|
||||
const lastCustomTime = getLastCustomSnoozeTime();
|
||||
if (lastCustomTime) {
|
||||
toggleStatus(wootConstants.STATUS_TYPE.SNOOZED, lastCustomTime);
|
||||
} else {
|
||||
// Fallback to showing custom modal if no saved time exists
|
||||
showCustomSnoozeModal.value = true;
|
||||
}
|
||||
} else {
|
||||
toggleStatus(
|
||||
wootConstants.STATUS_TYPE.SNOOZED,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import '@chatwoot/ninja-keys';
|
||||
import { ref, computed, watchEffect, onMounted } from 'vue';
|
||||
import { ref, computed, watchEffect, onMounted, onUnmounted } from 'vue';
|
||||
import { useStore } from 'dashboard/composables/store';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -11,6 +11,7 @@ import { useBulkActionsHotKeys } from 'dashboard/composables/commands/useBulkAct
|
||||
import { useConversationHotKeys } from 'dashboard/composables/commands/useConversationHotKeys';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import { GENERAL_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
|
||||
const store = useStore();
|
||||
const { t } = useI18n();
|
||||
@@ -26,7 +27,8 @@ const { goToAppearanceHotKeys } = useAppearanceHotKeys();
|
||||
const { inboxHotKeys } = useInboxHotKeys();
|
||||
const { goToCommandHotKeys } = useGoToCommandHotKeys();
|
||||
const { bulkActionsHotKeys } = useBulkActionsHotKeys();
|
||||
const { conversationHotKeys } = useConversationHotKeys();
|
||||
const { conversationHotKeys, forceSnoozeActionsUpdate } =
|
||||
useConversationHotKeys();
|
||||
|
||||
const placeholder = computed(() => t('COMMAND_BAR.SEARCH_PLACEHOLDER'));
|
||||
|
||||
@@ -78,7 +80,37 @@ watchEffect(() => {
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(setCommandBarData);
|
||||
onMounted(() => {
|
||||
setCommandBarData();
|
||||
|
||||
// Make emitter globally available for storage helpers
|
||||
window.emitter = emitter;
|
||||
|
||||
// Listen for custom snooze time changes to update actions
|
||||
emitter.on('CUSTOM_SNOOZE_TIME_CHANGED', () => {
|
||||
if (forceSnoozeActionsUpdate) {
|
||||
forceSnoozeActionsUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
// Backward compatibility
|
||||
emitter.on('CUSTOM_SNOOZE_TIME_SAVED', () => {
|
||||
if (forceSnoozeActionsUpdate) {
|
||||
forceSnoozeActionsUpdate();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// Clean up event listeners
|
||||
emitter.off('CUSTOM_SNOOZE_TIME_CHANGED');
|
||||
emitter.off('CUSTOM_SNOOZE_TIME_SAVED');
|
||||
|
||||
// Clean up global reference
|
||||
if (window.emitter === emitter) {
|
||||
delete window.emitter;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable vue/attribute-hyphenation -->
|
||||
|
||||
Reference in New Issue
Block a user