fix: implemented quick entry fields rendering for lead/deal/contact & organization

This commit is contained in:
Shariq Ansari 2024-06-07 19:55:38 +05:30
parent cf632969ac
commit 8c95d74290
4 changed files with 211 additions and 536 deletions

View File

@ -57,7 +57,11 @@
<div v-else>{{ field.value }}</div> <div v-else>{{ field.value }}</div>
</div> </div>
</div> </div>
<Fields v-else :sections="sections" :data="_contact" /> <Fields
v-else-if="filteredSections"
:sections="filteredSections"
:data="_contact"
/>
</div> </div>
</div> </div>
<div v-if="!detailMode" class="px-4 pb-7 pt-4 sm:px-6"> <div v-if="!detailMode" class="px-4 pb-7 pt-4 sm:px-6">
@ -87,7 +91,7 @@ import AddressIcon from '@/components/Icons/AddressIcon.vue'
import CertificateIcon from '@/components/Icons/CertificateIcon.vue' import CertificateIcon from '@/components/Icons/CertificateIcon.vue'
import EditIcon from '@/components/Icons/EditIcon.vue' import EditIcon from '@/components/Icons/EditIcon.vue'
import Dropdown from '@/components/frappe-ui/Dropdown.vue' import Dropdown from '@/components/frappe-ui/Dropdown.vue'
import { call } from 'frappe-ui' import { call, createResource } from 'frappe-ui'
import { ref, nextTick, watch, computed, h } from 'vue' import { ref, nextTick, watch, computed, h } from 'vue'
import { createToast } from '@/utils' import { createToast } from '@/utils'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
@ -230,199 +234,124 @@ const detailFields = computed(() => {
return details.filter((detail) => detail.value) return details.filter((detail) => detail.value)
}) })
const sections = computed(() => { const sections = createResource({
return [ url: 'crm.api.doc.get_quick_entry_fields',
{ cache: ['quickEntryFields', 'Contact'],
section: 'Salutation', params: { doctype: 'Contact' },
columns: 1, auto: true,
fields: [ })
{
label: 'Salutation', const filteredSections = computed(() => {
name: 'salutation', let allSections = sections.data || []
type: 'link', if (!allSections.length) return []
placeholder: 'Mr',
doctype: 'Salutation', allSections.forEach((s) => {
}, s.fields.forEach((field) => {
], if (field.name == 'email_id') {
}, field.type = props.contact?.data?.name ? 'Dropdown' : 'Data'
{ field.options =
section: 'Full Name', props.contact.data?.email_ids?.map((email) => {
columns: 2, return {
hideBorder: true, name: email.name,
fields: [ value: email.email_id,
{ selected: email.email_id === props.contact.data.email_id,
label: 'First Name', placeholder: 'john@doe.com',
name: 'first_name', onClick: () => {
type: 'data', _contact.value.email_id = email.email_id
mandatory: true, setAsPrimary('email', email.email_id)
placeholder: 'John', },
}, onSave: (option, isNew) => {
{ if (isNew) {
label: 'Last Name', createNew('email', option.value)
name: 'last_name', if (props.contact.data.email_ids.length === 1) {
type: 'data', _contact.value.email_id = option.value
placeholder: 'Doe', }
}, } else {
], editOption('Contact Email', option.name, option.value)
}, }
{ },
section: 'Email', onDelete: async (option, isNew) => {
columns: 1, props.contact.data.email_ids =
hideBorder: true, props.contact.data.email_ids.filter(
fields: [ (email) => email.name !== option.name
{ )
label: 'Email', !isNew && (await deleteOption('Contact Email', option.name))
name: 'email_id', if (_contact.value.email_id === option.value) {
type: props.contact?.data?.name ? 'dropdown' : 'data', if (props.contact.data.email_ids.length === 0) {
placeholder: 'john@doe.com', _contact.value.email_id = ''
options:
props.contact.data?.email_ids?.map((email) => {
return {
name: email.name,
value: email.email_id,
selected: email.email_id === props.contact.data.email_id,
placeholder: 'john@doe.com',
onClick: () => {
_contact.value.email_id = email.email_id
setAsPrimary('email', email.email_id)
},
onSave: (option, isNew) => {
if (isNew) {
createNew('email', option.value)
if (props.contact.data.email_ids.length === 1) {
_contact.value.email_id = option.value
}
} else { } else {
editOption('Contact Email', option.name, option.value) _contact.value.email_id = props.contact.data.email_ids.find(
(email) => email.is_primary
)?.email_id
} }
}, }
onDelete: async (option, isNew) => { },
props.contact.data.email_ids = }
props.contact.data.email_ids.filter( }) || []
(email) => email.name !== option.name field.create = () => {
) props.contact.data?.email_ids?.push({
!isNew && (await deleteOption('Contact Email', option.name)) name: 'new-1',
if (_contact.value.email_id === option.value) { value: '',
if (props.contact.data.email_ids.length === 0) { selected: false,
_contact.value.email_id = '' isNew: true,
} else { })
_contact.value.email_id = }
props.contact.data.email_ids.find( } else if (field.name == 'mobile_no' || field.name == 'actual_mobile_no') {
(email) => email.is_primary field.type = props.contact?.data?.name ? 'Dropdown' : 'Data'
)?.email_id field.name = 'actual_mobile_no'
} field.options =
props.contact.data?.phone_nos?.map((phone) => {
return {
name: phone.name,
value: phone.phone,
selected: phone.phone === props.contact.data.actual_mobile_no,
onClick: () => {
_contact.value.actual_mobile_no = phone.phone
_contact.value.mobile_no = phone.phone
setAsPrimary('mobile_no', phone.phone)
},
onSave: (option, isNew) => {
if (isNew) {
createNew('phone', option.value)
if (props.contact.data.phone_nos.length === 1) {
_contact.value.actual_mobile_no = option.value
} }
}, } else {
} editOption('Contact Phone', option.name, option.value)
}) || [], }
create: () => { },
props.contact.data?.email_ids?.push({ onDelete: async (option, isNew) => {
name: 'new-1', props.contact.data.phone_nos =
value: '', props.contact.data.phone_nos.filter(
selected: false, (phone) => phone.name !== option.name
isNew: true, )
}) !isNew && (await deleteOption('Contact Phone', option.name))
}, if (_contact.value.actual_mobile_no === option.value) {
}, if (props.contact.data.phone_nos.length === 0) {
], _contact.value.actual_mobile_no = ''
},
{
section: 'Mobile No. & Gender',
columns: 2,
hideBorder: true,
fields: [
{
label: 'Mobile No.',
name: 'actual_mobile_no',
type: props.contact?.data?.name ? 'dropdown' : 'data',
placeholder: '+91 9876543210',
options:
props.contact.data?.phone_nos?.map((phone) => {
return {
name: phone.name,
value: phone.phone,
selected: phone.phone === props.contact.data.actual_mobile_no,
placeholder: '+91 1234567890',
onClick: () => {
_contact.value.actual_mobile_no = phone.phone
_contact.value.mobile_no = phone.phone
setAsPrimary('mobile_no', phone.phone)
},
onSave: (option, isNew) => {
if (isNew) {
createNew('phone', option.value)
if (props.contact.data.phone_nos.length === 1) {
_contact.value.actual_mobile_no = option.value
}
} else { } else {
editOption('Contact Phone', option.name, option.value) _contact.value.actual_mobile_no =
props.contact.data.phone_nos.find(
(phone) => phone.is_primary_mobile_no
)?.phone
} }
}, }
onDelete: async (option, isNew) => { },
props.contact.data.phone_nos = }
props.contact.data.phone_nos.filter( }) || []
(phone) => phone.name !== option.name field.create = () => {
) props.contact.data?.phone_nos?.push({
!isNew && (await deleteOption('Contact Phone', option.name)) name: 'new-1',
if (_contact.value.actual_mobile_no === option.value) { value: '',
if (props.contact.data.phone_nos.length === 0) { selected: false,
_contact.value.actual_mobile_no = '' isNew: true,
} else { })
_contact.value.actual_mobile_no = }
props.contact.data.phone_nos.find( }
(phone) => phone.is_primary_mobile_no })
)?.phone })
}
} return allSections
},
}
}) || [],
create: () => {
props.contact.data?.phone_nos?.push({
name: 'new-1',
value: '',
selected: false,
isNew: true,
})
},
},
{
label: 'Gender',
name: 'gender',
type: 'link',
doctype: 'Gender',
placeholder: 'Male',
},
],
},
{
section: 'Organization',
columns: 1,
hideBorder: true,
fields: [
{
label: 'Organization',
name: 'company_name',
type: 'link',
doctype: 'CRM Organization',
placeholder: 'Frappé Technologies',
},
],
},
{
section: 'Designation',
columns: 1,
hideBorder: true,
fields: [
{
label: 'Designation',
name: 'designation',
type: 'data',
placeholder: 'CEO',
},
],
},
]
}) })
async function setAsPrimary(field, value) { async function setAsPrimary(field, value) {

View File

@ -7,7 +7,7 @@
}" }"
> >
<template #body-content> <template #body-content>
<div class="mb-4 grid sm:grid-cols-3 grid-cols-1 gap-4"> <div class="mb-4 grid grid-cols-1 gap-4 sm:grid-cols-3">
<div class="flex items-center gap-3 text-sm text-gray-600"> <div class="flex items-center gap-3 text-sm text-gray-600">
<div>{{ __('Choose Existing Organization') }}</div> <div>{{ __('Choose Existing Organization') }}</div>
<Switch v-model="chooseExistingOrganization" /> <Switch v-model="chooseExistingOrganization" />
@ -17,7 +17,12 @@
<Switch v-model="chooseExistingContact" /> <Switch v-model="chooseExistingContact" />
</div> </div>
</div> </div>
<Fields class="border-t pt-4" :sections="sections" :data="deal" /> <Fields
v-if="filteredSections"
class="border-t pt-4"
:sections="filteredSections"
:data="deal"
/>
<ErrorMessage class="mt-4" v-if="error" :message="__(error)" /> <ErrorMessage class="mt-4" v-if="error" :message="__(error)" />
</template> </template>
<template #actions> <template #actions>
@ -71,155 +76,66 @@ const isDealCreating = ref(false)
const chooseExistingContact = ref(false) const chooseExistingContact = ref(false)
const chooseExistingOrganization = ref(false) const chooseExistingOrganization = ref(false)
const sections = computed(() => { const sections = createResource({
let fields = [] url: 'crm.api.doc.get_quick_entry_fields',
cache: ['quickEntryFields', 'CRM Deal'],
params: { doctype: 'CRM Deal' },
auto: true,
transform: (data) => {
return data.forEach((section) => {
section.fields.forEach((field) => {
if (field.name == 'status') {
field.type = 'Select'
field.options = dealStatuses.value
field.prefix = getDealStatus(deal.status).iconColorClass
} else if (field.name == 'deal_owner') {
field.type = 'User'
}
})
})
},
})
const filteredSections = computed(() => {
let allSections = sections.data || []
if (!allSections.length) return []
let _filteredSections = []
if (chooseExistingOrganization.value) { if (chooseExistingOrganization.value) {
fields.push({ _filteredSections.push(
section: 'Select Organization', allSections.find((s) => s.label === 'Select Organization')
fields: [ )
{
label: 'Organization',
name: 'organization',
type: 'link',
placeholder: 'Frappé Technologies',
doctype: 'CRM Organization',
},
],
})
} else { } else {
fields.push({ _filteredSections.push(
section: 'Organization Details', allSections.find((s) => s.label === 'Organization Details')
fields: [ )
{
label: 'Organization Name',
name: 'organization_name',
type: 'data',
placeholder: 'Frappé Technologies',
},
{
label: 'Website',
name: 'website',
type: 'data',
placeholder: 'https://frappe.io',
},
{
label: 'No of Employees',
name: 'no_of_employees',
type: 'select',
options: [
{ label: __('1-10'), value: '1-10' },
{ label: __('11-50'), value: '11-50' },
{ label: __('51-200'), value: '51-200' },
{ label: __('201-500'), value: '201-500' },
{ label: __('501-1000'), value: '501-1000' },
{ label: __('1001-5000'), value: '1001-5000' },
{ label: __('5001-10000'), value: '5001-10000' },
{ label: __('10001+'), value: '10001+' },
],
placeholder: '1-10',
},
{
label: 'Territory',
name: 'territory',
type: 'link',
doctype: 'CRM Territory',
placeholder: 'India',
},
{
label: 'Annual Revenue',
name: 'annual_revenue',
type: 'data',
placeholder: '9,999,999',
},
{
label: 'Industry',
name: 'industry',
type: 'link',
doctype: 'CRM Industry',
placeholder: 'Technology',
},
],
})
} }
if (chooseExistingContact.value) { if (chooseExistingContact.value) {
fields.push({ _filteredSections.push(
section: 'Select Contact', allSections.find((s) => s.label === 'Select Contact')
fields: [ )
{
label: 'Contact',
name: 'contact',
type: 'link',
placeholder: 'John Doe',
doctype: 'Contact',
},
],
})
} else { } else {
fields.push({ _filteredSections.push(
section: 'Contact Details', allSections.find((s) => s.label === 'Contact Details')
fields: [ )
{
label: 'Salutation',
name: 'salutation',
type: 'link',
doctype: 'Salutation',
placeholder: 'Mr',
},
{
label: 'First Name',
name: 'first_name',
type: 'data',
placeholder: 'John',
},
{
label: 'Last Name',
name: 'last_name',
type: 'data',
placeholder: 'Doe',
},
{
label: 'Email',
name: 'email',
type: 'data',
placeholder: 'john@doe.com',
},
{
label: 'Mobile No',
name: 'mobile_no',
type: 'data',
placeholder: '+91 1234567890',
},
{
label: 'Gender',
name: 'gender',
type: 'link',
doctype: 'Gender',
placeholder: 'Male',
},
],
})
} }
fields.push({
section: 'Deal Details', allSections.forEach((s) => {
columns: 2, if (
fields: [ ![
{ 'Select Organization',
label: 'Status', 'Organization Details',
name: 'status', 'Select Contact',
type: 'select', 'Contact Details',
options: dealStatuses.value, ].includes(s.label)
prefix: getDealStatus(deal.status).iconColorClass, ) {
}, _filteredSections.push(s)
{ }
label: 'Deal Owner',
name: 'deal_owner',
type: 'user',
placeholder: 'Deal Owner',
doctype: 'User',
},
],
}) })
return fields
return _filteredSections
}) })
const dealStatuses = computed(() => { const dealStatuses = computed(() => {

View File

@ -7,7 +7,7 @@
}" }"
> >
<template #body-content> <template #body-content>
<Fields :sections="sections" :data="lead" /> <Fields v-if="sections.data" :sections="sections.data" :data="lead" />
<ErrorMessage class="mt-4" v-if="error" :message="__(error)" /> <ErrorMessage class="mt-4" v-if="error" :message="__(error)" />
</template> </template>
<template #actions> <template #actions>
@ -39,6 +39,26 @@ const router = useRouter()
const error = ref(null) const error = ref(null)
const isLeadCreating = ref(false) const isLeadCreating = ref(false)
const sections = createResource({
url: 'crm.api.doc.get_quick_entry_fields',
cache: ['quickEntryFields', 'CRM Lead'],
params: { doctype: 'CRM Lead' },
auto: true,
transform: (data) => {
return data.forEach((section) => {
section.fields.forEach((field) => {
if (field.name == 'status') {
field.type = 'Select'
field.options = leadStatuses.value
field.prefix = getLeadStatus(lead.status).iconColorClass
} else if (field.name == 'lead_owner') {
field.type = 'User'
}
})
})
},
})
const lead = reactive({ const lead = reactive({
salutation: '', salutation: '',
first_name: '', first_name: '',
@ -56,128 +76,6 @@ const lead = reactive({
lead_owner: '', lead_owner: '',
}) })
const sections = computed(() => {
return [
{
section: 'Contact Details',
fields: [
{
label: 'Salutation',
name: 'salutation',
type: 'link',
placeholder: 'Mr',
doctype: 'Salutation',
},
{
label: 'First Name',
name: 'first_name',
mandatory: true,
type: 'data',
placeholder: 'John',
},
{
label: 'Last Name',
name: 'last_name',
type: 'data',
placeholder: 'Doe',
},
{
label: 'Email',
name: 'email',
type: 'data',
placeholder: 'john@doe.com',
},
{
label: 'Mobile No',
name: 'mobile_no',
type: 'data',
placeholder: '+91 9876543210',
},
{
label: 'Gender',
name: 'gender',
type: 'link',
doctype: 'Gender',
placeholder: 'Male',
},
],
},
{
section: 'Organization Details',
fields: [
{
label: 'Organization',
name: 'organization',
type: 'data',
placeholder: 'Frappé Technologies',
},
{
label: 'Website',
name: 'website',
type: 'data',
placeholder: 'https://frappe.io',
},
{
label: 'No of Employees',
name: 'no_of_employees',
type: 'select',
options: [
{ label: __('1-10'), value: '1-10' },
{ label: __('11-50'), value: '11-50' },
{ label: __('51-200'), value: '51-200' },
{ label: __('201-500'), value: '201-500' },
{ label: __('501-1000'), value: '501-1000' },
{ label: __('1001-5000'), value: '1001-5000' },
{ label: __('5001-10000'), value: '5001-10000' },
{ label: __('10001+'), value: '10001+' },
],
placeholder: '1-10',
},
{
label: 'Territory',
name: 'territory',
type: 'link',
doctype: 'CRM Territory',
placeholder: 'India',
},
{
label: 'Annual Revenue',
name: 'annual_revenue',
type: 'data',
placeholder: '1000000',
},
{
label: 'Industry',
name: 'industry',
type: 'link',
doctype: 'CRM Industry',
placeholder: 'Technology',
},
],
},
{
section: 'Other Details',
columns: 2,
fields: [
{
label: 'Status',
name: 'status',
type: 'select',
options: leadStatuses.value,
prefix: getLeadStatus(lead.status).iconColorClass,
},
{
label: 'Lead Owner',
name: 'lead_owner',
type: 'user',
placeholder: 'Lead Owner',
doctype: 'User',
},
],
},
]
})
const createLead = createResource({ const createLead = createResource({
url: 'frappe.client.insert', url: 'frappe.client.insert',
makeParams(values) { makeParams(values) {

View File

@ -35,7 +35,11 @@
<div>{{ field.value }}</div> <div>{{ field.value }}</div>
</div> </div>
</div> </div>
<Fields v-else :sections="sections" :data="_organization" /> <Fields
v-else-if="sections.data"
:sections="sections.data"
:data="_organization"
/>
</div> </div>
</div> </div>
<div v-if="!detailMode" class="px-4 pb-7 pt-4 sm:px-6"> <div v-if="!detailMode" class="px-4 pb-7 pt-4 sm:px-6">
@ -60,7 +64,7 @@ import EditIcon from '@/components/Icons/EditIcon.vue'
import WebsiteIcon from '@/components/Icons/WebsiteIcon.vue' import WebsiteIcon from '@/components/Icons/WebsiteIcon.vue'
import OrganizationsIcon from '@/components/Icons/OrganizationsIcon.vue' import OrganizationsIcon from '@/components/Icons/OrganizationsIcon.vue'
import TerritoryIcon from '@/components/Icons/TerritoryIcon.vue' import TerritoryIcon from '@/components/Icons/TerritoryIcon.vue'
import { call, FeatherIcon } from 'frappe-ui' import { call, FeatherIcon, createResource } from 'frappe-ui'
import { ref, nextTick, watch, computed, h } from 'vue' import { ref, nextTick, watch, computed, h } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
@ -220,83 +224,11 @@ const fields = computed(() => {
return details.filter((field) => field.value) return details.filter((field) => field.value)
}) })
const sections = computed(() => { const sections = createResource({
return [ url: 'crm.api.doc.get_quick_entry_fields',
{ cache: ['quickEntryFields', 'CRM Organization'],
section: 'Organization Name', params: { doctype: 'CRM Organization' },
columns: 1, auto: true,
fields: [
{
label: 'Organization Name',
name: 'organization_name',
type: 'data',
placeholder: 'Frappé Technologies',
},
],
},
{
section: 'Website & Revenue',
columns: 2,
hideBorder: true,
fields: [
{
label: 'Website',
name: 'website',
type: 'data',
placeholder: 'https://example.com',
},
{
label: 'Annual Revenue',
name: 'annual_revenue',
type: 'data',
placeholder: '9,999,999',
},
],
},
{
section: 'Territory',
columns: 1,
hideBorder: true,
fields: [
{
label: 'Territory',
name: 'territory',
type: 'link',
doctype: 'CRM Territory',
placeholder: 'India',
},
],
},
{
section: 'No of Employees & Industry',
columns: 2,
hideBorder: true,
fields: [
{
label: 'No of Employees',
name: 'no_of_employees',
type: 'select',
options: [
{ label: __('1-10'), value: '1-10' },
{ label: __('11-50'), value: '11-50' },
{ label: __('51-200'), value: '51-200' },
{ label: __('201-500'), value: '201-500' },
{ label: __('501-1000'), value: '501-1000' },
{ label: __('1001-5000'), value: '1001-5000' },
{ label: __('5001-10000'), value: '5001-10000' },
{ label: __('10001+'), value: '10001+' },
],
},
{
label: 'Industry',
name: 'industry',
type: 'link',
doctype: 'CRM Industry',
placeholder: 'Technology',
},
],
},
]
}) })
watch( watch(