Merge branch 'develop' into fix/CW-6859
This commit is contained in:
@@ -1,49 +1,131 @@
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, ref, watch, onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useStore, useMapGetter } from 'dashboard/composables/store';
|
||||
import { frontendURL, conversationUrl } from 'dashboard/helper/URLHelper';
|
||||
import {
|
||||
isOnMentionsView,
|
||||
isOnUnattendedView,
|
||||
isOnFoldersView,
|
||||
} from 'dashboard/store/modules/conversations/helpers/actionHelpers';
|
||||
import ConversationCard from 'dashboard/components/widgets/conversation/ConversationCard.vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import ContextMenu from 'dashboard/components/ui/ContextMenu.vue';
|
||||
import ConversationContextMenu from 'dashboard/components/widgets/conversation/contextMenu/Index.vue';
|
||||
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ConversationCard,
|
||||
Spinner,
|
||||
},
|
||||
props: {
|
||||
contactId: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
conversationId: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
conversations() {
|
||||
return this.$store.getters['contactConversations/getContactConversation'](
|
||||
this.contactId
|
||||
);
|
||||
},
|
||||
previousConversations() {
|
||||
return this.conversations.filter(
|
||||
conversation => conversation.id !== Number(this.conversationId)
|
||||
);
|
||||
},
|
||||
...mapGetters({
|
||||
uiFlags: 'contactConversations/getUIFlags',
|
||||
}),
|
||||
},
|
||||
watch: {
|
||||
contactId(newContactId, prevContactId) {
|
||||
if (newContactId && newContactId !== prevContactId) {
|
||||
this.$store.dispatch('contactConversations/get', newContactId);
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('contactConversations/get', this.contactId);
|
||||
},
|
||||
const props = defineProps({
|
||||
contactId: { type: [String, Number], required: true },
|
||||
conversationId: { type: [String, Number], required: true },
|
||||
});
|
||||
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const currentChat = useMapGetter('getSelectedChat');
|
||||
const uiFlags = useMapGetter('contactConversations/getUIFlags');
|
||||
|
||||
const contactGetter = useMapGetter('contacts/getContact');
|
||||
const inboxGetter = useMapGetter('inboxes/getInbox');
|
||||
|
||||
const activeInbox = useMapGetter('getSelectedInbox');
|
||||
const inboxesList = useMapGetter('inboxes/getInboxes');
|
||||
const showInboxName = computed(
|
||||
() => !activeInbox.value && inboxesList.value.length > 1
|
||||
);
|
||||
|
||||
const contactConversationGetter = useMapGetter(
|
||||
'contactConversations/getContactConversation'
|
||||
);
|
||||
const conversations = computed(() =>
|
||||
contactConversationGetter.value(props.contactId)
|
||||
);
|
||||
|
||||
const previousConversations = computed(() =>
|
||||
conversations.value.filter(c => c.id !== Number(props.conversationId))
|
||||
);
|
||||
|
||||
const activeContextChat = ref(null);
|
||||
const showContextMenu = ref(false);
|
||||
const contextMenu = ref({ x: null, y: null });
|
||||
|
||||
const buildConversationUrl = conversationId => {
|
||||
const {
|
||||
params: { accountId, inbox_id: inboxId, label, teamId },
|
||||
name,
|
||||
} = route;
|
||||
|
||||
let conversationType = '';
|
||||
if (isOnMentionsView({ route: { name } })) {
|
||||
conversationType = 'mention';
|
||||
} else if (isOnUnattendedView({ route: { name } })) {
|
||||
conversationType = 'unattended';
|
||||
}
|
||||
|
||||
return frontendURL(
|
||||
conversationUrl({
|
||||
accountId,
|
||||
activeInbox: inboxId,
|
||||
id: conversationId,
|
||||
label,
|
||||
teamId,
|
||||
foldersId: isOnFoldersView({ route: { name } }) ? route.params.id : 0,
|
||||
conversationType,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const conversationPath = computed(() => {
|
||||
if (!activeContextChat.value) return '';
|
||||
return buildConversationUrl(activeContextChat.value.id);
|
||||
});
|
||||
|
||||
const onCardClick = (conversation, e) => {
|
||||
const path = buildConversationUrl(conversation.id);
|
||||
if (!path) return;
|
||||
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
window.open(
|
||||
`${window.chatwootConfig.hostURL}${path}`,
|
||||
'_blank',
|
||||
'noopener,noreferrer'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
router.push({ path });
|
||||
};
|
||||
|
||||
const openContextMenu = (conversation, e) => {
|
||||
e.preventDefault();
|
||||
activeContextChat.value = conversation;
|
||||
contextMenu.value.x = e.pageX || e.clientX;
|
||||
contextMenu.value.y = e.pageY || e.clientY;
|
||||
showContextMenu.value = true;
|
||||
};
|
||||
|
||||
const closeContextMenu = () => {
|
||||
showContextMenu.value = false;
|
||||
contextMenu.value.x = null;
|
||||
contextMenu.value.y = null;
|
||||
activeContextChat.value = null;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.contactId,
|
||||
(newId, oldId) => {
|
||||
if (newId && newId !== oldId) {
|
||||
showContextMenu.value = false;
|
||||
activeContextChat.value = null;
|
||||
store.dispatch('contactConversations/get', newId);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
store.dispatch('contactConversations/get', props.contactId);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -53,18 +135,43 @@ export default {
|
||||
{{ $t('CONTACT_PANEL.CONVERSATIONS.NO_RECORDS_FOUND') }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-else class="contact-conversation--list">
|
||||
<div
|
||||
v-else
|
||||
class="contact-conversation--list [&>.conversation:last-child]:!border-b-0 [&>.conversation:last-child:hover]:!border-b-0 [&>.conversation:last-child]:!rounded-b-lg"
|
||||
>
|
||||
<ConversationCard
|
||||
v-for="conversation in previousConversations"
|
||||
:key="conversation.id"
|
||||
:chat="conversation"
|
||||
:hide-inbox-name="false"
|
||||
:current-contact="contactGetter(conversation.meta?.sender?.id) || {}"
|
||||
:assignee="conversation.meta?.assignee || {}"
|
||||
:inbox="inboxGetter(conversation.inbox_id) || {}"
|
||||
:is-active-chat="currentChat.id === conversation.id"
|
||||
:show-inbox-name="showInboxName"
|
||||
hide-thumbnail
|
||||
enable-context-menu
|
||||
compact
|
||||
:allowed-context-menu-options="['open-new-tab', 'copy-link']"
|
||||
@click="onCardClick(conversation, $event)"
|
||||
@contextmenu="openContextMenu(conversation, $event)"
|
||||
/>
|
||||
</div>
|
||||
<ContextMenu
|
||||
v-if="showContextMenu && activeContextChat"
|
||||
:x="contextMenu.x"
|
||||
:y="contextMenu.y"
|
||||
@close="closeContextMenu"
|
||||
>
|
||||
<ConversationContextMenu
|
||||
:status="activeContextChat.status"
|
||||
:inbox-id="activeContextChat.inbox_id"
|
||||
:priority="activeContextChat.priority"
|
||||
:chat-id="activeContextChat.id"
|
||||
:has-unread-messages="activeContextChat.unread_count > 0"
|
||||
:conversation-labels="activeContextChat.labels"
|
||||
:conversation-url="conversationPath"
|
||||
:allowed-options="['open-new-tab', 'copy-link']"
|
||||
@close="closeContextMenu"
|
||||
/>
|
||||
</ContextMenu>
|
||||
</div>
|
||||
<div v-else class="flex items-center justify-center py-5">
|
||||
<Spinner />
|
||||
|
||||
@@ -12,19 +12,12 @@ import Avatar from 'next/avatar/Avatar.vue';
|
||||
import SocialIcons from './SocialIcons.vue';
|
||||
import EditContact from './EditContact.vue';
|
||||
import ContactMergeModal from 'dashboard/modules/contact/ContactMergeModal.vue';
|
||||
import ContactDeleteModal from 'dashboard/modules/contact/ContactDeleteModal.vue';
|
||||
import ComposeConversation from 'dashboard/components-next/NewConversation/ComposeConversation.vue';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
import VoiceCallButton from 'dashboard/components-next/Contacts/VoiceCallButton.vue';
|
||||
import InlineInput from 'dashboard/components-next/inline-input/InlineInput.vue';
|
||||
|
||||
import {
|
||||
isAConversationRoute,
|
||||
isAInboxViewRoute,
|
||||
getConversationDashboardRoute,
|
||||
} from '../../../../helper/routeHelpers';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NextButton,
|
||||
@@ -34,6 +27,7 @@ export default {
|
||||
ComposeConversation,
|
||||
SocialIcons,
|
||||
ContactMergeModal,
|
||||
ContactDeleteModal,
|
||||
VoiceCallButton,
|
||||
InlineInput,
|
||||
},
|
||||
@@ -57,7 +51,6 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
showEditModal: false,
|
||||
showDeleteModal: false,
|
||||
isEditingName: false,
|
||||
editName: '',
|
||||
};
|
||||
@@ -99,10 +92,6 @@ export default {
|
||||
telegram,
|
||||
};
|
||||
},
|
||||
// Delete Modal
|
||||
confirmDeleteMessage() {
|
||||
return ` ${this.contact.name}?`;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'contact.id': {
|
||||
@@ -117,28 +106,6 @@ export default {
|
||||
toggleEditModal() {
|
||||
this.showEditModal = !this.showEditModal;
|
||||
},
|
||||
openComposeConversationModal(toggleFn) {
|
||||
toggleFn();
|
||||
// Flag to prevent triggering drag n drop,
|
||||
// When compose modal is active
|
||||
emitter.emit(BUS_EVENTS.NEW_CONVERSATION_MODAL, true);
|
||||
},
|
||||
closeComposeConversationModal() {
|
||||
// Flag to enable drag n drop,
|
||||
// When compose modal is closed
|
||||
emitter.emit(BUS_EVENTS.NEW_CONVERSATION_MODAL, false);
|
||||
},
|
||||
toggleDeleteModal() {
|
||||
this.showDeleteModal = !this.showDeleteModal;
|
||||
},
|
||||
confirmDeletion() {
|
||||
this.deleteContact(this.contact);
|
||||
this.closeDelete();
|
||||
},
|
||||
closeDelete() {
|
||||
this.showDeleteModal = false;
|
||||
this.showEditModal = false;
|
||||
},
|
||||
findCountryFlag(countryCode, cityAndCountry) {
|
||||
try {
|
||||
if (!countryCode) {
|
||||
@@ -151,36 +118,6 @@ export default {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
async deleteContact({ id }) {
|
||||
try {
|
||||
await this.$store.dispatch('contacts/delete', id);
|
||||
this.$emit('panelClose');
|
||||
useAlert(this.$t('DELETE_CONTACT.API.SUCCESS_MESSAGE'));
|
||||
|
||||
if (isAConversationRoute(this.$route.name)) {
|
||||
this.$router.push({
|
||||
name: getConversationDashboardRoute(this.$route.name),
|
||||
});
|
||||
} else if (isAInboxViewRoute(this.$route.name)) {
|
||||
this.$router.push({
|
||||
name: 'inbox_view',
|
||||
});
|
||||
} else if (this.$route.name !== 'contacts_dashboard') {
|
||||
this.$router.push({
|
||||
name: 'contacts_dashboard',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
useAlert(
|
||||
error.message
|
||||
? error.message
|
||||
: this.$t('DELETE_CONTACT.API.ERROR_MESSAGE')
|
||||
);
|
||||
}
|
||||
},
|
||||
openMergeModal() {
|
||||
this.$refs.mergeModal?.open();
|
||||
},
|
||||
startEditingName() {
|
||||
this.editName = this.contact.name || '';
|
||||
this.isEditingName = true;
|
||||
@@ -354,19 +291,14 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center w-full mt-0.5 gap-2">
|
||||
<ComposeConversation
|
||||
:contact-id="String(contact.id)"
|
||||
is-modal
|
||||
@close="closeComposeConversationModal"
|
||||
>
|
||||
<template #trigger="{ toggle }">
|
||||
<ComposeConversation :contact-id="String(contact.id)">
|
||||
<template #trigger>
|
||||
<NextButton
|
||||
v-tooltip.top-end="$t('CONTACT_PANEL.NEW_MESSAGE')"
|
||||
icon="i-ph-chat-circle-dots"
|
||||
slate
|
||||
faded
|
||||
sm
|
||||
@click="openComposeConversationModal(toggle)"
|
||||
/>
|
||||
</template>
|
||||
</ComposeConversation>
|
||||
@@ -387,45 +319,41 @@ export default {
|
||||
sm
|
||||
@click="toggleEditModal"
|
||||
/>
|
||||
<NextButton
|
||||
v-tooltip.top-end="$t('CONTACT_PANEL.MERGE_CONTACT')"
|
||||
icon="i-ph-arrows-merge"
|
||||
slate
|
||||
faded
|
||||
sm
|
||||
:disabled="uiFlags.isMerging"
|
||||
@click="openMergeModal"
|
||||
/>
|
||||
<NextButton
|
||||
<ContactMergeModal :primary-contact="contact">
|
||||
<template #trigger>
|
||||
<NextButton
|
||||
v-tooltip.top-end="$t('CONTACT_PANEL.MERGE_CONTACT')"
|
||||
icon="i-ph-arrows-merge"
|
||||
slate
|
||||
faded
|
||||
sm
|
||||
:disabled="uiFlags.isMerging"
|
||||
/>
|
||||
</template>
|
||||
</ContactMergeModal>
|
||||
<ContactDeleteModal
|
||||
v-if="isAdmin"
|
||||
v-tooltip.top-end="$t('DELETE_CONTACT.BUTTON_LABEL')"
|
||||
icon="i-ph-trash"
|
||||
slate
|
||||
faded
|
||||
sm
|
||||
ruby
|
||||
:disabled="uiFlags.isDeleting"
|
||||
@click="toggleDeleteModal"
|
||||
/>
|
||||
:contact="contact"
|
||||
@deleted="$emit('panelClose')"
|
||||
>
|
||||
<template #trigger>
|
||||
<NextButton
|
||||
v-tooltip.top-end="$t('DELETE_CONTACT.BUTTON_LABEL')"
|
||||
icon="i-ph-trash"
|
||||
slate
|
||||
faded
|
||||
sm
|
||||
ruby
|
||||
:disabled="uiFlags.isDeleting"
|
||||
/>
|
||||
</template>
|
||||
</ContactDeleteModal>
|
||||
</div>
|
||||
<EditContact
|
||||
v-if="showEditModal"
|
||||
:show="showEditModal"
|
||||
:contact="contact"
|
||||
@cancel="toggleEditModal"
|
||||
/>
|
||||
<ContactMergeModal ref="mergeModal" :primary-contact="contact" />
|
||||
</div>
|
||||
<woot-delete-modal
|
||||
v-if="showDeleteModal"
|
||||
v-model:show="showDeleteModal"
|
||||
:on-close="closeDelete"
|
||||
:on-confirm="confirmDeletion"
|
||||
:title="$t('DELETE_CONTACT.CONFIRM.TITLE')"
|
||||
:message="$t('DELETE_CONTACT.CONFIRM.MESSAGE')"
|
||||
:message-value="confirmDeleteMessage"
|
||||
:confirm-text="$t('DELETE_CONTACT.CONFIRM.YES')"
|
||||
:reject-text="$t('DELETE_CONTACT.CONFIRM.NO')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,74 +1,70 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
<script setup>
|
||||
import { useStore } from 'dashboard/composables/store';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
|
||||
import ContactForm from './ContactForm.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ContactForm,
|
||||
},
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
contact: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
emits: ['cancel', 'update:show'],
|
||||
computed: {
|
||||
...mapGetters({
|
||||
uiFlags: 'contacts/getUIFlags',
|
||||
}),
|
||||
localShow: {
|
||||
get() {
|
||||
return this.show;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('update:show', value);
|
||||
},
|
||||
},
|
||||
},
|
||||
const props = defineProps({
|
||||
show: { type: Boolean, default: false },
|
||||
contact: { type: Object, default: () => ({}) },
|
||||
});
|
||||
|
||||
methods: {
|
||||
onCancel() {
|
||||
this.$emit('cancel');
|
||||
},
|
||||
onSuccess() {
|
||||
this.$emit('cancel');
|
||||
},
|
||||
async onSubmit(contactItem) {
|
||||
await this.$store.dispatch('contacts/update', contactItem);
|
||||
await this.$store.dispatch(
|
||||
'contacts/fetchContactableInbox',
|
||||
this.contact.id
|
||||
);
|
||||
},
|
||||
},
|
||||
const emit = defineEmits(['cancel']);
|
||||
|
||||
const store = useStore();
|
||||
const uiFlags = useMapGetter('contacts/getUIFlags');
|
||||
|
||||
const onCancel = () => emit('cancel');
|
||||
|
||||
const onSubmit = async contactItem => {
|
||||
await store.dispatch('contacts/update', contactItem);
|
||||
await store.dispatch('contacts/fetchContactableInbox', props.contact.id);
|
||||
};
|
||||
|
||||
// Restore Escape-to-close behavior that was provided by woot-modal before
|
||||
// this drawer was reimplemented as a plain fixed panel.
|
||||
useKeyboardEvents({
|
||||
Escape: {
|
||||
action: () => {
|
||||
if (props.show) onCancel();
|
||||
},
|
||||
allowOnFocusedInput: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<woot-modal
|
||||
v-model:show="localShow"
|
||||
:on-close="onCancel"
|
||||
modal-type="right-aligned"
|
||||
<transition
|
||||
enter-active-class="transition duration-200 ease-out"
|
||||
enter-from-class="ltr:translate-x-full rtl:-translate-x-full opacity-0"
|
||||
leave-active-class="transition duration-150 ease-in"
|
||||
leave-to-class="ltr:translate-x-[30%] rtl:-translate-x-[30%] opacity-0"
|
||||
>
|
||||
<div class="flex flex-col h-auto overflow-auto">
|
||||
<woot-modal-header
|
||||
:header-title="`${$t('EDIT_CONTACT.TITLE')} - ${
|
||||
contact.name || contact.email
|
||||
}`"
|
||||
:header-content="$t('EDIT_CONTACT.DESC')"
|
||||
/>
|
||||
<div
|
||||
v-if="show"
|
||||
class="fixed inset-y-0 ltr:right-0 rtl:left-0 z-50 flex flex-col w-[30rem] max-w-full h-full bg-n-surface-2 ltr:border-l rtl:border-r border-n-weak shadow-lg overflow-auto"
|
||||
>
|
||||
<div class="flex items-center justify-between px-8 pt-8 pb-2">
|
||||
<div>
|
||||
<h2 class="text-lg font-medium text-n-slate-12 mb-1">
|
||||
{{
|
||||
`${$t('EDIT_CONTACT.TITLE')} - ${contact.name || contact.email}`
|
||||
}}
|
||||
</h2>
|
||||
<p class="text-sm text-n-slate-11 mb-0">
|
||||
{{ $t('EDIT_CONTACT.DESC') }}
|
||||
</p>
|
||||
</div>
|
||||
<Button icon="i-lucide-x" slate ghost sm @click="onCancel" />
|
||||
</div>
|
||||
<ContactForm
|
||||
:contact="contact"
|
||||
:in-progress="uiFlags.isUpdating"
|
||||
:on-submit="onSubmit"
|
||||
@success="onSuccess"
|
||||
@success="onCancel"
|
||||
@cancel="onCancel"
|
||||
/>
|
||||
</div>
|
||||
</woot-modal>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user