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_BTN_TXT": "Add label",
"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",
"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": {
"404": "There are no labels available in this account.",
"TITLE": "Manage labels",
"DESC": "Labels let you group the conversations together.",
"TABLE_HEADER": ["Name", "Description", "Color"]
"TABLE_HEADER": [
"Name",
"Description",
"Color"
]
},
"FORM": {
"NAME": {

View File

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

View File

@@ -1,138 +1,137 @@
<script>
import { mapGetters } from 'vuex';
<script setup>
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 EditLabel from './EditLabel.vue';
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
import SettingsLayout from '../SettingsLayout.vue';
export default {
components: {
AddLabel,
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;
},
const getters = useStoreGetters();
const store = useStore();
const { t } = useI18n();
openEditPopup(response) {
this.showEditPopup = true;
this.selectedResponse = response;
},
hideEditPopup() {
this.showEditPopup = false;
},
const loading = ref({});
const showAddPopup = ref(false);
const showEditPopup = ref(false);
const showDeleteConfirmationPopup = ref(false);
const selectedLabel = ref({});
openDeletePopup(response) {
this.showDeleteConfirmationPopup = true;
this.selectedResponse = response;
},
closeDeletePopup() {
this.showDeleteConfirmationPopup = false;
},
const records = computed(() => getters['labels/getLabels'].value);
const uiFlags = computed(() => getters['labels/getUIFlags'].value);
confirmDeletion() {
this.loading[this.selectedResponse.id] = true;
this.closeDeletePopup();
this.deleteLabel(this.selectedResponse.id);
},
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 deleteMessage = computed(() => ` ${selectedLabel.value.title}?`);
const openAddPopup = () => {
showAddPopup.value = true;
};
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>
<template>
<div class="flex-1 overflow-auto">
<woot-button
color-scheme="success"
class-names="button--fixed-top"
icon="add-circle"
@click="openAddPopup"
>
{{ $t('LABEL_MGMT.HEADER_BTN_TXT') }}
</woot-button>
<div class="flex flex-row gap-4 p-8">
<div class="w-full xl:w-3/5">
<p
v-if="!uiFlags.isFetching && !records.length"
class="flex flex-col items-center justify-center h-full"
<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
class="button nice rounded-md"
icon="add-circle"
@click="openAddPopup"
>
{{ $t('LABEL_MGMT.HEADER_BTN_TXT') }}
</woot-button>
</template>
</BaseSettingsHeader>
</template>
<template #body>
<table
class="min-w-full overflow-x-auto divide-y divide-slate-75 dark:divide-slate-700"
>
<thead>
<th
v-for="thHeader in $t('LABEL_MGMT.LIST.TABLE_HEADER')"
:key="thHeader"
class="py-4 ltr:pr-4 rtl:pl-4 text-left font-semibold text-slate-700 dark:text-slate-300"
>
{{ thHeader }}
</th>
</thead>
<tbody
class="divide-y divide-slate-25 dark:divide-slate-800 flex-1 text-slate-700 dark:text-slate-100"
>
{{ $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>
<th
v-for="thHeader in $t('LABEL_MGMT.LIST.TABLE_HEADER')"
:key="thHeader"
>
{{ thHeader }}
</th>
</thead>
<tbody>
<tr v-for="(label, index) in records" :key="label.title">
<td class="label-title">
<span class="overflow-hidden whitespace-nowrap text-ellipsis">{{
label.title
}}</span>
</td>
<td>{{ label.description }}</td>
<td>
<div class="label-color--container">
<span
class="label-color--display"
:style="{ backgroundColor: label.color }"
/>
{{ label.color }}
</div>
</td>
<td class="button-wrapper">
<tr v-for="(label, index) in records" :key="label.title">
<td class="py-4 ltr:pr-4 rtl:pl-4">
<span
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 }"
/>
{{ label.color }}
</div>
</td>
<td class="py-4 min-w-xs">
<div class="flex gap-1">
<woot-button
v-tooltip.top="$t('LABEL_MGMT.FORM.EDIT')"
variant="smooth"
@@ -153,22 +152,19 @@ export default {
:is-loading="loading[label.id]"
@click="openDeletePopup(label, index)"
/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</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">
<AddLabel @close="hideAddPopup" />
</woot-modal>
<woot-modal :show.sync="showEditPopup" :on-close="hideEditPopup">
<EditLabel :selected-response="selectedResponse" @close="hideEditPopup" />
<EditLabel :selected-response="selectedLabel" @close="hideEditPopup" />
</woot-modal>
<woot-delete-modal
@@ -178,26 +174,8 @@ export default {
:title="$t('LABEL_MGMT.DELETE.CONFIRM.TITLE')"
:message="$t('LABEL_MGMT.DELETE.CONFIRM.MESSAGE')"
:message-value="deleteMessage"
:confirm-text="deleteConfirmText"
:reject-text="deleteRejectText"
:confirm-text="$t('LABEL_MGMT.DELETE.CONFIRM.YES')"
:reject-text="$t('LABEL_MGMT.DELETE.CONFIRM.NO')"
/>
</div>
</SettingsLayout>
</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';
const SettingsContent = () => import('../Wrapper.vue');
const SettingsWrapper = () => import('../SettingsWrapper.vue');
const Index = () => import('./Index.vue');
export default {
routes: [
{
path: frontendURL('accounts/:accountId/settings/labels'),
component: SettingsContent,
props: {
headerTitle: 'LABEL_MGMT.HEADER',
icon: 'tag',
showNewButton: false,
},
component: SettingsWrapper,
children: [
{
path: '',