Merge pull request #23 from shariquerik/make-contact-organization-link-field

refactor: convert organization field to a link field
This commit is contained in:
Shariq Ansari 2023-11-06 18:14:33 +05:30 committed by GitHub
commit 6ad550d986
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 470 additions and 286 deletions

View File

@ -29,9 +29,8 @@
"next_step", "next_step",
"organization_tab", "organization_tab",
"section_break_uixv", "section_break_uixv",
"organization_name", "organization",
"no_of_employees", "no_of_employees",
"organization_logo",
"column_break_dbsv", "column_break_dbsv",
"website", "website",
"job_title", "job_title",
@ -111,10 +110,12 @@
"search_index": 1 "search_index": 1
}, },
{ {
"fetch_from": "organization.website",
"fieldname": "website", "fieldname": "website",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Website", "label": "Website",
"options": "URL" "options": "URL",
"read_only": 1
}, },
{ {
"fieldname": "column_break_sijm", "fieldname": "column_break_sijm",
@ -146,17 +147,15 @@
"fieldname": "no_of_employees", "fieldname": "no_of_employees",
"fieldtype": "Select", "fieldtype": "Select",
"label": "No. of Employees", "label": "No. of Employees",
"options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+" "options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+",
}, "read_only": 1
{
"fieldname": "organization_name",
"fieldtype": "Data",
"label": "Organization Name"
}, },
{ {
"fetch_from": "organization.annual_revenue",
"fieldname": "annual_revenue", "fieldname": "annual_revenue",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Annual Revenue" "label": "Annual Revenue",
"read_only": 1
}, },
{ {
"fieldname": "lead_owner", "fieldname": "lead_owner",
@ -171,15 +170,11 @@
"options": "CRM Lead Source" "options": "CRM Lead Source"
}, },
{ {
"fetch_from": "organization.industry",
"fieldname": "industry", "fieldname": "industry",
"fieldtype": "Link", "fieldtype": "Data",
"label": "Industry", "label": "Industry",
"options": "CRM Industry" "read_only": 1
},
{
"fieldname": "organization_logo",
"fieldtype": "Attach Image",
"label": "Organization Logo"
}, },
{ {
"fieldname": "image", "fieldname": "image",
@ -214,9 +209,11 @@
"search_index": 1 "search_index": 1
}, },
{ {
"fetch_from": "organization.job_title",
"fieldname": "job_title", "fieldname": "job_title",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Job Title" "label": "Job Title",
"read_only": 1
}, },
{ {
"fieldname": "organization_tab", "fieldname": "organization_tab",
@ -259,12 +256,18 @@
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 1, "hidden": 1,
"label": "Created as Deal" "label": "Created as Deal"
},
{
"fieldname": "organization",
"fieldtype": "Link",
"label": "Organization",
"options": "CRM Organization"
} }
], ],
"image_field": "image", "image_field": "image",
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2023-09-27 18:54:18.196159", "modified": "2023-11-06 15:29:56.868755",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "FCRM", "module": "FCRM",
"name": "CRM Lead", "name": "CRM Lead",

View File

@ -26,15 +26,17 @@ class CRMLead(Document):
def set_lead_name(self): def set_lead_name(self):
if not self.lead_name: if not self.lead_name:
# Check for leads being created through data import # Check for leads being created through data import
if not self.organization_name and not self.email and not self.flags.ignore_mandatory: if not self.organization and not self.email and not self.flags.ignore_mandatory:
frappe.throw(_("A Lead requires either a person's name or an organization's name")) frappe.throw(_("A Lead requires either a person's name or an organization's name"))
elif self.organization_name: elif self.organization:
self.lead_name = self.organization_name self.lead_name = self.organization
else: elif self.email:
self.lead_name = self.email.split("@")[0] self.lead_name = self.email.split("@")[0]
else:
self.lead_name = "Unnamed Lead"
def set_title(self): def set_title(self):
self.title = self.organization_name or self.lead_name self.title = self.organization or self.lead_name
def validate_email(self): def validate_email(self):
if self.email: if self.email:
@ -106,7 +108,7 @@ class CRMLead(Document):
"salutation": self.salutation, "salutation": self.salutation,
"gender": self.gender, "gender": self.gender,
"designation": self.job_title, "designation": self.job_title,
"company_name": self.organization_name, "company_name": self.organization,
"image": self.image or "", "image": self.image or "",
} }
) )
@ -132,7 +134,7 @@ class CRMLead(Document):
{ "label": 'Modified', "value": 'modified' }, { "label": 'Modified', "value": 'modified' },
{ "label": 'Status', "value": 'status' }, { "label": 'Status', "value": 'status' },
{ "label": 'Lead owner', "value": 'lead_owner' }, { "label": 'Lead owner', "value": 'lead_owner' },
{ "label": 'Organization', "value": 'organization_name' }, { "label": 'Organization', "value": 'organization' },
{ "label": 'Name', "value": 'lead_name' }, { "label": 'Name', "value": 'lead_name' },
{ "label": 'First Name', "value": 'first_name' }, { "label": 'First Name', "value": 'first_name' },
{ "label": 'Last Name', "value": 'last_name' }, { "label": 'Last Name', "value": 'last_name' },

View File

