feat: Reverse the contact merge (#8057)

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
Sivin Varghese
2023-10-10 08:20:48 +05:30
committed by GitHub
parent 86ca90aa15
commit b6ba0f343e
6 changed files with 87 additions and 126 deletions

View File

@@ -9,7 +9,6 @@ class Api::V1::Accounts::Actions::ContactMergesController < Api::V1::Accounts::B
mergee_contact: @mergee_contact mergee_contact: @mergee_contact
) )
contact_merge_action.perform contact_merge_action.perform
render json: @base_contact
end end
private private

View File

@@ -343,17 +343,17 @@
"DESCRIPTION": "Merge contacts to combine two profiles into one, including all attributes and conversations. In case of conflict, the Primary contacts attributes will take precedence.", "DESCRIPTION": "Merge contacts to combine two profiles into one, including all attributes and conversations. In case of conflict, the Primary contacts attributes will take precedence.",
"PRIMARY": { "PRIMARY": {
"TITLE": "Primary contact", "TITLE": "Primary contact",
"HELP_LABEL": "To be kept" "HELP_LABEL": "To be deleted"
}, },
"CHILD": { "PARENT": {
"TITLE": "Contact to merge", "TITLE": "Contact to merge",
"PLACEHOLDER": "Search for a contact", "PLACEHOLDER": "Search for a contact",
"HELP_LABEL": "To be deleted" "HELP_LABEL": "To be kept"
}, },
"SUMMARY": { "SUMMARY": {
"TITLE": "Summary", "TITLE": "Summary",
"DELETE_WARNING": "Contact of <strong>%{childContactName}</strong> will be deleted.", "DELETE_WARNING": "Contact of <strong>%{primaryContactName}</strong> will be deleted.",
"ATTRIBUTE_WARNING": "Contact details of <strong>%{childContactName}</strong> will be copied to <strong>%{primaryContactName}</strong>." "ATTRIBUTE_WARNING": "Contact details of <strong>%{primaryContactName}</strong> will be copied to <strong>%{parentContactName}</strong>."
}, },
"SEARCH": { "SEARCH": {
"ERROR": "ERROR_MESSAGE" "ERROR": "ERROR_MESSAGE"

View File

@@ -73,12 +73,12 @@ export default {
this.isSearching = false; this.isSearching = false;
} }
}, },
async onMergeContacts(childContactId) { async onMergeContacts(parentContactId) {
this.$track(CONTACTS_EVENTS.MERGED_CONTACTS); this.$track(CONTACTS_EVENTS.MERGED_CONTACTS);
try { try {
await this.$store.dispatch('contacts/merge', { await this.$store.dispatch('contacts/merge', {
childId: childContactId, childId: this.primaryContact.id,
parentId: this.primaryContact.id, parentId: parentContactId,
}); });
this.showAlert(this.$t('MERGE_CONTACTS.FORM.SUCCESS_MESSAGE')); this.showAlert(this.$t('MERGE_CONTACTS.FORM.SUCCESS_MESSAGE'));
this.onClose(); this.onClose();

View File

@@ -1,62 +1,29 @@
<template> <template>
<form @submit.prevent="onSubmit"> <form @submit.prevent="onSubmit">
<div class="merge-contacts"> <div>
<div class="multiselect-wrap--medium"> <div>
<label class="multiselect__label">
{{ $t('MERGE_CONTACTS.PRIMARY.TITLE') }}
<woot-label
:title="$t('MERGE_CONTACTS.PRIMARY.HELP_LABEL')"
color-scheme="success"
small
class="label--merge-warning"
/>
</label>
<multiselect
:value="primaryContact"
disabled
:options="[]"
:show-labels="false"
label="name"
track-by="id"
>
<template slot="singleLabel" slot-scope="props">
<contact-dropdown-item
:thumbnail="props.option.thumbnail"
:name="props.option.name"
:identifier="props.option.id"
:email="props.option.email"
:phone-number="props.option.phoneNumber"
/>
</template>
</multiselect>
</div>
<div class="child-contact-wrap">
<div class="child-arrow">
<fluent-icon icon="arrow-up" class="up" size="17" />
</div>
<div <div
class="child-contact multiselect-wrap--medium" class="mt-1 multiselect-wrap--medium"
:class="{ error: $v.childContact.$error }" :class="{ error: $v.parentContact.$error }"
> >
<label class="multiselect__label"> <label class="multiselect__label">
{{ $t('MERGE_CONTACTS.CHILD.TITLE') {{ $t('MERGE_CONTACTS.PARENT.TITLE') }}
}}<woot-label <woot-label
:title="$t('MERGE_CONTACTS.CHILD.HELP_LABEL')" :title="$t('MERGE_CONTACTS.PARENT.HELP_LABEL')"
color-scheme="alert" color-scheme="success"
small small
class="label--merge-warning" class="ml-2"
/> />
</label> </label>
<multiselect <multiselect
v-model="childContact" v-model="parentContact"
:options="searchResults" :options="searchResults"
label="name" label="name"
track-by="id" track-by="id"
:internal-search="false" :internal-search="false"
:clear-on-select="false" :clear-on-select="false"
:show-labels="false" :show-labels="false"
:placeholder="$t('MERGE_CONTACTS.CHILD.PLACEHOLDER')" :placeholder="$t('MERGE_CONTACTS.PARENT.PLACEHOLDER')"
:allow-empty="true" :allow-empty="true"
:loading="isSearching" :loading="isSearching"
:max-height="150" :max-height="150"
@@ -85,17 +52,57 @@
{{ $t('AGENT_MGMT.SEARCH.NO_RESULTS') }} {{ $t('AGENT_MGMT.SEARCH.NO_RESULTS') }}
</span> </span>
</multiselect> </multiselect>
<span v-if="$v.childContact.$error" class="message"> <span v-if="$v.parentContact.$error" class="message">
{{ $t('MERGE_CONTACTS.FORM.CHILD_CONTACT.ERROR') }} {{ $t('MERGE_CONTACTS.FORM.CHILD_CONTACT.ERROR') }}
</span> </span>
</div> </div>
</div> </div>
<div class="multiselect-wrap--medium flex">
<div
class="w-8 relative text-base text-slate-100 dark:text-slate-600 after:content-[''] after:h-12 after:w-0 after:left-4 after:absolute after:border-l after:border-solid after:border-slate-100 after:dark:border-slate-600 before:content-[''] before:h-0 before:w-4 before:left-4 before:top-12 before:absolute before:border-b before:border-solid before:border-slate-100 before:dark:border-slate-600"
>
<fluent-icon
icon="arrow-up"
class="absolute -top-1 left-2"
size="17"
/>
</div>
<div class="flex flex-col w-full">
<label class="multiselect__label">
{{ $t('MERGE_CONTACTS.PRIMARY.TITLE') }}
<woot-label
:title="$t('MERGE_CONTACTS.PRIMARY.HELP_LABEL')"
color-scheme="alert"
small
class="ml-2"
/>
</label>
<multiselect
:value="primaryContact"
disabled
:options="[]"
:show-labels="false"
label="name"
track-by="id"
>
<template slot="singleLabel" slot-scope="props">
<contact-dropdown-item
:thumbnail="props.option.thumbnail"
:name="props.option.name"
:identifier="props.option.id"
:email="props.option.email"
:phone-number="props.option.phoneNumber"
/>
</template>
</multiselect>
</div>
</div>
</div> </div>
<merge-contact-summary <merge-contact-summary
:primary-contact-name="primaryContact.name" :primary-contact-name="primaryContact.name"
:child-contact-name="childContactName" :parent-contact-name="parentContactName"
/> />
<div class="footer"> <div class="mt-6 flex justify-end">
<woot-button variant="clear" @click.prevent="onCancel"> <woot-button variant="clear" @click.prevent="onCancel">
{{ $t('MERGE_CONTACTS.FORM.CANCEL') }} {{ $t('MERGE_CONTACTS.FORM.CANCEL') }}
</woot-button> </woot-button>
@@ -138,19 +145,19 @@ export default {
primaryContact: { primaryContact: {
required, required,
}, },
childContact: { parentContact: {
required, required,
}, },
}, },
data() { data() {
return { return {
childContact: undefined, parentContact: undefined,
}; };
}, },
computed: { computed: {
childContactName() { parentContactName() {
return this.childContact ? this.childContact.name : ''; return this.parentContact ? this.parentContact.name : '';
}, },
}, },
methods: { methods: {
@@ -162,7 +169,7 @@ export default {
if (this.$v.$invalid) { if (this.$v.$invalid) {
return; return;
} }
this.$emit('submit', this.childContact.id); this.$emit('submit', this.parentContact.id);
}, },
onCancel() { onCancel() {
this.$emit('cancel'); this.$emit('cancel');
@@ -172,51 +179,24 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.child-contact-wrap {
@apply flex w-full;
}
.child-contact {
@apply min-w-0 flex-grow flex-shrink-0;
}
.child-arrow {
@apply w-8 relative text-base text-slate-100 dark:text-slate-600;
}
.multiselect {
@apply mb-2;
}
.child-contact {
@apply mt-1;
}
.child-arrow::after {
@apply content-[''] h-12 w-0 left-5 absolute border-l border-solid border-slate-100 dark:border-slate-600;
}
.child-arrow::before {
@apply content-[''] h-0 w-4 left-5 top-12 absolute border-b border-solid border-slate-100 dark:border-slate-600;
}
.up {
@apply absolute -top-1 left-3;
}
.footer {
@apply mt-6 flex justify-end;
}
/* TDOD: Clean errors in forms style */ /* TDOD: Clean errors in forms style */
.error .message { .error .message {
@apply mt-0; @apply mt-0;
} }
.label--merge-warning {
@apply ml-2;
}
::v-deep { ::v-deep {
.multiselect { .multiselect {
@apply rounded-md; @apply rounded-md;
} }
.multiselect--disabled {
@apply border-0;
.multiselect__tags {
@apply border;
}
}
.multiselect__tags { .multiselect__tags {
@apply h-[52px]; @apply h-[52px];
} }

View File

@@ -1,26 +1,29 @@
<template> <template>
<div v-if="childContactName" class="merge-summary callout"> <div
<h5 class="text-block-title"> v-if="parentContactName"
class="my-4 relative p-2.5 border rounded-[4px] text-slate-800 dark:text-slate-100 border-slate-100 dark:border-slate-700 bg-white dark:bg-slate-800"
>
<h5 class="text-base font-medium text-slate-900 dark:text-white">
{{ $t('MERGE_CONTACTS.SUMMARY.TITLE') }} {{ $t('MERGE_CONTACTS.SUMMARY.TITLE') }}
</h5> </h5>
<ul class="summary-items"> <ul class="ml-0 list-none">
<li> <li>
<span class="bullet"></span> <span class="inline-block mr-1"></span>
<span <span
v-dompurify-html=" v-dompurify-html="
$t('MERGE_CONTACTS.SUMMARY.DELETE_WARNING', { $t('MERGE_CONTACTS.SUMMARY.DELETE_WARNING', {
childContactName, primaryContactName,
}) })
" "
/> />
</li> </li>
<li> <li>
<span class="bullet">✅</span> <span class="inline-block mr-1">✅</span>
<span <span
v-dompurify-html=" v-dompurify-html="
$t('MERGE_CONTACTS.SUMMARY.ATTRIBUTE_WARNING', { $t('MERGE_CONTACTS.SUMMARY.ATTRIBUTE_WARNING', {
childContactName,
primaryContactName, primaryContactName,
parentContactName,
}) })
" "
/> />
@@ -36,32 +39,10 @@ export default {
type: String, type: String,
default: '', default: '',
}, },
childContactName: { parentContactName: {
type: String, type: String,
default: '', default: '',
}, },
}, },
computed: {},
}; };
</script> </script>
<style lang="scss" scoped>
.merge-summary {
margin-top: var(--space-normal);
}
.summary-items {
margin-left: 0;
list-style: none;
li {
margin-bottom: var(--space-smaller);
}
}
.bullet {
display: inline-block;
margin-right: var(--space-smaller);
}
</style>

View File

@@ -0,0 +1 @@
json.partial! 'api/v1/models/contact', formats: [:json], resource: @base_contact