feat: Render conversation custom attributes (#3065)

This commit is contained in:
Muhsin Keloth
2021-10-30 07:09:46 +05:30
committed by GitHub
parent 69f55a25b6
commit ab77e03c92
32 changed files with 1233 additions and 182 deletions

View File

@@ -74,8 +74,4 @@ export default {
margin-bottom: var(--space-normal);
color: var(--b-500);
}
.conv-details--item {
padding-bottom: 0;
}
</style>

View File

@@ -61,10 +61,6 @@ export default {
margin-bottom: var(--space-normal);
}
.conv-details--item {
padding-bottom: 0;
}
.custom-attribute--row__attribute {
font-weight: 500;
}

View File

@@ -1,9 +1,8 @@
<template>
<div class="conv-details--item">
<div class="conv-details--item" :class="{ compact: compact }">
<h4 class="conv-details--item__label text-block-title">
<span class="title--icon">
<emoji-or-icon :icon="icon" :emoji="emoji" />
<span>{{ title }}</span>
<span class="item__title">
{{ title }}
</span>
<slot name="button"></slot>
</h4>
@@ -16,17 +15,13 @@
</template>
<script>
import EmojiOrIcon from 'shared/components/EmojiOrIcon';
export default {
components: {
EmojiOrIcon,
},
props: {
title: { type: String, required: true },
icon: { type: String, default: '' },
emoji: { type: String, default: '' },
value: { type: [String, Number], default: '' },
compact: { type: Boolean, default: false },
},
};
</script>
@@ -34,6 +29,12 @@ export default {
<style lang="scss" scoped>
.conv-details--item {
overflow: auto;
padding: var(--space-slab) var(--space-normal);
&.compact {
padding: 0;
}
.conv-details--item__label {
align-items: center;
display: flex;
@@ -46,19 +47,6 @@ export default {
.conv-details--item__value {
word-break: break-all;
margin-left: var(--space-medium);
margin-bottom: var(--space-normal);
}
.title--icon .icon--emoji,
.title--icon .icon--font {
display: inline-block;
width: var(--space-medium);
}
.title--icon {
display: flex;
align-items: center;
}
}
</style>

View File

@@ -13,6 +13,7 @@
<div>
<div class="multiselect-wrap--small">
<contact-details-item
compact
:title="$t('CONVERSATION_SIDEBAR.ASSIGNEE_LABEL')"
>
<template v-slot:button>
@@ -45,6 +46,7 @@
</div>
<div class="multiselect-wrap--small">
<contact-details-item
compact
:title="$t('CONVERSATION_SIDEBAR.TEAM_LABEL')"
/>
<multiselect-dropdown
@@ -64,68 +66,26 @@
/>
</div>
<contact-details-item
compact
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.CONVERSATION_LABELS')"
/>
<conversation-labels :conversation-id="conversationId" />
</div>
</accordion-item>
</div>
<accordion-item
v-if="browser.browser_name"
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.CONVERSATION_INFO')"
:is-open="isContactSidebarItemOpen('is_conv_details_open')"
compact
@click="value => toggleSidebarUIState('is_conv_details_open', value)"
>
<div class="conversation--details">
<contact-details-item
v-if="location"
:title="$t('CONTACT_FORM.FORM.LOCATION.LABEL')"
:value="location"
icon="ion-map"
emoji="📍"
/>
<contact-details-item
v-if="ipAddress"
:title="$t('CONTACT_PANEL.IP_ADDRESS')"
:value="ipAddress"
icon="ion-android-locate"
emoji="🧭"
/>
<contact-details-item
v-if="browser.browser_name"
:title="$t('CONTACT_PANEL.BROWSER')"
:value="browserName"
icon="ion-ios-world-outline"
emoji="🌐"
/>
<contact-details-item
v-if="browser.platform_name"
:title="$t('CONTACT_PANEL.OS')"
:value="platformName"
icon="ion-laptop"
emoji="💻"
/>
<contact-details-item
v-if="referer"
:title="$t('CONTACT_PANEL.INITIATED_FROM')"
:value="referer"
icon="ion-link"
emoji="🔗"
>
<a :href="referer" rel="noopener noreferrer nofollow" target="_blank">
{{ referer }}
</a>
</contact-details-item>
<contact-details-item
v-if="initiatedAt"
:title="$t('CONTACT_PANEL.INITIATED_AT')"
:value="initiatedAt.timestamp"
icon="ion-clock"
emoji="🕰"
/>
</div>
<conversation-info
:conversation-attributes="conversationAdditionalAttributes"
:contact-attributes="contactAdditionalAttributes"
>
</conversation-info>
</accordion-item>
<accordion-item
v-if="hasContactAttributes"
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.CONTACT_ATTRIBUTES')"
@@ -162,21 +122,21 @@ 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 uiSettingsMixin from 'dashboard/mixins/uiSettings';
import flag from 'country-code-emoji';
export default {
components: {
ContactCustomAttributes,
AccordionItem,
ContactConversations,
ContactCustomAttributes,
ContactDetailsItem,
ContactInfo,
ConversationInfo,
ConversationLabels,
MultiselectDropdown,
AccordionItem,
},
mixins: [alertMixin, agentMixin, uiSettingsMixin],
props: {
@@ -200,68 +160,30 @@ export default {
currentUser: 'getCurrentUser',
uiFlags: 'inboxAssignableAgents/getUIFlags',
}),
conversationAdditionalAttributes() {
return this.currentConversationMetaData.additional_attributes || {};
},
channelType() {
return this.currentChat.meta?.channel;
},
contact() {
return this.$store.getters['contacts/getContact'](this.contactId);
},
contactAdditionalAttributes() {
return this.contact.additional_attributes || {};
},
contactId() {
return this.currentChat.meta?.sender?.id;
},
currentConversationMetaData() {
return this.$store.getters[
'conversationMetadata/getConversationMetadata'
](this.conversationId);
},
additionalAttributes() {
return this.currentConversationMetaData.additional_attributes || {};
},
hasContactAttributes() {
const { custom_attributes: customAttributes } = this.contact;
return customAttributes && Object.keys(customAttributes).length;
},
browser() {
return this.additionalAttributes.browser || {};
},
referer() {
return this.additionalAttributes.referer;
},
initiatedAt() {
return this.additionalAttributes.initiated_at;
},
browserName() {
return `${this.browser.browser_name || ''} ${this.browser
.browser_version || ''}`;
},
contactAdditionalAttributes() {
return this.contact.additional_attributes || {};
},
ipAddress() {
const { created_at_ip: createdAtIp } = this.contactAdditionalAttributes;
return createdAtIp;
},
location() {
const {
country = '',
city = '',
country_code: countryCode,
} = this.contactAdditionalAttributes;
const cityAndCountry = [city, country].filter(item => !!item).join(', ');
if (!cityAndCountry) {
return '';
}
const countryFlag = countryCode ? flag(countryCode) : '🌎';
return `${cityAndCountry} ${countryFlag}`;
},
platformName() {
const {
platform_name: platformName,
platform_version: platformVersion,
} = this.browser;
return `${platformName || ''} ${platformVersion || ''}`;
},
channelType() {
return this.currentChat.meta?.channel;
},
contactId() {
return this.currentChat.meta?.sender?.id;
},
contact() {
return this.$store.getters['contacts/getContact'](this.contactId);
},
teamsList() {
if (this.assignedTeam) {
return [
@@ -330,6 +252,7 @@ export default {
},
mounted() {
this.getContactDetails();
this.$store.dispatch('attributes/get', 0);
},
methods: {
onPanelToggle() {
@@ -340,6 +263,11 @@ export default {
this.$store.dispatch('contacts/show', { id: this.contactId });
}
},
getAttributesByModel() {
if (this.contactId) {
this.$store.dispatch('contacts/show', { id: this.contactId });
}
},
openTranscriptModal() {
this.showTranscriptModal = true;
},

View File

@@ -0,0 +1,144 @@
<template>
<div class="conversation--details">
<contact-details-item
v-if="initiatedAt"
:title="$t('CONTACT_PANEL.INITIATED_AT')"
:value="initiatedAt.timestamp"
class="conversation--attribute"
/>
<contact-details-item
v-if="referer"
:title="$t('CONTACT_PANEL.INITIATED_FROM')"
:value="referer"
class="conversation--attribute"
>
<a :href="referer" rel="noopener noreferrer nofollow" target="_blank">
{{ referer }}
</a>
</contact-details-item>
<contact-details-item
v-if="browserName"
:title="$t('CONTACT_PANEL.BROWSER')"
:value="browserName"
class="conversation--attribute"
/>
<contact-details-item
v-if="platformName"
:title="$t('CONTACT_PANEL.OS')"
:value="platformName"
class="conversation--attribute"
/>
<contact-details-item
v-if="ipAddress"
:title="$t('CONTACT_PANEL.IP_ADDRESS')"
:value="ipAddress"
class="conversation--attribute"
/>
<custom-attributes
attribute-type="conversation_attribute"
attribute-class="conversation--attribute"
:class="customAttributeRowClass"
/>
<custom-attribute-selector attribute-type="conversation_attribute" />
</div>
</template>
<script>
import ContactDetailsItem from './ContactDetailsItem.vue';
import CustomAttributes from './customAttributes/CustomAttributes.vue';
import CustomAttributeSelector from './customAttributes/CustomAttributeSelector.vue';
export default {
components: {
ContactDetailsItem,
CustomAttributes,
CustomAttributeSelector,
},
props: {
conversationAttributes: {
type: Object,
default: () => ({}),
},
contactAttributes: {
type: Object,
default: () => ({}),
},
},
STATIC_ATTRIBUTES: [
{
name: 'initiated_at',
label: 'CONTACT_PANEL.INITIATED_AT',
},
{
name: 'referer',
label: 'CONTACT_PANEL.BROWSER',
},
{
name: 'browserName',
label: 'CONTACT_PANEL.BROWSER',
},
{
name: 'platformName',
label: 'CONTACT_PANEL.OS',
},
{
name: 'ipAddress',
label: 'CONTACT_PANEL.IP_ADDRESS',
},
],
computed: {
referer() {
return this.conversationAttributes.referer;
},
initiatedAt() {
return this.conversationAttributes.initiated_at;
},
browserName() {
if (!this.conversationAttributes.browser) {
return '';
}
const {
browser_name: browserName = '',
browser_version: browserVersion = '',
} = this.conversationAttributes.browser;
return `${browserName} ${browserVersion}`;
},
platformName() {
if (!this.conversationAttributes.browser) {
return '';
}
const {
platform_name: platformName,
platform_version: platformVersion,
} = this.conversationAttributes.browser;
return `${platformName || ''} ${platformVersion || ''}`;
},
ipAddress() {
const { created_at_ip: createdAtIp } = this.contactAttributes;
return createdAtIp;
},
customAttributeRowClass() {
const attributes = [
'initiatedAt',
'referer',
'browserName',
'platformName',
'ipAddress',
];
const availableAttributes = attributes.filter(
attribute => !!this[attribute]
);
return availableAttributes.length % 2 === 0 ? 'even' : 'odd';
},
},
};
</script>
<style scoped lang="scss">
.conversation--attribute {
border-bottom: 1px solid var(--color-border-light);
&:nth-child(2n) {
background: var(--b-50);
}
}
</style>

View File

@@ -41,19 +41,19 @@
emoji="📞"
:title="$t('CONTACT_PANEL.PHONE_NUMBER')"
/>
<contact-info-row
v-if="additionalAttributes.location"
:value="additionalAttributes.location"
icon="ion-map"
emoji="🌍"
:title="$t('CONTACT_PANEL.LOCATION')"
/>
<contact-info-row
:value="additionalAttributes.company_name"
icon="ion-briefcase"
emoji="🏢"
:title="$t('CONTACT_PANEL.COMPANY')"
/>
<contact-info-row
v-if="location || additionalAttributes.location"
:value="location || additionalAttributes.location"
icon="ion-map"
emoji="🌍"
:title="$t('CONTACT_PANEL.LOCATION')"
/>
</div>
</div>
<div class="contact-actions">
@@ -145,6 +145,7 @@ import ContactMergeModal from 'dashboard/modules/contact/ContactMergeModal';
import alertMixin from 'shared/mixins/alertMixin';
import adminMixin from '../../../../mixins/isAdmin';
import { mapGetters } from 'vuex';
import flag from 'country-code-emoji';
export default {
components: {
@@ -190,6 +191,20 @@ export default {
additionalAttributes() {
return this.contact.additional_attributes || {};
},
location() {
const {
country = '',
city = '',
country_code: countryCode,
} = this.additionalAttributes;
const cityAndCountry = [city, country].filter(item => !!item).join(', ');
if (!cityAndCountry) {
return '';
}
const countryFlag = countryCode ? flag(countryCode) : '🌎';
return `${cityAndCountry} ${countryFlag}`;
},
socialProfiles() {
const {
social_profiles: socialProfiles,
@@ -294,7 +309,7 @@ export default {
}
.contact--metadata {
margin-bottom: var(--space-small);
margin-bottom: var(--space-slab);
}
.contact-actions {

View File

@@ -0,0 +1,155 @@
<template>
<div class="dropdown-search-wrap">
<h4 class="text-block-title">
{{ $t('CUSTOM_ATTRIBUTES.FORM.ATTRIBUTE_SELECT.TITLE') }}
</h4>
<div class="search-wrap">
<input
ref="searchbar"
v-model="search"
type="text"
class="search-input"
autofocus="true"
:placeholder="$t('CUSTOM_ATTRIBUTES.FORM.ATTRIBUTE_SELECT.PLACEHOLDER')"
/>
</div>
<div class="list-wrap">
<div class="list">
<woot-dropdown-menu>
<custom-attribute-drop-down-item
v-for="attribute in filteredAttributes"
:key="attribute.attribute_display_name"
:title="attribute.attribute_display_name"
@click="onAddAttribute(attribute)"
/>
</woot-dropdown-menu>
<div v-if="noResult" class="no-result">
{{ $t('CUSTOM_ATTRIBUTES.FORM.ATTRIBUTE_SELECT.NO_RESULT') }}
</div>
<woot-button
variant="hollow"
class="add"
icon="ion-plus-round"
size="tiny"
@click="addNewAttribute"
>
{{ $t('CUSTOM_ATTRIBUTES.FORM.ADD.TITLE') }}
</woot-button>
</div>
</div>
</div>
</template>
<script>
import CustomAttributeDropDownItem from './CustomAttributeDropDownItem.vue';
import attributeMixin from 'dashboard/mixins/attributeMixin';
export default {
components: {
CustomAttributeDropDownItem,
},
mixins: [attributeMixin],
props: {
attributeType: {
type: String,
default: 'conversation_attribute',
},
},
data() {
return {
search: '',
};
},
computed: {
filteredAttributes() {
return this.attributes
.filter(
item =>
!Object.keys(this.customAttributes).includes(item.attribute_key)
)
.filter(attribute => {
return attribute.attribute_display_name
.toLowerCase()
.includes(this.search.toLowerCase());
});
},
noResult() {
return this.filteredAttributes.length === 0 && this.search !== '';
},
},
mounted() {
this.focusInput();
},
methods: {
focusInput() {
this.$refs.searchbar.focus();
},
addNewAttribute() {
this.$router.push(
`/app/accounts/${this.accountId}/settings/attributes/list`
);
},
async onAddAttribute(attribute) {
this.$emit('add-attribute', attribute);
},
},
};
</script>
<style lang="scss" scoped>
.dropdown-search-wrap {
display: flex;
flex-direction: column;
width: 100%;
max-height: 20rem;
.search-wrap {
margin-bottom: var(--space-small);
flex: 0 0 auto;
max-height: var(--space-large);
.search-input {
margin: 0;
width: 100%;
border: 1px solid transparent;
height: var(--space-large);
font-size: var(--font-size-small);
padding: var(--space-small);
background-color: var(--color-background);
}
input:focus {
border: 1px solid var(--w-500);
}
}
.list-wrap {
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex: 1 1 auto;
overflow: auto;
.list {
width: 100%;
.add {
float: right;
margin-top: var(--space-one);
}
}
.no-result {
display: flex;
justify-content: center;
color: var(--s-700);
padding: var(--space-smaller) var(--space-one);
font-weight: var(--font-weight-medium);
font-size: var(--font-size-small);
}
}
}
</style>

View File

@@ -0,0 +1,79 @@
<template>
<woot-dropdown-item>
<woot-button variant="clear" @click="onClick">
<span class="label-text" :title="title">{{ title }}</span>
</woot-button>
</woot-dropdown-item>
</template>
<script>
export default {
name: 'AttributeDropDownItem',
props: {
title: {
type: String,
default: '',
},
},
methods: {
onClick() {
this.$emit('click', this.title);
},
},
};
</script>
<style lang="scss" scoped>
.item-wrap {
display: flex;
::v-deep .button__content {
width: 100%;
}
.button-wrap {
display: flex;
justify-content: space-between;
width: 100%;
&.active {
display: flex;
font-weight: var(--font-weight-bold);
color: var(--w-700);
}
.name-label-wrap {
display: flex;
min-width: 0;
width: 100%;
.label-color--display {
margin-right: var(--space-small);
}
.label-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 1.1;
padding-right: var(--space-small);
padding-left: var(--space-small);
}
.icon {
font-size: var(--font-size-small);
}
}
}
.label-color--display {
border-radius: var(--border-radius-normal);
height: var(--space-slab);
margin-right: var(--space-smaller);
margin-top: var(--space-micro);
min-width: var(--space-slab);
width: var(--space-slab);
}
}
</style>

View File

@@ -0,0 +1,120 @@
<template>
<div class="custom-attribute--selector">
<div
v-on-clickaway="closeDropdown"
class="label-wrap"
@keyup.esc="closeDropdown"
>
<woot-button
size="small"
variant="link"
icon="ion-plus"
@click="toggleAttributeDropDown"
>
{{ $t('CUSTOM_ATTRIBUTES.ADD_BUTTON_TEXT') }}
</woot-button>
<div class="dropdown-wrap">
<div
:class="{ 'dropdown-pane--open': showAttributeDropDown }"
class="dropdown-pane"
>
<custom-attribute-drop-down
v-if="showAttributeDropDown"
:attribute-type="attributeType"
@add-attribute="addAttribute"
/>
</div>
</div>
</div>
</div>
</template>
<script>
import CustomAttributeDropDown from './CustomAttributeDropDown.vue';
import alertMixin from 'shared/mixins/alertMixin';
import attributeMixin from 'dashboard/mixins/attributeMixin';
import { mixin as clickaway } from 'vue-clickaway';
import { BUS_EVENTS } from 'shared/constants/busEvents';
export default {
components: {
CustomAttributeDropDown,
},
mixins: [clickaway, alertMixin, attributeMixin],
props: {
attributeType: {
type: String,
default: 'conversation_attribute',
},
},
data() {
return {
showAttributeDropDown: false,
};
},
methods: {
async addAttribute(attribute) {
const { attribute_key } = attribute;
try {
await this.$store.dispatch('updateCustomAttributes', {
conversationId: this.conversationId,
customAttributes: {
...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) {
const errorMessage =
error?.response?.message ||
this.$t('CUSTOM_ATTRIBUTES.FORM.ADD.ERROR');
this.showAlert(errorMessage);
} finally {
this.closeDropdown();
}
},
toggleAttributeDropDown() {
this.showAttributeDropDown = !this.showAttributeDropDown;
},
closeDropdown() {
this.showAttributeDropDown = false;
},
},
};
</script>
<style lang="scss" scoped>
.custom-attribute--selector {
width: 100%;
padding: var(--space-slab) var(--space-normal);
.label-wrap {
line-height: var(--space-medium);
position: relative;
.dropdown-wrap {
display: flex;
left: -1px;
margin-right: var(--space-medium);
position: absolute;
top: var(--space-medium);
width: 100%;
.dropdown-pane {
width: 100%;
box-sizing: border-box;
}
}
}
}
.error {
color: var(--r-500);
font-size: var(--font-size-mini);
font-weight: var(--font-weight-medium);
}
</style>

View File

@@ -0,0 +1,101 @@
<template>
<div class="custom-attributes--panel">
<custom-attribute
v-for="attribute in filteredAttributes"
:key="attribute.id"
:attribute-key="attribute.attribute_key"
:attribute-type="attribute.attribute_display_type"
:label="attribute.attribute_display_name"
:icon="attribute.icon"
emoji=""
:value="attribute.value"
:show-actions="true"
:class="attributeClass"
@update="onUpdate"
@delete="onDelete"
@copy="onCopy"
/>
</div>
</template>
<script>
import copy from 'copy-text-to-clipboard';
import CustomAttribute from 'dashboard/components/CustomAttribute.vue';
import alertMixin from 'shared/mixins/alertMixin';
import attributeMixin from 'dashboard/mixins/attributeMixin';
export default {
components: {
CustomAttribute,
},
mixins: [alertMixin, attributeMixin],
props: {
attributeType: {
type: String,
default: 'conversation_attribute',
},
attributeClass: {
type: String,
default: '',
},
},
methods: {
async onUpdate(key, value) {
try {
await this.$store.dispatch('updateCustomAttributes', {
conversationId: this.conversationId,
customAttributes: { ...this.customAttributes, [key]: value },
});
this.showAlert(this.$t('CUSTOM_ATTRIBUTES.FORM.UPDATE.SUCCESS'));
} catch (error) {
const errorMessage =
error?.response?.message ||
this.$t('CUSTOM_ATTRIBUTES.FORM.UPDATE.ERROR');
this.showAlert(errorMessage);
}
},
async onDelete(key) {
const { [key]: remove, ...updatedAttributes } = this.customAttributes;
try {
await this.$store.dispatch('updateCustomAttributes', {
conversationId: this.conversationId,
customAttributes: updatedAttributes,
});
this.showAlert(this.$t('CUSTOM_ATTRIBUTES.FORM.DELETE.SUCCESS'));
} catch (error) {
const errorMessage =
error?.response?.message ||
this.$t('CUSTOM_ATTRIBUTES.FORM.DELETE.ERROR');
this.showAlert(errorMessage);
}
},
onCopy(attributeValue) {
copy(attributeValue);
this.showAlert(this.$t('CUSTOM_ATTRIBUTES.COPY_SUCCESSFUL'));
},
},
};
</script>
<style scoped lang="scss">
.custom-attributes--panel {
.conversation--attribute {
border-bottom: 1px solid var(--color-border-light);
}
&.odd {
.conversation--attribute {
&:nth-child(2n + 1) {
background: var(--b-50);
}
}
}
&.even {
.conversation--attribute {
&:nth-child(2n) {
background: var(--b-50);
}
}
}
}
</style>

View File

@@ -3,8 +3,19 @@
<div class="column content-box">
<woot-modal-header :header-title="$t('ATTRIBUTES_MGMT.ADD.TITLE')" />
<form class="row" @submit.prevent="addAttributes()">
<form class="row" @submit.prevent="addAttributes">
<div class="medium-12 columns">
<label :class="{ error: $v.attributeModel.$error }">
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.MODEL.LABEL') }}
<select v-model="attributeModel">
<option v-for="model in models" :key="model.id" :value="model.id">
{{ model.option }}
</option>
</select>
<span v-if="$v.attributeModel.$error" class="message">
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.MODEL.ERROR') }}
</span>
</label>
<woot-input
v-model="displayName"
:label="$t('ATTRIBUTES_MGMT.ADD.FORM.NAME.LABEL')"
@@ -22,7 +33,7 @@
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.DESC.LABEL') }}
<textarea
v-model="description"
rows="5"
rows="3"
type="text"
:placeholder="$t('ATTRIBUTES_MGMT.ADD.FORM.DESC.PLACEHOLDER')"
@blur="$v.description.$touch"
@@ -31,18 +42,6 @@
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.DESC.ERROR') }}
</span>
</label>
<label :class="{ error: $v.attributeModel.$error }">
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.MODEL.LABEL') }}
<select v-model="attributeModel">
<option v-for="model in models" :key="model.id" :value="model.id">
{{ model.option }}
</option>
</select>
<span v-if="$v.attributeModel.$error" class="message">
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.MODEL.ERROR') }}
</span>
</label>
<label :class="{ error: $v.attributeType.$error }">
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.TYPE.LABEL') }}
<select v-model="attributeType">

View File

@@ -10,7 +10,7 @@
/>
</woot-tabs>
<div class="columns with-right-space ">
<div class="columns with-right-space">
<p
v-if="!uiFlags.isFetching && !attributes.length"
class="no-items-error-message"
@@ -135,10 +135,6 @@ export default {
key: 0,
name: this.$t('ATTRIBUTES_MGMT.TABS.CONVERSATION'),
},
{
key: 1,
name: this.$t('ATTRIBUTES_MGMT.TABS.CONTACT'),
},
];
},
deleteConfirmText() {

View File

@@ -3,10 +3,6 @@ export const ATTRIBUTE_MODELS = [
id: 0,
option: 'Conversation',
},
{
id: 1,
option: 'Contact',
},
];
export const ATTRIBUTE_TYPES = [
@@ -18,14 +14,6 @@ export const ATTRIBUTE_TYPES = [
id: 1,
option: 'Number',
},
{
id: 2,
option: 'Currency',
},
{
id: 3,
option: 'Percent',
},
{
id: 4,
option: 'Link',

View File

@@ -10,6 +10,7 @@ import profile from './profile/profile.routes';
import reports from './reports/reports.routes';
import campaigns from './campaigns/campaigns.routes';
import teams from './teams/teams.routes';
import attributes from './attributes/attributes.routes';
import store from '../../../store';
export default {
@@ -36,5 +37,6 @@ export default {
...teams.routes,
...campaigns.routes,
...integrationapps.routes,
...attributes.routes,
],
};