mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-03 12:37:56 +00:00
# Pull Request Template ## Description This PR includes the implementation of the new Compose Conversation form in the conversation sidebar, replacing the old one. ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? ### Loom video https://www.loom.com/share/4312e20a63714eb892d7b5cd0dcda893?sid=9bd5254e-2b1f-462c-b2c1-a3048a111683 ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules
157 lines
4.0 KiB
Vue
157 lines
4.0 KiB
Vue
<script setup>
|
|
import { computed, ref } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { INPUT_TYPES } from 'dashboard/components-next/taginput/helper/tagInputHelper.js';
|
|
|
|
import TagInput from 'dashboard/components-next/taginput/TagInput.vue';
|
|
import Button from 'dashboard/components-next/button/Button.vue';
|
|
|
|
const props = defineProps({
|
|
contacts: {
|
|
type: Array,
|
|
required: true,
|
|
},
|
|
selectedContact: {
|
|
type: Object,
|
|
default: null,
|
|
},
|
|
showContactsDropdown: {
|
|
type: Boolean,
|
|
required: true,
|
|
},
|
|
isLoading: {
|
|
type: Boolean,
|
|
required: true,
|
|
},
|
|
isCreatingContact: {
|
|
type: Boolean,
|
|
required: true,
|
|
},
|
|
contactId: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
contactableInboxesList: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
showInboxesDropdown: {
|
|
type: Boolean,
|
|
required: true,
|
|
},
|
|
hasErrors: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
});
|
|
|
|
const emit = defineEmits([
|
|
'searchContacts',
|
|
'setSelectedContact',
|
|
'clearSelectedContact',
|
|
'updateDropdown',
|
|
]);
|
|
|
|
const i18nPrefix = 'COMPOSE_NEW_CONVERSATION.FORM.CONTACT_SELECTOR';
|
|
const { t } = useI18n();
|
|
|
|
const inputType = ref(INPUT_TYPES.EMAIL);
|
|
|
|
const contactsList = computed(() => {
|
|
return props.contacts?.map(({ name, id, thumbnail, email, ...rest }) => ({
|
|
id,
|
|
label: email ? `${name} (${email})` : name,
|
|
value: id,
|
|
thumbnail: { name, src: thumbnail },
|
|
...rest,
|
|
name,
|
|
email,
|
|
action: 'contact',
|
|
}));
|
|
});
|
|
|
|
const selectedContactLabel = computed(() => {
|
|
const { name, email = '', phoneNumber = '' } = props.selectedContact || {};
|
|
if (email) {
|
|
return `${name} (${email})`;
|
|
}
|
|
if (phoneNumber) {
|
|
return `${name} (${phoneNumber})`;
|
|
}
|
|
return name || '';
|
|
});
|
|
|
|
const errorClass = computed(() => {
|
|
return props.hasErrors
|
|
? '[&_input]:placeholder:!text-n-ruby-9 [&_input]:dark:placeholder:!text-n-ruby-9'
|
|
: '';
|
|
});
|
|
|
|
const handleInput = value => {
|
|
// Update input type based on whether input starts with '+'
|
|
// If it does, set input type to 'tel'
|
|
// Otherwise, set input type to 'email'
|
|
inputType.value = value.startsWith('+') ? INPUT_TYPES.TEL : INPUT_TYPES.EMAIL;
|
|
emit('searchContacts', value);
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="relative flex-1 px-4 py-3 overflow-y-visible">
|
|
<div class="flex items-baseline w-full gap-3 min-h-7">
|
|
<label class="text-sm font-medium text-n-slate-11 whitespace-nowrap">
|
|
{{ t(`${i18nPrefix}.LABEL`) }}
|
|
</label>
|
|
|
|
<div
|
|
v-if="isCreatingContact"
|
|
class="flex items-center gap-1.5 rounded-md bg-n-alpha-2 px-3 min-h-7 min-w-0"
|
|
>
|
|
<span class="text-sm truncate text-n-slate-12">
|
|
{{ t(`${i18nPrefix}.CONTACT_CREATING`) }}
|
|
</span>
|
|
</div>
|
|
<div
|
|
v-else-if="selectedContact"
|
|
class="flex items-center gap-1.5 rounded-md bg-n-alpha-2 min-h-7 min-w-0"
|
|
:class="!contactId ? 'ltr:pl-3 rtl:pr-3 ltr:pr-1 rtl:pl-1' : 'px-3'"
|
|
>
|
|
<span class="text-sm truncate text-n-slate-12">
|
|
{{
|
|
isCreatingContact
|
|
? t(`${i18nPrefix}.CONTACT_CREATING`)
|
|
: selectedContactLabel
|
|
}}
|
|
</span>
|
|
<Button
|
|
v-if="!contactId"
|
|
variant="ghost"
|
|
icon="i-lucide-x"
|
|
color="slate"
|
|
:disabled="contactId"
|
|
size="xs"
|
|
@click="emit('clearSelectedContact')"
|
|
/>
|
|
</div>
|
|
<TagInput
|
|
v-else
|
|
:placeholder="t(`${i18nPrefix}.TAG_INPUT_PLACEHOLDER`)"
|
|
mode="single"
|
|
:menu-items="contactsList"
|
|
:show-dropdown="showContactsDropdown"
|
|
:is-loading="isLoading"
|
|
:disabled="contactableInboxesList?.length > 0 && showInboxesDropdown"
|
|
allow-create
|
|
:type="inputType"
|
|
class="flex-1 min-h-7"
|
|
:class="errorClass"
|
|
focus-on-mount
|
|
@input="handleInput"
|
|
@on-click-outside="emit('updateDropdown', 'contacts', false)"
|
|
@add="emit('setSelectedContact', $event)"
|
|
@remove="emit('clearSelectedContact')"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|