feat: Update the design for macros design page (#9999)

This is the continuation of the design update for settings page. This PR updates the design for the macros page.

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
Pranav
2024-08-21 18:27:53 +05:30
committed by GitHub
parent 77b718c22c
commit 44227de97e
5 changed files with 177 additions and 185 deletions

View File

@@ -11,6 +11,7 @@ const FEATURE_HELP_URLS = {
help_center: 'https://chwt.app/hc/help-center', help_center: 'https://chwt.app/hc/help-center',
integrations: 'https://chwt.app/hc/integrations', integrations: 'https://chwt.app/hc/integrations',
labels: 'https://chwt.app/hc/labels', labels: 'https://chwt.app/hc/labels',
macros: 'https://chwt.app/hc/macros',
message_reply_to: 'https://chwt.app/hc/reply-to', message_reply_to: 'https://chwt.app/hc/reply-to',
reports: 'https://chwt.app/hc/reports', reports: 'https://chwt.app/hc/reports',
sla: 'https://chwt.app/hc/sla', sla: 'https://chwt.app/hc/sla',

View File

@@ -1,10 +1,11 @@
{ {
"MACROS": { "MACROS": {
"HEADER": "Macros", "HEADER": "Macros",
"DESCRIPTION": "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.",
"LEARN_MORE": "Learn more about macros",
"HEADER_BTN_TXT": "Add a new macro", "HEADER_BTN_TXT": "Add a new macro",
"HEADER_BTN_TXT_SAVE": "Save macro", "HEADER_BTN_TXT_SAVE": "Save macro",
"LOADING": "Fetching macros", "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", "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.", "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": { "ADD": {
@@ -24,7 +25,12 @@
} }
}, },
"LIST": { "LIST": {
"TABLE_HEADER": ["Name", "Created by", "Last updated by", "Visibility"], "TABLE_HEADER": [
"Name",
"Created by",
"Last updated by",
"Visibility"
],
"404": "No macros found" "404": "No macros found"
}, },
"DELETE": { "DELETE": {

View File

@@ -1,117 +1,112 @@
<script> <script setup>
import { mapGetters } from 'vuex';
import { useAlert } from 'dashboard/composables'; import { useAlert } from 'dashboard/composables';
import { useAccount } from 'dashboard/composables/useAccount';
import MacrosTableRow from './MacrosTableRow.vue'; import MacrosTableRow from './MacrosTableRow.vue';
export default { import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
components: { import SettingsLayout from '../SettingsLayout.vue';
MacrosTableRow, import { computed, onMounted, ref } from 'vue';
}, import { useI18n } from 'dashboard/composables/useI18n';
setup() { import { useStoreGetters, useStore } from 'dashboard/composables/store';
const { accountScopedUrl } = useAccount();
return { const getters = useStoreGetters();
accountScopedUrl, const store = useStore();
}; const { t } = useI18n();
},
data() { const showDeleteConfirmationPopup = ref(false);
return { const selectedMacro = ref({});
showDeleteConfirmationPopup: false,
selectedResponse: {}, const records = computed(() => getters['macros/getMacros'].value);
loading: {}, const uiFlags = computed(() => getters['macros/getUIFlags'].value);
};
}, const deleteMessage = computed(() => ` ${selectedMacro.value.name}?`);
computed: {
...mapGetters({ onMounted(() => {
records: ['macros/getMacros'], store.dispatch('macros/get');
uiFlags: 'macros/getUIFlags', });
}),
deleteMessage() { const deleteMacro = async id => {
return ` ${this.selectedResponse.name}?`; try {
}, await store.dispatch('macros/delete', id);
}, useAlert(t('MACROS.DELETE.API.SUCCESS_MESSAGE'));
mounted() { } catch (error) {
this.$store.dispatch('macros/get'); useAlert(t('MACROS.DELETE.API.ERROR_MESSAGE'));
}, }
methods: { };
openDeletePopup(response) {
this.showDeleteConfirmationPopup = true; const openDeletePopup = response => {
this.selectedResponse = response; showDeleteConfirmationPopup.value = true;
}, selectedMacro.value = response;
closeDeletePopup() { };
this.showDeleteConfirmationPopup = false;
}, const closeDeletePopup = () => {
confirmDeletion() { showDeleteConfirmationPopup.value = false;
this.loading[this.selectedResponse.id] = true; };
this.closeDeletePopup();
this.deleteMacro(this.selectedResponse.id); const confirmDeletion = () => {
}, closeDeletePopup();
async deleteMacro(id) { deleteMacro(selectedMacro.value.id);
try {
await this.$store.dispatch('macros/delete', id);
useAlert(this.$t('MACROS.DELETE.API.SUCCESS_MESSAGE'));
this.loading[this.selectedResponse.id] = false;
} catch (error) {
useAlert(this.$t('MACROS.DELETE.API.ERROR_MESSAGE'));
}
},
},
}; };
</script> </script>
<template> <template>
<div class="flex-1 overflow-auto"> <SettingsLayout
<router-link :no-records-message="$t('MACROS.LIST.404')"
:to="accountScopedUrl('settings/macros/new')" :no-records-found="!records.length"
class="button success button--fixed-top button success button--fixed-top px-3.5 py-1 rounded-[5px] flex gap-2" :is-loading="uiFlags.isFetching"
> :loading-message="$t('MACROS.LOADING')"
<fluent-icon icon="add-circle" /> feature-name="macros"
<span class="button__content"> >
{{ $t('MACROS.HEADER_BTN_TXT') }} <template #header>
</span> <BaseSettingsHeader
</router-link> :title="$t('MACROS.HEADER')"
<div class="flex flex-row gap-4 p-8"> :description="$t('MACROS.DESCRIPTION')"
<div class="w-full lg:w-3/5"> :link-text="$t('MACROS.LEARN_MORE')"
<div v-if="!uiFlags.isFetching && !records.length" class="p-3"> feature-name="macros"
<p class="flex flex-col items-center justify-center h-full"> >
{{ $t('MACROS.LIST.404') }} <template #actions>
</p> <router-link
</div> :to="{ name: 'macros_new' }"
<woot-loading-state class="button rounded-md primary"
v-if="uiFlags.isFetching" >
:message="$t('MACROS.LOADING')" <fluent-icon icon="add-circle" />
/> <span class="button__content">
<table v-if="!uiFlags.isFetching && records.length" class="woot-table"> {{ $t('MACROS.HEADER_BTN_TXT') }}
<thead> </span>
<th </router-link>
v-for="thHeader in $t('MACROS.LIST.TABLE_HEADER')" </template>
:key="thHeader" </BaseSettingsHeader>
> </template>
{{ thHeader }} <template #body>
</th> <table class="min-w-full divide-y divide-slate-75 dark:divide-slate-700">
</thead> <thead>
<tbody> <th
<MacrosTableRow v-for="thHeader in $t('MACROS.LIST.TABLE_HEADER')"
v-for="(macro, index) in records" :key="thHeader"
:key="index" class="py-4 ltr:pr-4 rtl:pl-4 text-left font-semibold text-slate-700 dark:text-slate-300"
:macro="macro" >
@delete="openDeletePopup(macro, index)" {{ thHeader }}
/> </th>
</tbody> </thead>
</table> <tbody
</div> class="divide-y divide-slate-50 dark:divide-slate-800 text-slate-700 dark:text-slate-300"
<div class="hidden w-1/3 lg:block"> >
<span v-dompurify-html="$t('MACROS.SIDEBAR_TXT')" /> <MacrosTableRow
</div> v-for="(macro, index) in records"
</div> :key="index"
<woot-delete-modal :macro="macro"
:show.sync="showDeleteConfirmationPopup" @delete="openDeletePopup(macro)"
:on-close="closeDeletePopup" />
:on-confirm="confirmDeletion" </tbody>
:title="$t('LABEL_MGMT.DELETE.CONFIRM.TITLE')" </table>
:message="$t('MACROS.DELETE.CONFIRM.MESSAGE')" <woot-delete-modal
:message-value="deleteMessage" :show.sync="showDeleteConfirmationPopup"
:confirm-text="$t('MACROS.DELETE.CONFIRM.YES')" :on-close="closeDeletePopup"
:reject-text="$t('MACROS.DELETE.CONFIRM.NO')" :on-confirm="confirmDeletion"
/> :title="$t('LABEL_MGMT.DELETE.CONFIRM.TITLE')"
</div> :message="$t('MACROS.DELETE.CONFIRM.MESSAGE')"
:message-value="deleteMessage"
:confirm-text="$t('MACROS.DELETE.CONFIRM.YES')"
:reject-text="$t('MACROS.DELETE.CONFIRM.NO')"
/>
</template>
</SettingsLayout>
</template> </template>

View File

@@ -1,59 +1,56 @@
<script> <script setup>
import { useAccount } from 'dashboard/composables/useAccount'; import { computed } from 'vue';
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue'; import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
export default { import { useI18n } from 'dashboard/composables/useI18n';
components: {
Thumbnail,
},
props: {
macro: {
type: Object,
required: true,
},
},
setup() {
const { accountScopedUrl } = useAccount();
return { const props = defineProps({
accountScopedUrl, macro: {
}; type: Object,
required: true,
}, },
computed: { });
createdByName() { defineEmits(['delete']);
const createdBy = this.macro.created_by; const { t } = useI18n();
return createdBy.available_name ?? createdBy.email ?? '';
}, const createdByName = computed(() => {
updatedByName() { const createdBy = props.macro.created_by;
const updatedBy = this.macro.updated_by; return createdBy.available_name ?? createdBy.email ?? '';
return updatedBy.available_name ?? updatedBy.email ?? ''; });
},
visibilityLabel() { const updatedByName = computed(() => {
return this.macro.visibility === 'global' const updatedBy = props.macro.updated_by;
? this.$t('MACROS.EDITOR.VISIBILITY.GLOBAL.LABEL') return updatedBy.available_name ?? updatedBy.email ?? '';
: this.$t('MACROS.EDITOR.VISIBILITY.PERSONAL.LABEL'); });
},
}, const visibilityLabel = computed(() => {
}; const i18nKey =
props.macro.visibility === 'global'
? 'MACROS.EDITOR.VISIBILITY.GLOBAL.LABEL'
: 'MACROS.EDITOR.VISIBILITY.PERSONAL.LABEL';
return t(i18nKey);
});
</script> </script>
<template> <template>
<tr> <tr>
<td>{{ macro.name }}</td> <td class="py-4 ltr:pr-4 rtl:pl-4 truncate">{{ macro.name }}</td>
<td> <td class="py-4 ltr:pr-4 rtl:pl-4">
<div v-if="macro.created_by" class="avatar-container"> <div v-if="macro.created_by" class="flex items-center">
<Thumbnail :username="createdByName" size="24px" /> <Thumbnail :username="createdByName" size="24px" />
<span>{{ createdByName }}</span> <span class="mx-2">{{ createdByName }}</span>
</div> </div>
<div v-else>--</div>
</td> </td>
<td> <td class="py-4 ltr:pr-4 rtl:pl-4">
<div v-if="macro.updated_by" class="avatar-container"> <div v-if="macro.updated_by" class="flex items-center">
<Thumbnail :username="updatedByName" size="24px" /> <Thumbnail :username="updatedByName" size="24px" />
<span>{{ updatedByName }}</span> <span class="mx-2">{{ updatedByName }}</span>
</div> </div>
<div v-else>--</div>
</td> </td>
<td>{{ visibilityLabel }}</td> <td class="py-4 ltr:pr-4 rtl:pl-4">{{ visibilityLabel }}</td>
<td class="button-wrapper"> <td class="py-4 flex justify-end gap-1">
<router-link :to="accountScopedUrl(`settings/macros/${macro.id}/edit`)"> <router-link :to="{ name: 'macros_edit', params: { macroId: macro.id } }">
<woot-button <woot-button
v-tooltip.top="$t('MACROS.EDIT.TOOLTIP')" v-tooltip.top="$t('MACROS.EDIT.TOOLTIP')"
variant="smooth" variant="smooth"
@@ -75,15 +72,3 @@ export default {
</td> </td>
</tr> </tr>
</template> </template>
<style scoped lang="scss">
.avatar-container {
display: flex;
align-items: center;
span {
margin-left: var(--space-small);
margin-right: var(--space-small);
}
}
</style>

View File

@@ -1,6 +1,7 @@
import { frontendURL } from 'dashboard/helper/URLHelper'; import { frontendURL } from 'dashboard/helper/URLHelper';
const SettingsContent = () => import('../Wrapper.vue'); const SettingsContent = () => import('../Wrapper.vue');
const SettingsWrapper = () => import('../SettingsWrapper.vue');
const Macros = () => import('./Index.vue'); const Macros = () => import('./Index.vue');
const MacroEditor = () => import('./MacroEditor.vue'); const MacroEditor = () => import('./MacroEditor.vue');
@@ -8,16 +9,7 @@ export default {
routes: [ routes: [
{ {
path: frontendURL('accounts/:accountId/settings/macros'), path: frontendURL('accounts/:accountId/settings/macros'),
component: SettingsContent, component: SettingsWrapper,
props: params => {
const showBackButton = params.name !== 'macros_wrapper';
return {
headerTitle: 'MACROS.HEADER',
headerButtonText: 'MACROS.HEADER_BTN_TXT',
icon: 'flash-settings',
showBackButton,
};
},
children: [ children: [
{ {
path: '', path: '',
@@ -27,6 +19,27 @@ export default {
permissions: ['administrator', 'agent'], permissions: ['administrator', 'agent'],
}, },
}, },
],
},
{
path: frontendURL('accounts/:accountId/settings/macros'),
component: SettingsContent,
props: () => {
return {
headerTitle: 'MACROS.HEADER',
icon: 'flash-settings',
showBackButton: true,
};
},
children: [
{
path: ':macroId/edit',
name: 'macros_edit',
component: MacroEditor,
meta: {
permissions: ['administrator', 'agent'],
},
},
{ {
path: 'new', path: 'new',
name: 'macros_new', name: 'macros_new',
@@ -35,14 +48,6 @@ export default {
permissions: ['administrator', 'agent'], permissions: ['administrator', 'agent'],
}, },
}, },
{
path: ':macroId/edit',
name: 'macros_edit',
component: MacroEditor,
meta: {
permissions: ['administrator', 'agent'],
},
},
], ],
}, },
], ],