Feature: Improve label experience (#975)
Co-authored-by: Sojan <sojan@pepalo.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<div class="chat-list__top">
|
||||
<h1 class="page-title">
|
||||
<woot-sidemenu-icon />
|
||||
{{ inbox.name || $t('CHAT_LIST.TAB_HEADING') }}
|
||||
{{ pageTitle }}
|
||||
</h1>
|
||||
<chat-filter @statusFilterChange="updateStatusType" />
|
||||
</div>
|
||||
@@ -15,14 +15,15 @@
|
||||
@chatTabChange="updateAssigneeTab"
|
||||
/>
|
||||
|
||||
<p v-if="!chatListLoading && !getChatsForTab().length" class="content-box">
|
||||
<p v-if="!chatListLoading && !conversationList.length" class="content-box">
|
||||
{{ $t('CHAT_LIST.LIST.404') }}
|
||||
</p>
|
||||
|
||||
<div class="conversations-list">
|
||||
<conversation-card
|
||||
v-for="chat in getChatsForTab()"
|
||||
v-for="chat in conversationList"
|
||||
:key="chat.id"
|
||||
:active-label="label"
|
||||
:chat="chat"
|
||||
/>
|
||||
|
||||
@@ -40,7 +41,7 @@
|
||||
|
||||
<p
|
||||
v-if="
|
||||
getChatsForTab().length &&
|
||||
conversationList.length &&
|
||||
hasCurrentPageEndReached &&
|
||||
!chatListLoading
|
||||
"
|
||||
@@ -72,7 +73,16 @@ export default {
|
||||
ChatFilter,
|
||||
},
|
||||
mixins: [timeMixin, conversationMixin],
|
||||
props: ['conversationInbox'],
|
||||
props: {
|
||||
conversationInbox: {
|
||||
type: [String, Number],
|
||||
default: 0,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeAssigneeTab: wootConstants.ASSIGNEE_TYPE.ME,
|
||||
@@ -119,18 +129,51 @@ export default {
|
||||
assigneeType: this.activeAssigneeTab,
|
||||
status: this.activeStatus,
|
||||
page: this.currentPage + 1,
|
||||
labels: this.label ? [this.label] : undefined,
|
||||
};
|
||||
},
|
||||
pageTitle() {
|
||||
if (this.inbox.name) {
|
||||
return this.inbox.name;
|
||||
}
|
||||
if (this.label) {
|
||||
return `#${this.label}`;
|
||||
}
|
||||
return this.$t('CHAT_LIST.TAB_HEADING');
|
||||
},
|
||||
conversationList() {
|
||||
let conversationList = [];
|
||||
if (this.activeAssigneeTab === 'me') {
|
||||
conversationList = this.mineChatsList.slice();
|
||||
} else if (this.activeAssigneeTab === 'unassigned') {
|
||||
conversationList = this.unAssignedChatsList.slice();
|
||||
} else {
|
||||
conversationList = this.allChatList.slice();
|
||||
}
|
||||
|
||||
if (!this.label) {
|
||||
return conversationList;
|
||||
}
|
||||
|
||||
return conversationList.filter(conversation => {
|
||||
const labels = this.$store.getters[
|
||||
'conversationLabels/getConversationLabels'
|
||||
](conversation.id);
|
||||
return labels.includes(this.label);
|
||||
});
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
conversationInbox() {
|
||||
this.resetAndFetchData();
|
||||
},
|
||||
label() {
|
||||
this.resetAndFetchData();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('setChatFilter', this.activeStatus);
|
||||
this.resetAndFetchData();
|
||||
this.$store.dispatch('agents/get');
|
||||
|
||||
bus.$on('fetch_conversation_stats', () => {
|
||||
this.$store.dispatch('conversationStats/get', this.conversationFilters);
|
||||
@@ -159,17 +202,6 @@ export default {
|
||||
this.resetAndFetchData();
|
||||
}
|
||||
},
|
||||
getChatsForTab() {
|
||||
let copyList = [];
|
||||
if (this.activeAssigneeTab === 'me') {
|
||||
copyList = this.mineChatsList.slice();
|
||||
} else if (this.activeAssigneeTab === 'unassigned') {
|
||||
copyList = this.unAssignedChatsList.slice();
|
||||
} else {
|
||||
copyList = this.allChatList.slice();
|
||||
}
|
||||
return copyList;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -6,6 +6,7 @@ import Code from './Code';
|
||||
import ColorPicker from './widgets/ColorPicker';
|
||||
import DeleteModal from './widgets/modal/DeleteModal.vue';
|
||||
import Input from './widgets/forms/Input.vue';
|
||||
import Label from './widgets/Label.vue';
|
||||
import LoadingState from './widgets/LoadingState';
|
||||
import Modal from './Modal';
|
||||
import ModalHeader from './ModalHeader';
|
||||
@@ -25,6 +26,7 @@ const WootUIKit = {
|
||||
DeleteModal,
|
||||
Input,
|
||||
LoadingState,
|
||||
Label,
|
||||
Modal,
|
||||
ModalHeader,
|
||||
ReportStatsCard,
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
:key="inboxSection.toState"
|
||||
:menu-item="inboxSection"
|
||||
/>
|
||||
<sidebar-item
|
||||
v-if="shouldShowInboxes"
|
||||
:key="labelSection.toState"
|
||||
:menu-item="labelSection"
|
||||
/>
|
||||
</transition-group>
|
||||
</div>
|
||||
|
||||
@@ -125,6 +130,7 @@ export default {
|
||||
inboxes: 'inboxes/getInboxes',
|
||||
accountId: 'getCurrentAccountId',
|
||||
currentRole: 'getCurrentRole',
|
||||
accountLabels: 'labels/getLabelsOnSidebar',
|
||||
}),
|
||||
sidemenuItems() {
|
||||
return getSidebarItems(this.accountId);
|
||||
@@ -170,6 +176,25 @@ export default {
|
||||
})),
|
||||
};
|
||||
},
|
||||
labelSection() {
|
||||
return {
|
||||
icon: 'ion-pound',
|
||||
label: 'LABELS',
|
||||
hasSubMenu: true,
|
||||
key: 'label',
|
||||
cssClass: 'menu-title align-justify',
|
||||
toState: frontendURL(`accounts/${this.accountId}/settings/labels`),
|
||||
toStateName: 'labels_list',
|
||||
children: this.accountLabels.map(label => ({
|
||||
id: label.id,
|
||||
label: label.title,
|
||||
color: label.color,
|
||||
toState: frontendURL(
|
||||
`accounts/${this.accountId}/label/${label.title}`
|
||||
),
|
||||
})),
|
||||
};
|
||||
},
|
||||
dashboardPath() {
|
||||
return frontendURL(`accounts/${this.accountId}/dashboard`);
|
||||
},
|
||||
|
||||
@@ -36,7 +36,13 @@
|
||||
v-if="computedInboxClass(child)"
|
||||
class="inbox-icon"
|
||||
:class="computedInboxClass(child)"
|
||||
></i>
|
||||
/>
|
||||
<span
|
||||
v-if="child.color"
|
||||
class="label-color--display"
|
||||
:style="{ backgroundColor: child.color }"
|
||||
/>
|
||||
|
||||
{{ child.label }}
|
||||
</div>
|
||||
</a>
|
||||
@@ -126,8 +132,22 @@ export default {
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import '~dashboard/assets/scss/variables';
|
||||
|
||||
.sub-menu-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.label-color--display {
|
||||
border-radius: $space-smaller;
|
||||
height: $space-normal;
|
||||
margin-right: $space-small;
|
||||
width: $space-normal;
|
||||
}
|
||||
</style>
|
||||
|
||||
91
app/javascript/dashboard/components/widgets/Label.vue
Normal file
91
app/javascript/dashboard/components/widgets/Label.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div
|
||||
:class="labelClass"
|
||||
:style="{ background: bgColor, color: textColor }"
|
||||
:title="description"
|
||||
>
|
||||
<span v-if="!href">{{ title }}</span>
|
||||
<a v-else :href="href" :style="{ color: textColor }">{{ title }}</a>
|
||||
<i v-if="showIcon" class="label--icon" :class="icon" @click="onClick" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
href: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#1f93ff',
|
||||
},
|
||||
small: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showIcon: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'ion-close',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
textColor() {
|
||||
const color = this.bgColor.replace('#', '');
|
||||
const r = parseInt(color.slice(0, 2), 16);
|
||||
const g = parseInt(color.slice(2, 4), 16);
|
||||
const b = parseInt(color.slice(4, 6), 16);
|
||||
// http://stackoverflow.com/a/3943023/112731
|
||||
return r * 0.299 + g * 0.587 + b * 0.114 > 186 ? '#000000' : '#FFFFFF';
|
||||
},
|
||||
labelClass() {
|
||||
return `label ${this.small ? 'small' : ''}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$emit('click', this.title);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~dashboard/assets/scss/variables';
|
||||
|
||||
.label {
|
||||
display: inline-block;
|
||||
font-size: $font-size-small;
|
||||
line-height: 1;
|
||||
margin: $space-micro;
|
||||
|
||||
&.small {
|
||||
font-size: $font-size-mini;
|
||||
}
|
||||
|
||||
a {
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.label--icon {
|
||||
cursor: pointer;
|
||||
font-size: $font-size-micro;
|
||||
line-height: 1.5;
|
||||
margin-left: $space-smaller;
|
||||
}
|
||||
</style>
|
||||
@@ -57,6 +57,10 @@ export default {
|
||||
|
||||
mixins: [timeMixin, conversationMixin],
|
||||
props: {
|
||||
activeLabel: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
chat: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
@@ -116,7 +120,12 @@ export default {
|
||||
methods: {
|
||||
cardClick(chat) {
|
||||
const { activeInbox } = this;
|
||||
const path = conversationUrl(this.accountId, activeInbox, chat.id);
|
||||
const path = conversationUrl({
|
||||
accountId: this.accountId,
|
||||
activeInbox,
|
||||
id: chat.id,
|
||||
label: this.activeLabel,
|
||||
});
|
||||
router.push({ path: frontendURL(path) });
|
||||
},
|
||||
inboxName(inboxId) {
|
||||
|
||||
Reference in New Issue
Block a user