feat: Macros listing and Editor (#5606)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
parent
1fb1be3ddc
commit
22d5703b92
23 changed files with 1287 additions and 31 deletions
|
@ -54,3 +54,5 @@ exclude_patterns:
|
||||||
- 'app/javascript/widget/i18n/index.js'
|
- 'app/javascript/widget/i18n/index.js'
|
||||||
- 'app/javascript/survey/i18n/index.js'
|
- 'app/javascript/survey/i18n/index.js'
|
||||||
- 'app/javascript/shared/constants/locales.js'
|
- 'app/javascript/shared/constants/locales.js'
|
||||||
|
- 'app/javascript/dashboard/helper/specs/macrosFixtures.js'
|
||||||
|
- 'app/javascript/dashboard/routes/dashboard/settings/macros/constants.js'
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div class="filter" :class="actionInputStyles">
|
||||||
class="filter"
|
|
||||||
:class="{ error: v.action_params.$dirty && v.action_params.$error }"
|
|
||||||
>
|
|
||||||
<div class="filter-inputs">
|
<div class="filter-inputs">
|
||||||
<select
|
<select
|
||||||
v-model="action_name"
|
v-model="action_name"
|
||||||
|
@ -60,6 +57,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<woot-button
|
<woot-button
|
||||||
|
v-if="!isMacro"
|
||||||
icon="dismiss"
|
icon="dismiss"
|
||||||
variant="clear"
|
variant="clear"
|
||||||
color-scheme="secondary"
|
color-scheme="secondary"
|
||||||
|
@ -120,6 +118,10 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
isMacro: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
action_name: {
|
action_name: {
|
||||||
|
@ -146,6 +148,12 @@ export default {
|
||||||
return this.actionTypes.find(action => action.key === this.action_name)
|
return this.actionTypes.find(action => action.key === this.action_name)
|
||||||
.inputType;
|
.inputType;
|
||||||
},
|
},
|
||||||
|
actionInputStyles() {
|
||||||
|
return {
|
||||||
|
error: this.v.action_params.$dirty && this.v.action_params.$error,
|
||||||
|
'is-a-macro': this.isMacro,
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
removeAction() {
|
removeAction() {
|
||||||
|
@ -165,6 +173,18 @@ export default {
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
border-radius: var(--border-radius-medium);
|
border-radius: var(--border-radius-medium);
|
||||||
margin-bottom: var(--space-small);
|
margin-bottom: var(--space-small);
|
||||||
|
|
||||||
|
&.is-a-macro {
|
||||||
|
margin-bottom: 0;
|
||||||
|
background: var(--white);
|
||||||
|
padding: var(--space-zero);
|
||||||
|
border: unset;
|
||||||
|
border-radius: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-margin-bottom {
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter.error {
|
.filter.error {
|
||||||
|
|
71
app/javascript/dashboard/helper/specs/macrosFixtures.js
Normal file
71
app/javascript/dashboard/helper/specs/macrosFixtures.js
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
export const teams = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '⚙️ sales team',
|
||||||
|
description: 'This is our internal sales team',
|
||||||
|
allow_auto_assign: true,
|
||||||
|
account_id: 1,
|
||||||
|
is_member: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '🤷♂️ fayaz',
|
||||||
|
description: 'Test',
|
||||||
|
allow_auto_assign: true,
|
||||||
|
account_id: 1,
|
||||||
|
is_member: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '🇮🇳 apac sales',
|
||||||
|
description: 'Sales team for France Territory',
|
||||||
|
allow_auto_assign: true,
|
||||||
|
account_id: 1,
|
||||||
|
is_member: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const labels = [
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
title: 'sales',
|
||||||
|
description: 'sales team',
|
||||||
|
color: '#8EA20F',
|
||||||
|
show_on_sidebar: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: 'billing',
|
||||||
|
description: 'billing',
|
||||||
|
color: '#4077DA',
|
||||||
|
show_on_sidebar: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: 'snoozed',
|
||||||
|
description: 'Items marked for later',
|
||||||
|
color: '#D12F42',
|
||||||
|
show_on_sidebar: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
title: 'mobile-app',
|
||||||
|
description: 'tech team',
|
||||||
|
color: '#2DB1CC',
|
||||||
|
show_on_sidebar: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 14,
|
||||||
|
title: 'human-resources-department-with-long-title',
|
||||||
|
description: 'Test',
|
||||||
|
color: '#FF6E09',
|
||||||
|
show_on_sidebar: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 22,
|
||||||
|
title: 'priority',
|
||||||
|
description: 'For important sales leads',
|
||||||
|
color: '#7E7CED',
|
||||||
|
show_on_sidebar: true,
|
||||||
|
},
|
||||||
|
];
|
17
app/javascript/dashboard/helper/specs/macrosHelper.spec.js
Normal file
17
app/javascript/dashboard/helper/specs/macrosHelper.spec.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { emptyMacro } from '../../routes/dashboard/settings/macros/macroHelper';
|
||||||
|
|
||||||
|
describe('#emptyMacro', () => {
|
||||||
|
const defaultMacro = {
|
||||||
|
name: '',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action_name: 'assign_team',
|
||||||
|
action_params: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
visibility: 'global',
|
||||||
|
};
|
||||||
|
it('returns the default macro', () => {
|
||||||
|
expect(emptyMacro).toEqual(defaultMacro);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,5 +1,73 @@
|
||||||
{
|
{
|
||||||
"MACROS": {
|
"MACROS": {
|
||||||
"HEADER": "Macros"
|
"HEADER": "Macros",
|
||||||
|
"HEADER_BTN_TXT": "Add a new macro",
|
||||||
|
"HEADER_BTN_TXT_SAVE": "Save macro",
|
||||||
|
"LOADING": "Fetching macros",
|
||||||
|
"SIDEBAR_TXT": "<p><b>Macros</b><p>A macro is a set of saved actions that help customer service agents easily complete tasks. The agents can define a set of actions like tagging a conversation with a label, sending an email transcript, updating a custom attribute, etc., and they can run these actions in a single click. When the agents run the macro, the actions would be performed sequentially in the order they are defined. Macros improve productivity and increase consistency in actions. </p><p>A macro can be helpful in 2 ways. </p><p><b>As an agent assist:</b> If an agent performs a set of actions multiple times, they can save it as a macro and execute all the actions together using a single click.</p><p><b>As an option to onboard a team member:</b> Every agent has to perform many different checks/actions during each conversation. Onboarding a new support team member will be easy if pre-defined macros are available on the account. Instead of describing each step in detail, the manager/team lead can point to the macros used in different scenarios.</p>",
|
||||||
|
"ERROR": "Something went wrong. Please try again",
|
||||||
|
"ORDER_INFO": "Macros will run in the order you add your actions. You can rearrange them by dragging them by the handle beside each node.",
|
||||||
|
"ADD": {
|
||||||
|
"FORM": {
|
||||||
|
"NAME": {
|
||||||
|
"LABEL": "Macro name",
|
||||||
|
"PLACEHOLDER": "Enter a name for your macro",
|
||||||
|
"ERROR": "Name is required for creating a macro"
|
||||||
|
},
|
||||||
|
"ACTIONS": {
|
||||||
|
"LABEL": "Actions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro added successfully",
|
||||||
|
"ERROR_MESSAGE": "Unable to create macro, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"LIST": {
|
||||||
|
"TABLE_HEADER": [
|
||||||
|
"Name",
|
||||||
|
"Created by",
|
||||||
|
"Last updated by",
|
||||||
|
"Visibility"
|
||||||
|
],
|
||||||
|
"404": "No macros found"
|
||||||
|
},
|
||||||
|
"DELETE": {
|
||||||
|
"TOOLTIP": "Delete macro",
|
||||||
|
"CONFIRM": {
|
||||||
|
"MESSAGE": "Are you sure to delete ",
|
||||||
|
"YES": "Yes, Delete",
|
||||||
|
"NO": "No"
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro deleted successfully",
|
||||||
|
"ERROR_MESSAGE": "There was an error deleting the macro. Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDIT": {
|
||||||
|
"TOOLTIP": "Edit macro",
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro updated successfully",
|
||||||
|
"ERROR_MESSAGE": "Could not update Macro, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDITOR": {
|
||||||
|
"START_FLOW": "Start Flow",
|
||||||
|
"END_FLOW": "End Flow",
|
||||||
|
"LOADING": "Fetching macro",
|
||||||
|
"ADD_BTN_TOOLTIP": "Add new action",
|
||||||
|
"DELETE_BTN_TOOLTIP": "Delete Action",
|
||||||
|
"VISIBILITY": {
|
||||||
|
"LABEL": "Macro Visibility",
|
||||||
|
"GLOBAL": {
|
||||||
|
"LABEL": "Public",
|
||||||
|
"DESCRIPTION": "This macro is available publicly for all agents in this account."
|
||||||
|
},
|
||||||
|
"PERSONAL": {
|
||||||
|
"LABEL": "Private",
|
||||||
|
"DESCRIPTION": "This macro will be private to you and not be available to others."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
20
app/javascript/dashboard/mixins/macrosMixin.js
Normal file
20
app/javascript/dashboard/mixins/macrosMixin.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
getDropdownValues(type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'assign_team':
|
||||||
|
case 'send_email_to_team':
|
||||||
|
return this.teams;
|
||||||
|
case 'add_label':
|
||||||
|
return this.labels.map(i => {
|
||||||
|
return {
|
||||||
|
id: i.title,
|
||||||
|
name: i.title,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
41
app/javascript/dashboard/mixins/specs/macros.spec.js
Normal file
41
app/javascript/dashboard/mixins/specs/macros.spec.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { createWrapper } from '@vue/test-utils';
|
||||||
|
import macrosMixin from '../macrosMixin';
|
||||||
|
import Vue from 'vue';
|
||||||
|
import { teams, labels } from '../../helper/specs/macrosFixtures';
|
||||||
|
describe('webhookMixin', () => {
|
||||||
|
describe('#getEventLabel', () => {
|
||||||
|
it('returns correct i18n translation:', () => {
|
||||||
|
const Component = {
|
||||||
|
render() {},
|
||||||
|
title: 'MyComponent',
|
||||||
|
mixins: [macrosMixin],
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
teams,
|
||||||
|
labels,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
$t(text) {
|
||||||
|
return text;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolvedLabels = labels.map(i => {
|
||||||
|
return {
|
||||||
|
id: i.title,
|
||||||
|
name: i.title,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const Constructor = Vue.extend(Component);
|
||||||
|
const vm = new Constructor().$mount();
|
||||||
|
const wrapper = createWrapper(vm);
|
||||||
|
expect(wrapper.vm.getDropdownValues('assign_team')).toEqual(teams);
|
||||||
|
expect(wrapper.vm.getDropdownValues('send_email_to_team')).toEqual(teams);
|
||||||
|
expect(wrapper.vm.getDropdownValues('add_label')).toEqual(resolvedLabels);
|
||||||
|
expect(wrapper.vm.getDropdownValues()).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -225,7 +225,7 @@ export default {
|
||||||
mode === 'EDIT'
|
mode === 'EDIT'
|
||||||
? this.$t('AUTOMATION.EDIT.API.SUCCESS_MESSAGE')
|
? this.$t('AUTOMATION.EDIT.API.SUCCESS_MESSAGE')
|
||||||
: this.$t('AUTOMATION.ADD.API.SUCCESS_MESSAGE');
|
: this.$t('AUTOMATION.ADD.API.SUCCESS_MESSAGE');
|
||||||
await await this.$store.dispatch(action, payload);
|
await this.$store.dispatch(action, payload);
|
||||||
this.showAlert(this.$t(successMessage));
|
this.showAlert(this.$t(successMessage));
|
||||||
this.hideAddPopup();
|
this.hideAddPopup();
|
||||||
this.hideEditPopup();
|
this.hideEditPopup();
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
v-tooltip="tooltip"
|
||||||
|
class="macros__action-button"
|
||||||
|
:class="type"
|
||||||
|
@click="$emit('click')"
|
||||||
|
>
|
||||||
|
<fluent-icon :icon="icon" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'add',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.macros__action-button {
|
||||||
|
height: var(--space-three);
|
||||||
|
width: var(--space-three);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: var(--font-size-default);
|
||||||
|
border-radius: var(--border-radius-rounded);
|
||||||
|
position: relative;
|
||||||
|
margin-left: var(--space-one);
|
||||||
|
|
||||||
|
&.add {
|
||||||
|
background-color: var(--g-100);
|
||||||
|
color: var(--g-600);
|
||||||
|
}
|
||||||
|
&.delete {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(var(--space-three) / -2);
|
||||||
|
right: calc(var(--space-three) / -2);
|
||||||
|
background-color: var(--r-100);
|
||||||
|
color: var(--r-600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,11 +1,121 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="column content-box">
|
||||||
Macros
|
<router-link
|
||||||
|
:to="addAccountScoping('settings/macros/new')"
|
||||||
|
class="button success button--fixed-right-top"
|
||||||
|
>
|
||||||
|
<fluent-icon icon="add-circle" />
|
||||||
|
<span class="button__content">
|
||||||
|
{{ $t('MACROS.HEADER_BTN_TXT') }}
|
||||||
|
</span>
|
||||||
|
</router-link>
|
||||||
|
<div class="row">
|
||||||
|
<div class="small-8 columns with-right-space">
|
||||||
|
<div
|
||||||
|
v-if="!uiFlags.isFetching && !records.length"
|
||||||
|
class="macros__empty-state"
|
||||||
|
>
|
||||||
|
<p class="no-items-error-message">
|
||||||
|
{{ $t('MACROS.LIST.404') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<woot-loading-state
|
||||||
|
v-if="uiFlags.isFetching"
|
||||||
|
:message="$t('MACROS.LOADING')"
|
||||||
|
/>
|
||||||
|
<table v-if="!uiFlags.isFetching && records.length" class="woot-table">
|
||||||
|
<thead>
|
||||||
|
<th
|
||||||
|
v-for="thHeader in $t('MACROS.LIST.TABLE_HEADER')"
|
||||||
|
:key="thHeader"
|
||||||
|
>
|
||||||
|
{{ thHeader }}
|
||||||
|
</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<macros-table-row
|
||||||
|
v-for="(macro, index) in records"
|
||||||
|
:key="index"
|
||||||
|
:macro="macro"
|
||||||
|
@delete="openDeletePopup(macro, index)"
|
||||||
|
/>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="small-4 columns">
|
||||||
|
<span v-dompurify-html="$t('MACROS.SIDEBAR_TXT')" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<woot-delete-modal
|
||||||
|
:show.sync="showDeleteConfirmationPopup"
|
||||||
|
:on-close="closeDeletePopup"
|
||||||
|
:on-confirm="confirmDeletion"
|
||||||
|
:title="$t('LABEL_MGMT.DELETE.CONFIRM.TITLE')"
|
||||||
|
:message="$t('MACROS.DELETE.CONFIRM.MESSAGE')"
|
||||||
|
:message-value="deleteMessage"
|
||||||
|
:confirm-text="$t('MACROS.DELETE.CONFIRM.YES')"
|
||||||
|
:reject-text="$t('MACROS.DELETE.CONFIRM.NO')"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {};
|
import { mapGetters } from 'vuex';
|
||||||
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
|
import accountMixin from 'dashboard/mixins/account.js';
|
||||||
|
import MacrosTableRow from './MacrosTableRow';
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
MacrosTableRow,
|
||||||
|
},
|
||||||
|
mixins: [alertMixin, accountMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showDeleteConfirmationPopup: false,
|
||||||
|
selectedResponse: {},
|
||||||
|
loading: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
records: ['macros/getMacros'],
|
||||||
|
uiFlags: 'macros/getUIFlags',
|
||||||
|
}),
|
||||||
|
deleteMessage() {
|
||||||
|
return ` ${this.selectedResponse.name}?`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$store.dispatch('macros/get');
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openDeletePopup(response) {
|
||||||
|
this.showDeleteConfirmationPopup = true;
|
||||||
|
this.selectedResponse = response;
|
||||||
|
},
|
||||||
|
closeDeletePopup() {
|
||||||
|
this.showDeleteConfirmationPopup = false;
|
||||||
|
},
|
||||||
|
confirmDeletion() {
|
||||||
|
this.loading[this.selectedResponse.id] = true;
|
||||||
|
this.closeDeletePopup();
|
||||||
|
this.deleteMacro(this.selectedResponse.id);
|
||||||
|
},
|
||||||
|
async deleteMacro(id) {
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('macros/delete', id);
|
||||||
|
this.showAlert(this.$t('MACROS.DELETE.API.SUCCESS_MESSAGE'));
|
||||||
|
this.loading[this.selectedResponse.id] = false;
|
||||||
|
} catch (error) {
|
||||||
|
this.showAlert(this.$t('MACROS.DELETE.API.ERROR_MESSAGE'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style></style>
|
<style scoped>
|
||||||
|
.macros__empty-state {
|
||||||
|
padding: var(--space-slab);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,9 +1,137 @@
|
||||||
<template>
|
<template>
|
||||||
<div>MacrosEditor</div>
|
<div class="column content-box">
|
||||||
|
<woot-loading-state
|
||||||
|
v-if="uiFlags.isFetchingItem"
|
||||||
|
:message="$t('MACROS.EDITOR.LOADING')"
|
||||||
|
/>
|
||||||
|
<macro-form
|
||||||
|
v-if="macro && !uiFlags.isFetchingItem"
|
||||||
|
:macro-data.sync="macro"
|
||||||
|
@submit="saveMacro"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {};
|
import MacroForm from './MacroForm';
|
||||||
|
import { MACRO_ACTION_TYPES } from './constants';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import { emptyMacro } from './macroHelper';
|
||||||
|
import actionQueryGenerator from 'dashboard/helper/actionQueryGenerator.js';
|
||||||
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
|
import macrosMixin from 'dashboard/mixins/macrosMixin';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
MacroForm,
|
||||||
|
},
|
||||||
|
mixins: [alertMixin, macrosMixin],
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
macroActionTypes: this.macroActionTypes,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
macro: null,
|
||||||
|
mode: 'CREATE',
|
||||||
|
macroActionTypes: MACRO_ACTION_TYPES,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
uiFlags: 'macros/getUIFlags',
|
||||||
|
labels: 'labels/getLabels',
|
||||||
|
teams: 'teams/getTeams',
|
||||||
|
}),
|
||||||
|
macroId() {
|
||||||
|
return this.$route.params.macroId;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
$route: {
|
||||||
|
handler() {
|
||||||
|
if (this.$route.params.macroId) {
|
||||||
|
this.fetchMacro();
|
||||||
|
} else {
|
||||||
|
this.initNewMacro();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchMacro() {
|
||||||
|
this.mode = 'EDIT';
|
||||||
|
this.$store.dispatch('agents/get');
|
||||||
|
this.$store.dispatch('teams/get');
|
||||||
|
this.$store.dispatch('labels/get');
|
||||||
|
this.manifestMacro();
|
||||||
|
},
|
||||||
|
async manifestMacro() {
|
||||||
|
await this.$store.dispatch('macros/getSingleMacro', this.macroId);
|
||||||
|
const singleMacro = this.$store.getters['macros/getMacro'](this.macroId);
|
||||||
|
this.macro = this.formatMacro(singleMacro);
|
||||||
|
},
|
||||||
|
formatMacro(macro) {
|
||||||
|
const formattedActions = macro.actions.map(action => {
|
||||||
|
let actionParams = [];
|
||||||
|
if (action.action_params.length) {
|
||||||
|
const inputType = this.macroActionTypes.find(
|
||||||
|
item => item.key === action.action_name
|
||||||
|
).inputType;
|
||||||
|
if (inputType === 'multi_select') {
|
||||||
|
actionParams = [
|
||||||
|
...this.getDropdownValues(action.action_name, this.$store),
|
||||||
|
].filter(item => [...action.action_params].includes(item.id));
|
||||||
|
} else if (inputType === 'team_message') {
|
||||||
|
actionParams = {
|
||||||
|
team_ids: [
|
||||||
|
...this.getDropdownValues(action.action_name, this.$store),
|
||||||
|
].filter(item =>
|
||||||
|
[...action.action_params[0].team_ids].includes(item.id)
|
||||||
|
),
|
||||||
|
message: action.action_params[0].message,
|
||||||
|
};
|
||||||
|
} else actionParams = [...action.action_params];
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...action,
|
||||||
|
action_params: actionParams,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
...macro,
|
||||||
|
actions: formattedActions,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
initNewMacro() {
|
||||||
|
this.mode = 'CREATE';
|
||||||
|
this.macro = emptyMacro;
|
||||||
|
},
|
||||||
|
async saveMacro(macro) {
|
||||||
|
try {
|
||||||
|
const action = this.mode === 'EDIT' ? 'macros/update' : 'macros/create';
|
||||||
|
let successMessage =
|
||||||
|
this.mode === 'EDIT'
|
||||||
|
? this.$t('MACROS.EDIT.API.SUCCESS_MESSAGE')
|
||||||
|
: this.$t('MACROS.ADD.API.SUCCESS_MESSAGE');
|
||||||
|
let serializedMacro = JSON.parse(JSON.stringify(macro));
|
||||||
|
serializedMacro.actions = actionQueryGenerator(serializedMacro.actions);
|
||||||
|
await this.$store.dispatch(action, serializedMacro);
|
||||||
|
this.showAlert(successMessage);
|
||||||
|
this.$router.push({ name: 'macros_wrapper' });
|
||||||
|
} catch (error) {
|
||||||
|
this.showAlert(this.$t('MACROS.ERROR'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style></style>
|
<style scoped>
|
||||||
|
.content-box {
|
||||||
|
padding: 0;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
<template>
|
||||||
|
<div class="row">
|
||||||
|
<div class="small-8 columns with-right-space macros-canvas">
|
||||||
|
<macro-nodes
|
||||||
|
v-model="macro.actions"
|
||||||
|
@addNewNode="appendNode"
|
||||||
|
@deleteNode="deleteNode"
|
||||||
|
@resetAction="resetNode"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="small-4 columns">
|
||||||
|
<macro-properties
|
||||||
|
:macro-name="macro.name"
|
||||||
|
:macro-visibility="macro.visibility"
|
||||||
|
@update:name="updateName"
|
||||||
|
@update:visibility="updateVisibility"
|
||||||
|
@submit="submit"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import MacroNodes from './MacroNodes';
|
||||||
|
import MacroProperties from './MacroProperties';
|
||||||
|
import { required, requiredIf } from 'vuelidate/lib/validators';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
MacroNodes,
|
||||||
|
MacroProperties,
|
||||||
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
$v: this.$v,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
macroData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
macro: this.macroData,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
macroData: {
|
||||||
|
handler() {
|
||||||
|
this.macro = this.macroData;
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validations: {
|
||||||
|
macro: {
|
||||||
|
name: {
|
||||||
|
required,
|
||||||
|
},
|
||||||
|
visibility: {
|
||||||
|
required,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
required,
|
||||||
|
$each: {
|
||||||
|
action_params: {
|
||||||
|
required: requiredIf(prop => {
|
||||||
|
if (prop.action_name === 'send_email_to_team') return true;
|
||||||
|
return !(
|
||||||
|
prop.action_name === 'mute_conversation' ||
|
||||||
|
prop.action_name === 'snooze_conversation' ||
|
||||||
|
prop.action_name === 'resolve_conversation'
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$v.$reset();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateName(value) {
|
||||||
|
this.macro.name = value;
|
||||||
|
},
|
||||||
|
updateVisibility(value) {
|
||||||
|
this.macro.visibility = value;
|
||||||
|
},
|
||||||
|
appendNode() {
|
||||||
|
this.macro.actions.push({
|
||||||
|
action_name: 'assign_team',
|
||||||
|
action_params: [],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteNode(index) {
|
||||||
|
this.macro.actions.splice(index, 1);
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
this.$v.$touch();
|
||||||
|
if (this.$v.$invalid) return;
|
||||||
|
this.$emit('submit', this.macro);
|
||||||
|
},
|
||||||
|
resetNode(index) {
|
||||||
|
this.macro.actions[index].action_params = [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.row {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.macros-canvas {
|
||||||
|
background-image: radial-gradient(var(--s-100) 1.2px, transparent 0);
|
||||||
|
background-size: var(--space-normal) var(--space-normal);
|
||||||
|
height: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
padding: var(--space-normal) var(--space-three);
|
||||||
|
max-height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,149 @@
|
||||||
|
<template>
|
||||||
|
<div class="macro__node-action-container">
|
||||||
|
<fluent-icon
|
||||||
|
v-if="!singleNode"
|
||||||
|
size="20"
|
||||||
|
icon="navigation"
|
||||||
|
class="macros__node-drag-handle"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="macro__node-action-item"
|
||||||
|
:class="{
|
||||||
|
'has-error': hasError($v.macro.actions.$each[index]),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<action-input
|
||||||
|
v-model="actionData"
|
||||||
|
:action-types="macroActionTypes"
|
||||||
|
:dropdown-values="dropdownValues()"
|
||||||
|
:show-action-input="showActionInput"
|
||||||
|
:show-remove-button="false"
|
||||||
|
:is-macro="true"
|
||||||
|
:v="$v.macro.actions.$each[index]"
|
||||||
|
@resetAction="$emit('resetAction')"
|
||||||
|
/>
|
||||||
|
<macro-action-button
|
||||||
|
v-if="!singleNode"
|
||||||
|
icon="dismiss-circle"
|
||||||
|
class="macro__node macro__node-action-button-delete"
|
||||||
|
type="delete"
|
||||||
|
:tooltip="$t('MACROS.EDITOR.DELETE_BTN_TOOLTIP')"
|
||||||
|
@click="$emit('deleteNode')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ActionInput from 'dashboard/components/widgets/AutomationActionInput';
|
||||||
|
import MacroActionButton from './ActionButton.vue';
|
||||||
|
import macrosMixin from 'dashboard/mixins/macrosMixin';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ActionInput,
|
||||||
|
MacroActionButton,
|
||||||
|
},
|
||||||
|
mixins: [macrosMixin],
|
||||||
|
inject: ['macroActionTypes', '$v'],
|
||||||
|
props: {
|
||||||
|
singleNode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
labels: 'labels/getLabels',
|
||||||
|
teams: 'teams/getTeams',
|
||||||
|
}),
|
||||||
|
actionData: {
|
||||||
|
get() {
|
||||||
|
return this.value;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$emit('input', value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
showActionInput() {
|
||||||
|
if (
|
||||||
|
this.actionData.action_name === 'send_email_to_team' ||
|
||||||
|
this.actionData.action_name === 'send_message'
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
const type = this.macroActionTypes.find(
|
||||||
|
action => action.key === this.actionData.action_name
|
||||||
|
).inputType;
|
||||||
|
return !!type;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
dropdownValues() {
|
||||||
|
return this.getDropdownValues(this.value.action_name, this.$store);
|
||||||
|
},
|
||||||
|
hasError(v) {
|
||||||
|
return !!(v.action_params.$dirty && v.action_params.$error);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.macro__node-action-container {
|
||||||
|
position: relative;
|
||||||
|
.macros__node-drag-handle {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--space-minus-medium);
|
||||||
|
top: var(--space-smaller);
|
||||||
|
cursor: move;
|
||||||
|
color: var(--s-400);
|
||||||
|
}
|
||||||
|
.macro__node-action-item {
|
||||||
|
background-color: var(--white);
|
||||||
|
padding: var(--space-slab);
|
||||||
|
border-radius: var(--border-radius-medium);
|
||||||
|
box-shadow: rgb(0 0 0 / 3%) 0px 6px 24px 0px,
|
||||||
|
rgb(0 0 0 / 6%) 0px 0px 0px 1px;
|
||||||
|
|
||||||
|
.macro__node-action-button-delete {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
.macro__node-action-button-delete {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.has-error {
|
||||||
|
animation: shake 0.3s ease-in-out 0s 2;
|
||||||
|
background-color: var(--r-50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shake {
|
||||||
|
0% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: translateX(0.375rem);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateX(-0.375rem);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: translateX(0.375rem);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,109 @@
|
||||||
|
<template>
|
||||||
|
<div class="macros__nodes">
|
||||||
|
<macros-pill :label="$t('MACROS.EDITOR.START_FLOW')" class="macro__node" />
|
||||||
|
<draggable
|
||||||
|
:list="actionData"
|
||||||
|
animation="200"
|
||||||
|
ghost-class="ghost"
|
||||||
|
tag="div"
|
||||||
|
class="macros__nodes-draggable"
|
||||||
|
handle=".macros__node-drag-handle"
|
||||||
|
@start="dragging = true"
|
||||||
|
@end="dragging = false"
|
||||||
|
>
|
||||||
|
<div v-for="(action, i) in actionData" :key="i" class="macro__node">
|
||||||
|
<macro-node
|
||||||
|
v-model="actionData[i]"
|
||||||
|
class="macros__node-action"
|
||||||
|
type="add"
|
||||||
|
:index="i"
|
||||||
|
:single-node="actionData.length === 1"
|
||||||
|
@resetAction="$emit('resetAction', i)"
|
||||||
|
@deleteNode="$emit('deleteNode', i)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</draggable>
|
||||||
|
<macro-action-button
|
||||||
|
icon="add-circle"
|
||||||
|
class="macro__node"
|
||||||
|
:tooltip="$t('MACROS.EDITOR.ADD_BTN_TOOLTIP')"
|
||||||
|
type="add"
|
||||||
|
@click="$emit('addNewNode')"
|
||||||
|
/>
|
||||||
|
<macros-pill :label="$t('MACROS.EDITOR.END_FLOW')" class="macro__node" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import MacrosPill from './Pill.vue';
|
||||||
|
import Draggable from 'vuedraggable';
|
||||||
|
import MacroNode from './MacroNode.vue';
|
||||||
|
import MacroActionButton from './ActionButton.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Draggable,
|
||||||
|
MacrosPill,
|
||||||
|
MacroNode,
|
||||||
|
MacroActionButton,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dragging: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
actionData: {
|
||||||
|
get() {
|
||||||
|
return this.value;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$emit('input', value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.macros__nodes {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.macro__node:not(:last-child) {
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: var(--space-three);
|
||||||
|
}
|
||||||
|
|
||||||
|
.macro__node:not(:last-child):not(.sortable-chosen):after,
|
||||||
|
.macros__nodes-draggable:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
height: var(--space-three);
|
||||||
|
width: var(--space-smaller);
|
||||||
|
margin-left: var(--space-medium);
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg width='4' height='30' viewBox='0 0 4 30' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cline x1='1.50098' y1='0.579529' x2='1.50098' y2='30.5795' stroke='%2393afc8' stroke-width='2' stroke-dasharray='5 5'/%3E%3C/svg%3E%0A");
|
||||||
|
}
|
||||||
|
|
||||||
|
.macros__nodes-draggable {
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: var(--space-three);
|
||||||
|
}
|
||||||
|
|
||||||
|
.macros__node-action-container {
|
||||||
|
position: relative;
|
||||||
|
.drag-handle {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--space-minus-medium);
|
||||||
|
top: var(--space-smaller);
|
||||||
|
cursor: move;
|
||||||
|
color: var(--s-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,174 @@
|
||||||
|
<template>
|
||||||
|
<div class="macros__properties-panel">
|
||||||
|
<div>
|
||||||
|
<woot-input
|
||||||
|
:value="macroName"
|
||||||
|
:label="$t('MACROS.ADD.FORM.NAME.LABEL')"
|
||||||
|
:placeholder="$t('MACROS.ADD.FORM.NAME.PLACEHOLDER')"
|
||||||
|
:error="$v.macro.name.$error ? $t('MACROS.ADD.FORM.NAME.ERROR') : null"
|
||||||
|
:class="{ error: $v.macro.name.$error }"
|
||||||
|
@input="onUpdateName($event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="title">{{ $t('MACROS.EDITOR.VISIBILITY.LABEL') }}</p>
|
||||||
|
<div class="macros__form-visibility">
|
||||||
|
<button
|
||||||
|
class="card"
|
||||||
|
:class="isActive('global')"
|
||||||
|
@click="onUpdateVisibility('global')"
|
||||||
|
>
|
||||||
|
<fluent-icon
|
||||||
|
v-if="macroVisibility === 'global'"
|
||||||
|
icon="checkmark-circle"
|
||||||
|
type="solid"
|
||||||
|
class="visibility-check"
|
||||||
|
/>
|
||||||
|
<p class="title">
|
||||||
|
{{ $t('MACROS.EDITOR.VISIBILITY.GLOBAL.LABEL') }}
|
||||||
|
</p>
|
||||||
|
<p class="subtitle">
|
||||||
|
{{ $t('MACROS.EDITOR.VISIBILITY.GLOBAL.DESCRIPTION') }}
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="card"
|
||||||
|
:class="isActive('personal')"
|
||||||
|
@click="onUpdateVisibility('personal')"
|
||||||
|
>
|
||||||
|
<fluent-icon
|
||||||
|
v-if="macroVisibility === 'personal'"
|
||||||
|
icon="checkmark-circle"
|
||||||
|
type="solid"
|
||||||
|
class="visibility-check"
|
||||||
|
/>
|
||||||
|
<p class="title">
|
||||||
|
{{ $t('MACROS.EDITOR.VISIBILITY.PERSONAL.LABEL') }}
|
||||||
|
</p>
|
||||||
|
<p class="subtitle">
|
||||||
|
{{ $t('MACROS.EDITOR.VISIBILITY.PERSONAL.DESCRIPTION') }}
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="macros__info-panel">
|
||||||
|
<fluent-icon icon="info" size="20" />
|
||||||
|
<p>
|
||||||
|
{{ $t('MACROS.ORDER_INFO') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="macros__submit-button">
|
||||||
|
<woot-button
|
||||||
|
size="expanded"
|
||||||
|
color-scheme="success"
|
||||||
|
@click="$emit('submit')"
|
||||||
|
>
|
||||||
|
{{ $t('MACROS.HEADER_BTN_TXT_SAVE') }}
|
||||||
|
</woot-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
inject: ['$v'],
|
||||||
|
props: {
|
||||||
|
macroName: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
macroVisibility: {
|
||||||
|
type: String,
|
||||||
|
default: 'global',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
isActive(key) {
|
||||||
|
return { active: this.macroVisibility === key };
|
||||||
|
},
|
||||||
|
onUpdateName(value) {
|
||||||
|
this.$emit('update:name', value);
|
||||||
|
},
|
||||||
|
onUpdateVisibility(value) {
|
||||||
|
this.$emit('update:visibility', value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.macros__properties-panel {
|
||||||
|
padding: var(--space-slab);
|
||||||
|
background-color: var(--white);
|
||||||
|
// full screen height subtracted by the height of the header
|
||||||
|
height: calc(100vh - 5.6rem);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-left: 1px solid var(--s-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
.macros__submit-button {
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.macros__form-visibility {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: var(--space-slab);
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: var(--space-small);
|
||||||
|
border-radius: var(--border-radius-normal);
|
||||||
|
border: 1px solid var(--s-200);
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: var(--w-25);
|
||||||
|
border: 1px solid var(--w-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
color: var(--s-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: 1.8;
|
||||||
|
color: var(--color-body);
|
||||||
|
}
|
||||||
|
|
||||||
|
.visibility-check {
|
||||||
|
position: absolute;
|
||||||
|
color: var(--w-500);
|
||||||
|
top: var(--space-small);
|
||||||
|
right: var(--space-small);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.macros__info-panel {
|
||||||
|
margin-top: var(--space-small);
|
||||||
|
display: flex;
|
||||||
|
background-color: var(--s-50);
|
||||||
|
padding: var(--space-small);
|
||||||
|
border-radius: var(--border-radius-normal);
|
||||||
|
align-items: flex-start;
|
||||||
|
svg {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin-left: var(--space-small);
|
||||||
|
color: var(--s-600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep input[type='text'] {
|
||||||
|
margin-bottom: var(--space-small);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,74 @@
|
||||||
|
<template>
|
||||||
|
<tr>
|
||||||
|
<td>{{ macro.name }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="avatar-container">
|
||||||
|
<thumbnail :username="macro.created_by.name" size="24px" />
|
||||||
|
<span class="ml-2">{{ macro.created_by.name }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="avatar-container">
|
||||||
|
<thumbnail :username="macro.updated_by.name" size="24px" />
|
||||||
|
<span class="ml-2">{{ macro.updated_by.name }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>{{ visibilityLabel }}</td>
|
||||||
|
<td class="button-wrapper">
|
||||||
|
<router-link :to="addAccountScoping(`settings/macros/${macro.id}/edit`)">
|
||||||
|
<woot-button
|
||||||
|
v-tooltip.top="$t('MACROS.EDIT.TOOLTIP')"
|
||||||
|
variant="smooth"
|
||||||
|
size="tiny"
|
||||||
|
color-scheme="secondary"
|
||||||
|
class-names="grey-btn"
|
||||||
|
icon="edit"
|
||||||
|
/>
|
||||||
|
</router-link>
|
||||||
|
<woot-button
|
||||||
|
v-tooltip.top="$t('MACROS.DELETE.TOOLTIP')"
|
||||||
|
variant="smooth"
|
||||||
|
color-scheme="alert"
|
||||||
|
size="tiny"
|
||||||
|
icon="dismiss-circle"
|
||||||
|
class-names="grey-btn"
|
||||||
|
@click="$emit('delete')"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Thumbnail from 'dashboard/components/widgets/Thumbnail';
|
||||||
|
import accountMixin from 'dashboard/mixins/account.js';
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Thumbnail,
|
||||||
|
},
|
||||||
|
mixins: [accountMixin],
|
||||||
|
props: {
|
||||||
|
macro: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
visibilityLabel() {
|
||||||
|
return this.macro.visibility === 'global'
|
||||||
|
? this.$t('MACROS.EDITOR.VISIBILITY.GLOBAL.LABEL')
|
||||||
|
: this.$t('MACROS.EDITOR.VISIBILITY.PERSONAL.LABEL');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.avatar-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-left: var(--space-one);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,30 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="macros-item macros-pill">
|
||||||
|
<span>{{ label }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.macros-pill {
|
||||||
|
padding: var(--space-slab);
|
||||||
|
background-color: var(--w-500);
|
||||||
|
max-width: max-content;
|
||||||
|
color: var(--white);
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
border-radius: var(--border-radius-full);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,42 @@
|
||||||
|
export const MACRO_ACTION_TYPES = [
|
||||||
|
{
|
||||||
|
key: 'assign_team',
|
||||||
|
label: 'Assign a team',
|
||||||
|
inputType: 'multi_select',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'add_label',
|
||||||
|
label: 'Add a label',
|
||||||
|
inputType: 'multi_select',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'send_email_transcript',
|
||||||
|
label: 'Send an email transcript',
|
||||||
|
inputType: 'email',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'mute_conversation',
|
||||||
|
label: 'Mute conversation',
|
||||||
|
inputType: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'snooze_conversation',
|
||||||
|
label: 'Snooze conversation',
|
||||||
|
inputType: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'resolve_conversation',
|
||||||
|
label: 'Resolve conversation',
|
||||||
|
inputType: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'send_attachment',
|
||||||
|
label: 'Send Attachment',
|
||||||
|
inputType: 'attachment',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'send_message',
|
||||||
|
label: 'Send a message',
|
||||||
|
inputType: 'textarea',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,10 @@
|
||||||
|
export const emptyMacro = {
|
||||||
|
name: '',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action_name: 'assign_team',
|
||||||
|
action_params: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
visibility: 'global',
|
||||||
|
};
|
|
@ -8,10 +8,14 @@ export default {
|
||||||
{
|
{
|
||||||
path: frontendURL('accounts/:accountId/settings/macros'),
|
path: frontendURL('accounts/:accountId/settings/macros'),
|
||||||
component: SettingsContent,
|
component: SettingsContent,
|
||||||
props: {
|
props: params => {
|
||||||
|
const showBackButton = params.name !== 'macros_wrapper';
|
||||||
|
return {
|
||||||
headerTitle: 'MACROS.HEADER',
|
headerTitle: 'MACROS.HEADER',
|
||||||
|
headerButtonText: 'MACROS.HEADER_BTN_TXT',
|
||||||
icon: 'flash-settings',
|
icon: 'flash-settings',
|
||||||
showNewButton: false,
|
showBackButton,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,14 +2,16 @@ import Vue from 'vue';
|
||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
|
|
||||||
import accounts from './modules/accounts';
|
import accounts from './modules/accounts';
|
||||||
import agents from './modules/agents';
|
|
||||||
import agentBots from './modules/agentBots';
|
import agentBots from './modules/agentBots';
|
||||||
|
import agents from './modules/agents';
|
||||||
|
import articles from './modules/helpCenterArticles';
|
||||||
import attributes from './modules/attributes';
|
import attributes from './modules/attributes';
|
||||||
import auth from './modules/auth';
|
import auth from './modules/auth';
|
||||||
import automations from './modules/automations';
|
import automations from './modules/automations';
|
||||||
import bulkActions from './modules/bulkActions';
|
import bulkActions from './modules/bulkActions';
|
||||||
import campaigns from './modules/campaigns';
|
import campaigns from './modules/campaigns';
|
||||||
import cannedResponse from './modules/cannedResponse';
|
import cannedResponse from './modules/cannedResponse';
|
||||||
|
import categories from './modules/helpCenterCategories';
|
||||||
import contactConversations from './modules/contactConversations';
|
import contactConversations from './modules/contactConversations';
|
||||||
import contactLabels from './modules/contactLabels';
|
import contactLabels from './modules/contactLabels';
|
||||||
import contactNotes from './modules/contactNotes';
|
import contactNotes from './modules/contactNotes';
|
||||||
|
@ -30,28 +32,29 @@ import inboxes from './modules/inboxes';
|
||||||
import inboxMembers from './modules/inboxMembers';
|
import inboxMembers from './modules/inboxMembers';
|
||||||
import integrations from './modules/integrations';
|
import integrations from './modules/integrations';
|
||||||
import labels from './modules/labels';
|
import labels from './modules/labels';
|
||||||
|
import macros from './modules/macros';
|
||||||
import notifications from './modules/notifications';
|
import notifications from './modules/notifications';
|
||||||
|
import portals from './modules/helpCenterPortals';
|
||||||
import reports from './modules/reports';
|
import reports from './modules/reports';
|
||||||
import teamMembers from './modules/teamMembers';
|
import teamMembers from './modules/teamMembers';
|
||||||
import teams from './modules/teams';
|
import teams from './modules/teams';
|
||||||
import userNotificationSettings from './modules/userNotificationSettings';
|
import userNotificationSettings from './modules/userNotificationSettings';
|
||||||
import webhooks from './modules/webhooks';
|
import webhooks from './modules/webhooks';
|
||||||
import articles from './modules/helpCenterArticles';
|
|
||||||
import portals from './modules/helpCenterPortals';
|
|
||||||
import categories from './modules/helpCenterCategories';
|
|
||||||
|
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
export default new Vuex.Store({
|
export default new Vuex.Store({
|
||||||
modules: {
|
modules: {
|
||||||
accounts,
|
accounts,
|
||||||
agents,
|
|
||||||
agentBots,
|
agentBots,
|
||||||
|
agents,
|
||||||
|
articles,
|
||||||
attributes,
|
attributes,
|
||||||
auth,
|
auth,
|
||||||
automations,
|
automations,
|
||||||
bulkActions,
|
bulkActions,
|
||||||
campaigns,
|
campaigns,
|
||||||
cannedResponse,
|
cannedResponse,
|
||||||
|
categories,
|
||||||
contactConversations,
|
contactConversations,
|
||||||
contactLabels,
|
contactLabels,
|
||||||
contactNotes,
|
contactNotes,
|
||||||
|
@ -72,14 +75,13 @@ export default new Vuex.Store({
|
||||||
inboxMembers,
|
inboxMembers,
|
||||||
integrations,
|
integrations,
|
||||||
labels,
|
labels,
|
||||||
|
macros,
|
||||||
notifications,
|
notifications,
|
||||||
|
portals,
|
||||||
reports,
|
reports,
|
||||||
teamMembers,
|
teamMembers,
|
||||||
teams,
|
teams,
|
||||||
userNotificationSettings,
|
userNotificationSettings,
|
||||||
webhooks,
|
webhooks,
|
||||||
articles,
|
|
||||||
portals,
|
|
||||||
categories,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
:root {
|
:root {
|
||||||
// border-radius
|
|
||||||
--border-radius-small: 0.3rem;
|
--border-radius-small: 0.3rem;
|
||||||
--border-radius-normal: 0.5rem;
|
--border-radius-normal: 0.5rem;
|
||||||
--border-radius-medium: 0.7rem;
|
--border-radius-medium: 0.7rem;
|
||||||
--border-radius-large: 0.9rem;
|
--border-radius-large: 0.9rem;
|
||||||
|
--border-radius-full: 10rem;
|
||||||
--border-radius-rounded: 50%;
|
--border-radius-rounded: 50%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,6 +107,7 @@
|
||||||
"microphone-stop-outline": "M18,18H6V6H18V18Z",
|
"microphone-stop-outline": "M18,18H6V6H18V18Z",
|
||||||
"microphone-pause-outline": "M14,19H18V5H14M6,19H10V5H6V19Z",
|
"microphone-pause-outline": "M14,19H18V5H14M6,19H10V5H6V19Z",
|
||||||
"microphone-play-outline": "M8,5.14V19.14L19,12.14L8,5.14Z",
|
"microphone-play-outline": "M8,5.14V19.14L19,12.14L8,5.14Z",
|
||||||
|
"navigation-outline": "M3 17h18a1 1 0 0 1 .117 1.993L21 19H3a1 1 0 0 1-.117-1.993L3 17h18H3Zm0-6l18-.002a1 1 0 0 1 .117 1.993l-.117.007L3 13a1 1 0 0 1-.117-1.993L3 11l18-.002L3 11Zm0-6h18a1 1 0 0 1 .117 1.993L21 7H3a1 1 0 0 1-.117-1.993L3 5h18H3Z",
|
||||||
"number-symbol-outline": "M10.987 2.89a.75.75 0 1 0-1.474-.28L8.494 7.999 3.75 8a.75.75 0 1 0 0 1.5l4.46-.002-.946 5-4.514.002a.75.75 0 0 0 0 1.5l4.23-.002-.967 5.116a.75.75 0 1 0 1.474.278l1.02-5.395 5.474-.002-.968 5.119a.75.75 0 1 0 1.474.278l1.021-5.398 4.742-.002a.75.75 0 1 0 0-1.5l-4.458.002.946-5 4.512-.002a.75.75 0 1 0 0-1.5l-4.229.002.966-5.104a.75.75 0 0 0-1.474-.28l-1.018 5.385-5.474.002.966-5.107Zm-1.25 6.608 5.474-.003-.946 5-5.474.002.946-5Z",
|
"number-symbol-outline": "M10.987 2.89a.75.75 0 1 0-1.474-.28L8.494 7.999 3.75 8a.75.75 0 1 0 0 1.5l4.46-.002-.946 5-4.514.002a.75.75 0 0 0 0 1.5l4.23-.002-.967 5.116a.75.75 0 1 0 1.474.278l1.02-5.395 5.474-.002-.968 5.119a.75.75 0 1 0 1.474.278l1.021-5.398 4.742-.002a.75.75 0 1 0 0-1.5l-4.458.002.946-5 4.512-.002a.75.75 0 1 0 0-1.5l-4.229.002.966-5.104a.75.75 0 0 0-1.474-.28l-1.018 5.385-5.474.002.966-5.107Zm-1.25 6.608 5.474-.003-.946 5-5.474.002.946-5Z",
|
||||||
"open-outline": "M6.25 4.5A1.75 1.75 0 0 0 4.5 6.25v11.5c0 .966.783 1.75 1.75 1.75h11.5a1.75 1.75 0 0 0 1.75-1.75v-4a.75.75 0 0 1 1.5 0v4A3.25 3.25 0 0 1 17.75 21H6.25A3.25 3.25 0 0 1 3 17.75V6.25A3.25 3.25 0 0 1 6.25 3h4a.75.75 0 0 1 0 1.5h-4ZM13 3.75a.75.75 0 0 1 .75-.75h6.5a.75.75 0 0 1 .75.75v6.5a.75.75 0 0 1-1.5 0V5.56l-5.22 5.22a.75.75 0 0 1-1.06-1.06l5.22-5.22h-4.69a.75.75 0 0 1-.75-.75Z",
|
"open-outline": "M6.25 4.5A1.75 1.75 0 0 0 4.5 6.25v11.5c0 .966.783 1.75 1.75 1.75h11.5a1.75 1.75 0 0 0 1.75-1.75v-4a.75.75 0 0 1 1.5 0v4A3.25 3.25 0 0 1 17.75 21H6.25A3.25 3.25 0 0 1 3 17.75V6.25A3.25 3.25 0 0 1 6.25 3h4a.75.75 0 0 1 0 1.5h-4ZM13 3.75a.75.75 0 0 1 .75-.75h6.5a.75.75 0 0 1 .75.75v6.5a.75.75 0 0 1-1.5 0V5.56l-5.22 5.22a.75.75 0 0 1-1.06-1.06l5.22-5.22h-4.69a.75.75 0 0 1-.75-.75Z",
|
||||||
"panel-sidebar-outline": "M4.75 4A2.75 2.75 0 0 0 2 6.75v10.5A2.75 2.75 0 0 0 4.75 20h14.5A2.75 2.75 0 0 0 22 17.25V6.75A2.75 2.75 0 0 0 19.25 4H4.75ZM9 18.5v-13h10.25c.69 0 1.25.56 1.25 1.25v10.5c0 .69-.56 1.25-1.25 1.25H9ZM5.5 3.5h4M5.5 5.5h4M5.5 7.5h4M5.5 9.5h4",
|
"panel-sidebar-outline": "M4.75 4A2.75 2.75 0 0 0 2 6.75v10.5A2.75 2.75 0 0 0 4.75 20h14.5A2.75 2.75 0 0 0 22 17.25V6.75A2.75 2.75 0 0 0 19.25 4H4.75ZM9 18.5v-13h10.25c.69 0 1.25.56 1.25 1.25v10.5c0 .69-.56 1.25-1.25 1.25H9ZM5.5 3.5h4M5.5 5.5h4M5.5 7.5h4M5.5 9.5h4",
|
||||||
|
|
Loading…
Reference in a new issue