mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-02 20:18:08 +00:00
feat: Adds new inbox selector with more info for new message modal [cw-1358] (#6823)
This commit is contained in:
committed by
GitHub
parent
76d4c22c2d
commit
bd1e69e4b4
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
.multiselect--active {
|
.multiselect--active {
|
||||||
>.multiselect__tags {
|
>.multiselect__tags {
|
||||||
border-color: $color-woot;
|
border-color: var(--w-500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,16 +75,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.multiselect__option--selected {
|
&.multiselect__option--selected {
|
||||||
background: var(--w-400);
|
background: var(--w-75);
|
||||||
color: var(--white);
|
|
||||||
|
|
||||||
&.multiselect__option--highlight:hover {
|
&.multiselect__option--highlight:hover {
|
||||||
background: var(--w-600);
|
background: var(--w-75);
|
||||||
color: var(--white);
|
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--white);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after:hover {
|
&::after:hover {
|
||||||
@@ -196,6 +193,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-small);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
max-height: 3.8rem;
|
||||||
padding: var(--space-smaller) var(--space-micro);
|
padding: var(--space-smaller) var(--space-micro);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import InboxDropdownItem from './InboxDropdownItem';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/DropDowns/InboxDropdownItem',
|
||||||
|
component: InboxDropdownItem,
|
||||||
|
argTypes: {
|
||||||
|
name: {
|
||||||
|
defaultValue: 'My new inbox',
|
||||||
|
control: {
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
inboxIdentifier: {
|
||||||
|
defaultValue: 'nithin@mail.com',
|
||||||
|
control: {
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
channelType: {
|
||||||
|
defaultValue: 'email',
|
||||||
|
control: {
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const Template = (args, { argTypes }) => ({
|
||||||
|
props: Object.keys(argTypes),
|
||||||
|
components: { InboxDropdownItem },
|
||||||
|
template: '<inbox-dropdown-item v-bind="$props" ></inbox-dropdown-item>',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Banner = Template.bind({});
|
||||||
|
Banner.args = {};
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
<template>
|
||||||
|
<div class="option-item--inbox">
|
||||||
|
<span class="badge--icon">
|
||||||
|
<fluent-icon :icon="computedInboxIcon" size="14" />
|
||||||
|
</span>
|
||||||
|
<div class="option__user-data">
|
||||||
|
<h5 class="option__title">
|
||||||
|
{{ name }}
|
||||||
|
</h5>
|
||||||
|
<p class="option__body text-truncate" :title="inboxIdentifier">
|
||||||
|
{{ inboxIdentifier || computedInboxType }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
getInboxClassByType,
|
||||||
|
getReadableInboxByType,
|
||||||
|
} from 'dashboard/helper/inbox';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
props: {
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
inboxIdentifier: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
channelType: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
computedInboxIcon() {
|
||||||
|
if (!this.channelType) return 'chat';
|
||||||
|
const classByType = getInboxClassByType(
|
||||||
|
this.channelType,
|
||||||
|
this.inboxIdentifier
|
||||||
|
);
|
||||||
|
return classByType;
|
||||||
|
},
|
||||||
|
computedInboxType() {
|
||||||
|
if (!this.channelType) return 'chat';
|
||||||
|
const classByType = getReadableInboxByType(
|
||||||
|
this.channelType,
|
||||||
|
this.inboxIdentifier
|
||||||
|
);
|
||||||
|
return classByType;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.option-item--inbox {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 3.8rem;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0 var(--space-smaller);
|
||||||
|
}
|
||||||
|
.badge--icon {
|
||||||
|
display: inline-flex;
|
||||||
|
border-radius: var(--border-radius-small);
|
||||||
|
margin-right: var(--space-smaller);
|
||||||
|
background: var(--s-25);
|
||||||
|
padding: var(--space-micro);
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
justify-content: center;
|
||||||
|
width: var(--space-medium);
|
||||||
|
height: var(--space-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option__user-data {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
margin-left: var(--space-smaller);
|
||||||
|
margin-right: var(--space-smaller);
|
||||||
|
}
|
||||||
|
.option__body {
|
||||||
|
display: inline-block;
|
||||||
|
color: var(--s-600);
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
line-height: 1.3;
|
||||||
|
min-width: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.option__title {
|
||||||
|
line-height: 1.1;
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,55 @@
|
|||||||
import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
|
import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
|
||||||
|
|
||||||
|
export const getInboxSource = (type, phoneNumber, inbox) => {
|
||||||
|
switch (type) {
|
||||||
|
case INBOX_TYPES.WEB:
|
||||||
|
return inbox.website_url || '';
|
||||||
|
|
||||||
|
case INBOX_TYPES.TWILIO:
|
||||||
|
case INBOX_TYPES.WHATSAPP:
|
||||||
|
return phoneNumber || '';
|
||||||
|
|
||||||
|
case INBOX_TYPES.EMAIL:
|
||||||
|
return inbox.email || '';
|
||||||
|
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const getReadableInboxByType = (type, phoneNumber) => {
|
||||||
|
switch (type) {
|
||||||
|
case INBOX_TYPES.WEB:
|
||||||
|
return 'livechat';
|
||||||
|
|
||||||
|
case INBOX_TYPES.FB:
|
||||||
|
return 'facebook';
|
||||||
|
|
||||||
|
case INBOX_TYPES.TWITTER:
|
||||||
|
return 'twitter';
|
||||||
|
|
||||||
|
case INBOX_TYPES.TWILIO:
|
||||||
|
return phoneNumber?.startsWith('whatsapp') ? 'whatsapp' : 'sms';
|
||||||
|
|
||||||
|
case INBOX_TYPES.WHATSAPP:
|
||||||
|
return 'whatsapp';
|
||||||
|
|
||||||
|
case INBOX_TYPES.API:
|
||||||
|
return 'api';
|
||||||
|
|
||||||
|
case INBOX_TYPES.EMAIL:
|
||||||
|
return 'email';
|
||||||
|
|
||||||
|
case INBOX_TYPES.TELEGRAM:
|
||||||
|
return 'telegram';
|
||||||
|
|
||||||
|
case INBOX_TYPES.LINE:
|
||||||
|
return 'line';
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 'chat';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const getInboxClassByType = (type, phoneNumber) => {
|
export const getInboxClassByType = (type, phoneNumber) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case INBOX_TYPES.WEB:
|
case INBOX_TYPES.WEB:
|
||||||
|
|||||||
@@ -182,7 +182,8 @@
|
|||||||
"LABEL": "To"
|
"LABEL": "To"
|
||||||
},
|
},
|
||||||
"INBOX": {
|
"INBOX": {
|
||||||
"LABEL": "Inbox",
|
"LABEL": "Via Inbox",
|
||||||
|
"PLACEHOLDER": "Choose source inbox",
|
||||||
"ERROR": "Select an inbox"
|
"ERROR": "Select an inbox"
|
||||||
},
|
},
|
||||||
"SUBJECT": {
|
"SUBJECT": {
|
||||||
|
|||||||
@@ -8,17 +8,43 @@
|
|||||||
<div v-else>
|
<div v-else>
|
||||||
<div class="row gutter-small">
|
<div class="row gutter-small">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<label :class="{ error: $v.targetInbox.$error }">
|
<label>
|
||||||
{{ $t('NEW_CONVERSATION.FORM.INBOX.LABEL') }}
|
{{ $t('NEW_CONVERSATION.FORM.INBOX.LABEL') }}
|
||||||
<select v-model="targetInbox">
|
</label>
|
||||||
<option
|
<div class="multiselect-wrap--small">
|
||||||
v-for="contactableInbox in inboxes"
|
<multiselect
|
||||||
:key="contactableInbox.inbox.id"
|
v-model="targetInbox"
|
||||||
:value="contactableInbox"
|
track-by="id"
|
||||||
>
|
label="name"
|
||||||
{{ contactableInbox.inbox.name }}
|
:placeholder="$t('FORMS.MULTISELECT.SELECT')"
|
||||||
</option>
|
selected-label=""
|
||||||
</select>
|
select-label=""
|
||||||
|
deselect-label=""
|
||||||
|
:max-height="160"
|
||||||
|
:close-on-select="true"
|
||||||
|
:options="[...inboxes, ...inboxes]"
|
||||||
|
>
|
||||||
|
<template slot="singleLabel" slot-scope="{ option }">
|
||||||
|
<inbox-dropdown-item
|
||||||
|
v-if="option.name"
|
||||||
|
:name="option.name"
|
||||||
|
:inbox-identifier="computedInboxSource(option)"
|
||||||
|
:channel-type="option.channel_type"
|
||||||
|
/>
|
||||||
|
<span v-else>
|
||||||
|
{{ $t('NEW_CONVERSATION.FORM.INBOX.PLACEHOLDER') }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template slot="option" slot-scope="{ option }">
|
||||||
|
<inbox-dropdown-item
|
||||||
|
:name="option.name"
|
||||||
|
:inbox-identifier="computedInboxSource(option)"
|
||||||
|
:channel-type="option.channel_type"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</multiselect>
|
||||||
|
</div>
|
||||||
|
<label :class="{ error: $v.targetInbox.$error }">
|
||||||
<span v-if="$v.targetInbox.$error" class="message">
|
<span v-if="$v.targetInbox.$error" class="message">
|
||||||
{{ $t('NEW_CONVERSATION.FORM.INBOX.ERROR') }}
|
{{ $t('NEW_CONVERSATION.FORM.INBOX.ERROR') }}
|
||||||
</span>
|
</span>
|
||||||
@@ -129,10 +155,12 @@ import Thumbnail from 'dashboard/components/widgets/Thumbnail';
|
|||||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor';
|
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor';
|
||||||
import ReplyEmailHead from 'dashboard/components/widgets/conversation/ReplyEmailHead';
|
import ReplyEmailHead from 'dashboard/components/widgets/conversation/ReplyEmailHead';
|
||||||
import CannedResponse from 'dashboard/components/widgets/conversation/CannedResponse.vue';
|
import CannedResponse from 'dashboard/components/widgets/conversation/CannedResponse.vue';
|
||||||
|
import InboxDropdownItem from 'dashboard/components/widgets/InboxDropdownItem.vue';
|
||||||
import WhatsappTemplates from './WhatsappTemplates.vue';
|
import WhatsappTemplates from './WhatsappTemplates.vue';
|
||||||
import alertMixin from 'shared/mixins/alertMixin';
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
|
import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
|
||||||
import { ExceptionWithMessage } from 'shared/helpers/CustomErrors';
|
import { ExceptionWithMessage } from 'shared/helpers/CustomErrors';
|
||||||
|
import { getInboxSource } from 'dashboard/helper/inbox';
|
||||||
import { required, requiredIf } from 'vuelidate/lib/validators';
|
import { required, requiredIf } from 'vuelidate/lib/validators';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -142,6 +170,7 @@ export default {
|
|||||||
ReplyEmailHead,
|
ReplyEmailHead,
|
||||||
CannedResponse,
|
CannedResponse,
|
||||||
WhatsappTemplates,
|
WhatsappTemplates,
|
||||||
|
InboxDropdownItem,
|
||||||
},
|
},
|
||||||
mixins: [alertMixin],
|
mixins: [alertMixin],
|
||||||
props: {
|
props: {
|
||||||
@@ -161,9 +190,9 @@ export default {
|
|||||||
message: '',
|
message: '',
|
||||||
showCannedResponseMenu: false,
|
showCannedResponseMenu: false,
|
||||||
cannedResponseSearchKey: '',
|
cannedResponseSearchKey: '',
|
||||||
selectedInbox: '',
|
|
||||||
bccEmails: '',
|
bccEmails: '',
|
||||||
ccEmails: '',
|
ccEmails: '',
|
||||||
|
targetInbox: {},
|
||||||
whatsappTemplateSelected: false,
|
whatsappTemplateSelected: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -186,8 +215,8 @@ export default {
|
|||||||
}),
|
}),
|
||||||
emailMessagePayload() {
|
emailMessagePayload() {
|
||||||
const payload = {
|
const payload = {
|
||||||
inboxId: this.targetInbox.inbox.id,
|
inboxId: this.targetInbox.id,
|
||||||
sourceId: this.targetInbox.source_id,
|
sourceId: this.targetInbox.sourceId,
|
||||||
contactId: this.contact.id,
|
contactId: this.contact.id,
|
||||||
message: { content: this.message },
|
message: { content: this.message },
|
||||||
mailSubject: this.subject,
|
mailSubject: this.subject,
|
||||||
@@ -202,12 +231,17 @@ export default {
|
|||||||
}
|
}
|
||||||
return payload;
|
return payload;
|
||||||
},
|
},
|
||||||
targetInbox: {
|
selectedInbox: {
|
||||||
get() {
|
get() {
|
||||||
return this.selectedInbox || {};
|
const inboxList = this.contact.contactableInboxes || [];
|
||||||
|
return (
|
||||||
|
inboxList.find(inbox => inbox.inbox.id === this.targetInbox.id) || {
|
||||||
|
inbox: {},
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
this.selectedInbox = value;
|
this.targetInbox = value.inbox;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
showNoInboxAlert() {
|
showNoInboxAlert() {
|
||||||
@@ -217,7 +251,11 @@ export default {
|
|||||||
return this.inboxes.length === 0 && !this.uiFlags.isFetchingInboxes;
|
return this.inboxes.length === 0 && !this.uiFlags.isFetchingInboxes;
|
||||||
},
|
},
|
||||||
inboxes() {
|
inboxes() {
|
||||||
return this.contact.contactableInboxes || [];
|
const inboxList = this.contact.contactableInboxes || [];
|
||||||
|
return inboxList.map(inbox => ({
|
||||||
|
...inbox.inbox,
|
||||||
|
sourceId: inbox.source_id,
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
isAnEmailInbox() {
|
isAnEmailInbox() {
|
||||||
return (
|
return (
|
||||||
@@ -267,8 +305,8 @@ export default {
|
|||||||
},
|
},
|
||||||
prepareWhatsAppMessagePayload({ message: content, templateParams }) {
|
prepareWhatsAppMessagePayload({ message: content, templateParams }) {
|
||||||
const payload = {
|
const payload = {
|
||||||
inboxId: this.targetInbox.inbox.id,
|
inboxId: this.targetInbox.id,
|
||||||
sourceId: this.targetInbox.source_id,
|
sourceId: this.targetInbox.sourceId,
|
||||||
contactId: this.contact.id,
|
contactId: this.contact.id,
|
||||||
message: { content, template_params: templateParams },
|
message: { content, template_params: templateParams },
|
||||||
assigneeId: this.currentUser.id,
|
assigneeId: this.currentUser.id,
|
||||||
@@ -311,6 +349,18 @@ export default {
|
|||||||
const payload = this.prepareWhatsAppMessagePayload(messagePayload);
|
const payload = this.prepareWhatsAppMessagePayload(messagePayload);
|
||||||
await this.createConversation(payload);
|
await this.createConversation(payload);
|
||||||
},
|
},
|
||||||
|
inboxReadableIdentifier(inbox) {
|
||||||
|
return `${inbox.name} (${inbox.channel_type})`;
|
||||||
|
},
|
||||||
|
computedInboxSource(inbox) {
|
||||||
|
if (!inbox.channel_type) return '';
|
||||||
|
const classByType = getInboxSource(
|
||||||
|
inbox.channel_type,
|
||||||
|
inbox.phone_number,
|
||||||
|
inbox
|
||||||
|
);
|
||||||
|
return classByType;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -352,11 +402,23 @@ export default {
|
|||||||
gap: var(--space-small);
|
gap: var(--space-small);
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .mention--box {
|
::v-deep {
|
||||||
left: 0;
|
.mention--box {
|
||||||
margin: auto;
|
left: 0;
|
||||||
right: 0;
|
margin: auto;
|
||||||
top: unset;
|
right: 0;
|
||||||
height: fit-content;
|
top: unset;
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: Remove when have standardized a component out of multiselect */
|
||||||
|
.multiselect .multiselect__content .multiselect__option span {
|
||||||
|
display: inline-flex;
|
||||||
|
width: var(--space-medium);
|
||||||
|
color: var(--s-600);
|
||||||
|
}
|
||||||
|
.multiselect .multiselect__content .multiselect__option {
|
||||||
|
padding: var(--space-micro) var(--space-smaller);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user