feat: Render contact custom attributes in contact/conversation sidebar (#3310)

This commit is contained in:
Muhsin Keloth
2021-11-11 15:23:33 +05:30
committed by GitHub
parent e12edb51a2
commit 76370267f3
33 changed files with 416 additions and 124 deletions

View File

@@ -13,12 +13,21 @@
@panel-close="onClose"
/>
<accordion-item
:title="$t('CONTACT_PANEL.SIDEBAR_SECTIONS.CUSTOM_ATTRIBUTES')"
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.CONTACT_ATTRIBUTES')"
:is-open="isContactSidebarItemOpen('is_ct_custom_attr_open')"
compact
@click="value => toggleSidebarUIState('is_ct_custom_attr_open', value)"
>
<contact-custom-attributes
<custom-attributes
:contact-id="contact.id"
attribute-type="contact_attribute"
attribute-class="conversation--attribute"
:custom-attributes="contact.custom_attributes"
class="even"
/>
<custom-attribute-selector
attribute-type="contact_attribute"
:contact-id="contact.id"
/>
</accordion-item>
<accordion-item
@@ -45,9 +54,10 @@
<script>
import AccordionItem from 'dashboard/components/Accordion/AccordionItem';
import ContactConversations from 'dashboard/routes/dashboard/conversation/ContactConversations';
import ContactCustomAttributes from 'dashboard/routes/dashboard/conversation/ContactCustomAttributes';
import ContactInfo from 'dashboard/routes/dashboard/conversation/contact/ContactInfo';
import ContactLabel from 'dashboard/routes/dashboard/contacts/components/ContactLabels.vue';
import CustomAttributes from 'dashboard/routes/dashboard/conversation/customAttributes/CustomAttributes.vue';
import CustomAttributeSelector from 'dashboard/routes/dashboard/conversation/customAttributes/CustomAttributeSelector.vue';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
@@ -55,9 +65,10 @@ export default {
components: {
AccordionItem,
ContactConversations,
ContactCustomAttributes,
ContactInfo,
ContactLabel,
CustomAttributes,
CustomAttributeSelector,
},
mixins: [uiSettingsMixin],
props: {

View File

@@ -85,19 +85,26 @@
>
</conversation-info>
</accordion-item>
<accordion-item
v-if="hasContactAttributes"
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.CONTACT_ATTRIBUTES')"
:is-open="isContactSidebarItemOpen('is_contact_attributes_open')"
compact
@click="
value => toggleSidebarUIState('is_contact_attributes_open', value)
"
>
<contact-custom-attributes
:custom-attributes="contact.custom_attributes"
<custom-attributes
attribute-type="contact_attribute"
attribute-class="conversation--attribute"
class="even"
:contact-id="contact.id"
/>
<custom-attribute-selector
attribute-type="contact_attribute"
:contact-id="contact.id"
/>
</accordion-item>
<accordion-item
v-if="contact.id"
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.PREVIOUS_CONVERSATION')"
@@ -119,24 +126,27 @@ import agentMixin from '../../../mixins/agentMixin';
import AccordionItem from 'dashboard/components/Accordion/AccordionItem';
import ContactConversations from './ContactConversations.vue';
import ContactCustomAttributes from './ContactCustomAttributes';
import ContactDetailsItem from './ContactDetailsItem.vue';
import ContactInfo from './contact/ContactInfo';
import ConversationInfo from './ConversationInfo';
import ConversationLabels from './labels/LabelBox.vue';
import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown.vue';
import CustomAttributes from './customAttributes/CustomAttributes.vue';
import CustomAttributeSelector from './customAttributes/CustomAttributeSelector.vue';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
export default {
components: {
AccordionItem,
ContactConversations,
ContactCustomAttributes,
ContactDetailsItem,
ContactInfo,
ConversationInfo,
ConversationLabels,
MultiselectDropdown,
CustomAttributes,
CustomAttributeSelector,
},
mixins: [alertMixin, agentMixin, uiSettingsMixin],
props: {

View File

@@ -27,7 +27,6 @@
{{ $t('CUSTOM_ATTRIBUTES.FORM.ATTRIBUTE_SELECT.NO_RESULT') }}
</div>
<woot-button
variant="hollow"
class="add"
icon="ion-plus-round"
size="tiny"
@@ -53,6 +52,7 @@ export default {
type: String,
default: 'conversation_attribute',
},
contactId: { type: Number, default: null },
},
data() {
@@ -76,7 +76,7 @@ export default {
},
noResult() {
return this.filteredAttributes.length === 0 && this.search !== '';
return this.filteredAttributes.length === 0;
},
},
@@ -90,7 +90,7 @@ export default {
},
addNewAttribute() {
this.$router.push(
`/app/accounts/${this.accountId}/settings/attributes/list`
`/app/accounts/${this.accountId}/settings/custom-attributes/list`
);
},
async onAddAttribute(attribute) {
@@ -138,7 +138,6 @@ export default {
width: 100%;
.add {
float: right;
margin-top: var(--space-one);
}
}

View File

@@ -22,6 +22,7 @@
<custom-attribute-drop-down
v-if="showAttributeDropDown"
:attribute-type="attributeType"
:contact-id="contactId"
@add-attribute="addAttribute"
/>
</div>
@@ -47,6 +48,7 @@ export default {
type: String,
default: 'conversation_attribute',
},
contactId: { type: Number, default: null },
},
data() {
return {
@@ -55,15 +57,25 @@ export default {
},
methods: {
async addAttribute(attribute) {
const { attribute_key } = attribute;
try {
await this.$store.dispatch('updateCustomAttributes', {
conversationId: this.conversationId,
customAttributes: {
...this.customAttributes,
[attribute_key]: null,
},
});
const { attribute_key } = attribute;
if (this.attributeType === 'conversation_attribute') {
await this.$store.dispatch('updateCustomAttributes', {
conversationId: this.conversationId,
customAttributes: {
...this.customAttributes,
[attribute_key]: null,
},
});
} else {
await this.$store.dispatch('contacts/update', {
id: this.contactId,
custom_attributes: {
...this.customAttributes,
[attribute_key]: null,
},
});
}
bus.$emit(BUS_EVENTS.FOCUS_CUSTOM_ATTRIBUTE, attribute_key);
this.showAlert(this.$t('CUSTOM_ATTRIBUTES.FORM.ADD.SUCCESS'));
} catch (error) {

View File

@@ -37,14 +37,23 @@ export default {
type: String,
default: '',
},
contactId: { type: Number, default: null },
},
methods: {
async onUpdate(key, value) {
const updatedAttributes = { ...this.customAttributes, [key]: value };
try {
await this.$store.dispatch('updateCustomAttributes', {
conversationId: this.conversationId,
customAttributes: { ...this.customAttributes, [key]: value },
});
if (this.attributeType === 'conversation_attribute') {
await this.$store.dispatch('updateCustomAttributes', {
conversationId: this.conversationId,
customAttributes: updatedAttributes,
});
} else {
this.$store.dispatch('contacts/update', {
id: this.contactId,
custom_attributes: updatedAttributes,
});
}
this.showAlert(this.$t('CUSTOM_ATTRIBUTES.FORM.UPDATE.SUCCESS'));
} catch (error) {
const errorMessage =
@@ -54,13 +63,20 @@ export default {
}
},
async onDelete(key) {
const { [key]: remove, ...updatedAttributes } = this.customAttributes;
try {
await this.$store.dispatch('updateCustomAttributes', {
conversationId: this.conversationId,
customAttributes: updatedAttributes,
});
const { [key]: remove, ...updatedAttributes } = this.customAttributes;
if (this.attributeType === 'conversation_attribute') {
await this.$store.dispatch('updateCustomAttributes', {
conversationId: this.conversationId,
customAttributes: updatedAttributes,
});
} else {
this.$store.dispatch('contacts/deleteCustomAttributes', {
id: this.contactId,
customAttributes: [key],
});
}
this.showAlert(this.$t('CUSTOM_ATTRIBUTES.FORM.DELETE.SUCCESS'));
} catch (error) {
const errorMessage =

View File

@@ -27,6 +27,7 @@
: ''
"
:placeholder="$t('ATTRIBUTES_MGMT.ADD.FORM.NAME.PLACEHOLDER')"
@input="onDisplayNameChange"
@blur="$v.displayName.$touch"
/>
<label :class="{ error: $v.description.$error }">
@@ -53,22 +54,22 @@
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.TYPE.ERROR') }}
</span>
</label>
<div v-if="displayName" class="medium-12 columns">
<label>
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.KEY.LABEL') }}
<i class="ion-help" />
</label>
<p class="key-value text-truncate">
{{ attributeKey }}
</p>
</div>
<woot-input
v-model="attributeKey"
:label="$t('ATTRIBUTES_MGMT.ADD.FORM.KEY.LABEL')"
type="text"
:class="{ error: $v.attributeKey.$error }"
:error="
$v.attributeKey.$error
? $t('ATTRIBUTES_MGMT.ADD.FORM.KEY.ERROR')
: ''
"
:placeholder="$t('ATTRIBUTES_MGMT.ADD.FORM.KEY.PLACEHOLDER')"
@blur="$v.attributeKey.$touch"
/>
<div class="modal-footer">
<woot-submit-button
:disabled="
$v.displayName.$invalid ||
$v.description.$invalid ||
uiFlags.isCreating
"
:disabled="isButtonDisabled"
:button-text="$t('ATTRIBUTES_MGMT.ADD.SUBMIT')"
/>
<button class="button clear" @click.prevent="onClose">
@@ -103,6 +104,7 @@ export default {
description: '',
attributeModel: 0,
attributeType: 0,
attributeKey: '',
models: ATTRIBUTE_MODELS,
types: ATTRIBUTE_TYPES,
show: true,
@@ -113,8 +115,12 @@ export default {
...mapGetters({
uiFlags: 'getUIFlags',
}),
attributeKey() {
return convertToSlug(this.displayName);
isButtonDisabled() {
return (
this.$v.displayName.$invalid ||
this.$v.description.$invalid ||
this.uiFlags.isCreating
);
},
},
@@ -132,9 +138,15 @@ export default {
attributeType: {
required,
},
attributeKey: {
required,
},
},
methods: {
onDisplayNameChange() {
this.attributeKey = convertToSlug(this.displayName);
},
async addAttributes() {
try {
await this.$store.dispatch('attributes/create', {

View File

@@ -135,6 +135,10 @@ export default {
key: 0,
name: this.$t('ATTRIBUTES_MGMT.TABS.CONVERSATION'),
},
{
key: 1,
name: this.$t('ATTRIBUTES_MGMT.TABS.CONTACT'),
},
];
},
deleteConfirmText() {

View File

@@ -41,15 +41,20 @@
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.TYPE.ERROR') }}
</span>
</label>
<div v-if="displayName" class="medium-12 columns">
<label>
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.KEY.LABEL') }}
<i class="ion-help" />
</label>
<p class="key-value text-truncate">
{{ attributeKey }}
</p>
</div>
<woot-input
v-model.trim="attributeKey"
:label="$t('ATTRIBUTES_MGMT.ADD.FORM.KEY.LABEL')"
type="text"
:class="{ error: $v.attributeKey.$error }"
:error="
$v.attributeKey.$error
? $t('ATTRIBUTES_MGMT.ADD.FORM.KEY.ERROR')
: ''
"
:placeholder="$t('ATTRIBUTES_MGMT.ADD.FORM.KEY.PLACEHOLDER')"
readonly
@blur="$v.attributeKey.$touch"
/>
</div>
<div class="modal-footer">
<woot-button
@@ -69,7 +74,6 @@
<script>
import { mapGetters } from 'vuex';
import { required, minLength } from 'vuelidate/lib/validators';
import { convertToSlug } from 'dashboard/helper/commons.js';
import { ATTRIBUTE_TYPES } from './constants';
import alertMixin from 'shared/mixins/alertMixin';
export default {
@@ -92,6 +96,7 @@ export default {
attributeType: 0,
types: ATTRIBUTE_TYPES,
show: true,
attributeKey: '',
};
},
validations: {
@@ -105,6 +110,9 @@ export default {
required,
minLength: minLength(1),
},
attributeKey: {
required,
},
},
computed: {
...mapGetters({
@@ -115,9 +123,6 @@ export default {
this.selectedAttribute.attribute_display_name
}`;
},
attributeKey() {
return convertToSlug(this.displayName);
},
selectedAttributeType() {
return this.types.find(
item =>
@@ -137,6 +142,7 @@ export default {
this.displayName = this.selectedAttribute.attribute_display_name;
this.description = this.selectedAttribute.attribute_description;
this.attributeType = this.selectedAttributeType;
this.attributeKey = this.selectedAttribute.attribute_key;
},
async editAttributes() {
this.$v.$touch();

View File

@@ -5,7 +5,7 @@ import { frontendURL } from '../../../../helper/URLHelper';
export default {
routes: [
{
path: frontendURL('accounts/:accountId/settings/attributes'),
path: frontendURL('accounts/:accountId/settings/custom-attributes'),
component: SettingsContent,
props: {
headerTitle: 'ATTRIBUTES_MGMT.HEADER',

View File

@@ -3,6 +3,10 @@ export const ATTRIBUTE_MODELS = [
id: 0,
option: 'Conversation',
},
{
id: 1,
option: 'Contact',
},
];
export const ATTRIBUTE_TYPES = [