Macros submission and listing

This commit is contained in:
fayazara 2022-09-20 16:35:41 +05:30
parent 611ecfe46e
commit 6ce46ea4f5
8 changed files with 354 additions and 50 deletions

View file

@ -0,0 +1,9 @@
import ApiClient from './ApiClient';
class MacrosAPI extends ApiClient {
constructor() {
super('macros', { accountScoped: true });
}
}
export default new MacrosAPI();

View file

@ -33,6 +33,7 @@ const settings = accountId => ({
'billing_settings_index', 'billing_settings_index',
'automation_list', 'automation_list',
'macros_wrapper', 'macros_wrapper',
'macros_new',
], ],
menuItems: [ menuItems: [
{ {

View file

@ -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();

View file

@ -8,7 +8,7 @@
</router-link> </router-link>
<div class="row"> <div class="row">
<div class="small-8 columns with-right-space"> <div class="small-8 columns with-right-space">
<!-- <p <p
v-if="!uiFlags.isFetching && !records.length" v-if="!uiFlags.isFetching && !records.length"
class="no-items-error-message" class="no-items-error-message"
> >
@ -17,7 +17,7 @@
<woot-loading-state <woot-loading-state
v-if="uiFlags.isFetching" v-if="uiFlags.isFetching"
:message="$t('MACROS.LOADING')" :message="$t('MACROS.LOADING')"
/> --> />
<table class="woot-table"> <table class="woot-table">
<thead> <thead>
<th <th
@ -28,20 +28,21 @@
</th> </th>
</thead> </thead>
<tbody> <tbody>
<td>Spam Message</td> <tr v-for="(macro, index) in records" :key="index">
<td>Global</td> <td>{{ macro.name }}</td>
<td> <td>
<div class="avatar-container"> <div class="avatar-container">
<thumbnail :username="'Fayaz'" size="24px" /> <thumbnail :username="macro.created_by.name" size="24px" />
<span class="ml-2">Fayaz</span> <span class="ml-2">{{ macro.created_by.name }}</span>
</div> </div>
</td> </td>
<td> <td>
<div class="avatar-container"> <div class="avatar-container">
<thumbnail :username="'Fayaz'" size="24px" /> <thumbnail :username="macro.updated_by.name" size="24px" />
<span class="ml-2">Fayaz</span> <span class="ml-2">{{ macro.updated_by.name }}</span>
</div> </div>
</td> </td>
<td class="macro-visibility">{{ macro.visibility }}</td>
<td class="button-wrapper"> <td class="button-wrapper">
<woot-button <woot-button
v-tooltip.top="$t('MACROS.FORM.EDIT')" v-tooltip.top="$t('MACROS.FORM.EDIT')"
@ -60,6 +61,7 @@
class-names="grey-btn" class-names="grey-btn"
/> />
</td> </td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -71,11 +73,22 @@
</template> </template>
<script> <script>
import { mapGetters } from 'vuex';
import Thumbnail from 'dashboard/components/widgets/Thumbnail'; import Thumbnail from 'dashboard/components/widgets/Thumbnail';
export default { export default {
components: { components: {
Thumbnail, Thumbnail,
}, },
computed: {
...mapGetters({
records: ['macros/getMacros'],
uiFlags: 'macros/getUIFlags',
}),
},
mounted() {
this.$store.dispatch('macros/get');
},
}; };
</script> </script>
@ -88,4 +101,8 @@ export default {
margin-left: var(--space-one); margin-left: var(--space-one);
} }
} }
.macro-visibility {
text-transform: capitalize;
}
</style> </style>

View file

@ -1,8 +1,5 @@
<template> <template>
<div class="column content-box"> <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="row">
<div class="small-8 columns with-right-space macros-canvas"> <div class="small-8 columns with-right-space macros-canvas">
<ul class="macros-feed-container"> <ul class="macros-feed-container">
@ -30,10 +27,15 @@
class="macros-feed-item" class="macros-feed-item"
> >
<div class="macros-action-item-container"> <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" /> <fluent-icon size="20" icon="navigation" />
</div> </div>
<div class="macros-action-item"> <div
class="macros-action-item"
:class="{
'has-error': hasError($v.macro.actions.$each[i]),
}"
>
<action-input <action-input
v-model="macro.actions[i]" v-model="macro.actions[i]"
:action-types="macroActionTypes" :action-types="macroActionTypes"
@ -88,14 +90,65 @@
v-model.trim="macro.name" v-model.trim="macro.name"
label="Macro name" label="Macro name"
placeholder="Enter a name for your macro" placeholder="Enter a name for your macro"
error="Please enter a name"
:class="{ error: $v.macro.name.$error }"
/> />
</div> </div>
<div> <div>
<input-radio-group <p class="title">Macro Visibility</p>
name="macro-visibility" <div class="macros-form-radio-group">
label="Macro Visibility" <button
:items="macroVisibilityOptions" 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> </div>
</div> </div>
@ -104,18 +157,18 @@
</template> </template>
<script> <script>
import InputRadioGroup from 'dashboard/routes/dashboard/settings/inbox/components/InputRadioGroup.vue';
import ActionInput from 'dashboard/components/widgets/AutomationActionInput.vue'; import ActionInput from 'dashboard/components/widgets/AutomationActionInput.vue';
import { AUTOMATION_ACTION_TYPES } from 'dashboard/routes/dashboard/settings/automation/constants.js'; import { AUTOMATION_ACTION_TYPES } from 'dashboard/routes/dashboard/settings/automation/constants.js';
import { required, requiredIf } from 'vuelidate/lib/validators'; import { required, requiredIf } from 'vuelidate/lib/validators';
import draggable from 'vuedraggable'; import draggable from 'vuedraggable';
import actionQueryGenerator from 'dashboard/helper/actionQueryGenerator.js';
import alertMixin from 'shared/mixins/alertMixin';
export default { export default {
components: { components: {
InputRadioGroup,
ActionInput, ActionInput,
draggable, draggable,
}, },
mixins: [alertMixin],
validations: { validations: {
macro: { macro: {
name: { name: {
@ -211,6 +264,22 @@ export default {
onDragEnd() { onDragEnd() {
this.dragging = false; 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> </script>
@ -224,10 +293,17 @@ export default {
background-size: 16px 16px; background-size: 16px 16px;
height: 100%; height: 100%;
max-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 { .macros-properties-panel {
padding: var(--space-slab); 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 { .macros-feed-container {
@ -246,7 +322,7 @@ export default {
height: 30px; height: 30px;
width: 3px; width: 3px;
margin-left: 24px; 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 { .macros-feed-draggable {
position: relative; 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> </style>

View file

@ -38,6 +38,7 @@ import webhooks from './modules/webhooks';
import articles from './modules/helpCenterArticles'; import articles from './modules/helpCenterArticles';
import portals from './modules/helpCenterPortals'; import portals from './modules/helpCenterPortals';
import categories from './modules/helpCenterCategories'; import categories from './modules/helpCenterCategories';
import macros from './modules/macros';
Vue.use(Vuex); Vue.use(Vuex);
export default new Vuex.Store({ export default new Vuex.Store({
@ -79,5 +80,6 @@ export default new Vuex.Store({
articles, articles,
portals, portals,
categories, categories,
macros,
}, },
}); });

View 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,
};

View file

@ -245,4 +245,11 @@ export default {
UPDATE_CATEGORY: 'UPDATE_CATEGORY', UPDATE_CATEGORY: 'UPDATE_CATEGORY',
REMOVE_CATEGORY: 'REMOVE_CATEGORY', REMOVE_CATEGORY: 'REMOVE_CATEGORY',
REMOVE_CATEGORY_ID: 'REMOVE_CATEGORY_ID', 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',
}; };