mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-03 12:37:56 +00:00
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:
committed by
GitHub
parent
64718eb879
commit
26ba8e6ff7
@@ -2,6 +2,7 @@ import { addDecorator } from '@storybook/vue';
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
import VueI18n from 'vue-i18n';
|
import VueI18n from 'vue-i18n';
|
||||||
|
import Vuelidate from 'vuelidate';
|
||||||
|
|
||||||
import WootUiKit from '../app/javascript/dashboard/components';
|
import WootUiKit from '../app/javascript/dashboard/components';
|
||||||
import i18n from '../app/javascript/dashboard/i18n';
|
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';
|
import '../app/javascript/dashboard/assets/scss/storybook.scss';
|
||||||
|
|
||||||
Vue.use(VueI18n);
|
Vue.use(VueI18n);
|
||||||
|
Vue.use(Vuelidate);
|
||||||
Vue.use(WootUiKit);
|
Vue.use(WootUiKit);
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,6 @@
|
|||||||
"NO_RECORDS_FOUND": "There are no previous conversations associated to this contact.",
|
"NO_RECORDS_FOUND": "There are no previous conversations associated to this contact.",
|
||||||
"TITLE": "Previous Conversations"
|
"TITLE": "Previous Conversations"
|
||||||
},
|
},
|
||||||
"CUSTOM_ATTRIBUTES": {
|
|
||||||
"TITLE": "Custom Attributes"
|
|
||||||
},
|
|
||||||
"LABELS": {
|
"LABELS": {
|
||||||
"TITLE": "Conversation Labels",
|
"TITLE": "Conversation Labels",
|
||||||
"MODAL": {
|
"MODAL": {
|
||||||
@@ -170,5 +167,26 @@
|
|||||||
"FOOTER": {
|
"FOOTER": {
|
||||||
"BUTTON": "View all notes"
|
"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
|
<woot-button
|
||||||
v-if="showEdit"
|
v-if="showEdit"
|
||||||
variant="clear link"
|
variant="clear link"
|
||||||
size="tiny"
|
size="small"
|
||||||
color-scheme="secondary"
|
color-scheme="secondary"
|
||||||
icon="ion-compose"
|
icon="ion-compose"
|
||||||
class-names="edit-button"
|
class-names="edit-button"
|
||||||
|
|||||||
@@ -26,6 +26,27 @@
|
|||||||
:show-edit="true"
|
:show-edit="true"
|
||||||
@update="onLocationUpdate"
|
@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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
@@ -48,17 +69,33 @@ export default {
|
|||||||
const { company = {} } = this.contact;
|
const { company = {} } = this.contact;
|
||||||
return company;
|
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: {
|
methods: {
|
||||||
onEmailUpdate(value) {
|
onEmailUpdate(value) {
|
||||||
this.$emit('update', { email: value });
|
this.$emit('update', { email: value });
|
||||||
},
|
},
|
||||||
onPhoneUpdate(value) {
|
onPhoneUpdate(value) {
|
||||||
this.$emit('update', { phone: value });
|
this.$emit('update', { phone_number: value });
|
||||||
},
|
},
|
||||||
onLocationUpdate(value) {
|
onLocationUpdate(value) {
|
||||||
this.$emit('update', { location: value });
|
this.$emit('update', { location: value });
|
||||||
},
|
},
|
||||||
|
handleCustomCreate() {
|
||||||
|
this.$emit('create-attribute');
|
||||||
|
},
|
||||||
|
onCustomAttributeUpdate(key, value) {
|
||||||
|
this.$emit('update', { custom_attributes: { [key]: value } });
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -5,7 +5,11 @@
|
|||||||
@message="toggleConversationModal"
|
@message="toggleConversationModal"
|
||||||
@edit="toggleEditModal"
|
@edit="toggleEditModal"
|
||||||
/>
|
/>
|
||||||
<contact-fields :contact="contact" :edit="null" />
|
<contact-fields
|
||||||
|
:contact="contact"
|
||||||
|
@update="updateField"
|
||||||
|
@create-attribute="toggleCustomAttributeModal"
|
||||||
|
/>
|
||||||
<edit-contact
|
<edit-contact
|
||||||
v-if="showEditModal"
|
v-if="showEditModal"
|
||||||
:show="showEditModal"
|
:show="showEditModal"
|
||||||
@@ -13,24 +17,32 @@
|
|||||||
@cancel="toggleEditModal"
|
@cancel="toggleEditModal"
|
||||||
/>
|
/>
|
||||||
<new-conversation
|
<new-conversation
|
||||||
|
v-if="enableNewConversation"
|
||||||
:show="showConversationModal"
|
:show="showConversationModal"
|
||||||
:contact="contact"
|
:contact="contact"
|
||||||
@cancel="toggleConversationModal"
|
@cancel="toggleConversationModal"
|
||||||
/>
|
/>
|
||||||
|
<add-custom-attribute
|
||||||
|
:show="showCustomAttributeModal"
|
||||||
|
@cancel="toggleCustomAttributeModal"
|
||||||
|
@create="createCustomAttribute"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import EditContact from 'dashboard/routes/dashboard/conversation/contact/EditContact';
|
import EditContact from 'dashboard/routes/dashboard/conversation/contact/EditContact';
|
||||||
import NewConversation from 'dashboard/routes/dashboard/conversation/contact/NewConversation';
|
import NewConversation from 'dashboard/routes/dashboard/conversation/contact/NewConversation';
|
||||||
|
import AddCustomAttribute from 'dashboard/modules/contact/components/AddCustomAttribute';
|
||||||
import ContactIntro from './ContactIntro';
|
import ContactIntro from './ContactIntro';
|
||||||
import ContactFields from './ContactFields';
|
import ContactFields from './ContactFields';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
AddCustomAttribute,
|
||||||
|
ContactFields,
|
||||||
|
ContactIntro,
|
||||||
EditContact,
|
EditContact,
|
||||||
NewConversation,
|
NewConversation,
|
||||||
ContactIntro,
|
|
||||||
ContactFields,
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
contact: {
|
contact: {
|
||||||
@@ -40,17 +52,48 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
showCustomAttributeModal: false,
|
||||||
showEditModal: false,
|
showEditModal: false,
|
||||||
showConversationModal: false,
|
showConversationModal: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
enableNewConversation() {
|
||||||
|
return this.contact && this.contact.id;
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
toggleCustomAttributeModal() {
|
||||||
|
this.showCustomAttributeModal = !this.showCustomAttributeModal;
|
||||||
|
},
|
||||||
toggleEditModal() {
|
toggleEditModal() {
|
||||||
this.showEditModal = !this.showEditModal;
|
this.showEditModal = !this.showEditModal;
|
||||||
},
|
},
|
||||||
toggleConversationModal() {
|
toggleConversationModal() {
|
||||||
this.showConversationModal = !this.showConversationModal;
|
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>
|
</script>
|
||||||
|
|||||||
@@ -55,10 +55,14 @@ export default {
|
|||||||
|
|
||||||
.wrap {
|
.wrap {
|
||||||
@include three-column-grid(27.2rem);
|
@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);
|
border-top: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
.left {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
.center {
|
.center {
|
||||||
border-right: 1px solid var(--color-border);
|
border-right: 1px solid var(--color-border);
|
||||||
border-left: 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),
|
props: Object.keys(argTypes),
|
||||||
components: { ContactFields },
|
components: { ContactFields },
|
||||||
template:
|
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({});
|
export const DefaultContactFields = Template.bind({});
|
||||||
@@ -39,4 +39,5 @@ DefaultContactFields.args = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
onUpdate: action('update'),
|
onUpdate: action('update'),
|
||||||
|
onCreate: action('create'),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ export default {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.contact-manage-view {
|
.contact-manage-view {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
flex: 1 1 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user