Macros submission and listing
This commit is contained in:
parent
611ecfe46e
commit
6ce46ea4f5
8 changed files with 354 additions and 50 deletions
9
app/javascript/dashboard/api/macros.js
Normal file
9
app/javascript/dashboard/api/macros.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import ApiClient from './ApiClient';
|
||||
|
||||
class MacrosAPI extends ApiClient {
|
||||
constructor() {
|
||||
super('macros', { accountScoped: true });
|
||||
}
|
||||
}
|
||||
|
||||
export default new MacrosAPI();
|
|
@ -33,6 +33,7 @@ const settings = accountId => ({
|
|||
'billing_settings_index',
|
||||
'automation_list',
|
||||
'macros_wrapper',
|
||||
'macros_new',
|
||||
],
|
||||
menuItems: [
|
||||
{
|
||||
|
|
|
@ -225,7 +225,7 @@ export default {
|
|||
mode === 'EDIT'
|
||||
? this.$t('AUTOMATION.EDIT.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.hideAddPopup();
|
||||
this.hideEditPopup();
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</router-link>
|
||||
<div class="row">
|
||||
<div class="small-8 columns with-right-space">
|
||||
<!-- <p
|
||||
<p
|
||||
v-if="!uiFlags.isFetching && !records.length"
|
||||
class="no-items-error-message"
|
||||
>
|
||||
|
@ -17,7 +17,7 @@
|
|||
<woot-loading-state
|
||||
v-if="uiFlags.isFetching"
|
||||
:message="$t('MACROS.LOADING')"
|
||||
/> -->
|
||||
/>
|
||||
<table class="woot-table">
|
||||
<thead>
|
||||
<th
|
||||
|
@ -28,38 +28,40 @@
|
|||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<td>Spam Message</td>
|
||||
<td>Global</td>
|
||||
<td>
|
||||
<div class="avatar-container">
|
||||
<thumbnail :username="'Fayaz'" size="24px" />
|
||||
<span class="ml-2">Fayaz</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="avatar-container">
|
||||
<thumbnail :username="'Fayaz'" size="24px" />
|
||||
<span class="ml-2">Fayaz</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="button-wrapper">
|
||||
<woot-button
|
||||
v-tooltip.top="$t('MACROS.FORM.EDIT')"
|
||||
variant="smooth"
|
||||
size="tiny"
|
||||
color-scheme="secondary"
|
||||
class-names="grey-btn"
|
||||
icon="edit"
|
||||
/>
|
||||
<woot-button
|
||||
v-tooltip.top="$t('MACROS.FORM.DELETE')"
|
||||
variant="smooth"
|
||||
color-scheme="alert"
|
||||
size="tiny"
|
||||
icon="dismiss-circle"
|
||||
class-names="grey-btn"
|
||||
/>
|
||||
</td>
|
||||
<tr v-for="(macro, index) in records" :key="index">
|
||||
<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 class="macro-visibility">{{ macro.visibility }}</td>
|
||||
<td class="button-wrapper">
|
||||
<woot-button
|
||||
v-tooltip.top="$t('MACROS.FORM.EDIT')"
|
||||
variant="smooth"
|
||||
size="tiny"
|
||||
color-scheme="secondary"
|
||||
class-names="grey-btn"
|
||||
icon="edit"
|
||||
/>
|
||||
<woot-button
|
||||
v-tooltip.top="$t('MACROS.FORM.DELETE')"
|
||||
variant="smooth"
|
||||
color-scheme="alert"
|
||||
size="tiny"
|
||||
icon="dismiss-circle"
|
||||
class-names="grey-btn"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -71,11 +73,22 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail';
|
||||
export default {
|
||||
components: {
|
||||
Thumbnail,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
records: ['macros/getMacros'],
|
||||
uiFlags: 'macros/getUIFlags',
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('macros/get');
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -88,4 +101,8 @@ export default {
|
|||
margin-left: var(--space-one);
|
||||
}
|
||||
}
|
||||
|
||||
.macro-visibility {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
<template>
|
||||
<div class="column content-box">
|
||||
<woot-button color-scheme="success" class-names="button--fixed-right-top">
|
||||
{{ $t('MACROS.HEADER_BTN_TXT_SAVE') }}
|
||||
</woot-button>
|
||||
<div class="row">
|
||||
<div class="small-8 columns with-right-space macros-canvas">
|
||||
<ul class="macros-feed-container">
|
||||
|
@ -30,10 +27,15 @@
|
|||
class="macros-feed-item"
|
||||
>
|
||||
<div class="macros-action-item-container">
|
||||
<div class="drag-handle">
|
||||
<div v-if="macro.actions.length > 1" class="drag-handle">
|
||||
<fluent-icon size="20" icon="navigation" />
|
||||
</div>
|
||||
<div class="macros-action-item">
|
||||
<div
|
||||
class="macros-action-item"
|
||||
:class="{
|
||||
'has-error': hasError($v.macro.actions.$each[i]),
|
||||
}"
|
||||
>
|
||||
<action-input
|
||||
v-model="macro.actions[i]"
|
||||
:action-types="macroActionTypes"
|
||||
|
@ -88,14 +90,65 @@
|
|||
v-model.trim="macro.name"
|
||||
label="Macro name"
|
||||
placeholder="Enter a name for your macro"
|
||||
error="Please enter a name"
|
||||
:class="{ error: $v.macro.name.$error }"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input-radio-group
|
||||
name="macro-visibility"
|
||||
label="Macro Visibility"
|
||||
:items="macroVisibilityOptions"
|
||||
/>
|
||||
<p class="title">Macro Visibility</p>
|
||||
<div class="macros-form-radio-group">
|
||||
<button
|
||||
class="card"
|
||||
:class="{ active: macro.visibility === 'global' }"
|
||||
@click="macro.visibility = 'global'"
|
||||
>
|
||||
<fluent-icon
|
||||
v-if="macro.visibility === 'global'"
|
||||
icon="checkmark-circle"
|
||||
type="solid"
|
||||
class="visibility-check"
|
||||
/>
|
||||
<p class="title">Global</p>
|
||||
<p class="subtitle">
|
||||
This macro is available globally for all agents in this
|
||||
account.
|
||||
</p>
|
||||
</button>
|
||||
<button
|
||||
class="card"
|
||||
:class="{ active: macro.visibility === 'private' }"
|
||||
@click="macro.visibility = 'private'"
|
||||
>
|
||||
<fluent-icon
|
||||
v-if="macro.visibility === 'private'"
|
||||
icon="checkmark-circle"
|
||||
type="solid"
|
||||
class="visibility-check"
|
||||
/>
|
||||
<p class="title">Private</p>
|
||||
<p class="subtitle">
|
||||
This macro will be private to you and not be available to
|
||||
others.
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
<div class="macros-information-panel">
|
||||
<fluent-icon icon="info" size="20" />
|
||||
<p>
|
||||
Macros will run in the order you add yout actions. You can
|
||||
rearrange them by dragging them by the handle beside each
|
||||
action.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="macros-submit-btn">
|
||||
<woot-button
|
||||
size="expanded"
|
||||
color-scheme="success"
|
||||
@click="saveMacro"
|
||||
>
|
||||
{{ $t('MACROS.HEADER_BTN_TXT_SAVE') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -104,18 +157,18 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import InputRadioGroup from 'dashboard/routes/dashboard/settings/inbox/components/InputRadioGroup.vue';
|
||||
import ActionInput from 'dashboard/components/widgets/AutomationActionInput.vue';
|
||||
import { AUTOMATION_ACTION_TYPES } from 'dashboard/routes/dashboard/settings/automation/constants.js';
|
||||
import { required, requiredIf } from 'vuelidate/lib/validators';
|
||||
import draggable from 'vuedraggable';
|
||||
|
||||
import actionQueryGenerator from 'dashboard/helper/actionQueryGenerator.js';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
export default {
|
||||
components: {
|
||||
InputRadioGroup,
|
||||
ActionInput,
|
||||
draggable,
|
||||
},
|
||||
mixins: [alertMixin],
|
||||
validations: {
|
||||
macro: {
|
||||
name: {
|
||||
|
@ -211,6 +264,22 @@ export default {
|
|||
onDragEnd() {
|
||||
this.dragging = false;
|
||||
},
|
||||
hasError(v) {
|
||||
return !!(v.action_params.$dirty && v.action_params.$error);
|
||||
},
|
||||
async saveMacro() {
|
||||
this.$v.$touch();
|
||||
if (this.$v.$invalid) return;
|
||||
try {
|
||||
const macro = JSON.parse(JSON.stringify(this.macro));
|
||||
macro.actions = actionQueryGenerator(macro.actions);
|
||||
await this.$store.dispatch('macros/create', macro);
|
||||
this.showAlert('Macro created Succesfully');
|
||||
this.$router.push({ name: 'macros_wrapper' });
|
||||
} catch (error) {
|
||||
this.showAlert('Something went wrong, please try again');
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -224,10 +293,17 @@ export default {
|
|||
background-size: 16px 16px;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
padding-left: var(--space-three);
|
||||
padding: var(--space-normal) var(--space-three);
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.macros-properties-panel {
|
||||
padding: var(--space-slab);
|
||||
background-color: var(--white);
|
||||
height: calc(100vh - 5.6rem);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-left: 1px solid var(--s-50);
|
||||
}
|
||||
|
||||
.macros-feed-container {
|
||||
|
@ -246,7 +322,7 @@ export default {
|
|||
height: 30px;
|
||||
width: 3px;
|
||||
margin-left: 24px;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='3' height='31' viewBox='0 0 3 31' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cline x1='1.50098' y1='0.579529' x2='1.50098' y2='30.5795' stroke='%2394A3B8' stroke-width='2' stroke-dasharray='5 5'/%3E%3C/svg%3E%0A");
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='3' height='31' viewBox='0 0 3 31' 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-feed-draggable {
|
||||
position: relative;
|
||||
|
@ -322,7 +398,91 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-error {
|
||||
animation: shake 0.3s ease-in-out 0s 2;
|
||||
background-color: var(--r-50);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.macros-submit-btn {
|
||||
margin-top: auto;
|
||||
}
|
||||
.macros-form-radio-group {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1rem;
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.visibility-check {
|
||||
position: absolute;
|
||||
color: var(--w-300);
|
||||
top: var(--space-small);
|
||||
right: var(--space-small);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
display: block;
|
||||
margin: 0;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.8;
|
||||
color: #3c4858;
|
||||
}
|
||||
.content-box {
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
.macros-information-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);
|
||||
}
|
||||
}
|
||||
@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>
|
||||
|
|
|
@ -38,6 +38,7 @@ import webhooks from './modules/webhooks';
|
|||
import articles from './modules/helpCenterArticles';
|
||||
import portals from './modules/helpCenterPortals';
|
||||
import categories from './modules/helpCenterCategories';
|
||||
import macros from './modules/macros';
|
||||
|
||||
Vue.use(Vuex);
|
||||
export default new Vuex.Store({
|
||||
|
@ -79,5 +80,6 @@ export default new Vuex.Store({
|
|||
articles,
|
||||
portals,
|
||||
categories,
|
||||
macros,
|
||||
},
|
||||
});
|
||||
|
|
108
app/javascript/dashboard/store/modules/macros.js
Normal file
108
app/javascript/dashboard/store/modules/macros.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
|
||||
import types from '../mutation-types';
|
||||
import MacrosAPI from '../../api/macros';
|
||||
|
||||
export const state = {
|
||||
records: [],
|
||||
uiFlags: {
|
||||
isFetching: false,
|
||||
isCreating: false,
|
||||
isDeleting: false,
|
||||
isUpdating: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const getters = {
|
||||
getMacros(_state) {
|
||||
return _state.records;
|
||||
},
|
||||
getUIFlags(_state) {
|
||||
return _state.uiFlags;
|
||||
},
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
get: async function getMacros({ commit }) {
|
||||
commit(types.SET_MACROS_UI_FLAG, { isFetching: true });
|
||||
try {
|
||||
const response = await MacrosAPI.get();
|
||||
commit(types.SET_MACROS, response.data.payload);
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
} finally {
|
||||
commit(types.SET_MACROS_UI_FLAG, { isFetching: false });
|
||||
}
|
||||
},
|
||||
create: async function createMacro({ commit }, macrosObj) {
|
||||
commit(types.SET_MACROS_UI_FLAG, { isCreating: true });
|
||||
try {
|
||||
const response = await MacrosAPI.create(macrosObj);
|
||||
commit(types.ADD_MACRO, response.data);
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
commit(types.SET_MACROS_UI_FLAG, { isCreating: false });
|
||||
}
|
||||
},
|
||||
update: async ({ commit }, { id, ...updateObj }) => {
|
||||
commit(types.SET_MACROS_UI_FLAG, { isUpdating: true });
|
||||
try {
|
||||
const response = await MacrosAPI.update(id, updateObj);
|
||||
commit(types.EDIT_MACRO, response.data.payload);
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
commit(types.SET_MACROS_UI_FLAG, { isUpdating: false });
|
||||
}
|
||||
},
|
||||
delete: async ({ commit }, id) => {
|
||||
commit(types.SET_MACROS_UI_FLAG, { isDeleting: true });
|
||||
try {
|
||||
await MacrosAPI.delete(id);
|
||||
commit(types.DELETE_MACRO, id);
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
commit(types.SET_MACROS_UI_FLAG, { isDeleting: false });
|
||||
}
|
||||
},
|
||||
clone: async ({ commit }, id) => {
|
||||
commit(types.SET_MACROS_UI_FLAG, { isCloning: true });
|
||||
try {
|
||||
await MacrosAPI.clone(id);
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
commit(types.SET_MACROS_UI_FLAG, { isCloning: false });
|
||||
}
|
||||
},
|
||||
uploadAttachment: async (_, file) => {
|
||||
try {
|
||||
const { data } = await MacrosAPI.attachment(file);
|
||||
return data.blob_id;
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export const mutations = {
|
||||
[types.SET_MACROS_UI_FLAG](_state, data) {
|
||||
_state.uiFlags = {
|
||||
..._state.uiFlags,
|
||||
...data,
|
||||
};
|
||||
},
|
||||
[types.ADD_MACRO]: MutationHelpers.create,
|
||||
[types.SET_MACROS]: MutationHelpers.set,
|
||||
[types.EDIT_MACRO]: MutationHelpers.update,
|
||||
[types.DELETE_MACRO]: MutationHelpers.destroy,
|
||||
};
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
actions,
|
||||
state,
|
||||
getters,
|
||||
mutations,
|
||||
};
|
|
@ -245,4 +245,11 @@ export default {
|
|||
UPDATE_CATEGORY: 'UPDATE_CATEGORY',
|
||||
REMOVE_CATEGORY: 'REMOVE_CATEGORY',
|
||||
REMOVE_CATEGORY_ID: 'REMOVE_CATEGORY_ID',
|
||||
|
||||
// MACROS
|
||||
SET_MACROS_UI_FLAG: 'SET_MACROS_UI_FLAG',
|
||||
SET_MACROS: 'SET_MACROS',
|
||||
ADD_MACRO: 'ADD_MACRO',
|
||||
EDIT_MACRO: 'EDIT_MACRO',
|
||||
DELETE_MACRO: 'DELETE_MACRO',
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue