diff --git a/app/javascript/dashboard/components/CustomSnoozeModal.vue b/app/javascript/dashboard/components/CustomSnoozeModal.vue index 0bab6f521..43f0a6fdb 100644 --- a/app/javascript/dashboard/components/CustomSnoozeModal.vue +++ b/app/javascript/dashboard/components/CustomSnoozeModal.vue @@ -31,6 +31,7 @@ export default { 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); } }, diff --git a/app/javascript/dashboard/composables/commands/useBulkActionsHotKeys.js b/app/javascript/dashboard/composables/commands/useBulkActionsHotKeys.js index 2b43ad443..262333d55 100644 --- a/app/javascript/dashboard/composables/commands/useBulkActionsHotKeys.js +++ b/app/javascript/dashboard/composables/commands/useBulkActionsHotKeys.js @@ -2,7 +2,6 @@ import { computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { useMapGetter } from 'dashboard/composables/store'; import wootConstants from 'dashboard/constants/globals'; -import { hasLastCustomSnoozeTime } from 'dashboard/helper/customSnoozeStorage'; import { CMD_BULK_ACTION_SNOOZE_CONVERSATION, @@ -28,16 +27,7 @@ const SNOOZE_CONVERSATION_BULK_ACTIONS = [ title: 'COMMAND_BAR.COMMANDS.SNOOZE_CONVERSATION', section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS', icon: ICON_SNOOZE_CONVERSATION, - children: () => { - const availableOptions = Object.values(SNOOZE_OPTIONS); - // Only include UNTIL_LAST_CUSTOM_TIME if there's a saved custom time - return availableOptions.filter(option => { - if (option === SNOOZE_OPTIONS.UNTIL_LAST_CUSTOM_TIME) { - return hasLastCustomSnoozeTime(); - } - return true; - }); - }, + children: Object.values(SNOOZE_OPTIONS), }, ...createSnoozeHandlers( CMD_BULK_ACTION_SNOOZE_CONVERSATION, @@ -76,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, })); }; diff --git a/app/javascript/dashboard/composables/commands/useConversationHotKeys.js b/app/javascript/dashboard/composables/commands/useConversationHotKeys.js index 30e138165..c79053370 100644 --- a/app/javascript/dashboard/composables/commands/useConversationHotKeys.js +++ b/app/javascript/dashboard/composables/commands/useConversationHotKeys.js @@ -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, }; } diff --git a/app/javascript/dashboard/helper/commandbar/actions.js b/app/javascript/dashboard/helper/commandbar/actions.js index d8f804b40..6aac522d2 100644 --- a/app/javascript/dashboard/helper/commandbar/actions.js +++ b/app/javascript/dashboard/helper/commandbar/actions.js @@ -32,21 +32,69 @@ export const OPEN_CONVERSATION_ACTIONS = [ ]; export const createSnoozeHandlers = (busEventName, parentId, section) => { - // Only include UNTIL_LAST_CUSTOM_TIME in handlers if there's a saved custom time + // Import the helper functions inline to avoid import issues const LAST_CUSTOM_SNOOZE_KEY = 'chatwoot_last_custom_snooze_time'; - const hasLastCustomSnoozeTime = () => { + + const getLastCustomSnoozeTime = () => { try { const stored = localStorage.getItem(LAST_CUSTOM_SNOOZE_KEY); - if (!stored) return false; + 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; - return now - data.savedAt <= sevenDaysInMs; - } catch { - return false; + + // 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) { @@ -55,14 +103,26 @@ export const createSnoozeHandlers = (busEventName, parentId, section) => { return true; }); - return snoozeOptions.map(option => ({ - id: option, - title: `COMMAND_BAR.COMMANDS.${option.toUpperCase()}`, - parent: parentId, - section: section, - icon: ICON_SNOOZE_CONVERSATION, - handler: () => emitter.emit(busEventName, option), - })); + 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 = [ diff --git a/app/javascript/dashboard/helper/customSnoozeStorage.js b/app/javascript/dashboard/helper/customSnoozeStorage.js index aa1c2f29c..497a6a501 100644 --- a/app/javascript/dashboard/helper/customSnoozeStorage.js +++ b/app/javascript/dashboard/helper/customSnoozeStorage.js @@ -11,6 +11,10 @@ export const saveLastCustomSnoozeTime = 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); @@ -23,15 +27,21 @@ export const getLastCustomSnoozeTime = () => { if (!stored) return null; const data = JSON.parse(stored); - - // Check if the saved time is still in the future (within 7 days of when it was saved) const now = Date.now(); + const nowUnix = Math.floor(now / 1000); const savedAt = data.savedAt; const sevenDaysInMs = 7 * 24 * 60 * 60 * 1000; - // If saved more than 7 days ago, consider it stale - if (now - savedAt > sevenDaysInMs) { + // 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; } @@ -40,6 +50,10 @@ export const getLastCustomSnoozeTime = () => { // 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; } }; @@ -54,7 +68,24 @@ export const formatLastCustomSnoozeTime = () => { try { const date = new Date(timestamp * 1000); // Convert Unix timestamp to milliseconds - return date.toLocaleString(); + + // 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; } diff --git a/app/javascript/dashboard/i18n/locale/en/generalSettings.json b/app/javascript/dashboard/i18n/locale/en/generalSettings.json index 7a8f2b105..70379ebdd 100644 --- a/app/javascript/dashboard/i18n/locale/en/generalSettings.json +++ b/app/javascript/dashboard/i18n/locale/en/generalSettings.json @@ -233,7 +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": "Use last custom time", + "UNTIL_LAST_CUSTOM_TIME": "Last snoozed {time}", "UNTIL_CUSTOM_TIME": "Custom...", "CHANGE_APPEARANCE": "Change Appearance", "LIGHT_MODE": "Light", diff --git a/app/javascript/dashboard/routes/dashboard/commands/commandbar.vue b/app/javascript/dashboard/routes/dashboard/commands/commandbar.vue index afd15283e..b449ff8f8 100644 --- a/app/javascript/dashboard/routes/dashboard/commands/commandbar.vue +++ b/app/javascript/dashboard/routes/dashboard/commands/commandbar.vue @@ -1,6 +1,6 @@