mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-29 18:22:53 +00:00
feat: Update UX for adding label in a conversation (#2243)
This commit is contained in:
@@ -89,6 +89,7 @@ export default {
|
|||||||
.label--icon,
|
.label--icon,
|
||||||
.close--icon {
|
.close--icon {
|
||||||
font-size: var(--font-size-micro);
|
font-size: var(--font-size-micro);
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.small .label--icon,
|
&.small .label--icon,
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
</multiselect>
|
</multiselect>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<conversation-labels :conversation-id="conversationId" />
|
||||||
<div v-if="browser.browser_name" class="conversation--details">
|
<div v-if="browser.browser_name" class="conversation--details">
|
||||||
<contact-details-item
|
<contact-details-item
|
||||||
v-if="location"
|
v-if="location"
|
||||||
@@ -105,7 +106,6 @@
|
|||||||
v-if="hasContactAttributes"
|
v-if="hasContactAttributes"
|
||||||
:custom-attributes="contact.custom_attributes"
|
:custom-attributes="contact.custom_attributes"
|
||||||
/>
|
/>
|
||||||
<conversation-labels :conversation-id="conversationId" />
|
|
||||||
<contact-conversations
|
<contact-conversations
|
||||||
v-if="contact.id"
|
v-if="contact.id"
|
||||||
:contact-id="contact.id"
|
:contact-id="contact.id"
|
||||||
@@ -359,7 +359,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.conversation--actions {
|
.conversation--actions {
|
||||||
padding: 0 var(--space-normal) var(--space-small);
|
padding: 0 var(--space-normal) var(--space-smaller);
|
||||||
}
|
}
|
||||||
|
|
||||||
.multiselect__label {
|
.multiselect__label {
|
||||||
|
|||||||
@@ -8,30 +8,35 @@
|
|||||||
:title="$t('CONTACT_PANEL.LABELS.TITLE')"
|
:title="$t('CONTACT_PANEL.LABELS.TITLE')"
|
||||||
icon="ion-pricetags"
|
icon="ion-pricetags"
|
||||||
emoji="🏷️"
|
emoji="🏷️"
|
||||||
:show-edit="true"
|
|
||||||
@edit="onEdit"
|
|
||||||
/>
|
/>
|
||||||
<div class="label-wrap">
|
<div v-on-clickaway="closeDropdownLabel" class="label-wrap">
|
||||||
|
<add-label @add="toggleLabels" />
|
||||||
<woot-label
|
<woot-label
|
||||||
v-for="label in activeLabels"
|
v-for="label in activeLabels"
|
||||||
:key="label.id"
|
:key="label.id"
|
||||||
:title="label.title"
|
:title="label.title"
|
||||||
:description="label.description"
|
:description="label.description"
|
||||||
|
:show-close="true"
|
||||||
:bg-color="label.color"
|
:bg-color="label.color"
|
||||||
|
@click="removeItem"
|
||||||
/>
|
/>
|
||||||
<div v-if="!activeLabels.length" class="no-label-message">
|
|
||||||
<span>{{ $t('CONTACT_PANEL.LABELS.NO_AVAILABLE_LABELS') }}</span>
|
<div class="dropdown-wrap">
|
||||||
|
<div
|
||||||
|
:class="{ 'dropdown-pane--open': showSearchDropdownLabel }"
|
||||||
|
class="dropdown-pane"
|
||||||
|
>
|
||||||
|
<label-dropdown
|
||||||
|
v-if="showSearchDropdownLabel"
|
||||||
|
:account-labels="accountLabels"
|
||||||
|
:selected-labels="savedLabels"
|
||||||
|
:conversation-id="conversationId"
|
||||||
|
@add="addItem"
|
||||||
|
@remove="removeItem"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<add-label-to-conversation
|
|
||||||
v-if="isEditing"
|
|
||||||
:conversation-id="conversationId"
|
|
||||||
:account-labels="accountLabels"
|
|
||||||
:saved-labels="savedLabels"
|
|
||||||
:show.sync="isEditing"
|
|
||||||
:on-close="closeEditModal"
|
|
||||||
:update-labels="onUpdateLabels"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<spinner v-else></spinner>
|
<spinner v-else></spinner>
|
||||||
</div>
|
</div>
|
||||||
@@ -39,45 +44,55 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import AddLabelToConversation from './AddLabelToConversation';
|
|
||||||
import ContactDetailsItem from '../ContactDetailsItem';
|
import ContactDetailsItem from '../ContactDetailsItem';
|
||||||
import Spinner from 'shared/components/Spinner';
|
import Spinner from 'shared/components/Spinner';
|
||||||
|
import LabelDropdown from 'shared/components/ui/label/LabelDropdown';
|
||||||
|
import AddLabel from 'shared/components/ui/dropdown/AddLabel';
|
||||||
|
import { mixin as clickaway } from 'vue-clickaway';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
AddLabelToConversation,
|
|
||||||
ContactDetailsItem,
|
ContactDetailsItem,
|
||||||
Spinner,
|
Spinner,
|
||||||
|
LabelDropdown,
|
||||||
|
AddLabel,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mixins: [clickaway],
|
||||||
props: {
|
props: {
|
||||||
conversationId: {
|
conversationId: {
|
||||||
type: [String, Number],
|
type: [String, Number],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isEditing: false,
|
|
||||||
selectedLabels: [],
|
selectedLabels: [],
|
||||||
|
showSearchDropdownLabel: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
savedLabels() {
|
savedLabels() {
|
||||||
return this.$store.getters['conversationLabels/getConversationLabels'](
|
return this.$store.getters['conversationLabels/getConversationLabels'](
|
||||||
this.conversationId
|
this.conversationId
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
conversationUiFlags: 'contactConversations/getUIFlags',
|
conversationUiFlags: 'contactConversations/getUIFlags',
|
||||||
labelUiFlags: 'conversationLabels/getUIFlags',
|
labelUiFlags: 'conversationLabels/getUIFlags',
|
||||||
accountLabels: 'labels/getLabels',
|
accountLabels: 'labels/getLabels',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
activeLabels() {
|
activeLabels() {
|
||||||
return this.accountLabels.filter(({ title }) =>
|
return this.accountLabels.filter(({ title }) =>
|
||||||
this.savedLabels.includes(title)
|
this.savedLabels.includes(title)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
conversationId(newConversationId, prevConversationId) {
|
conversationId(newConversationId, prevConversationId) {
|
||||||
if (newConversationId && newConversationId !== prevConversationId) {
|
if (newConversationId && newConversationId !== prevConversationId) {
|
||||||
@@ -85,10 +100,12 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
const { conversationId } = this;
|
const { conversationId } = this;
|
||||||
this.fetchLabels(conversationId);
|
this.fetchLabels(conversationId);
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async onUpdateLabels(selectedLabels) {
|
async onUpdateLabels(selectedLabels) {
|
||||||
try {
|
try {
|
||||||
@@ -100,13 +117,28 @@ export default {
|
|||||||
// Ignore error
|
// Ignore error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onEdit() {
|
|
||||||
this.isEditing = true;
|
toggleLabels() {
|
||||||
|
this.showSearchDropdownLabel = !this.showSearchDropdownLabel;
|
||||||
},
|
},
|
||||||
closeEditModal() {
|
|
||||||
bus.$emit('fetch_conversation_stats');
|
addItem(value) {
|
||||||
this.isEditing = false;
|
const result = this.activeLabels.map(item => item.title);
|
||||||
|
result.push(value.title);
|
||||||
|
this.onUpdateLabels(result);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
removeItem(value) {
|
||||||
|
const result = this.activeLabels
|
||||||
|
.map(label => label.title)
|
||||||
|
.filter(label => label !== value);
|
||||||
|
this.onUpdateLabels(result);
|
||||||
|
},
|
||||||
|
|
||||||
|
closeDropdownLabel() {
|
||||||
|
this.showSearchDropdownLabel = false;
|
||||||
|
},
|
||||||
|
|
||||||
async fetchLabels(conversationId) {
|
async fetchLabels(conversationId) {
|
||||||
if (!conversationId) {
|
if (!conversationId) {
|
||||||
return;
|
return;
|
||||||
@@ -122,53 +154,38 @@ export default {
|
|||||||
@import '~dashboard/assets/scss/mixins';
|
@import '~dashboard/assets/scss/mixins';
|
||||||
|
|
||||||
.contact-conversation--panel {
|
.contact-conversation--panel {
|
||||||
padding: var(--space-medium) var(--space-slab) var(--space-two);
|
padding: var(--space-micro) var(--space-slab) var(--space-one)
|
||||||
|
var(--space-slab);
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-conversation--list .conv-details--item {
|
.contact-conversation--list {
|
||||||
padding-bottom: 0;
|
width: 100%;
|
||||||
}
|
|
||||||
.conversation--label {
|
|
||||||
color: $color-white;
|
|
||||||
margin-right: $space-small;
|
|
||||||
font-size: $font-size-small;
|
|
||||||
padding: $space-smaller;
|
|
||||||
}
|
|
||||||
.label-wrap {
|
|
||||||
margin-left: var(--space-medium);
|
|
||||||
}
|
|
||||||
.no-label-message {
|
|
||||||
color: var(--b-500);
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-tags {
|
.label-wrap {
|
||||||
.multiselect {
|
margin-left: var(--space-two);
|
||||||
&:hover {
|
position: relative;
|
||||||
cursor: pointer;
|
line-height: var(--space-medium);
|
||||||
|
bottom: var(--space-small);
|
||||||
|
|
||||||
|
.dropdown-wrap {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
margin-right: var(--space-medium);
|
||||||
|
top: var(--space-medium);
|
||||||
|
width: 100%;
|
||||||
|
left: -1px;
|
||||||
|
|
||||||
|
.dropdown-pane {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
transition: $transition-ease-in;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
|
||||||
margin-top: $space-small;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-results-wrap {
|
|
||||||
padding: 0 $space-small;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-results {
|
|
||||||
margin: $space-normal 0 0 0;
|
|
||||||
color: $color-gray;
|
|
||||||
font-weight: $font-weight-normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
color: $alert-color;
|
color: var(--r-500);
|
||||||
font-size: $font-size-mini;
|
font-size: var(--font-size-mini);
|
||||||
font-weight: $font-weight-medium;
|
font-weight: var(--font-weight-medium);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<woot-button
|
||||||
<woot-button
|
variant="hollow"
|
||||||
variant="hollow"
|
size="tiny"
|
||||||
size="tiny"
|
icon="ion-plus-round"
|
||||||
icon="ion-plus-round"
|
color-scheme="secondary"
|
||||||
color-scheme="secondary"
|
class-names="button-wrap"
|
||||||
@click="toggleLabels"
|
@click="toggleLabels"
|
||||||
>
|
>
|
||||||
{{ $t('CONTACT_PANEL.LABELS.MODAL.ADD_BUTTON') }}
|
{{ $t('CONTACT_PANEL.LABELS.MODAL.ADD_BUTTON') }}
|
||||||
</woot-button>
|
</woot-button>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -22,4 +21,14 @@ export default {
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped>
|
||||||
|
.button-wrap {
|
||||||
|
padding: var(--space-micro) var(--space-small);
|
||||||
|
display: inline;
|
||||||
|
line-height: 1.2;
|
||||||
|
|
||||||
|
&::v-deep .icon {
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user