@ -7,8 +7,13 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"organization_name", "organization_name",
"no_of_employees",
"organization_logo",
"column_break_pnpp",
"website", "website",
"organization_logo" "job_title",
"annual_revenue",
"industry"
], ],
"fields": [ "fields": [
{ {
@ -20,18 +25,45 @@
{ {
"fieldname": "website", "fieldname": "website",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Website" "label": "Website",
"options": "URL"
}, },
{ {
"fieldname": "organization_logo", "fieldname": "organization_logo",
"fieldtype": "Attach Image", "fieldtype": "Attach Image",
"label": "Organization Logo" "label": "Organization Logo"
},
{
"fieldname": "no_of_employees",
"fieldtype": "Select",
"label": "No. of Employees",
"options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+"
},
{
"fieldname": "column_break_pnpp",
"fieldtype": "Column Break"
},
{
"fieldname": "job_title",
"fieldtype": "Data",
"label": "Job Title"
},
{
"fieldname": "annual_revenue",
"fieldtype": "Currency",
"label": "Annual Revenue"
},
{
"fieldname": "industry",
"fieldtype": "Link",
"label": "Industry",
"options": "CRM Industry"
} }
], ],
"image_field": "organization_logo", "image_field": "organization_logo",
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2023-11-03 16:25:25.366741", "modified": "2023-11-06 15:28:26.610882",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "FCRM", "module": "FCRM",
"name": "CRM Organization", "name": "CRM Organization",

View File

@ -0,0 +1,16 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.86813 10.1317C5.67286 9.93647 5.67286 9.61988 5.86813 9.42462L12.7926 2.5001L9 2.5001C8.72386 2.5001 8.5 2.27624 8.5 2.0001C8.5 1.72396 8.72386 1.5001 9 1.5001L13.9899 1.5001C14.1076 1.49777 14.2261 1.53678 14.3215 1.61714C14.4306 1.70886 14.5 1.84638 14.5 2.0001V7.0001C14.5 7.27624 14.2761 7.5001 14 7.5001C13.7239 7.5001 13.5 7.27624 13.5 7.0001V3.20696L6.57523 10.1317C6.37997 10.327 6.06339 10.327 5.86813 10.1317ZM2.5 4.0001C2.5 3.17167 3.17157 2.5001 4 2.5001H5.8C6.07614 2.5001 6.3 2.27624 6.3 2.0001C6.3 1.72396 6.07614 1.5001 5.8 1.5001H4C2.61929 1.5001 1.5 2.61939 1.5 4.0001V12.0001C1.5 13.3808 2.61929 14.5001 4 14.5001H12C13.3807 14.5001 14.5 13.3808 14.5 12.0001V10.2001C14.5 9.92396 14.2761 9.7001 14 9.7001C13.7239 9.7001 13.5 9.92396 13.5 10.2001V12.0001C13.5 12.8285 12.8284 13.5001 12 13.5001H4C3.17157 13.5001 2.5 12.8285 2.5 12.0001V4.0001Z"
fill="currentColor"
/>
</svg>
</template>

View File

@ -21,7 +21,7 @@
<div v-if="column.key === 'deal_status'"> <div v-if="column.key === 'deal_status'">
<IndicatorIcon :class="item.color" /> <IndicatorIcon :class="item.color" />
</div> </div>
<div v-else-if="column.key === 'organization_name'"> <div v-else-if="column.key === 'organization'">
<Avatar <Avatar
v-if="item.label" v-if="item.label"
class="flex items-center" class="flex items-center"

View File

@ -30,7 +30,7 @@
size="sm" size="sm"
/> />
</div> </div>
<div v-else-if="column.key === 'organization_name'"> <div v-else-if="column.key === 'organization'">
<Avatar <Avatar
v-if="item.label" v-if="item.label"
class="flex items-center" class="flex items-center"

View File

@ -3,7 +3,7 @@
<div v-for="section in allFields" :key="section.section"> <div v-for="section in allFields" :key="section.section">
<div class="grid grid-cols-3 gap-4"> <div class="grid grid-cols-3 gap-4">
<div v-for="field in section.fields" :key="field.name"> <div v-for="field in section.fields" :key="field.name">
<div class="text-gray-600 text-sm mb-2">{{ field.label }}</div> <div class="mb-2 text-sm text-gray-600">{{ field.label }}</div>
<FormControl <FormControl
v-if="field.type === 'select'" v-if="field.type === 'select'"
type="select" type="select"
@ -19,8 +19,18 @@
type="email" type="email"
v-model="newDeal[field.name]" v-model="newDeal[field.name]"
/> />
<Autocomplete <FormControl
v-else-if="field.type === 'link'" v-else-if="field.type === 'link'"
type="autocomplete"
:value="newDeal[field.name]"
:options="field.options"
@change="(e) => field.change(e)"
:placeholder="field.placeholder"
class="form-control"
/>
<FormControl
v-else-if="field.type === 'user'"
type="autocomplete"
:options="activeAgents" :options="activeAgents"
:value="getUser(newDeal[field.name]).full_name" :value="getUser(newDeal[field.name]).full_name"
@change="(option) => (newDeal[field.name] = option.email)" @change="(option) => (newDeal[field.name] = option.email)"
@ -32,7 +42,7 @@
<template #item-prefix="{ option }"> <template #item-prefix="{ option }">
<UserAvatar class="mr-2" :user="option.email" size="sm" /> <UserAvatar class="mr-2" :user="option.email" size="sm" />
</template> </template>
</Autocomplete> </FormControl>
<Dropdown <Dropdown
v-else-if="field.type === 'dropdown'" v-else-if="field.type === 'dropdown'"
:options="statusDropdownOptions(newDeal, 'deal')" :options="statusDropdownOptions(newDeal, 'deal')"
@ -41,10 +51,12 @@
<template #default="{ open }"> <template #default="{ open }">
<Button <Button
:label="newDeal[field.name]" :label="newDeal[field.name]"
class="justify-between w-full" class="w-full justify-between"
> >
<template #prefix> <template #prefix>
<IndicatorIcon :class="dealStatuses[newDeal[field.name]].color" /> <IndicatorIcon
:class="dealStatuses[newDeal[field.name]].color"
/>
</template> </template>
<template #default>{{ newDeal[field.name] }}</template> <template #default>{{ newDeal[field.name] }}</template>
<template #suffix> <template #suffix>
@ -67,17 +79,13 @@
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue' import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import UserAvatar from '@/components/UserAvatar.vue' import UserAvatar from '@/components/UserAvatar.vue'
import { usersStore } from '@/stores/users' import { usersStore } from '@/stores/users'
import { organizationsStore } from '@/stores/organizations'
import { dealStatuses, statusDropdownOptions, activeAgents } from '@/utils' import { dealStatuses, statusDropdownOptions, activeAgents } from '@/utils'
import { import { FormControl, Button, Dropdown, FeatherIcon } from 'frappe-ui'
FormControl,
Button, const { getUser } = usersStore()
Autocomplete, const { getOrganizationOptions } = organizationsStore()
Dropdown,
FeatherIcon,
} from 'frappe-ui'
import { computed } from 'vue'
const { getUser, users } = usersStore()
const props = defineProps({ const props = defineProps({
newDeal: { newDeal: {
type: Object, type: Object,
@ -135,8 +143,13 @@ const allFields = [
fields: [ fields: [
{ {
label: 'Organization', label: 'Organization',
name: 'organization_name', name: 'organization',
type: 'data', type: 'link',
placeholder: 'Organization',
options: getOrganizationOptions(),
change: (option) => {
newDeal.organization = option.name
},
}, },
{ {
label: 'Status', label: 'Status',

View File

@ -19,8 +19,18 @@
type="email" type="email"
v-model="newLead[field.name]" v-model="newLead[field.name]"
/> />
<Autocomplete <FormControl
v-else-if="field.type === 'link'" v-else-if="field.type === 'link'"
type="autocomplete"
:value="newLead[field.name]"
:options="field.options"
@change="(e) => field.change(e)"
:placeholder="field.placeholder"
class="form-control"
/>
<FormControl
v-else-if="field.type === 'user'"
type="autocomplete"
:options="activeAgents" :options="activeAgents"
:value="getUser(newLead[field.name]).full_name" :value="getUser(newLead[field.name]).full_name"
@change="(option) => (newLead[field.name] = option.email)" @change="(option) => (newLead[field.name] = option.email)"
@ -32,7 +42,7 @@
<template #item-prefix="{ option }"> <template #item-prefix="{ option }">
<UserAvatar class="mr-2" :user="option.email" size="sm" /> <UserAvatar class="mr-2" :user="option.email" size="sm" />
</template> </template>
</Autocomplete> </FormControl>
<Dropdown <Dropdown
v-else-if="field.type === 'dropdown'" v-else-if="field.type === 'dropdown'"
:options="statusDropdownOptions(newLead)" :options="statusDropdownOptions(newLead)"
@ -69,17 +79,13 @@
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue' import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import UserAvatar from '@/components/UserAvatar.vue' import UserAvatar from '@/components/UserAvatar.vue'
import { usersStore } from '@/stores/users' import { usersStore } from '@/stores/users'
import { organizationsStore } from '@/stores/organizations'
import { leadStatuses, statusDropdownOptions, activeAgents } from '@/utils' import { leadStatuses, statusDropdownOptions, activeAgents } from '@/utils'
import { import { FormControl, Button, Dropdown, FeatherIcon } from 'frappe-ui'
FormControl,
Button, const { getUser } = usersStore()
Autocomplete, const { getOrganizationOptions } = organizationsStore()
Dropdown,
FeatherIcon,
} from 'frappe-ui'
import { computed } from 'vue'
const { getUser, users } = usersStore()
const props = defineProps({ const props = defineProps({
newLead: { newLead: {
type: Object, type: Object,
@ -137,8 +143,13 @@ const allFields = [
fields: [ fields: [
{ {
label: 'Organization', label: 'Organization',
name: 'organization_name', name: 'organization',
type: 'data', type: 'link',
placeholder: 'Organization',
options: getOrganizationOptions(),
change: (option) => {
newLead.organization = option.name
},
}, },
{ {
label: 'Status', label: 'Status',
@ -149,7 +160,7 @@ const allFields = [
{ {
label: 'Lead owner', label: 'Lead owner',
name: 'lead_owner', name: 'lead_owner',
type: 'link', type: 'user',
placeholder: 'Lead owner', placeholder: 'Lead owner',
}, },
], ],

View File

@ -91,10 +91,10 @@
:key="field.name" :key="field.name"
class="flex items-center gap-2 px-3 text-base leading-5 last:mb-3" class="flex items-center gap-2 px-3 text-base leading-5 last:mb-3"
> >
<div class="w-[106px] text-gray-600"> <div class="w-[106px] shrink-0 text-gray-600">
{{ field.label }} {{ field.label }}
</div> </div>
<div class="flex-1"> <div class="flex-1 overflow-hidden">
<FormControl <FormControl
v-if="field.type === 'email'" v-if="field.type === 'email'"
type="email" type="email"
@ -121,6 +121,11 @@
:debounce="500" :debounce="500"
/> />
</div> </div>
<ExternalLinkIcon
v-if="field.type === 'link' && field.link && contact[field.name]"
class="h-4 w-4 shrink-0 cursor-pointer text-gray-600"
@click="field.link(contact[field.name])"
/>
</div> </div>
</div> </div>
</div> </div>
@ -197,6 +202,7 @@ import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import CameraIcon from '@/components/Icons/CameraIcon.vue' import CameraIcon from '@/components/Icons/CameraIcon.vue'
import LeadsIcon from '@/components/Icons/LeadsIcon.vue' import LeadsIcon from '@/components/Icons/LeadsIcon.vue'
import DealsIcon from '@/components/Icons/DealsIcon.vue' import DealsIcon from '@/components/Icons/DealsIcon.vue'
import ExternalLinkIcon from '@/components/Icons/ExternalLinkIcon.vue'
import LeadsListView from '@/components/ListViews/LeadsListView.vue' import LeadsListView from '@/components/ListViews/LeadsListView.vue'
import DealsListView from '@/components/ListViews/DealsListView.vue' import DealsListView from '@/components/ListViews/DealsListView.vue'
import { import {
@ -210,10 +216,12 @@ import {
} from '@/utils' } from '@/utils'
import { usersStore } from '@/stores/users.js' import { usersStore } from '@/stores/users.js'
import { contactsStore } from '@/stores/contacts.js' import { contactsStore } from '@/stores/contacts.js'
import { organizationsStore } from '@/stores/organizations.js'
import { ref, computed, h } from 'vue' import { ref, computed, h } from 'vue'
const { getContactByName, contacts } = contactsStore() const { getContactByName, contacts } = contactsStore()
const { getUser } = usersStore() const { getUser } = usersStore()
const { getOrganization, getOrganizationOptions } = organizationsStore()
const showContactModal = ref(false) const showContactModal = ref(false)
@ -297,8 +305,7 @@ const leads = createListResource({
'first_name', 'first_name',
'lead_name', 'lead_name',
'image', 'image',
'organization_name', 'organization',
'organization_logo',
'status', 'status',
'email', 'email',
'mobile_no', 'mobile_no',
@ -320,8 +327,7 @@ const deals = createListResource({
cache: ['deals', props.contactId], cache: ['deals', props.contactId],
fields: [ fields: [
'name', 'name',
'organization_name', 'organization',
'organization_logo',
'annual_revenue', 'annual_revenue',
'deal_status', 'deal_status',
'email', 'email',
@ -361,9 +367,9 @@ function getLeadRowObject(lead) {
image: lead.image, image: lead.image,
image_label: lead.first_name, image_label: lead.first_name,
}, },
organization_name: { organization: {
label: lead.organization_name, label: lead.organization,
logo: lead.organization_logo, logo: getOrganization(lead.organization)?.organization_logo,
}, },
status: { status: {
label: lead.status, label: lead.status,
@ -385,9 +391,9 @@ function getLeadRowObject(lead) {
function getDealRowObject(deal) { function getDealRowObject(deal) {
return { return {
name: deal.name, name: deal.name,
organization_name: { organization: {
label: deal.organization_name, label: deal.organization,
logo: deal.organization_logo, logo: getOrganization(deal.organization)?.organization_logo,
}, },
annual_revenue: formatNumberIntoCurrency(deal.annual_revenue), annual_revenue: formatNumberIntoCurrency(deal.annual_revenue),
deal_status: { deal_status: {
@ -415,7 +421,7 @@ const leadColumns = [
}, },
{ {
label: 'Organization', label: 'Organization',
key: 'organization_name', key: 'organization',
width: '10rem', width: '10rem',
}, },
{ {
@ -448,7 +454,7 @@ const leadColumns = [
const dealColumns = [ const dealColumns = [
{ {
label: 'Organization', label: 'Organization',
key: 'organization_name', key: 'organization',
width: '11rem', width: '11rem',
}, },
{ {
@ -483,54 +489,68 @@ const dealColumns = [
}, },
] ]
const details = [ const details = computed(() => {
{ return [
label: 'Salutation', {
type: 'link', label: 'Salutation',
name: 'salutation', type: 'link',
placeholder: 'Mr./Mrs./Ms.', name: 'salutation',
options: [ placeholder: 'Mr./Mrs./Ms.',
{ label: 'Dr', value: 'Dr' }, options: [
{ label: 'Mr', value: 'Mr' }, { label: 'Dr', value: 'Dr' },
{ label: 'Mrs', value: 'Mrs' }, { label: 'Mr', value: 'Mr' },
{ label: 'Ms', value: 'Ms' }, { label: 'Mrs', value: 'Mrs' },
{ label: 'Mx', value: 'Mx' }, { label: 'Ms', value: 'Ms' },
{ label: 'Prof', value: 'Prof' }, { label: 'Mx', value: 'Mx' },
{ label: 'Master', value: 'Master' }, { label: 'Prof', value: 'Prof' },
{ label: 'Madam', value: 'Madam' }, { label: 'Master', value: 'Master' },
{ label: 'Miss', value: 'Miss' }, { label: 'Madam', value: 'Madam' },
], { label: 'Miss', value: 'Miss' },
change: (data) => { ],
contact.value.salutation = data.value change: (data) => {
updateContact('salutation', data.value) contact.value.salutation = data.value
updateContact('salutation', data.value)
},
}, },
}, {
{ label: 'First name',
label: 'First name', type: 'data',
type: 'data', name: 'first_name',
name: 'first_name', },
}, {
{ label: 'Last name',
label: 'Last name', type: 'data',
type: 'data', name: 'last_name',
name: 'last_name', },
}, {
{ label: 'Email',
label: 'Email', type: 'email',
type: 'email', name: 'email',
name: 'email', },
}, {
{ label: 'Mobile no.',
label: 'Mobile no.', type: 'phone',
type: 'phone', name: 'mobile_no',
name: 'mobile_no', },
}, {
{ label: 'Organization',
label: 'Organization', type: 'link',
type: 'data', name: 'company_name',
name: 'company_name', placeholder: 'Select organization',
}, options: getOrganizationOptions(),
] change: (data) => {
contact.value.company_name = data.value
updateContact('company_name', data.value)
},
link: (data) => {
router.push({
name: 'Organization',
params: { organizationId: data.value },
})
},
},
]
})
function updateContact(fieldname, value) { function updateContact(fieldname, value) {
createResource({ createResource({
@ -570,7 +590,18 @@ function updateContact(fieldname, value) {
background: white; background: white;
} }
:deep(.form-control button) {
gap: 0;
}
:deep(.form-control button > div) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
:deep(.form-control button svg) { :deep(.form-control button svg) {
color: white; color: white;
width: 0;
} }
</style> </style>

View File

@ -4,7 +4,8 @@
<Breadcrumbs :items="breadcrumbs" /> <Breadcrumbs :items="breadcrumbs" />
</template> </template>
<template #right-header> <template #right-header>
<Autocomplete <FormControl
type="autocomplete"
:options="activeAgents" :options="activeAgents"
:value="getUser(deal.data.lead_owner).full_name" :value="getUser(deal.data.lead_owner).full_name"
@change="(option) => updateAssignedAgent(option.email)" @change="(option) => updateAssignedAgent(option.email)"
@ -16,7 +17,7 @@
<template #item-prefix="{ option }"> <template #item-prefix="{ option }">
<UserAvatar class="mr-2" :user="option.email" size="sm" /> <UserAvatar class="mr-2" :user="option.email" size="sm" />
</template> </template>
</Autocomplete> </FormControl>
<Dropdown :options="statusDropdownOptions(deal.data, 'deal', updateDeal)"> <Dropdown :options="statusDropdownOptions(deal.data, 'deal', updateDeal)">
<template #default="{ open }"> <template #default="{ open }">
<Button :label="deal.data.deal_status"> <Button :label="deal.data.deal_status">
@ -45,79 +46,47 @@
> >
About this deal About this deal
</div> </div>
<FileUploader @success="changeDealImage" :validateFile="validateFile"> <div class="flex items-center justify-start gap-5 border-b p-5">
<template #default="{ openFileSelector, error }"> <Tooltip
<div class="flex items-center justify-start gap-5 border-b p-5"> text="Organization logo"
<div class="group relative h-[88px] w-[88px]"> class="group relative h-[88px] w-[88px]"
<Avatar >
size="3xl" <Avatar
class="h-[88px] w-[88px]" size="3xl"
:label="deal.data.organization_name" class="h-[88px] w-[88px]"
:image="deal.data.organization_logo" :label="organization?.name"
/> :image="organization?.organization_logo"
<Dropdown />
:options="[ </Tooltip>
{ <div class="flex flex-col gap-2.5 truncate">
icon: 'upload', <Tooltip :text="organization?.name">
label: deal.data.organization_logo <div class="truncate text-2xl font-medium">
? 'Change image' {{ organization?.name }}
: 'Upload image', </div>
onClick: openFileSelector, </Tooltip>
}, <div class="flex gap-1.5">
{ <Tooltip text="Make a call...">
icon: 'trash-2', <Button
label: 'Remove image', class="h-7 w-7"
onClick: () => { @click="() => makeCall(deal.data.mobile_no)"
deal.data.organization_logo = ''
updateDeal('organization_logo', '')
},
},
]"
class="!absolute bottom-0 left-0 right-0"
> >
<div <PhoneIcon class="h-4 w-4" />
class="z-1 absolute bottom-0 left-0 right-0 flex h-11 cursor-pointer items-center justify-center rounded-b-full bg-black bg-opacity-40 pt-3 opacity-0 duration-300 ease-in-out group-hover:opacity-100" </Button>
style=" </Tooltip>
-webkit-clip-path: inset(12px 0 0 0); <Button class="h-7 w-7">
clip-path: inset(12px 0 0 0); <EmailIcon class="h-4 w-4" />
" </Button>
> <Tooltip text="Go to website...">
<CameraIcon class="h-6 w-6 cursor-pointer text-white" /> <Button class="h-7 w-7">
</div> <LinkIcon
</Dropdown> class="h-4 w-4"
</div> @click="openWebsite(deal.data.website)"
<div class="flex flex-col gap-2.5 truncate"> />
<Tooltip :text="deal.data.organization_name"> </Button>
<div class="truncate text-2xl font-medium"> </Tooltip>
{{ deal.data.organization_name }}
</div>
</Tooltip>
<div class="flex gap-1.5">
<Tooltip text="Make a call...">
<Button
class="h-7 w-7"
@click="() => makeCall(deal.data.mobile_no)"
>
<PhoneIcon class="h-4 w-4" />
</Button>
</Tooltip>
<Button class="h-7 w-7">
<EmailIcon class="h-4 w-4" />
</Button>
<Tooltip text="Go to website...">
<Button class="h-7 w-7">
<LinkIcon
class="h-4 w-4"
@click="openWebsite(deal.data.website)"
/>
</Button>
</Tooltip>
</div>
<ErrorMessage :message="error" />
</div>
</div> </div>
</template> </div>
</FileUploader> </div>
<div class="flex flex-1 flex-col justify-between overflow-hidden"> <div class="flex flex-1 flex-col justify-between overflow-hidden">
<div class="flex flex-col overflow-y-auto"> <div class="flex flex-col overflow-y-auto">
<div <div
@ -152,10 +121,10 @@
:key="field.label" :key="field.label"
class="flex items-center gap-2 px-3 text-base leading-5 first:mt-3" class="flex items-center gap-2 px-3 text-base leading-5 first:mt-3"
> >
<div class="w-[106px] text-gray-600"> <div class="w-[106px] shrink-0 text-gray-600">
{{ field.label }} {{ field.label }}
</div> </div>
<div class="flex-1"> <div class="flex-1 overflow-hidden">
<FormControl <FormControl
v-if="field.type === 'select'" v-if="field.type === 'select'"
type="select" type="select"
@ -183,16 +152,18 @@
" "
:debounce="500" :debounce="500"
/> />
<Autocomplete <FormControl
v-else-if="field.type === 'link'" v-else-if="field.type === 'link'"
type="autocomplete"
:value="deal.data[field.name]" :value="deal.data[field.name]"
:options="field.options" :options="field.options"
@change="(e) => field.change(e)" @change="(e) => field.change(e)"
:placeholder="field.placeholder" :placeholder="field.placeholder"
class="form-control" class="form-control"
/> />
<Autocomplete <FormControl
v-else-if="field.type === 'user'" v-else-if="field.type === 'user'"
type="autocomplete"
:options="activeAgents" :options="activeAgents"
:value="getUser(deal.data[field.name]).full_name" :value="getUser(deal.data[field.name]).full_name"
@change="(option) => updateAssignedAgent(option.email)" @change="(option) => updateAssignedAgent(option.email)"
@ -221,7 +192,7 @@
size="sm" size="sm"
/> />
</template> </template>
</Autocomplete> </FormControl>
<Dropdown <Dropdown
v-else-if="field.type === 'dropdown'" v-else-if="field.type === 'dropdown'"
:options=" :options="
@ -283,6 +254,12 @@
:debounce="500" :debounce="500"
class="form-control" class="form-control"
/> />
<Tooltip :text="field.tooltip"
class="flex h-7 cursor-pointer items-center px-2 py-1"
v-else-if="field.type === 'read_only'"
>
{{ field.value }}
</Tooltip>
<FormControl <FormControl
v-else v-else
type="text" type="text"
@ -294,6 +271,11 @@
class="form-control" class="form-control"
/> />
</div> </div>
<ExternalLinkIcon
v-if="field.type === 'link' && field.link && deal.data[field.name]"
class="h-4 w-4 shrink-0 cursor-pointer text-gray-600"
@click="field.link(deal.data[field.name])"
/>
</div> </div>
</div> </div>
</transition> </transition>
@ -311,8 +293,8 @@ import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import TaskIcon from '@/components/Icons/TaskIcon.vue' import TaskIcon from '@/components/Icons/TaskIcon.vue'
import NoteIcon from '@/components/Icons/NoteIcon.vue' import NoteIcon from '@/components/Icons/NoteIcon.vue'
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue' import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import CameraIcon from '@/components/Icons/CameraIcon.vue'
import LinkIcon from '@/components/Icons/LinkIcon.vue' import LinkIcon from '@/components/Icons/LinkIcon.vue'
import ExternalLinkIcon from '@/components/Icons/ExternalLinkIcon.vue'
import LayoutHeader from '@/components/LayoutHeader.vue' import LayoutHeader from '@/components/LayoutHeader.vue'
import Toggler from '@/components/Toggler.vue' import Toggler from '@/components/Toggler.vue'
import Activities from '@/components/Activities.vue' import Activities from '@/components/Activities.vue'
@ -326,12 +308,10 @@ import {
} from '@/utils' } from '@/utils'
import { usersStore } from '@/stores/users' import { usersStore } from '@/stores/users'
import { contactsStore } from '@/stores/contacts' import { contactsStore } from '@/stores/contacts'
import { organizationsStore } from '@/stores/organizations'
import { import {
createResource, createResource,
FeatherIcon, FeatherIcon,
FileUploader,
ErrorMessage,
Autocomplete,
FormControl, FormControl,
Dropdown, Dropdown,
Tooltip, Tooltip,
@ -340,9 +320,12 @@ import {
Breadcrumbs, Breadcrumbs,
} from 'frappe-ui' } from 'frappe-ui'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
const { getUser, users } = usersStore() const { getUser } = usersStore()
const { contacts } = contactsStore() const { contacts } = contactsStore()
const { getOrganization, getOrganizationOptions } = organizationsStore()
const router = useRouter()
const props = defineProps({ const props = defineProps({
dealId: { dealId: {
@ -394,7 +377,7 @@ function updateDeal(fieldname, value) {
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: 'Deals', route: { name: 'Deals' } }] let items = [{ label: 'Deals', route: { name: 'Deals' } }]
items.push({ items.push({
label: deal.data.organization_name, label: organization.value.name,
route: { name: 'Deal', params: { dealId: deal.data.name } }, route: { name: 'Deal', params: { dealId: deal.data.name } },
}) })
return items return items
@ -424,18 +407,6 @@ const tabs = [
}, },
] ]
function changeDealImage(file) {
deal.data.organization_logo = file.file_url
updateDeal('organization_logo', file.file_url)
}
function validateFile(file) {
let extn = file.name.split('.').pop().toLowerCase()
if (!['png', 'jpg', 'jpeg'].includes(extn)) {
return 'Only PNG and JPG images are allowed'
}
}
const detailSections = computed(() => { const detailSections = computed(() => {
return [ return [
{ {
@ -444,13 +415,27 @@ const detailSections = computed(() => {
fields: [ fields: [
{ {
label: 'Organization', label: 'Organization',
type: 'data', type: 'link',
name: 'organization_name', name: 'organization',
placeholder: 'Select organization',
options: getOrganizationOptions(),
change: (data) => {
deal.data.organization = data.value
updateDeal('organization', data.value)
},
link: () => {
router.push({
name: 'Organization',
params: { organizationId: organization.value.name },
})
},
}, },
{ {
label: 'Website', label: 'Website',
type: 'data', type: 'read_only',
name: 'website', name: 'website',
value: organization.value?.website,
tooltip: 'It is a read only field, value is fetched from organization',
}, },
{ {
label: 'Amount', label: 'Amount',
@ -524,6 +509,10 @@ const detailSections = computed(() => {
] ]
}) })
const organization = computed(() => {
return getOrganization(deal.data.organization)
})
function updateAssignedAgent(email) { function updateAssignedAgent(email) {
deal.data.lead_owner = email deal.data.lead_owner = email
updateDeal('lead_owner', email) updateDeal('lead_owner', email)
@ -538,7 +527,18 @@ function updateAssignedAgent(email) {
background: white; background: white;
} }
:deep(.form-control button) {
gap: 0;
}
:deep(.form-control button > div) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
:deep(.form-control button svg) { :deep(.form-control button svg) {
color: white; color: white;
width: 0;
} }
</style> </style>

View File

@ -37,7 +37,7 @@
<Dialog <Dialog
v-model="showNewDialog" v-model="showNewDialog"
:options="{ :options="{
width: '3xl', size: '3xl',
title: 'New Deal', title: 'New Deal',
actions: [{ label: 'Save', variant: 'solid' }], actions: [{ label: 'Save', variant: 'solid' }],
}" }"
@ -60,6 +60,7 @@ import NewDeal from '@/components/NewDeal.vue'
import SortBy from '@/components/SortBy.vue' import SortBy from '@/components/SortBy.vue'
import Filter from '@/components/Filter.vue' import Filter from '@/components/Filter.vue'
import { usersStore } from '@/stores/users' import { usersStore } from '@/stores/users'
import { organizationsStore } from '@/stores/organizations'
import { useOrderBy } from '@/composables/orderby' import { useOrderBy } from '@/composables/orderby'
import { useFilter } from '@/composables/filter' import { useFilter } from '@/composables/filter'
import { useDebounceFn } from '@vueuse/core' import { useDebounceFn } from '@vueuse/core'
@ -85,6 +86,7 @@ import { ref, computed, reactive, watch } from 'vue'
const breadcrumbs = [{ label: 'Deals', route: { name: 'Deals' } }] const breadcrumbs = [{ label: 'Deals', route: { name: 'Deals' } }]
const { getUser } = usersStore() const { getUser } = usersStore()
const { getOrganization } = organizationsStore()
const { get: getOrderBy } = useOrderBy() const { get: getOrderBy } = useOrderBy()
const { getArgs, storage } = useFilter() const { getArgs, storage } = useFilter()
@ -105,8 +107,7 @@ const leads = createListResource({
doctype: 'CRM Lead', doctype: 'CRM Lead',
fields: [ fields: [
'name', 'name',
'organization_name', 'organization',
'organization_logo',
'annual_revenue', 'annual_revenue',
'deal_status', 'deal_status',
'email', 'email',
@ -143,7 +144,7 @@ watch(
const columns = [ const columns = [
{ {
label: 'Organization', label: 'Organization',
key: 'organization_name', key: 'organization',
width: '11rem', width: '11rem',
}, },
{ {
@ -183,9 +184,9 @@ const rows = computed(() => {
return leads.data.map((lead) => { return leads.data.map((lead) => {
return { return {
name: lead.name, name: lead.name,
organization_name: { organization: {
label: lead.organization_name, label: lead.organization,
logo: lead.organization_logo, logo: getOrganization(lead.organization)?.organization_logo,
}, },
annual_revenue: formatNumberIntoCurrency(lead.annual_revenue), annual_revenue: formatNumberIntoCurrency(lead.annual_revenue),
deal_status: { deal_status: {
@ -256,7 +257,7 @@ let newDeal = reactive({
first_name: '', first_name: '',
last_name: '', last_name: '',
lead_name: '', lead_name: '',
organization_name: '', organization: '',
deal_status: 'Qualification', deal_status: 'Qualification',
email: '', email: '',
mobile_no: '', mobile_no: '',

View File

@ -4,7 +4,8 @@
<Breadcrumbs :items="breadcrumbs" /> <Breadcrumbs :items="breadcrumbs" />
</template> </template>
<template #right-header> <template #right-header>
<Autocomplete <FormControl
type="autocomplete"
:options="activeAgents" :options="activeAgents"
:value="getUser(lead.data.lead_owner).full_name" :value="getUser(lead.data.lead_owner).full_name"
@change="(option) => updateAssignedAgent(option.email)" @change="(option) => updateAssignedAgent(option.email)"
@ -16,7 +17,7 @@
<template #item-prefix="{ option }"> <template #item-prefix="{ option }">
<UserAvatar class="mr-2" :user="option.email" size="sm" /> <UserAvatar class="mr-2" :user="option.email" size="sm" />
</template> </template>
</Autocomplete> </FormControl>
<Dropdown :options="statusDropdownOptions(lead.data, 'lead', updateLead)"> <Dropdown :options="statusDropdownOptions(lead.data, 'lead', updateLead)">
<template #default="{ open }"> <template #default="{ open }">
<Button :label="lead.data.status"> <Button :label="lead.data.status">
@ -58,26 +59,32 @@
:label="lead.data.first_name" :label="lead.data.first_name"
:image="lead.data.image" :image="lead.data.image"
/> />
<Dropdown <component
:options="[ :is="lead.data.image ? Dropdown : 'div'"
{ v-bind="
icon: 'upload', lead.data.image
label: lead.data.image ? 'Change photo' : 'Upload photo', ? {
onClick: openFileSelector, options: [
}, {
{ icon: 'upload',
icon: 'trash-2', label: lead.data.image
label: 'Remove photo', ? 'Change image'
onClick: () => { : 'Upload image',
lead.data.image = '' onClick: openFileSelector,
updateLead('image', '') },
}, {
}, icon: 'trash-2',
]" label: 'Remove image',
onClick: () => changeLeadImage(''),
},
],
}
: { onClick: openFileSelector }
"
class="!absolute bottom-0 left-0 right-0" class="!absolute bottom-0 left-0 right-0"
> >
<div <div
class="z-1 absolute bottom-0 left-0 right-0 flex h-11 cursor-pointer items-center justify-center rounded-b-full bg-black bg-opacity-40 pt-3 opacity-0 duration-300 ease-in-out group-hover:opacity-100" class="z-1 absolute bottom-0 left-0 right-0 flex h-13 cursor-pointer items-center justify-center rounded-b-full bg-black bg-opacity-40 pt-3 opacity-0 duration-300 ease-in-out group-hover:opacity-100"
style=" style="
-webkit-clip-path: inset(12px 0 0 0); -webkit-clip-path: inset(12px 0 0 0);
clip-path: inset(12px 0 0 0); clip-path: inset(12px 0 0 0);
@ -85,7 +92,7 @@
> >
<CameraIcon class="h-6 w-6 cursor-pointer text-white" /> <CameraIcon class="h-6 w-6 cursor-pointer text-white" />
</div> </div>
</Dropdown> </component>
</div> </div>
<div class="flex flex-col gap-2.5 truncate"> <div class="flex flex-col gap-2.5 truncate">
<Tooltip :text="lead.data.lead_name"> <Tooltip :text="lead.data.lead_name">
@ -122,7 +129,7 @@
<div class="flex flex-1 flex-col justify-between overflow-hidden"> <div class="flex flex-1 flex-col justify-between overflow-hidden">
<div class="flex flex-col overflow-y-auto"> <div class="flex flex-col overflow-y-auto">
<div <div
v-for="(section, i) in detailSections" v-for="section in detailSections"
:key="section.label" :key="section.label"
class="flex flex-col" class="flex flex-col"
> >
@ -154,10 +161,10 @@
:key="field.name" :key="field.name"
class="flex items-center gap-2 px-3 text-base leading-5 last:mb-3" class="flex items-center gap-2 px-3 text-base leading-5 last:mb-3"
> >
<div class="w-[106px] text-gray-600"> <div class="w-[106px] shrink-0 text-gray-600">
{{ field.label }} {{ field.label }}
</div> </div>
<div class="flex-1"> <div class="flex-1 overflow-hidden">
<FormControl <FormControl
v-if="field.type === 'select'" v-if="field.type === 'select'"
type="select" type="select"
@ -185,16 +192,18 @@
" "
:debounce="500" :debounce="500"
/> />
<Autocomplete <FormControl
v-else-if="field.type === 'link'" v-else-if="field.type === 'link'"
type="autocomplete"
:value="lead.data[field.name]" :value="lead.data[field.name]"
:options="field.options" :options="field.options"
@change="(e) => field.change(e)" @change="(e) => field.change(e)"
:placeholder="field.placeholder" :placeholder="field.placeholder"
class="form-control" class="form-control"
/> />
<Autocomplete <FormControl
v-else-if="field.type === 'user'" v-else-if="field.type === 'user'"
type="autocomplete"
:options="activeAgents" :options="activeAgents"
:value="getUser(lead.data[field.name]).full_name" :value="getUser(lead.data[field.name]).full_name"
@change="(option) => updateAssignedAgent(option.email)" @change="(option) => updateAssignedAgent(option.email)"
@ -223,7 +232,7 @@
size="sm" size="sm"
/> />
</template> </template>
</Autocomplete> </FormControl>
<Dropdown <Dropdown
v-else-if="field.type === 'dropdown'" v-else-if="field.type === 'dropdown'"
:options=" :options="
@ -255,6 +264,13 @@
</Button> </Button>
</template> </template>
</Dropdown> </Dropdown>
<Tooltip
:text="field.tooltip"
class="flex h-7 cursor-pointer items-center px-2 py-1"
v-else-if="field.type === 'read_only'"
>
{{ field.value }}
</Tooltip>
<FormControl <FormControl
v-else v-else
type="text" type="text"
@ -266,6 +282,15 @@
:debounce="500" :debounce="500"
/> />
</div> </div>
<ExternalLinkIcon
v-if="
field.type === 'link' &&
field.link &&
lead.data[field.name]
"
class="h-4 w-4 shrink-0 cursor-pointer text-gray-600"
@click="field.link(lead.data[field.name])"
/>
</div> </div>
</div> </div>
</transition> </transition>
@ -283,7 +308,9 @@ import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import TaskIcon from '@/components/Icons/TaskIcon.vue' import TaskIcon from '@/components/Icons/TaskIcon.vue'
import NoteIcon from '@/components/Icons/NoteIcon.vue' import NoteIcon from '@/components/Icons/NoteIcon.vue'
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue' import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import CameraIcon from '@/components/Icons/CameraIcon.vue'
import LinkIcon from '@/components/Icons/LinkIcon.vue' import LinkIcon from '@/components/Icons/LinkIcon.vue'
import ExternalLinkIcon from '@/components/Icons/ExternalLinkIcon.vue'
import LayoutHeader from '@/components/LayoutHeader.vue' import LayoutHeader from '@/components/LayoutHeader.vue'
import Toggler from '@/components/Toggler.vue' import Toggler from '@/components/Toggler.vue'
import Activities from '@/components/Activities.vue' import Activities from '@/components/Activities.vue'
@ -297,12 +324,12 @@ import {
} from '@/utils' } from '@/utils'
import { usersStore } from '@/stores/users' import { usersStore } from '@/stores/users'
import { contactsStore } from '@/stores/contacts' import { contactsStore } from '@/stores/contacts'
import { organizationsStore } from '@/stores/organizations'
import { import {
createResource, createResource,
FileUploader, FileUploader,
ErrorMessage, ErrorMessage,
FeatherIcon, FeatherIcon,
Autocomplete,
FormControl, FormControl,
Dropdown, Dropdown,
Tooltip, Tooltip,
@ -312,10 +339,10 @@ import {
} from 'frappe-ui' } from 'frappe-ui'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import CameraIcon from '../components/Icons/CameraIcon.vue'
const { getUser, users } = usersStore() const { getUser } = usersStore()
const { contacts } = contactsStore() const { contacts } = contactsStore()
const { getOrganization, getOrganizationOptions } = organizationsStore()
const router = useRouter() const router = useRouter()
const props = defineProps({ const props = defineProps({
@ -421,13 +448,28 @@ const detailSections = computed(() => {
fields: [ fields: [
{ {
label: 'Organization', label: 'Organization',
type: 'data', type: 'link',
name: 'organization_name', name: 'organization',
placeholder: 'Select organization',
options: getOrganizationOptions(),
change: (data) => {
lead.data.organization = data.value
updateLead('organization', data.value)
},
link: () => {
router.push({
name: 'Organization',
params: { organizationId: organization.value?.name },
})
},
}, },
{ {
label: 'Website', label: 'Website',
type: 'data', type: 'read_only',
name: 'website', name: 'website',
value: organization.value?.website,
tooltip:
'It is a read only field, value is fetched from organization',
}, },
{ {
label: 'Job title', label: 'Job title',
@ -517,6 +559,10 @@ const detailSections = computed(() => {
] ]
}) })
const organization = computed(() => {
return getOrganization(lead.data.organization)
})
function convertToDeal() { function convertToDeal() {
lead.data.status = 'Qualified' lead.data.status = 'Qualified'
lead.data.is_deal = 1 lead.data.is_deal = 1
@ -537,7 +583,18 @@ function updateAssignedAgent(email) {
background: white; background: white;
} }
:deep(.form-control button) {
gap: 0;
}
:deep(.form-control button > div) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
:deep(.form-control button svg) { :deep(.form-control button svg) {
color: white; color: white;
width: 0;
} }
</style> </style>

View File

@ -59,6 +59,7 @@ import NewLead from '@/components/NewLead.vue'
import SortBy from '@/components/SortBy.vue' import SortBy from '@/components/SortBy.vue'
import Filter from '@/components/Filter.vue' import Filter from '@/components/Filter.vue'
import { usersStore } from '@/stores/users' import { usersStore } from '@/stores/users'
import { organizationsStore } from '@/stores/organizations'
import { useOrderBy } from '@/composables/orderby' import { useOrderBy } from '@/composables/orderby'
import { useFilter } from '@/composables/filter' import { useFilter } from '@/composables/filter'
import { useDebounceFn } from '@vueuse/core' import { useDebounceFn } from '@vueuse/core'
@ -78,6 +79,7 @@ import { ref, computed, reactive, watch } from 'vue'
const breadcrumbs = [{ label: 'Leads', route: { name: 'Leads' } }] const breadcrumbs = [{ label: 'Leads', route: { name: 'Leads' } }]
const { getUser } = usersStore() const { getUser } = usersStore()
const { getOrganization } = organizationsStore()
const { get: getOrderBy } = useOrderBy() const { get: getOrderBy } = useOrderBy()
const { getArgs, storage } = useFilter() const { getArgs, storage } = useFilter()
@ -105,8 +107,7 @@ const leads = createListResource({
'first_name', 'first_name',
'lead_name', 'lead_name',
'image', 'image',
'organization_name', 'organization',
'organization_logo',
'status', 'status',
'email', 'email',
'mobile_no', 'mobile_no',
@ -147,7 +148,7 @@ const columns = [
}, },
{ {
label: 'Organization', label: 'Organization',
key: 'organization_name', key: 'organization',
width: '10rem', width: '10rem',
}, },
{ {
@ -187,9 +188,9 @@ const rows = computed(() => {
image: lead.image, image: lead.image,
image_label: lead.first_name, image_label: lead.first_name,
}, },
organization_name: { organization: {
label: lead.organization_name, label: lead.organization,
logo: lead.organization_logo, logo: getOrganization(lead.organization)?.organization_logo,
}, },
status: { status: {
label: lead.status, label: lead.status,
@ -259,7 +260,7 @@ let newLead = reactive({
first_name: '', first_name: '',
last_name: '', last_name: '',
lead_name: '', lead_name: '',
organization_name: '', organization: '',
status: 'Open', status: 'Open',
email: '', email: '',
mobile_no: '', mobile_no: '',

View File

@ -289,8 +289,7 @@ const leads = createListResource({
'first_name', 'first_name',
'lead_name', 'lead_name',
'image', 'image',
'organization_name', 'organization',
'organization_logo',
'status', 'status',
'email', 'email',
'mobile_no', 'mobile_no',
@ -298,7 +297,7 @@ const leads = createListResource({
'modified', 'modified',
], ],
filters: { filters: {
organization_name: props.organization.name, organization: props.organization.name,
is_deal: 0, is_deal: 0,
}, },
orderBy: 'modified desc', orderBy: 'modified desc',
@ -312,8 +311,7 @@ const deals = createListResource({
cache: ['deals', props.organization.name], cache: ['deals', props.organization.name],
fields: [ fields: [
'name', 'name',
'organization_name', 'organization',
'organization_logo',
'annual_revenue', 'annual_revenue',
'deal_status', 'deal_status',
'email', 'email',
@ -322,7 +320,7 @@ const deals = createListResource({
'modified', 'modified',
], ],
filters: { filters: {
organization_name: props.organization.name, organization: props.organization.name,
is_deal: 1, is_deal: 1,
}, },
orderBy: 'modified desc', orderBy: 'modified desc',
@ -334,7 +332,15 @@ const contacts = createListResource({
type: 'list', type: 'list',
doctype: 'Contact', doctype: 'Contact',
cache: ['contacts', props.organization.name], cache: ['contacts', props.organization.name],
fields: ['name', 'email_id', 'mobile_no', 'company_name', 'modified'], fields: [
'name',
'full_name',
'image',
'email_id',
'mobile_no',
'company_name',
'modified',
],
filters: { filters: {
company_name: props.organization.name, company_name: props.organization.name,
}, },
@ -374,9 +380,9 @@ function getLeadRowObject(lead) {
image: lead.image, image: lead.image,
image_label: lead.first_name, image_label: lead.first_name,
}, },
organization_name: { organization: {
label: lead.organization_name, label: lead.organization,
logo: lead.organization_logo, logo: props.organization?.organization_logo,
}, },
status: { status: {
label: lead.status, label: lead.status,
@ -398,9 +404,9 @@ function getLeadRowObject(lead) {
function getDealRowObject(deal) { function getDealRowObject(deal) {
return { return {
name: deal.name, name: deal.name,
organization_name: { organization: {
label: deal.organization_name, label: deal.organization,
logo: deal.organization_logo, logo: props.organization?.organization_logo,
}, },
annual_revenue: formatNumberIntoCurrency(deal.annual_revenue), annual_revenue: formatNumberIntoCurrency(deal.annual_revenue),
deal_status: { deal_status: {
@ -449,7 +455,7 @@ const leadColumns = [
}, },
{ {
label: 'Organization', label: 'Organization',
key: 'organization_name', key: 'organization',
width: '10rem', width: '10rem',
}, },
{ {
@ -482,7 +488,7 @@ const leadColumns = [
const dealColumns = [ const dealColumns = [
{ {
label: 'Organization', label: 'Organization',
key: 'organization_name', key: 'organization',
width: '11rem', width: '11rem',
}, },
{ {
@ -546,8 +552,8 @@ const contactColumns = [
] ]
function reload(val) { function reload(val) {
leads.filters.organization_name = val leads.filters.organization = val
deals.filters.organization_name = val deals.filters.organization = val
contacts.filters.company_name = val contacts.filters.company_name = val
leads.reload() leads.reload()
deals.reload() deals.reload()

View File

@ -1,6 +1,6 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { createResource } from 'frappe-ui' import { createResource } from 'frappe-ui'
import { reactive } from 'vue' import { reactive, computed } from 'vue'
export const organizationsStore = defineStore('crm-organizations', () => { export const organizationsStore = defineStore('crm-organizations', () => {
let organizationsByName = reactive({}) let organizationsByName = reactive({})
@ -27,8 +27,19 @@ export const organizationsStore = defineStore('crm-organizations', () => {
return organizationsByName[name] return organizationsByName[name]
} }
function getOrganizationOptions() {
return [
{ label: '', value: '' },
...organizations.data?.map((org) => ({
label: org.name,
value: org.name,
})),
]
}
return { return {
organizations, organizations,
getOrganizationOptions,
getOrganization, getOrganization,
} }
}) })