feat: Update the design for label management page (#9932)

This PR is part of the settings design update series. It updates the design for the label management page. I've made a few changes to the SettingsLayout page to reduce boilerplate code.
This commit is contained in:
Pranav
2024-08-11 20:59:39 -07:00
committed by GitHub
parent d5f34bf9d0
commit 4a63d1d896
4 changed files with 160 additions and 168 deletions

View File

@@ -3,13 +3,18 @@
"HEADER": "Labels", "HEADER": "Labels",
"HEADER_BTN_TXT": "Add label", "HEADER_BTN_TXT": "Add label",
"LOADING": "Fetching labels", "LOADING": "Fetching labels",
"DESCRIPTION": "Labels help you categorize and prioritize conversations and leads. You can assign a label to a conversation or contact using the side panel.",
"LEARN_MORE": "Learn more about labels",
"SEARCH_404": "There are no items matching this query", "SEARCH_404": "There are no items matching this query",
"SIDEBAR_TXT": "<p><b>Labels</b> <p>Labels help you to categorize conversations and prioritize them. You can assign label to a conversation from the sidepanel. <br /><br />Labels are tied to the account and can be used to create custom workflows in your organization. You can assign custom color to a label, it makes it easier to identify the label. You will be able to display the label on the sidebar to filter the conversations easily.</p>",
"LIST": { "LIST": {
"404": "There are no labels available in this account.", "404": "There are no labels available in this account.",
"TITLE": "Manage labels", "TITLE": "Manage labels",
"DESC": "Labels let you group the conversations together.", "DESC": "Labels let you group the conversations together.",
"TABLE_HEADER": ["Name", "Description", "Color"] "TABLE_HEADER": [
"Name",
"Description",
"Color"
]
}, },
"FORM": { "FORM": {
"NAME": { "NAME": {

View File

@@ -4,21 +4,35 @@ defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
noRecordsFound: {
type: Boolean,
default: false,
},
loadingMessage: { loadingMessage: {
type: String, type: String,
default: '', default: '',
}, },
noRecordsMessage: {
type: String,
default: '',
},
}); });
</script> </script>
<template> <template>
<div class="flex flex-col w-full h-full gap-10 font-inter"> <div class="flex flex-col w-full h-full gap-10 font-inter">
<slot name="header" /> <slot name="header" />
<div>
<slot v-if="isLoading" name="loading"> <slot v-if="isLoading" name="loading">
<woot-loading-state :message="loadingMessage" /> <woot-loading-state :message="loadingMessage" />
</slot> </slot>
<p
v-else-if="noRecordsFound"
class="flex-1 text-slate-700 dark:text-slate-100 flex items-center justify-center text-base"
>
{{ noRecordsMessage }}
</p>
<slot v-else name="body" /> <slot v-else name="body" />
</div> <!-- Do not delete the slot below. It is required to render anything that is not defined in the above slots. -->
<slot />
</div> </div>
</template> </template>

View File

@@ -1,138 +1,137 @@
<script> <script setup>
import { mapGetters } from 'vuex';
import { useAlert } from 'dashboard/composables'; import { useAlert } from 'dashboard/composables';
import { computed, onBeforeMount, ref } from 'vue';
import { useI18n } from 'dashboard/composables/useI18n';
import { useStoreGetters, useStore } from 'dashboard/composables/store';
import AddLabel from './AddLabel.vue'; import AddLabel from './AddLabel.vue';
import EditLabel from './EditLabel.vue'; import EditLabel from './EditLabel.vue';
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
import SettingsLayout from '../SettingsLayout.vue';
export default { const getters = useStoreGetters();
components: { const store = useStore();
AddLabel, const { t } = useI18n();
EditLabel,
},
data() {
return {
loading: {},
showAddPopup: false,
showEditPopup: false,
showDeleteConfirmationPopup: false,
selectedResponse: {},
};
},
computed: {
...mapGetters({
records: 'labels/getLabels',
uiFlags: 'labels/getUIFlags',
}),
// Delete Modal
deleteConfirmText() {
return this.$t('LABEL_MGMT.DELETE.CONFIRM.YES');
},
deleteRejectText() {
return this.$t('LABEL_MGMT.DELETE.CONFIRM.NO');
},
deleteMessage() {
return ` ${this.selectedResponse.title}?`;
},
},
mounted() {
this.$store.dispatch('labels/get');
},
methods: {
openAddPopup() {
this.showAddPopup = true;
},
hideAddPopup() {
this.showAddPopup = false;
},
openEditPopup(response) { const loading = ref({});
this.showEditPopup = true; const showAddPopup = ref(false);
this.selectedResponse = response; const showEditPopup = ref(false);
}, const showDeleteConfirmationPopup = ref(false);
hideEditPopup() { const selectedLabel = ref({});
this.showEditPopup = false;
},
openDeletePopup(response) { const records = computed(() => getters['labels/getLabels'].value);
this.showDeleteConfirmationPopup = true; const uiFlags = computed(() => getters['labels/getUIFlags'].value);
this.selectedResponse = response;
},
closeDeletePopup() {
this.showDeleteConfirmationPopup = false;
},
confirmDeletion() { const deleteMessage = computed(() => ` ${selectedLabel.value.title}?`);
this.loading[this.selectedResponse.id] = true;
this.closeDeletePopup(); const openAddPopup = () => {
this.deleteLabel(this.selectedResponse.id); showAddPopup.value = true;
},
deleteLabel(id) {
this.$store
.dispatch('labels/delete', id)
.then(() => {
useAlert(this.$t('LABEL_MGMT.DELETE.API.SUCCESS_MESSAGE'));
})
.catch(() => {
useAlert(this.$t('LABEL_MGMT.DELETE.API.ERROR_MESSAGE'));
})
.finally(() => {
this.loading[this.selectedResponse.id] = false;
});
},
},
}; };
const hideAddPopup = () => {
showAddPopup.value = false;
};
const openEditPopup = response => {
showEditPopup.value = true;
selectedLabel.value = response;
};
const hideEditPopup = () => {
showEditPopup.value = false;
};
const openDeletePopup = response => {
showDeleteConfirmationPopup.value = true;
selectedLabel.value = response;
};
const closeDeletePopup = () => {
showDeleteConfirmationPopup.value = false;
};
const deleteLabel = async id => {
try {
await store.dispatch('labels/delete', id);
useAlert(t('LABEL_MGMT.DELETE.API.SUCCESS_MESSAGE'));
} catch (error) {
const errorMessage =
error?.message || t('LABEL_MGMT.DELETE.API.ERROR_MESSAGE');
useAlert(errorMessage);
} finally {
loading.value[selectedLabel.value.id] = false;
}
};
const confirmDeletion = () => {
loading.value[selectedLabel.value.id] = true;
closeDeletePopup();
deleteLabel(selectedLabel.value.id);
};
onBeforeMount(() => {
store.dispatch('labels/get');
});
</script> </script>
<template> <template>
<div class="flex-1 overflow-auto"> <SettingsLayout
:is-loading="uiFlags.isFetching"
:loading-message="$t('LABEL_MGMT.LOADING')"
:no-records-found="!records.length"
:no-records-message="$t('LABEL_MGMT.LIST.404')"
>
<template #header>
<BaseSettingsHeader
:title="$t('LABEL_MGMT.HEADER')"
:description="$t('LABEL_MGMT.DESCRIPTION')"
:link-text="$t('LABEL_MGMT.LEARN_MORE')"
feature-name="labels"
>
<template #actions>
<woot-button <woot-button
color-scheme="success" class="button nice rounded-md"
class-names="button--fixed-top"
icon="add-circle" icon="add-circle"
@click="openAddPopup" @click="openAddPopup"
> >
{{ $t('LABEL_MGMT.HEADER_BTN_TXT') }} {{ $t('LABEL_MGMT.HEADER_BTN_TXT') }}
</woot-button> </woot-button>
<div class="flex flex-row gap-4 p-8"> </template>
<div class="w-full xl:w-3/5"> </BaseSettingsHeader>
<p </template>
v-if="!uiFlags.isFetching && !records.length" <template #body>
class="flex flex-col items-center justify-center h-full" <table
class="min-w-full overflow-x-auto divide-y divide-slate-75 dark:divide-slate-700"
> >
{{ $t('LABEL_MGMT.LIST.404') }}
</p>
<woot-loading-state
v-if="uiFlags.isFetching"
:message="$t('LABEL_MGMT.LOADING')"
/>
<table v-if="!uiFlags.isFetching && records.length" class="woot-table">
<thead> <thead>
<th <th
v-for="thHeader in $t('LABEL_MGMT.LIST.TABLE_HEADER')" v-for="thHeader in $t('LABEL_MGMT.LIST.TABLE_HEADER')"
:key="thHeader" :key="thHeader"
class="py-4 ltr:pr-4 rtl:pl-4 text-left font-semibold text-slate-700 dark:text-slate-300"
> >
{{ thHeader }} {{ thHeader }}
</th> </th>
</thead> </thead>
<tbody> <tbody
class="divide-y divide-slate-25 dark:divide-slate-800 flex-1 text-slate-700 dark:text-slate-100"
>
<tr v-for="(label, index) in records" :key="label.title"> <tr v-for="(label, index) in records" :key="label.title">
<td class="label-title"> <td class="py-4 ltr:pr-4 rtl:pl-4">
<span class="overflow-hidden whitespace-nowrap text-ellipsis">{{
label.title
}}</span>
</td>
<td>{{ label.description }}</td>
<td>
<div class="label-color--container">
<span <span
class="label-color--display" class="font-medium break-words text-slate-700 dark:text-slate-100 mb-1"
>
{{ label.title }}
</span>
</td>
<td class="py-4 ltr:pr-4 rtl:pl-4">{{ label.description }}</td>
<td class="leading-6 py-4 ltr:pr-4 rtl:pl-4">
<div class="flex items-center">
<span
class="rounded h-4 w-4 mr-1 rtl:mr-0 rtl:ml-1 border border-solid border-slate-50 dark:border-slate-700"
:style="{ backgroundColor: label.color }" :style="{ backgroundColor: label.color }"
/> />
{{ label.color }} {{ label.color }}
</div> </div>
</td> </td>
<td class="button-wrapper"> <td class="py-4 min-w-xs">
<div class="flex gap-1">
<woot-button <woot-button
v-tooltip.top="$t('LABEL_MGMT.FORM.EDIT')" v-tooltip.top="$t('LABEL_MGMT.FORM.EDIT')"
variant="smooth" variant="smooth"
@@ -153,22 +152,19 @@ export default {
:is-loading="loading[label.id]" :is-loading="loading[label.id]"
@click="openDeletePopup(label, index)" @click="openDeletePopup(label, index)"
/> />
</div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </template>
<div class="hidden w-1/3 xl:block">
<span v-dompurify-html="$t('LABEL_MGMT.SIDEBAR_TXT')" />
</div>
</div>
<woot-modal :show.sync="showAddPopup" :on-close="hideAddPopup"> <woot-modal :show.sync="showAddPopup" :on-close="hideAddPopup">
<AddLabel @close="hideAddPopup" /> <AddLabel @close="hideAddPopup" />
</woot-modal> </woot-modal>
<woot-modal :show.sync="showEditPopup" :on-close="hideEditPopup"> <woot-modal :show.sync="showEditPopup" :on-close="hideEditPopup">
<EditLabel :selected-response="selectedResponse" @close="hideEditPopup" /> <EditLabel :selected-response="selectedLabel" @close="hideEditPopup" />
</woot-modal> </woot-modal>
<woot-delete-modal <woot-delete-modal
@@ -178,26 +174,8 @@ export default {
:title="$t('LABEL_MGMT.DELETE.CONFIRM.TITLE')" :title="$t('LABEL_MGMT.DELETE.CONFIRM.TITLE')"
:message="$t('LABEL_MGMT.DELETE.CONFIRM.MESSAGE')" :message="$t('LABEL_MGMT.DELETE.CONFIRM.MESSAGE')"
:message-value="deleteMessage" :message-value="deleteMessage"
:confirm-text="deleteConfirmText" :confirm-text="$t('LABEL_MGMT.DELETE.CONFIRM.YES')"
:reject-text="deleteRejectText" :reject-text="$t('LABEL_MGMT.DELETE.CONFIRM.NO')"
/> />
</div> </SettingsLayout>
</template> </template>
<style scoped lang="scss">
@import '~dashboard/assets/scss/variables';
.label-color--container {
@apply flex items-center;
}
.label-color--display {
@apply rounded h-4 w-4 mr-1 rtl:mr-0 rtl:ml-1 border border-solid border-slate-50 dark:border-slate-700;
}
.label-title {
span {
@apply w-60 inline-block;
}
}
</style>

View File

@@ -1,18 +1,13 @@
import { frontendURL } from '../../../../helper/URLHelper'; import { frontendURL } from '../../../../helper/URLHelper';
const SettingsContent = () => import('../Wrapper.vue'); const SettingsWrapper = () => import('../SettingsWrapper.vue');
const Index = () => import('./Index.vue'); const Index = () => import('./Index.vue');
export default { export default {
routes: [ routes: [
{ {
path: frontendURL('accounts/:accountId/settings/labels'), path: frontendURL('accounts/:accountId/settings/labels'),
component: SettingsContent, component: SettingsWrapper,
props: {
headerTitle: 'LABEL_MGMT.HEADER',
icon: 'tag',
showNewButton: false,
},
children: [ children: [
{ {
path: '', path: '',