feat: Adds support for all snooze option in bulk actions (#9361)
* feat: Add support for bulk snooze until * feat: Adds support for all snooze option in bulk actions * chore: Adds comment * chore: Review fixes * chore: Minor fix * chore: Minor fix * chore: Review fixes * chore: yarn changes * fix: terminal waring * chore: Adds spec * Update conversationHotKeys.js --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
@@ -288,7 +288,6 @@ export default {
|
|||||||
foldersQuery: {},
|
foldersQuery: {},
|
||||||
showAddFoldersModal: false,
|
showAddFoldersModal: false,
|
||||||
showDeleteFoldersModal: false,
|
showDeleteFoldersModal: false,
|
||||||
selectedConversations: [],
|
|
||||||
selectedInboxes: [],
|
selectedInboxes: [],
|
||||||
isContextMenuOpen: false,
|
isContextMenuOpen: false,
|
||||||
appliedFilter: [],
|
appliedFilter: [],
|
||||||
@@ -329,6 +328,7 @@ export default {
|
|||||||
inboxesList: 'inboxes/getInboxes',
|
inboxesList: 'inboxes/getInboxes',
|
||||||
campaigns: 'campaigns/getAllCampaigns',
|
campaigns: 'campaigns/getAllCampaigns',
|
||||||
labels: 'labels/getLabels',
|
labels: 'labels/getLabels',
|
||||||
|
selectedConversations: 'bulkActions/getSelectedConversationIds',
|
||||||
}),
|
}),
|
||||||
hasAppliedFilters() {
|
hasAppliedFilters() {
|
||||||
return this.appliedFilters.length !== 0;
|
return this.appliedFilters.length !== 0;
|
||||||
@@ -799,7 +799,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
resetBulkActions() {
|
resetBulkActions() {
|
||||||
this.selectedConversations = [];
|
this.$store.dispatch('bulkActions/clearSelectedConversationIds');
|
||||||
this.selectedInboxes = [];
|
this.selectedInboxes = [];
|
||||||
},
|
},
|
||||||
onBasicFilterChange(value, type) {
|
onBasicFilterChange(value, type) {
|
||||||
@@ -830,12 +830,16 @@ export default {
|
|||||||
return this.selectedConversations.includes(id);
|
return this.selectedConversations.includes(id);
|
||||||
},
|
},
|
||||||
selectConversation(conversationId, inboxId) {
|
selectConversation(conversationId, inboxId) {
|
||||||
this.selectedConversations.push(conversationId);
|
this.$store.dispatch(
|
||||||
|
'bulkActions/setSelectedConversationIds',
|
||||||
|
conversationId
|
||||||
|
);
|
||||||
this.selectedInboxes.push(inboxId);
|
this.selectedInboxes.push(inboxId);
|
||||||
},
|
},
|
||||||
deSelectConversation(conversationId, inboxId) {
|
deSelectConversation(conversationId, inboxId) {
|
||||||
this.selectedConversations = this.selectedConversations.filter(
|
this.$store.dispatch(
|
||||||
item => item !== conversationId
|
'bulkActions/removeSelectedConversationIds',
|
||||||
|
conversationId
|
||||||
);
|
);
|
||||||
this.selectedInboxes = this.selectedInboxes.filter(
|
this.selectedInboxes = this.selectedInboxes.filter(
|
||||||
item => item !== inboxId
|
item => item !== inboxId
|
||||||
@@ -843,7 +847,10 @@ export default {
|
|||||||
},
|
},
|
||||||
selectAllConversations(check) {
|
selectAllConversations(check) {
|
||||||
if (check) {
|
if (check) {
|
||||||
this.selectedConversations = this.conversationList.map(item => item.id);
|
this.$store.dispatch(
|
||||||
|
'bulkActions/setSelectedConversationIds',
|
||||||
|
this.conversationList.map(item => item.id)
|
||||||
|
);
|
||||||
this.selectedInboxes = this.conversationList.map(item => item.inbox_id);
|
this.selectedInboxes = this.conversationList.map(item => item.inbox_id);
|
||||||
} else {
|
} else {
|
||||||
this.resetBulkActions();
|
this.resetBulkActions();
|
||||||
@@ -859,7 +866,7 @@ export default {
|
|||||||
assignee_id: agent.id,
|
assignee_id: agent.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.selectedConversations = [];
|
this.$store.dispatch('bulkActions/clearSelectedConversationIds');
|
||||||
if (conversationId) {
|
if (conversationId) {
|
||||||
this.showAlert(
|
this.showAlert(
|
||||||
this.$t(
|
this.$t(
|
||||||
@@ -957,7 +964,7 @@ export default {
|
|||||||
add: labels,
|
add: labels,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.selectedConversations = [];
|
this.$store.dispatch('bulkActions/clearSelectedConversationIds');
|
||||||
if (conversationId) {
|
if (conversationId) {
|
||||||
this.showAlert(
|
this.showAlert(
|
||||||
this.$t(
|
this.$t(
|
||||||
@@ -984,13 +991,13 @@ export default {
|
|||||||
team_id: team.id,
|
team_id: team.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.selectedConversations = [];
|
this.$store.dispatch('bulkActions/clearSelectedConversationIds');
|
||||||
this.showAlert(this.$t('BULK_ACTION.TEAMS.ASSIGN_SUCCESFUL'));
|
this.showAlert(this.$t('BULK_ACTION.TEAMS.ASSIGN_SUCCESFUL'));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.showAlert(this.$t('BULK_ACTION.TEAMS.ASSIGN_FAILED'));
|
this.showAlert(this.$t('BULK_ACTION.TEAMS.ASSIGN_FAILED'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onUpdateConversations(status) {
|
async onUpdateConversations(status, snoozedUntil) {
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('bulkActions/process', {
|
await this.$store.dispatch('bulkActions/process', {
|
||||||
type: 'Conversation',
|
type: 'Conversation',
|
||||||
@@ -998,8 +1005,9 @@ export default {
|
|||||||
fields: {
|
fields: {
|
||||||
status,
|
status,
|
||||||
},
|
},
|
||||||
|
snoozed_until: snoozedUntil,
|
||||||
});
|
});
|
||||||
this.selectedConversations = [];
|
this.$store.dispatch('bulkActions/clearSelectedConversationIds');
|
||||||
this.showAlert(this.$t('BULK_ACTION.UPDATE.UPDATE_SUCCESFUL'));
|
this.showAlert(this.$t('BULK_ACTION.UPDATE.UPDATE_SUCCESFUL'));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.showAlert(this.$t('BULK_ACTION.UPDATE.UPDATE_FAILED'));
|
this.showAlert(this.$t('BULK_ACTION.UPDATE.UPDATE_FAILED'));
|
||||||
|
|||||||
@@ -95,20 +95,40 @@
|
|||||||
<div v-if="allConversationsSelected" class="bulk-action__alert">
|
<div v-if="allConversationsSelected" class="bulk-action__alert">
|
||||||
{{ $t('BULK_ACTION.ALL_CONVERSATIONS_SELECTED_ALERT') }}
|
{{ $t('BULK_ACTION.ALL_CONVERSATIONS_SELECTED_ALERT') }}
|
||||||
</div>
|
</div>
|
||||||
|
<woot-modal
|
||||||
|
:show.sync="showCustomTimeSnoozeModal"
|
||||||
|
:on-close="hideCustomSnoozeModal"
|
||||||
|
>
|
||||||
|
<custom-snooze-modal
|
||||||
|
@close="hideCustomSnoozeModal"
|
||||||
|
@choose-time="customSnoozeTime"
|
||||||
|
/>
|
||||||
|
</woot-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { getUnixTime } from 'date-fns';
|
||||||
|
import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers';
|
||||||
|
import wootConstants from 'dashboard/constants/globals';
|
||||||
|
import {
|
||||||
|
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
|
||||||
|
CMD_BULK_ACTION_REOPEN_CONVERSATION,
|
||||||
|
CMD_BULK_ACTION_RESOLVE_CONVERSATION,
|
||||||
|
} from 'dashboard/routes/dashboard/commands/commandBarBusEvents';
|
||||||
|
|
||||||
import AgentSelector from './AgentSelector.vue';
|
import AgentSelector from './AgentSelector.vue';
|
||||||
import UpdateActions from './UpdateActions.vue';
|
import UpdateActions from './UpdateActions.vue';
|
||||||
import LabelActions from './LabelActions.vue';
|
import LabelActions from './LabelActions.vue';
|
||||||
import TeamActions from './TeamActions.vue';
|
import TeamActions from './TeamActions.vue';
|
||||||
|
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue';
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
AgentSelector,
|
AgentSelector,
|
||||||
UpdateActions,
|
UpdateActions,
|
||||||
LabelActions,
|
LabelActions,
|
||||||
TeamActions,
|
TeamActions,
|
||||||
|
CustomSnoozeModal,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
conversations: {
|
conversations: {
|
||||||
@@ -143,17 +163,56 @@ export default {
|
|||||||
showLabelActions: false,
|
showLabelActions: false,
|
||||||
showTeamsList: false,
|
showTeamsList: false,
|
||||||
popoverPositions: {},
|
popoverPositions: {},
|
||||||
|
showCustomTimeSnoozeModal: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
bus.$on(CMD_BULK_ACTION_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
|
||||||
|
bus.$on(CMD_BULK_ACTION_REOPEN_CONVERSATION, this.onCmdReopenConversation);
|
||||||
|
bus.$on(
|
||||||
|
CMD_BULK_ACTION_RESOLVE_CONVERSATION,
|
||||||
|
this.onCmdResolveConversation
|
||||||
|
);
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
bus.$off(CMD_BULK_ACTION_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
|
||||||
|
bus.$off(CMD_BULK_ACTION_REOPEN_CONVERSATION, this.onCmdReopenConversation);
|
||||||
|
bus.$off(
|
||||||
|
CMD_BULK_ACTION_RESOLVE_CONVERSATION,
|
||||||
|
this.onCmdResolveConversation
|
||||||
|
);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onCmdSnoozeConversation(snoozeType) {
|
||||||
|
if (snoozeType === wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME) {
|
||||||
|
this.showCustomTimeSnoozeModal = true;
|
||||||
|
} else {
|
||||||
|
this.updateConversations('snoozed', findSnoozeTime(snoozeType) || null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCmdReopenConversation() {
|
||||||
|
this.updateConversations('open', null);
|
||||||
|
},
|
||||||
|
onCmdResolveConversation() {
|
||||||
|
this.updateConversations('resolved', null);
|
||||||
|
},
|
||||||
|
customSnoozeTime(customSnoozedTime) {
|
||||||
|
this.showCustomTimeSnoozeModal = false;
|
||||||
|
if (customSnoozedTime) {
|
||||||
|
this.updateConversations('snoozed', getUnixTime(customSnoozedTime));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hideCustomSnoozeModal() {
|
||||||
|
this.showCustomTimeSnoozeModal = false;
|
||||||
|
},
|
||||||
selectAll(e) {
|
selectAll(e) {
|
||||||
this.$emit('select-all-conversations', e.target.checked);
|
this.$emit('select-all-conversations', e.target.checked);
|
||||||
},
|
},
|
||||||
submit(agent) {
|
submit(agent) {
|
||||||
this.$emit('assign-agent', agent);
|
this.$emit('assign-agent', agent);
|
||||||
},
|
},
|
||||||
updateConversations(status) {
|
updateConversations(status, snoozedUntil) {
|
||||||
this.$emit('update-conversations', status);
|
this.$emit('update-conversations', status, snoozedUntil);
|
||||||
},
|
},
|
||||||
assignLabels(labels) {
|
assignLabels(labels) {
|
||||||
this.$emit('assign-labels', labels);
|
this.$emit('assign-labels', labels);
|
||||||
|
|||||||
@@ -1,12 +1,98 @@
|
|||||||
|
<script setup>
|
||||||
|
import { useI18n } from 'dashboard/composables/useI18n';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
|
||||||
|
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const emits = defineEmits(['update', 'close']);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
selectedInboxes: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
conversationCount: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
showResolve: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
showReopen: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
showSnooze: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const actions = ref([
|
||||||
|
{ icon: 'checkmark', key: 'resolved' },
|
||||||
|
{ icon: 'arrow-redo', key: 'open' },
|
||||||
|
{ icon: 'send-clock', key: 'snoozed' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const updateConversations = key => {
|
||||||
|
if (key === 'snoozed') {
|
||||||
|
// If the user clicks on the snooze option from the bulk action change status dropdown.
|
||||||
|
// Open the snooze option for bulk action in the cmd bar.
|
||||||
|
const ninja = document.querySelector('ninja-keys');
|
||||||
|
ninja?.open({ parent: 'bulk_action_snooze_conversation' });
|
||||||
|
} else {
|
||||||
|
emits('update', key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
emits('close');
|
||||||
|
};
|
||||||
|
|
||||||
|
const showAction = key => {
|
||||||
|
const actionsMap = {
|
||||||
|
resolved: props.showResolve,
|
||||||
|
open: props.showReopen,
|
||||||
|
snoozed: props.showSnooze,
|
||||||
|
};
|
||||||
|
return actionsMap[key] || false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const actionLabel = key => {
|
||||||
|
const labelsMap = {
|
||||||
|
resolved: t('CONVERSATION.HEADER.RESOLVE_ACTION'),
|
||||||
|
open: t('CONVERSATION.HEADER.REOPEN_ACTION'),
|
||||||
|
snoozed: t('BULK_ACTION.UPDATE.SNOOZE_UNTIL'),
|
||||||
|
};
|
||||||
|
return labelsMap[key] || '';
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-on-clickaway="onClose" class="actions-container">
|
<div
|
||||||
<div class="triangle">
|
v-on-clickaway="onClose"
|
||||||
|
class="absolute right-2 top-12 origin-top-right w-auto z-20 bg-white dark:bg-slate-800 rounded-lg border border-solid border-slate-50 dark:border-slate-700 shadow-md"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="right-[var(--triangle-position)] block z-10 absolute text-left -top-3"
|
||||||
|
>
|
||||||
<svg height="12" viewBox="0 0 24 12" width="24">
|
<svg height="12" viewBox="0 0 24 12" width="24">
|
||||||
<path d="M20 12l-8-8-12 12" fill-rule="evenodd" stroke-width="1px" />
|
<path
|
||||||
|
d="M20 12l-8-8-12 12"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
stroke-width="1px"
|
||||||
|
class="fill-white dark:fill-slate-800 stroke-slate-50 dark:stroke-slate-600/50"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="header flex items-center justify-between">
|
<div class="p-2.5 flex gap-1 items-center justify-between">
|
||||||
<span>{{ $t('BULK_ACTION.UPDATE.CHANGE_STATUS') }}</span>
|
<span class="text-sm font-medium text-slate-600 dark:text-slate-100">
|
||||||
|
{{ $t('BULK_ACTION.UPDATE.CHANGE_STATUS') }}
|
||||||
|
</span>
|
||||||
<woot-button
|
<woot-button
|
||||||
size="tiny"
|
size="tiny"
|
||||||
variant="clear"
|
variant="clear"
|
||||||
@@ -15,8 +101,8 @@
|
|||||||
@click="onClose"
|
@click="onClose"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="container">
|
<div class="px-2.5 pt-0 pb-2.5">
|
||||||
<woot-dropdown-menu>
|
<woot-dropdown-menu class="m-0 list-none">
|
||||||
<template v-for="action in actions">
|
<template v-for="action in actions">
|
||||||
<woot-dropdown-item v-if="showAction(action.key)" :key="action.key">
|
<woot-dropdown-item v-if="showAction(action.key)" :key="action.key">
|
||||||
<woot-button
|
<woot-button
|
||||||
@@ -34,114 +120,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
|
|
||||||
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
WootDropdownItem,
|
|
||||||
WootDropdownMenu,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
selectedInboxes: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
conversationCount: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
showResolve: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
showReopen: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
showSnooze: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
query: '',
|
|
||||||
selectedAction: null,
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
icon: 'checkmark',
|
|
||||||
key: 'resolved',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'arrow-redo',
|
|
||||||
key: 'open',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'send-clock',
|
|
||||||
key: 'snoozed',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateConversations(key) {
|
|
||||||
this.$emit('update', key);
|
|
||||||
},
|
|
||||||
goBack() {
|
|
||||||
this.selectedAgent = null;
|
|
||||||
},
|
|
||||||
onClose() {
|
|
||||||
this.$emit('close');
|
|
||||||
},
|
|
||||||
showAction(key) {
|
|
||||||
const actionsMap = {
|
|
||||||
resolved: this.showResolve,
|
|
||||||
open: this.showReopen,
|
|
||||||
snoozed: this.showSnooze,
|
|
||||||
};
|
|
||||||
return actionsMap[key] || false;
|
|
||||||
},
|
|
||||||
actionLabel(key) {
|
|
||||||
const labelsMap = {
|
|
||||||
resolved: this.$t('CONVERSATION.HEADER.RESOLVE_ACTION'),
|
|
||||||
open: this.$t('CONVERSATION.HEADER.REOPEN_ACTION'),
|
|
||||||
snoozed: this.$t('BULK_ACTION.UPDATE.SNOOZE_UNTIL_NEXT_REPLY'),
|
|
||||||
};
|
|
||||||
return labelsMap[key] || '';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.actions-container {
|
|
||||||
@apply absolute right-2 top-12 origin-top-right w-auto z-20 bg-white dark:bg-slate-800 rounded-lg border border-solid border-slate-50 dark:border-slate-700 shadow-md;
|
|
||||||
|
|
||||||
.header {
|
|
||||||
@apply p-2.5;
|
|
||||||
|
|
||||||
span {
|
|
||||||
@apply text-sm font-medium;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
@apply px-2.5 pt-0 pb-2.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.triangle {
|
|
||||||
right: var(--triangle-position);
|
|
||||||
@apply block z-10 absolute text-left -top-3;
|
|
||||||
|
|
||||||
svg path {
|
|
||||||
@apply fill-white dark:fill-slate-800 stroke-slate-50 dark:stroke-slate-600/50;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
@apply m-0 list-none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"AGENT_LIST_LOADING": "Loading agents",
|
"AGENT_LIST_LOADING": "Loading agents",
|
||||||
"UPDATE": {
|
"UPDATE": {
|
||||||
"CHANGE_STATUS": "Change status",
|
"CHANGE_STATUS": "Change status",
|
||||||
"SNOOZE_UNTIL_NEXT_REPLY": "Snooze until next reply.",
|
"SNOOZE_UNTIL": "Snooze",
|
||||||
"UPDATE_SUCCESFUL": "Conversation status updated successfully.",
|
"UPDATE_SUCCESFUL": "Conversation status updated successfully.",
|
||||||
"UPDATE_FAILED": "Failed to update conversations. Please try again."
|
"UPDATE_FAILED": "Failed to update conversations. Please try again."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -107,6 +107,7 @@
|
|||||||
"GENERAL": "General",
|
"GENERAL": "General",
|
||||||
"REPORTS": "Reports",
|
"REPORTS": "Reports",
|
||||||
"CONVERSATION": "Conversation",
|
"CONVERSATION": "Conversation",
|
||||||
|
"BULK_ACTIONS": "Bulk Actions",
|
||||||
"CHANGE_ASSIGNEE": "Change Assignee",
|
"CHANGE_ASSIGNEE": "Change Assignee",
|
||||||
"CHANGE_PRIORITY": "Change Priority",
|
"CHANGE_PRIORITY": "Change Priority",
|
||||||
"CHANGE_TEAM": "Change Team",
|
"CHANGE_TEAM": "Change Team",
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import wootConstants from 'dashboard/constants/globals';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
|
||||||
|
CMD_BULK_ACTION_REOPEN_CONVERSATION,
|
||||||
|
CMD_BULK_ACTION_RESOLVE_CONVERSATION,
|
||||||
|
} from './commandBarBusEvents';
|
||||||
|
import {
|
||||||
|
ICON_SNOOZE_CONVERSATION,
|
||||||
|
ICON_REOPEN_CONVERSATION,
|
||||||
|
ICON_RESOLVE_CONVERSATION,
|
||||||
|
} from './CommandBarIcons';
|
||||||
|
|
||||||
|
const SNOOZE_OPTIONS = wootConstants.SNOOZE_OPTIONS;
|
||||||
|
|
||||||
|
export const SNOOZE_CONVERSATION_BULK_ACTIONS = [
|
||||||
|
{
|
||||||
|
id: 'bulk_action_snooze_conversation',
|
||||||
|
title: 'COMMAND_BAR.COMMANDS.SNOOZE_CONVERSATION',
|
||||||
|
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
|
||||||
|
icon: ICON_SNOOZE_CONVERSATION,
|
||||||
|
children: Object.values(SNOOZE_OPTIONS),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: SNOOZE_OPTIONS.UNTIL_NEXT_REPLY,
|
||||||
|
title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_REPLY',
|
||||||
|
parent: 'bulk_action_snooze_conversation',
|
||||||
|
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
|
||||||
|
icon: ICON_SNOOZE_CONVERSATION,
|
||||||
|
handler: () =>
|
||||||
|
bus.$emit(
|
||||||
|
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
|
||||||
|
SNOOZE_OPTIONS.UNTIL_NEXT_REPLY
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: SNOOZE_OPTIONS.AN_HOUR_FROM_NOW,
|
||||||
|
title: 'COMMAND_BAR.COMMANDS.AN_HOUR_FROM_NOW',
|
||||||
|
parent: 'bulk_action_snooze_conversation',
|
||||||
|
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
|
||||||
|
icon: ICON_SNOOZE_CONVERSATION,
|
||||||
|
handler: () =>
|
||||||
|
bus.$emit(
|
||||||
|
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
|
||||||
|
SNOOZE_OPTIONS.AN_HOUR_FROM_NOW
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: SNOOZE_OPTIONS.UNTIL_TOMORROW,
|
||||||
|
title: 'COMMAND_BAR.COMMANDS.UNTIL_TOMORROW',
|
||||||
|
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
|
||||||
|
parent: 'bulk_action_snooze_conversation',
|
||||||
|
icon: ICON_SNOOZE_CONVERSATION,
|
||||||
|
handler: () =>
|
||||||
|
bus.$emit(
|
||||||
|
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
|
||||||
|
SNOOZE_OPTIONS.UNTIL_TOMORROW
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: SNOOZE_OPTIONS.UNTIL_NEXT_WEEK,
|
||||||
|
title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_WEEK',
|
||||||
|
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
|
||||||
|
parent: 'bulk_action_snooze_conversation',
|
||||||
|
icon: ICON_SNOOZE_CONVERSATION,
|
||||||
|
handler: () =>
|
||||||
|
bus.$emit(
|
||||||
|
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
|
||||||
|
SNOOZE_OPTIONS.UNTIL_NEXT_WEEK
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: SNOOZE_OPTIONS.UNTIL_NEXT_MONTH,
|
||||||
|
title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_MONTH',
|
||||||
|
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
|
||||||
|
parent: 'bulk_action_snooze_conversation',
|
||||||
|
icon: ICON_SNOOZE_CONVERSATION,
|
||||||
|
handler: () =>
|
||||||
|
bus.$emit(
|
||||||
|
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
|
||||||
|
SNOOZE_OPTIONS.UNTIL_NEXT_MONTH
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME,
|
||||||
|
title: 'COMMAND_BAR.COMMANDS.CUSTOM',
|
||||||
|
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
|
||||||
|
parent: 'bulk_action_snooze_conversation',
|
||||||
|
icon: ICON_SNOOZE_CONVERSATION,
|
||||||
|
handler: () =>
|
||||||
|
bus.$emit(
|
||||||
|
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
|
||||||
|
SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const RESOLVED_CONVERSATION_BULK_ACTIONS = [
|
||||||
|
{
|
||||||
|
id: 'bulk_action_reopen_conversation',
|
||||||
|
title: 'COMMAND_BAR.COMMANDS.REOPEN_CONVERSATION',
|
||||||
|
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
|
||||||
|
icon: ICON_REOPEN_CONVERSATION,
|
||||||
|
handler: () => bus.$emit(CMD_BULK_ACTION_REOPEN_CONVERSATION),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const OPEN_CONVERSATION_BULK_ACTIONS = [
|
||||||
|
{
|
||||||
|
id: 'bulk_action_resolve_conversation',
|
||||||
|
title: 'COMMAND_BAR.COMMANDS.RESOLVE_CONVERSATION',
|
||||||
|
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
|
||||||
|
icon: ICON_RESOLVE_CONVERSATION,
|
||||||
|
handler: () => bus.$emit(CMD_BULK_ACTION_RESOLVE_CONVERSATION),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
selectedConversations: 'bulkActions/getSelectedConversationIds',
|
||||||
|
}),
|
||||||
|
bulkActionsHotKeys() {
|
||||||
|
let actions = [];
|
||||||
|
if (this.selectedConversations.length > 0) {
|
||||||
|
actions = [
|
||||||
|
...SNOOZE_CONVERSATION_BULK_ACTIONS,
|
||||||
|
...RESOLVED_CONVERSATION_BULK_ACTIONS,
|
||||||
|
...OPEN_CONVERSATION_BULK_ACTIONS,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return this.prepareActions(actions);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
selectedConversations() {
|
||||||
|
this.setCommandbarData();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
prepareActions(actions) {
|
||||||
|
return actions.map(action => ({
|
||||||
|
...action,
|
||||||
|
title: this.$t(action.title),
|
||||||
|
section: this.$t(action.section),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -16,5 +16,13 @@ export const CMD_RESOLVE_CONVERSATION = 'CMD_RESOLVE_CONVERSATION';
|
|||||||
export const CMD_SNOOZE_CONVERSATION = 'CMD_SNOOZE_CONVERSATION';
|
export const CMD_SNOOZE_CONVERSATION = 'CMD_SNOOZE_CONVERSATION';
|
||||||
export const CMD_AI_ASSIST = 'CMD_AI_ASSIST';
|
export const CMD_AI_ASSIST = 'CMD_AI_ASSIST';
|
||||||
|
|
||||||
|
// Bulk Actions
|
||||||
|
export const CMD_BULK_ACTION_SNOOZE_CONVERSATION =
|
||||||
|
'CMD_BULK_ACTION_SNOOZE_CONVERSATION';
|
||||||
|
export const CMD_BULK_ACTION_REOPEN_CONVERSATION =
|
||||||
|
'CMD_BULK_ACTION_REOPEN_CONVERSATION';
|
||||||
|
export const CMD_BULK_ACTION_RESOLVE_CONVERSATION =
|
||||||
|
'CMD_BULK_ACTION_RESOLVE_CONVERSATION';
|
||||||
|
|
||||||
// Inbox Commands (Notifications)
|
// Inbox Commands (Notifications)
|
||||||
export const CMD_SNOOZE_NOTIFICATION = 'CMD_SNOOZE_NOTIFICATION';
|
export const CMD_SNOOZE_NOTIFICATION = 'CMD_SNOOZE_NOTIFICATION';
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import 'ninja-keys';
|
import 'ninja-keys';
|
||||||
import conversationHotKeysMixin from './conversationHotKeys';
|
import conversationHotKeysMixin from './conversationHotKeys';
|
||||||
|
import bulkActionsHotKeysMixin from './bulkActionsHotKeys';
|
||||||
import inboxHotKeysMixin from './inboxHotKeys';
|
import inboxHotKeysMixin from './inboxHotKeys';
|
||||||
import goToCommandHotKeys from './goToCommandHotKeys';
|
import goToCommandHotKeys from './goToCommandHotKeys';
|
||||||
import appearanceHotKeys from './appearanceHotKeys';
|
import appearanceHotKeys from './appearanceHotKeys';
|
||||||
@@ -26,6 +27,7 @@ export default {
|
|||||||
adminMixin,
|
adminMixin,
|
||||||
agentMixin,
|
agentMixin,
|
||||||
conversationHotKeysMixin,
|
conversationHotKeysMixin,
|
||||||
|
bulkActionsHotKeysMixin,
|
||||||
inboxHotKeysMixin,
|
inboxHotKeysMixin,
|
||||||
conversationLabelMixin,
|
conversationLabelMixin,
|
||||||
conversationTeamMixin,
|
conversationTeamMixin,
|
||||||
@@ -46,6 +48,7 @@ export default {
|
|||||||
return [
|
return [
|
||||||
...this.inboxHotKeys,
|
...this.inboxHotKeys,
|
||||||
...this.conversationHotKeys,
|
...this.conversationHotKeys,
|
||||||
|
...this.bulkActionsHotKeys,
|
||||||
...this.goToCommandHotKeys,
|
...this.goToCommandHotKeys,
|
||||||
...this.goToAppearanceHotKeys,
|
...this.goToAppearanceHotKeys,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import types from '../mutation-types';
|
|||||||
import BulkActionsAPI from '../../api/bulkActions';
|
import BulkActionsAPI from '../../api/bulkActions';
|
||||||
|
|
||||||
export const state = {
|
export const state = {
|
||||||
|
selectedConversationIds: [],
|
||||||
uiFlags: {
|
uiFlags: {
|
||||||
isUpdating: false,
|
isUpdating: false,
|
||||||
},
|
},
|
||||||
@@ -11,6 +12,9 @@ export const getters = {
|
|||||||
getUIFlags(_state) {
|
getUIFlags(_state) {
|
||||||
return _state.uiFlags;
|
return _state.uiFlags;
|
||||||
},
|
},
|
||||||
|
getSelectedConversationIds(_state) {
|
||||||
|
return _state.selectedConversationIds;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
@@ -24,6 +28,15 @@ export const actions = {
|
|||||||
commit(types.SET_BULK_ACTIONS_FLAG, { isUpdating: false });
|
commit(types.SET_BULK_ACTIONS_FLAG, { isUpdating: false });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setSelectedConversationIds({ commit }, id) {
|
||||||
|
commit(types.SET_SELECTED_CONVERSATION_IDS, id);
|
||||||
|
},
|
||||||
|
removeSelectedConversationIds({ commit }, id) {
|
||||||
|
commit(types.REMOVE_SELECTED_CONVERSATION_IDS, id);
|
||||||
|
},
|
||||||
|
clearSelectedConversationIds({ commit }) {
|
||||||
|
commit(types.CLEAR_SELECTED_CONVERSATION_IDS);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mutations = {
|
export const mutations = {
|
||||||
@@ -33,6 +46,23 @@ export const mutations = {
|
|||||||
...data,
|
...data,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
[types.SET_SELECTED_CONVERSATION_IDS](_state, ids) {
|
||||||
|
// Check if ids is an array, if not, convert it to an array
|
||||||
|
const idsArray = Array.isArray(ids) ? ids : [ids];
|
||||||
|
|
||||||
|
// Concatenate the new IDs ensuring no duplicates
|
||||||
|
_state.selectedConversationIds = [
|
||||||
|
...new Set([..._state.selectedConversationIds, ...idsArray]),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
[types.REMOVE_SELECTED_CONVERSATION_IDS](_state, id) {
|
||||||
|
_state.selectedConversationIds = _state.selectedConversationIds.filter(
|
||||||
|
item => item !== id
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[types.CLEAR_SELECTED_CONVERSATION_IDS](_state) {
|
||||||
|
_state.selectedConversationIds = [];
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -25,4 +25,28 @@ describe('#actions', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('#setSelectedConversationIds', () => {
|
||||||
|
it('sends correct actions if API is success', async () => {
|
||||||
|
await actions.setSelectedConversationIds({ commit }, payload.ids);
|
||||||
|
expect(commit.mock.calls).toEqual([
|
||||||
|
[types.default.SET_SELECTED_CONVERSATION_IDS, payload.ids],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#removeSelectedConversationIds', () => {
|
||||||
|
it('sends correct actions if API is success', async () => {
|
||||||
|
await actions.removeSelectedConversationIds({ commit }, payload.ids);
|
||||||
|
expect(commit.mock.calls).toEqual([
|
||||||
|
[types.default.REMOVE_SELECTED_CONVERSATION_IDS, payload.ids],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#clearSelectedConversationIds', () => {
|
||||||
|
it('sends correct actions if API is success', async () => {
|
||||||
|
await actions.clearSelectedConversationIds({ commit });
|
||||||
|
expect(commit.mock.calls).toEqual([
|
||||||
|
[types.default.CLEAR_SELECTED_CONVERSATION_IDS],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,4 +11,10 @@ describe('#getters', () => {
|
|||||||
isUpdating: false,
|
isUpdating: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('getSelectedConversationIds', () => {
|
||||||
|
const state = {
|
||||||
|
selectedConversationIds: [1, 2, 3],
|
||||||
|
};
|
||||||
|
expect(getters.getSelectedConversationIds(state)).toEqual([1, 2, 3]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,4 +9,25 @@ describe('#mutations', () => {
|
|||||||
expect(state.uiFlags.isUpdating).toEqual(true);
|
expect(state.uiFlags.isUpdating).toEqual(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('#setSelectedConversationIds', () => {
|
||||||
|
it('set selected conversation ids', () => {
|
||||||
|
const state = { selectedConversationIds: [] };
|
||||||
|
mutations[types.SET_SELECTED_CONVERSATION_IDS](state, [1, 2, 3]);
|
||||||
|
expect(state.selectedConversationIds).toEqual([1, 2, 3]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#removeSelectedConversationIds', () => {
|
||||||
|
it('remove selected conversation ids', () => {
|
||||||
|
const state = { selectedConversationIds: [1, 2, 3] };
|
||||||
|
mutations[types.REMOVE_SELECTED_CONVERSATION_IDS](state, 1);
|
||||||
|
expect(state.selectedConversationIds).toEqual([2, 3]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#clearSelectedConversationIds', () => {
|
||||||
|
it('clear selected conversation ids', () => {
|
||||||
|
const state = { selectedConversationIds: [1, 2, 3] };
|
||||||
|
mutations[types.CLEAR_SELECTED_CONVERSATION_IDS](state);
|
||||||
|
expect(state.selectedConversationIds).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -160,6 +160,9 @@ export default {
|
|||||||
SET_CONVERSATION_LABELS_UI_FLAG: 'SET_CONVERSATION_LABELS_UI_FLAG',
|
SET_CONVERSATION_LABELS_UI_FLAG: 'SET_CONVERSATION_LABELS_UI_FLAG',
|
||||||
SET_CONVERSATION_LABELS: 'SET_CONVERSATION_LABELS',
|
SET_CONVERSATION_LABELS: 'SET_CONVERSATION_LABELS',
|
||||||
SET_BULK_CONVERSATION_LABELS: 'SET_BULK_CONVERSATION_LABELS',
|
SET_BULK_CONVERSATION_LABELS: 'SET_BULK_CONVERSATION_LABELS',
|
||||||
|
SET_SELECTED_CONVERSATION_IDS: 'SET_SELECTED_CONVERSATION_IDS',
|
||||||
|
CLEAR_SELECTED_CONVERSATION_IDS: 'CLEAR_SELECTED_CONVERSATION_IDS',
|
||||||
|
REMOVE_SELECTED_CONVERSATION_IDS: 'REMOVE_SELECTED_CONVERSATION_IDS',
|
||||||
|
|
||||||
// Reports
|
// Reports
|
||||||
SET_ACCOUNT_REPORTS: 'SET_ACCOUNT_REPORTS',
|
SET_ACCOUNT_REPORTS: 'SET_ACCOUNT_REPORTS',
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, defineProps, useAttrs } from 'vue';
|
import { computed, useAttrs } from 'vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
variant: {
|
variant: {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps, defineEmits, computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
isChecked: {
|
isChecked: {
|
||||||
|
|||||||
Reference in New Issue
Block a user