Add team option in bulk actions (#5885)
This commit is contained in:
parent
47676c3cce
commit
33aacb3401
9 changed files with 287 additions and 20 deletions
|
@ -102,6 +102,7 @@
|
|||
@assign-agent="onAssignAgent"
|
||||
@update-conversations="onUpdateConversations"
|
||||
@assign-labels="onAssignLabels"
|
||||
@assign-team="onAssignTeamsForBulk"
|
||||
/>
|
||||
<div
|
||||
ref="activeConversation"
|
||||
|
@ -685,6 +686,21 @@ export default {
|
|||
this.showAlert(this.$t('BULK_ACTION.LABELS.ASSIGN_FAILED'));
|
||||
}
|
||||
},
|
||||
async onAssignTeamsForBulk(team) {
|
||||
try {
|
||||
await this.$store.dispatch('bulkActions/process', {
|
||||
type: 'Conversation',
|
||||
ids: this.selectedConversations,
|
||||
fields: {
|
||||
team_id: team.id,
|
||||
},
|
||||
});
|
||||
this.selectedConversations = [];
|
||||
this.showAlert(this.$t('BULK_ACTION.TEAMS.ASSIGN_SUCCESFUL'));
|
||||
} catch (err) {
|
||||
this.showAlert(this.$t('BULK_ACTION.TEAMS.ASSIGN_FAILED'));
|
||||
}
|
||||
},
|
||||
async onUpdateConversations(status) {
|
||||
try {
|
||||
await this.$store.dispatch('bulkActions/process', {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="bulk-action__agents">
|
||||
<div class="triangle">
|
||||
<div class="triangle" :style="cssVars">
|
||||
<svg height="12" viewBox="0 0 24 12" width="24">
|
||||
<path
|
||||
d="M20 12l-8-8-12 12"
|
||||
|
@ -105,13 +105,14 @@ import { mapGetters } from 'vuex';
|
|||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import Spinner from 'shared/components/Spinner';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import bulkActionsMixin from 'dashboard/mixins/bulkActionsMixin.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Thumbnail,
|
||||
Spinner,
|
||||
},
|
||||
mixins: [clickaway],
|
||||
mixins: [clickaway, bulkActionsMixin],
|
||||
props: {
|
||||
selectedInboxes: {
|
||||
type: Array,
|
||||
|
@ -233,7 +234,7 @@ export default {
|
|||
z-index: var(--z-index-one);
|
||||
position: absolute;
|
||||
top: calc(var(--space-slab) * -1);
|
||||
right: var(--space-micro);
|
||||
right: var(--triangle-position);
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,25 +43,26 @@
|
|||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
icon="person-assign"
|
||||
class="margin-right-smaller"
|
||||
@click="toggleAgentList"
|
||||
/>
|
||||
<woot-button
|
||||
v-tooltip="$t('BULK_ACTION.ASSIGN_TEAM_TOOLTIP')"
|
||||
size="tiny"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
icon="people-team-add"
|
||||
@click="toggleTeamsList"
|
||||
/>
|
||||
</div>
|
||||
<transition name="popover-animation">
|
||||
<label-actions
|
||||
v-if="showLabelActions"
|
||||
triangle-position="8.5"
|
||||
@assign="assignLabels"
|
||||
@close="showLabelActions = false"
|
||||
/>
|
||||
</transition>
|
||||
<transition name="popover-animation">
|
||||
<agent-selector
|
||||
v-if="showAgentsList"
|
||||
:selected-inboxes="selectedInboxes"
|
||||
:conversation-count="conversations.length"
|
||||
@select="submit"
|
||||
@close="showAgentsList = false"
|
||||
/>
|
||||
</transition>
|
||||
<transition name="popover-animation">
|
||||
<update-actions
|
||||
v-if="showUpdateActions"
|
||||
|
@ -70,10 +71,29 @@
|
|||
:show-resolve="!showResolvedAction"
|
||||
:show-reopen="!showOpenAction"
|
||||
:show-snooze="!showSnoozedAction"
|
||||
triangle-position="5.6"
|
||||
@update="updateConversations"
|
||||
@close="showUpdateActions = false"
|
||||
/>
|
||||
</transition>
|
||||
<transition name="popover-animation">
|
||||
<agent-selector
|
||||
v-if="showAgentsList"
|
||||
:selected-inboxes="selectedInboxes"
|
||||
:conversation-count="conversations.length"
|
||||
triangle-position="2.8"
|
||||
@select="submit"
|
||||
@close="showAgentsList = false"
|
||||
/>
|
||||
</transition>
|
||||
<transition name="popover-animation">
|
||||
<team-actions
|
||||
v-if="showTeamsList"
|
||||
triangle-position="0.2"
|
||||
@assign-team="assignTeam"
|
||||
@close="showTeamsList = false"
|
||||
/>
|
||||
</transition>
|
||||
</div>
|
||||
<div v-if="allConversationsSelected" class="bulk-action__alert">
|
||||
{{ $t('BULK_ACTION.ALL_CONVERSATIONS_SELECTED_ALERT') }}
|
||||
|
@ -85,11 +105,13 @@
|
|||
import AgentSelector from './AgentSelector.vue';
|
||||
import UpdateActions from './UpdateActions.vue';
|
||||
import LabelActions from './LabelActions.vue';
|
||||
import TeamActions from './TeamActions.vue';
|
||||
export default {
|
||||
components: {
|
||||
AgentSelector,
|
||||
UpdateActions,
|
||||
LabelActions,
|
||||
TeamActions,
|
||||
},
|
||||
props: {
|
||||
conversations: {
|
||||
|
@ -122,6 +144,8 @@ export default {
|
|||
showAgentsList: false,
|
||||
showUpdateActions: false,
|
||||
showLabelActions: false,
|
||||
showTeamsList: false,
|
||||
popoverPositions: {},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -137,6 +161,9 @@ export default {
|
|||
assignLabels(labels) {
|
||||
this.$emit('assign-labels', labels);
|
||||
},
|
||||
assignTeam(team) {
|
||||
this.$emit('assign-team', team);
|
||||
},
|
||||
resolveConversations() {
|
||||
this.$emit('resolve-conversations');
|
||||
},
|
||||
|
@ -149,6 +176,9 @@ export default {
|
|||
toggleAgentList() {
|
||||
this.showAgentsList = !this.showAgentsList;
|
||||
},
|
||||
toggleTeamsList() {
|
||||
this.showTeamsList = !this.showTeamsList;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div v-on-clickaway="onClose" class="labels-container">
|
||||
<div class="triangle">
|
||||
<div class="triangle" :style="cssVars">
|
||||
<svg height="12" viewBox="0 0 24 12" width="24">
|
||||
<path
|
||||
d="M20 12l-8-8-12 12"
|
||||
|
@ -75,9 +75,10 @@
|
|||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import { mapGetters } from 'vuex';
|
||||
import bulkActionsMixin from 'dashboard/mixins/bulkActionsMixin.js';
|
||||
|
||||
export default {
|
||||
mixins: [clickaway],
|
||||
mixins: [clickaway, bulkActionsMixin],
|
||||
data() {
|
||||
return {
|
||||
query: '',
|
||||
|
@ -160,7 +161,7 @@ export default {
|
|||
max-width: var(--space-giga);
|
||||
min-width: var(--space-giga);
|
||||
position: absolute;
|
||||
right: 4.5rem;
|
||||
right: var(--space-small);
|
||||
top: var(--space-larger);
|
||||
transform-origin: top right;
|
||||
width: auto;
|
||||
|
@ -204,7 +205,7 @@ export default {
|
|||
.triangle {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: var(--space-two);
|
||||
right: var(--triangle-position);
|
||||
text-align: left;
|
||||
top: calc(var(--space-slab) * -1);
|
||||
z-index: var(--z-index-one);
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
<template>
|
||||
<div v-on-clickaway="onClose" class="bulk-action__teams">
|
||||
<div class="triangle" :style="cssVars">
|
||||
<svg height="12" viewBox="0 0 24 12" width="24">
|
||||
<path
|
||||
d="M20 12l-8-8-12 12"
|
||||
fill="var(--white)"
|
||||
fill-rule="evenodd"
|
||||
stroke="var(--s-50)"
|
||||
stroke-width="1px"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="header flex-between">
|
||||
<span>{{ $t('BULK_ACTION.TEAMS.TEAM_SELECT_LABEL') }}</span>
|
||||
<woot-button
|
||||
size="tiny"
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
icon="dismiss"
|
||||
@click="onClose"
|
||||
/>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="team__list-container">
|
||||
<ul>
|
||||
<li class="search-container">
|
||||
<div class="agent-list-search flex-between">
|
||||
<fluent-icon icon="search" class="search-icon" size="16" />
|
||||
<input
|
||||
ref="search"
|
||||
v-model="query"
|
||||
type="search"
|
||||
placeholder="Search"
|
||||
class="agent--search_input"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
<template v-if="filteredTeams.length">
|
||||
<li v-for="team in filteredTeams" :key="team.id">
|
||||
<div class="team__list-item" @click="assignTeam(team)">
|
||||
<span class="reports-option__title">{{ team.name }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
<li v-else>
|
||||
<div class="team__list-item">
|
||||
<span class="reports-option__title">{{
|
||||
$t('BULK_ACTION.TEAMS.NO_TEAMS_AVAILABLE')
|
||||
}}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import { mapGetters } from 'vuex';
|
||||
import bulkActionsMixin from 'dashboard/mixins/bulkActionsMixin.js';
|
||||
export default {
|
||||
mixins: [clickaway, bulkActionsMixin],
|
||||
data() {
|
||||
return {
|
||||
query: '',
|
||||
selectedteams: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({ teams: 'teams/getTeams' }),
|
||||
filteredTeams() {
|
||||
return [
|
||||
{ name: 'None', id: 0 },
|
||||
...this.teams.filter(team =>
|
||||
team.name.toLowerCase().includes(this.query.toLowerCase())
|
||||
),
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
assignTeam(key) {
|
||||
this.$emit('assign-team', key);
|
||||
},
|
||||
onClose() {
|
||||
this.$emit('close');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.bulk-action__teams {
|
||||
background-color: var(--white);
|
||||
border-radius: var(--border-radius-large);
|
||||
border: 1px solid var(--s-50);
|
||||
box-shadow: var(--shadow-dropdown-pane);
|
||||
max-width: 75%;
|
||||
position: absolute;
|
||||
right: var(--space-small);
|
||||
top: var(--space-larger);
|
||||
transform-origin: top right;
|
||||
width: auto;
|
||||
z-index: var(--z-index-twenty);
|
||||
min-width: var(--space-giga);
|
||||
.header {
|
||||
padding: var(--space-one);
|
||||
|
||||
span {
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
max-height: var(--space-giga);
|
||||
overflow-y: auto;
|
||||
.team__list-container {
|
||||
height: 100%;
|
||||
}
|
||||
.agent-list-search {
|
||||
padding: 0 var(--space-one);
|
||||
border: 1px solid var(--s-100);
|
||||
border-radius: var(--border-radius-medium);
|
||||
background-color: var(--s-50);
|
||||
.search-icon {
|
||||
color: var(--s-400);
|
||||
}
|
||||
|
||||
.agent--search_input {
|
||||
border: 0;
|
||||
font-size: var(--font-size-mini);
|
||||
margin: 0;
|
||||
background-color: transparent;
|
||||
height: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
.triangle {
|
||||
display: block;
|
||||
z-index: var(--z-index-one);
|
||||
position: absolute;
|
||||
top: calc(var(--space-slab) * -1);
|
||||
right: var(--triangle-position);
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
ul {
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.team__list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--space-one);
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--s-50);
|
||||
}
|
||||
span {
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
}
|
||||
|
||||
.search-container {
|
||||
padding: 0 var(--space-one);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: var(--z-index-twenty);
|
||||
background-color: var(--white);
|
||||
}
|
||||
</style>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div v-on-clickaway="onClose" class="actions-container">
|
||||
<div class="triangle">
|
||||
<div class="triangle" :style="cssVars">
|
||||
<svg height="12" viewBox="0 0 24 12" width="24">
|
||||
<path
|
||||
d="M20 12l-8-8-12 12"
|
||||
|
@ -45,12 +45,14 @@
|
|||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
|
||||
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
|
||||
import bulkActionsMixin from 'dashboard/mixins/bulkActionsMixin.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
WootDropdownItem,
|
||||
WootDropdownMenu,
|
||||
},
|
||||
mixins: [clickaway],
|
||||
mixins: [clickaway, bulkActionsMixin],
|
||||
props: {
|
||||
selectedInboxes: {
|
||||
type: Array,
|
||||
|
@ -131,7 +133,7 @@ export default {
|
|||
box-shadow: var(--shadow-dropdown-pane);
|
||||
position: absolute;
|
||||
right: var(--space-small);
|
||||
top: 48px;
|
||||
top: var(--space-larger);
|
||||
transform-origin: top right;
|
||||
width: auto;
|
||||
z-index: var(--z-index-twenty);
|
||||
|
@ -152,7 +154,7 @@ export default {
|
|||
.triangle {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: 2.8rem;
|
||||
right: var(--triangle-position);
|
||||
text-align: left;
|
||||
top: calc(var(--space-slab) * -1);
|
||||
z-index: var(--z-index-one);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"ASSIGN_LABEL": "Assign",
|
||||
"YES": "Yes",
|
||||
"ASSIGN_AGENT_TOOLTIP": "Assign Agent",
|
||||
"ASSIGN_TEAM_TOOLTIP": "Assign team",
|
||||
"ASSIGN_SUCCESFUL": "Conversations assigned successfully",
|
||||
"ASSIGN_FAILED": "Failed to assign conversations, please try again",
|
||||
"RESOLVE_SUCCESFUL": "Conversations resolved successfully",
|
||||
|
@ -26,6 +27,14 @@
|
|||
"ASSIGN_SELECTED_LABELS": "Assign selected labels",
|
||||
"ASSIGN_SUCCESFUL": "Labels assigned successfully",
|
||||
"ASSIGN_FAILED": "Failed to assign labels, please try again"
|
||||
},
|
||||
"TEAMS": {
|
||||
"TEAM_SELECT_LABEL": "Select Team",
|
||||
"NONE": "None",
|
||||
"NO_TEAMS_AVAILABLE": "There are no teams added to this account yet.",
|
||||
"ASSIGN_SELECTED_TEAMS": "Assign selected team",
|
||||
"ASSIGN_SUCCESFUL": "Teams assiged successfully",
|
||||
"ASSIGN_FAILED": "Failed to assign team, please try again"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
15
app/javascript/dashboard/mixins/bulkActionsMixin.js
Normal file
15
app/javascript/dashboard/mixins/bulkActionsMixin.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
export default {
|
||||
props: {
|
||||
trianglePosition: {
|
||||
type: String,
|
||||
default: '0',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
cssVars() {
|
||||
return {
|
||||
'--triangle-position': this.trianglePosition + 'rem',
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
19
app/javascript/dashboard/mixins/specs/bulkActions.spec.js
Normal file
19
app/javascript/dashboard/mixins/specs/bulkActions.spec.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import bulkActionsMixin from '../bulkActionsMixin';
|
||||
describe('bulkActionsMixin', () => {
|
||||
it('returns the prop and computed values for triangle position:', async () => {
|
||||
const Component = {
|
||||
render() {},
|
||||
title: 'MyComponent',
|
||||
mixins: [bulkActionsMixin],
|
||||
};
|
||||
const wrapper = mount(Component);
|
||||
await wrapper.setProps({
|
||||
trianglePosition: '10',
|
||||
});
|
||||
expect(wrapper.props().trianglePosition).toEqual('10');
|
||||
expect(wrapper.vm.cssVars).toEqual({
|
||||
'--triangle-position': '10rem',
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue