feat: Create custom attributes for a contact from CRM (#2299)
* feat: Creates cutom attributes for a contact from CRM * Review fixes * Change inline forms edit icon size * Review fixes * Fix validation labels color Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
parent
64718eb879
commit
26ba8e6ff7
10 changed files with 248 additions and 11 deletions
|
@ -2,6 +2,7 @@ import { addDecorator } from '@storybook/vue';
|
|||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import VueI18n from 'vue-i18n';
|
||||
import Vuelidate from 'vuelidate';
|
||||
|
||||
import WootUiKit from '../app/javascript/dashboard/components';
|
||||
import i18n from '../app/javascript/dashboard/i18n';
|
||||
|
@ -9,6 +10,7 @@ import i18n from '../app/javascript/dashboard/i18n';
|
|||
import '../app/javascript/dashboard/assets/scss/storybook.scss';
|
||||
|
||||
Vue.use(VueI18n);
|
||||
Vue.use(Vuelidate);
|
||||
Vue.use(WootUiKit);
|
||||
Vue.use(Vuex);
|
||||
|
||||
|
|
|
@ -17,9 +17,6 @@
|
|||
"NO_RECORDS_FOUND": "There are no previous conversations associated to this contact.",
|
||||
"TITLE": "Previous Conversations"
|
||||
},
|
||||
"CUSTOM_ATTRIBUTES": {
|
||||
"TITLE": "Custom Attributes"
|
||||
},
|
||||
"LABELS": {
|
||||
"TITLE": "Conversation Labels",
|
||||
"MODAL": {
|
||||
|
@ -170,5 +167,26 @@
|
|||
"FOOTER": {
|
||||
"BUTTON": "View all notes"
|
||||
}
|
||||
},
|
||||
"CUSTOM_ATTRIBUTES": {
|
||||
"TITLE": "Custom Attributes",
|
||||
"BUTTON": "Add custom attribute",
|
||||
"ADD": {
|
||||
"TITLE": "Create custom attribute",
|
||||
"DESC": "Add custom information to this contact."
|
||||
},
|
||||
"FORM": {
|
||||
"CREATE": "Add attribute",
|
||||
"CANCEL": "Cancel",
|
||||
"NAME": {
|
||||
"LABEL": "Custom attribute name",
|
||||
"PLACEHOLDER": "Eg: shopify id",
|
||||
"ERROR": "Invalid custom attribute name"
|
||||
},
|
||||
"VALUE": {
|
||||
"LABEL": "Attribute value",
|
||||
"PLACEHOLDER": "Eg: 11901 "
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
<template>
|
||||
<modal :show.sync="show" :on-close="onClose">
|
||||
<woot-modal-header
|
||||
:header-title="$t('CUSTOM_ATTRIBUTES.ADD.TITLE')"
|
||||
:header-content="$t('CUSTOM_ATTRIBUTES.ADD.DESC')"
|
||||
/>
|
||||
<form class="row" @submit.prevent="addCustomAttribute">
|
||||
<woot-input
|
||||
v-model.trim="attributeName"
|
||||
:class="{ error: $v.attributeName.$error }"
|
||||
class="medium-12 columns"
|
||||
:error="attributeNameError"
|
||||
:label="$t('CUSTOM_ATTRIBUTES.FORM.NAME.LABEL')"
|
||||
:placeholder="$t('CUSTOM_ATTRIBUTES.FORM.NAME.PLACEHOLDER')"
|
||||
@input="$v.attributeName.$touch"
|
||||
/>
|
||||
<woot-input
|
||||
v-model.trim="attributeValue"
|
||||
class="medium-12 columns"
|
||||
:label="$t('CUSTOM_ATTRIBUTES.FORM.VALUE.LABEL')"
|
||||
:placeholder="$t('CUSTOM_ATTRIBUTES.FORM.VALUE.PLACEHOLDER')"
|
||||
/>
|
||||
<div class="modal-footer">
|
||||
<woot-button
|
||||
:is-disabled="$v.attributeName.$invalid || isCreating"
|
||||
:is-loading="isCreating"
|
||||
>
|
||||
{{ $t('CUSTOM_ATTRIBUTES.FORM.CREATE') }}
|
||||
</woot-button>
|
||||
<woot-button variant="clear" @click.prevent="onClose">
|
||||
{{ $t('CUSTOM_ATTRIBUTES.FORM.CANCEL') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</form>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Modal from 'dashboard/components/Modal';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { required, minLength } from 'vuelidate/lib/validators';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Modal,
|
||||
},
|
||||
mixins: [alertMixin],
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
isCreating: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
attributeValue: '',
|
||||
attributeName: '',
|
||||
};
|
||||
},
|
||||
validations: {
|
||||
attributeName: {
|
||||
required,
|
||||
minLength: minLength(2),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
attributeNameError() {
|
||||
if (this.$v.attributeName.$error) {
|
||||
return this.$t('CUSTOM_ATTRIBUTES.FORM.NAME.ERROR');
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
addCustomAttribute() {
|
||||
this.$emit('create', {
|
||||
attributeValue: this.attributeValue,
|
||||
attributeName: this.attributeName || '',
|
||||
});
|
||||
this.reset();
|
||||
this.$emit('cancel');
|
||||
},
|
||||
onClose() {
|
||||
this.reset();
|
||||
this.$emit('cancel');
|
||||
},
|
||||
reset() {
|
||||
this.attributeValue = '';
|
||||
this.attributeName = '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -34,7 +34,7 @@
|
|||
<woot-button
|
||||
v-if="showEdit"
|
||||
variant="clear link"
|
||||
size="tiny"
|
||||
size="small"
|
||||
color-scheme="secondary"
|
||||
icon="ion-compose"
|
||||
class-names="edit-button"
|
||||
|
|
|
@ -26,6 +26,27 @@
|
|||
:show-edit="true"
|
||||
@update="onLocationUpdate"
|
||||
/>
|
||||
<div
|
||||
v-for="attribute in customAttributekeys"
|
||||
:key="attribute"
|
||||
class="custom-attribute--row"
|
||||
>
|
||||
<attribute
|
||||
:label="attribute"
|
||||
icon="ion-arrow-right-c"
|
||||
:value="customAttributes[attribute]"
|
||||
:show-edit="true"
|
||||
@update="value => onCustomAttributeUpdate(attribute, value)"
|
||||
/>
|
||||
</div>
|
||||
<woot-button
|
||||
size="small"
|
||||
variant="link"
|
||||
icon="ion-plus"
|
||||
@click="handleCustomCreate"
|
||||
>
|
||||
{{ $t('CUSTOM_ATTRIBUTES.ADD.TITLE') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
@ -48,17 +69,33 @@ export default {
|
|||
const { company = {} } = this.contact;
|
||||
return company;
|
||||
},
|
||||
customAttributes() {
|
||||
const { custom_attributes: customAttributes = {} } = this.contact;
|
||||
return customAttributes;
|
||||
},
|
||||
customAttributekeys() {
|
||||
return Object.keys(this.customAttributes).filter(key => {
|
||||
const value = this.customAttributes[key];
|
||||
return value !== null && value !== undefined;
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onEmailUpdate(value) {
|
||||
this.$emit('update', { email: value });
|
||||
},
|
||||
onPhoneUpdate(value) {
|
||||
this.$emit('update', { phone: value });
|
||||
this.$emit('update', { phone_number: value });
|
||||
},
|
||||
onLocationUpdate(value) {
|
||||
this.$emit('update', { location: value });
|
||||
},
|
||||
handleCustomCreate() {
|
||||
this.$emit('create-attribute');
|
||||
},
|
||||
onCustomAttributeUpdate(key, value) {
|
||||
this.$emit('update', { custom_attributes: { [key]: value } });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
@message="toggleConversationModal"
|
||||
@edit="toggleEditModal"
|
||||
/>
|
||||
<contact-fields :contact="contact" :edit="null" />
|
||||
<contact-fields
|
||||
:contact="contact"
|
||||
@update="updateField"
|
||||
@create-attribute="toggleCustomAttributeModal"
|
||||
/>
|
||||
<edit-contact
|
||||
v-if="showEditModal"
|
||||
:show="showEditModal"
|
||||
|
@ -13,24 +17,32 @@
|
|||
@cancel="toggleEditModal"
|
||||
/>
|
||||
<new-conversation
|
||||
v-if="enableNewConversation"
|
||||
:show="showConversationModal"
|
||||
:contact="contact"
|
||||
@cancel="toggleConversationModal"
|
||||
/>
|
||||
<add-custom-attribute
|
||||
:show="showCustomAttributeModal"
|
||||
@cancel="toggleCustomAttributeModal"
|
||||
@create="createCustomAttribute"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import EditContact from 'dashboard/routes/dashboard/conversation/contact/EditContact';
|
||||
import NewConversation from 'dashboard/routes/dashboard/conversation/contact/NewConversation';
|
||||
import AddCustomAttribute from 'dashboard/modules/contact/components/AddCustomAttribute';
|
||||
import ContactIntro from './ContactIntro';
|
||||
import ContactFields from './ContactFields';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AddCustomAttribute,
|
||||
ContactFields,
|
||||
ContactIntro,
|
||||
EditContact,
|
||||
NewConversation,
|
||||
ContactIntro,
|
||||
ContactFields,
|
||||
},
|
||||
props: {
|
||||
contact: {
|
||||
|
@ -40,17 +52,48 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
showCustomAttributeModal: false,
|
||||
showEditModal: false,
|
||||
showConversationModal: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
enableNewConversation() {
|
||||
return this.contact && this.contact.id;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleCustomAttributeModal() {
|
||||
this.showCustomAttributeModal = !this.showCustomAttributeModal;
|
||||
},
|
||||
toggleEditModal() {
|
||||
this.showEditModal = !this.showEditModal;
|
||||
},
|
||||
toggleConversationModal() {
|
||||
this.showConversationModal = !this.showConversationModal;
|
||||
},
|
||||
createCustomAttribute(data) {
|
||||
const { id } = this.contact;
|
||||
const { attributeValue, attributeName } = data;
|
||||
const updatedFields = {
|
||||
id,
|
||||
custom_attributes: {
|
||||
[attributeName]: attributeValue,
|
||||
},
|
||||
};
|
||||
this.updateContact(updatedFields);
|
||||
},
|
||||
updateField(data) {
|
||||
const { id } = this.contact;
|
||||
const updatedFields = {
|
||||
id,
|
||||
...data,
|
||||
};
|
||||
this.updateContact(updatedFields);
|
||||
},
|
||||
updateContact(contactItem) {
|
||||
this.$store.dispatch('contacts/update', contactItem);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -55,10 +55,14 @@ export default {
|
|||
|
||||
.wrap {
|
||||
@include three-column-grid(27.2rem);
|
||||
background: var(--color-background);
|
||||
min-height: 0;
|
||||
|
||||
background: var(--color-background-light);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.left {
|
||||
overflow: auto;
|
||||
}
|
||||
.center {
|
||||
border-right: 1px solid var(--color-border);
|
||||
border-left: 1px solid var(--color-border);
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import AddCustomAttribute from '../components/AddCustomAttribute';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
export default {
|
||||
title: 'Components/Contact/AddCustomAttribute',
|
||||
component: AddCustomAttribute,
|
||||
argTypes: {
|
||||
show: {
|
||||
defaultValue: true,
|
||||
control: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
isCreating: {
|
||||
defaultValue: false,
|
||||
control: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { AddCustomAttribute },
|
||||
template: '<add-custom-attribute v-bind="$props" @create="onCreate" />',
|
||||
});
|
||||
|
||||
export const DefaultAttribute = Template.bind({});
|
||||
DefaultAttribute.args = {
|
||||
onCreate: action('edit'),
|
||||
};
|
|
@ -10,7 +10,7 @@ const Template = (args, { argTypes }) => ({
|
|||
props: Object.keys(argTypes),
|
||||
components: { ContactFields },
|
||||
template:
|
||||
'<contact-fields v-bind="$props" :contact="contact" @update="onUpdate" />',
|
||||
'<contact-fields v-bind="$props" :contact="contact" @update="onUpdate" @create-attribute="onCreate" />',
|
||||
});
|
||||
|
||||
export const DefaultContactFields = Template.bind({});
|
||||
|
@ -39,4 +39,5 @@ DefaultContactFields.args = {
|
|||
},
|
||||
},
|
||||
onUpdate: action('update'),
|
||||
onCreate: action('create'),
|
||||
};
|
||||
|
|
|
@ -73,6 +73,9 @@ export default {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.contact-manage-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
flex: 1 1 0;
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in a new issue