fix: added more fields in lead and deal page & listview

This commit is contained in:
Shariq Ansari 2023-08-26 12:01:08 +05:30
parent 66420a1444
commit 1154bce89c
6 changed files with 263 additions and 87 deletions

View File

@ -39,6 +39,8 @@ def get_activities(doc, docinfo):
for version in docinfo.versions: for version in docinfo.versions:
data = json.loads(version.data) data = json.loads(version.data)
if not data.get("changed"):
continue
if change := data.get("changed")[0]: if change := data.get("changed")[0]:
activity_type = "changed" activity_type = "changed"
field_label = next((f.label for f in lead_fields_meta if f.fieldname == change[0]), None) field_label = next((f.label for f in lead_fields_meta if f.fieldname == change[0]), None)

View File

@ -19,9 +19,14 @@
</Autocomplete> </Autocomplete>
<Dropdown :options="statusDropdownOptions(deal.data, 'deal')"> <Dropdown :options="statusDropdownOptions(deal.data, 'deal')">
<template #default="{ open }"> <template #default="{ open }">
<Button :label="deal.data.deal_status"> <Button
:label="deal.data.deal_status"
:class="dealStatuses[deal.data.deal_status].bgColor"
>
<template #prefix> <template #prefix>
<IndicatorIcon :class="dealStatuses[deal.data.deal_status].color" /> <IndicatorIcon
:class="dealStatuses[deal.data.deal_status].color"
/>
</template> </template>
<template #suffix <template #suffix
><FeatherIcon ><FeatherIcon
@ -31,7 +36,6 @@
</Button> </Button>
</template> </template>
</Dropdown> </Dropdown>
<Button icon="more-horizontal" />
<Button label="Save" variant="solid" @click="() => updateDeal()" /> <Button label="Save" variant="solid" @click="() => updateDeal()" />
</template> </template>
</LayoutHeader> </LayoutHeader>
@ -80,10 +84,11 @@
> >
<Avatar <Avatar
size="3xl" size="3xl"
:label="deal.data.first_name" shape="square"
:image="deal.data.image" :label="deal.data.organization_name"
:image="deal.data.organization_logo"
/> />
<div class="font-medium text-2xl">{{ deal.data.lead_name }}</div> <div class="font-medium text-2xl">{{ deal.data.organization_name }}</div>
<div class="flex gap-3"> <div class="flex gap-3">
<Tooltip text="Make a call..."> <Tooltip text="Make a call...">
<Button <Button
@ -96,7 +101,13 @@
<Button class="rounded-full h-8 w-8"> <Button class="rounded-full h-8 w-8">
<EmailIcon class="h-4 w-4" /> <EmailIcon class="h-4 w-4" />
</Button> </Button>
<Button icon="message-square" class="rounded-full h-8 w-8" /> <Tooltip text="Go to website...">
<Button
icon="link"
@click="openWebsite(deal.data.website)"
class="rounded-full h-8 w-8"
/>
</Tooltip>
<Button icon="more-horizontal" class="rounded-full h-8 w-8" /> <Button icon="more-horizontal" class="rounded-full h-8 w-8" />
</div> </div>
</div> </div>
@ -218,6 +229,24 @@
</Button> </Button>
</template> </template>
</Dropdown> </Dropdown>
<FormControl
v-else-if="field.type === 'date'"
type="date"
v-model="deal.data[field.name]"
class="form-control"
/>
<FormControl
v-else-if="field.type === 'number'"
type="number"
v-model="deal.data[field.name]"
class="form-control"
/>
<FormControl
v-else-if="field.type === 'tel'"
type="tel"
v-model="deal.data[field.name]"
class="form-control"
/>
<FormControl <FormControl
v-else v-else
type="text" type="text"
@ -264,7 +293,14 @@ import UserAvatar from '@/components/UserAvatar.vue'
import CommunicationArea from '@/components/CommunicationArea.vue' import CommunicationArea from '@/components/CommunicationArea.vue'
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from '@headlessui/vue' import { TabGroup, TabList, Tab, TabPanels, TabPanel } from '@headlessui/vue'
import { TransitionPresets, useTransition } from '@vueuse/core' import { TransitionPresets, useTransition } from '@vueuse/core'
import { dateFormat, timeAgo, dateTooltipFormat, dealStatuses, statusDropdownOptions } from '@/utils' import {
dateFormat,
timeAgo,
dateTooltipFormat,
dealStatuses,
statusDropdownOptions,
openWebsite,
} from '@/utils'
import { usersStore } from '@/stores/users' import { usersStore } from '@/stores/users'
import { import {
createResource, createResource,
@ -315,7 +351,7 @@ function updateDeal() {
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.lead_name, label: deal.data.organization_name,
route: { name: 'Deal', params: { dealId: deal.data.name } }, route: { name: 'Deal', params: { dealId: deal.data.name } },
}) })
return items return items
@ -345,11 +381,11 @@ const tabs = computed(() => {
), ),
activityTitle: 'Calls', activityTitle: 'Calls',
}, },
{ // {
label: 'Tasks', // label: 'Tasks',
icon: TaskIcon, // icon: TaskIcon,
activityTitle: 'Tasks', // activityTitle: 'Tasks',
}, // },
{ {
label: 'Notes', label: 'Notes',
icon: NoteIcon, icon: NoteIcon,
@ -376,20 +412,9 @@ function onTabChange(index) {
const detailSections = computed(() => { const detailSections = computed(() => {
return [ return [
{ {
label: 'About this deal', label: 'Organization',
opened: true, opened: true,
fields: [ fields: [
{
label: 'Status',
type: 'select',
name: 'deal_status',
options: statusDropdownOptions(deal.data, 'deal'),
},
{
label: 'Deal owner',
type: 'link',
name: 'lead_owner',
},
{ {
label: 'Organization', label: 'Organization',
type: 'data', type: 'data',
@ -400,10 +425,30 @@ const detailSections = computed(() => {
type: 'data', type: 'data',
name: 'website', name: 'website',
}, },
{
label: 'Amount',
type: 'number',
name: 'annual_revenue',
},
{
label: 'Close date',
type: 'date',
name: 'close_date',
},
{
label: 'Probability',
type: 'data',
name: 'probability',
},
{
label: 'Next step',
type: 'data',
name: 'next_step',
}
], ],
}, },
{ {
label: 'Person', label: 'Contacts',
opened: true, opened: true,
fields: [ fields: [
{ {
@ -423,7 +468,7 @@ const detailSections = computed(() => {
}, },
{ {
label: 'Mobile no.', label: 'Mobile no.',
type: 'phone', type: 'tel',
name: 'mobile_no', name: 'mobile_no',
}, },
], ],

View File

@ -102,11 +102,9 @@ const leads = createListResource({
doctype: 'CRM Lead', doctype: 'CRM Lead',
fields: [ fields: [
'name', 'name',
'first_name',
'lead_name',
'image',
'organization_name', 'organization_name',
'organization_logo', 'organization_logo',
'annual_revenue',
'deal_status', 'deal_status',
'email', 'email',
'mobile_no', 'mobile_no',
@ -140,23 +138,23 @@ watch(
) )
const columns = [ const columns = [
{
label: 'Name',
key: 'lead_name',
type: 'avatar',
size: 'w-44',
},
{ {
label: 'Organization', label: 'Organization',
key: 'organization_name', key: 'organization_name',
type: 'logo', type: 'logo',
size: 'w-44', size: 'w-48',
},
{
label: 'Amount',
key: 'annual_revenue',
type: 'data',
size: 'w-24',
}, },
{ {
label: 'Status', label: 'Status',
key: 'deal_status', key: 'deal_status',
type: 'indicator', type: 'indicator',
size: 'w-44', size: 'w-36',
}, },
{ {
label: 'Email', label: 'Email',
@ -168,13 +166,19 @@ const columns = [
label: 'Mobile no', label: 'Mobile no',
key: 'mobile_no', key: 'mobile_no',
type: 'phone', type: 'phone',
size: 'w-44', size: 'w-32',
}, },
{ {
label: 'Lead owner', label: 'Lead owner',
key: 'lead_owner', key: 'lead_owner',
type: 'avatar', type: 'avatar',
size: 'w-44', size: 'w-36',
},
{
label: 'Last modified',
key: 'modified',
type: 'pretty_date',
size: 'w-28',
}, },
] ]
@ -182,15 +186,11 @@ const rows = computed(() => {
return leads.data?.map((lead) => { return leads.data?.map((lead) => {
return { return {
name: lead.name, name: lead.name,
lead_name: {
label: lead.lead_name,
image: lead.image,
image_label: lead.first_name,
},
organization_name: { organization_name: {
label: lead.organization_name, label: lead.organization_name,
logo: lead.organization_logo, logo: lead.organization_logo,
}, },
annual_revenue: lead.annual_revenue,
deal_status: { deal_status: {
label: lead.deal_status, label: lead.deal_status,
color: dealStatuses[lead.deal_status]?.color, color: dealStatuses[lead.deal_status]?.color,
@ -198,6 +198,7 @@ const rows = computed(() => {
email: lead.email, email: lead.email,
mobile_no: lead.mobile_no, mobile_no: lead.mobile_no,
lead_owner: lead.lead_owner && getUser(lead.lead_owner), lead_owner: lead.lead_owner && getUser(lead.lead_owner),
modified: lead.modified,
} }
}) })
}) })

View File

@ -19,7 +19,10 @@
</Autocomplete> </Autocomplete>
<Dropdown :options="statusDropdownOptions(lead.data)"> <Dropdown :options="statusDropdownOptions(lead.data)">
<template #default="{ open }"> <template #default="{ open }">
<Button :label="lead.data.status"> <Button
:label="lead.data.status"
:class="leadStatuses[lead.data.status].bgColor"
>
<template #prefix> <template #prefix>
<IndicatorIcon :class="leadStatuses[lead.data.status].color" /> <IndicatorIcon :class="leadStatuses[lead.data.status].color" />
</template> </template>
@ -31,8 +34,8 @@
</Button> </Button>
</template> </template>
</Dropdown> </Dropdown>
<Button icon="more-horizontal" /> <Button label="Save" variant="solid" @click="updateLead()" />
<Button label="Save" variant="solid" @click="() => updateLead()" /> <Button label="Convert to deal" variant="solid" @click="convertToDeal()" />
</template> </template>
</LayoutHeader> </LayoutHeader>
<TabGroup v-slot="{ selectedIndex }" v-if="lead.data" @change="onTabChange"> <TabGroup v-slot="{ selectedIndex }" v-if="lead.data" @change="onTabChange">
@ -96,7 +99,13 @@
<Button class="rounded-full h-8 w-8"> <Button class="rounded-full h-8 w-8">
<EmailIcon class="h-4 w-4" /> <EmailIcon class="h-4 w-4" />
</Button> </Button>
<Button icon="message-square" class="rounded-full h-8 w-8" /> <Tooltip text="Go to website...">
<Button
icon="link"
@click="openWebsite(lead.data.website)"
class="rounded-full h-8 w-8"
/>
</Tooltip>
<Button icon="more-horizontal" class="rounded-full h-8 w-8" /> <Button icon="more-horizontal" class="rounded-full h-8 w-8" />
</div> </div>
</div> </div>
@ -158,6 +167,14 @@
/> />
<Autocomplete <Autocomplete
v-else-if="field.type === 'link'" v-else-if="field.type === 'link'"
:value="lead.data[field.name]"
:options="field.options"
@change="(e) => field.change(e)"
:placeholder="field.placeholder"
class="form-control"
/>
<Autocomplete
v-else-if="field.type === 'user'"
:options="activeAgents" :options="activeAgents"
:value="getUser(lead.data[field.name]).full_name" :value="getUser(lead.data[field.name]).full_name"
@change=" @change="
@ -264,7 +281,14 @@ import UserAvatar from '@/components/UserAvatar.vue'
import CommunicationArea from '@/components/CommunicationArea.vue' import CommunicationArea from '@/components/CommunicationArea.vue'
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from '@headlessui/vue' import { TabGroup, TabList, Tab, TabPanels, TabPanel } from '@headlessui/vue'
import { TransitionPresets, useTransition } from '@vueuse/core' import { TransitionPresets, useTransition } from '@vueuse/core'
import { dateFormat, timeAgo, dateTooltipFormat, leadStatuses, statusDropdownOptions } from '@/utils' import {
dateFormat,
timeAgo,
dateTooltipFormat,
leadStatuses,
statusDropdownOptions,
openWebsite,
} from '@/utils'
import { usersStore } from '@/stores/users' import { usersStore } from '@/stores/users'
import { import {
createResource, createResource,
@ -277,8 +301,10 @@ import {
Avatar, Avatar,
} from 'frappe-ui' } from 'frappe-ui'
import { ref, computed, inject } from 'vue' import { ref, computed, inject } from 'vue'
import { useRouter } from 'vue-router'
const { getUser, users } = usersStore() const { getUser, users } = usersStore()
const router = useRouter()
const makeCall = inject('makeOutgoingCall') const makeCall = inject('makeOutgoingCall')
@ -345,11 +371,11 @@ const tabs = computed(() => {
), ),
activityTitle: 'Calls', activityTitle: 'Calls',
}, },
{ // {
label: 'Tasks', // label: 'Tasks',
icon: TaskIcon, // icon: TaskIcon,
activityTitle: 'Tasks', // activityTitle: 'Tasks',
}, // },
{ {
label: 'Notes', label: 'Notes',
icon: NoteIcon, icon: NoteIcon,
@ -379,17 +405,6 @@ const detailSections = computed(() => {
label: 'About this lead', label: 'About this lead',
opened: true, opened: true,
fields: [ fields: [
{
label: 'Status',
type: 'select',
name: 'status',
options: statusDropdownOptions(lead.data),
},
{
label: 'Lead owner',
type: 'link',
name: 'lead_owner',
},
{ {
label: 'Organization', label: 'Organization',
type: 'data', type: 'data',
@ -400,12 +415,66 @@ const detailSections = computed(() => {
type: 'data', type: 'data',
name: 'website', name: 'website',
}, },
{
label: 'Job title',
type: 'data',
name: 'job_title',
},
{
label: 'Source',
type: 'link',
name: 'source',
placeholder: 'Select source...',
options: [
{ label: 'Advertisement', value: 'Advertisement' },
{ label: 'Web', value: 'Web' },
{ label: 'Others', value: 'Others' },
],
change: (data) => {
lead.data.source = data.value
},
},
{
label: 'Industry',
type: 'link',
name: 'industry',
placeholder: 'Select industry...',
options: [
{ label: 'Advertising', value: 'Advertising' },
{ label: 'Agriculture', value: 'Agriculture' },
{ label: 'Banking', value: 'Banking' },
{ label: 'Others', value: 'Others' },
],
change: (data) => {
lead.data.industry = data.value
},
},
], ],
}, },
{ {
label: 'Person', label: 'Person',
opened: true, opened: true,
fields: [ fields: [
{
label: 'Salutation',
type: 'link',
name: 'salutation',
placeholder: 'Mr./Mrs./Ms.',
options: [
{ label: 'Dr', value: 'Dr' },
{ label: 'Mr', value: 'Mr' },
{ label: 'Mrs', value: 'Mrs' },
{ label: 'Ms', value: 'Ms' },
{ label: 'Mx', value: 'Mx' },
{ label: 'Prof', value: 'Prof' },
{ label: 'Master', value: 'Master' },
{ label: 'Madam', value: 'Madam' },
{ label: 'Miss', value: 'Miss' },
],
change: (data) => {
lead.data.salutation = data.value
},
},
{ {
label: 'First name', label: 'First name',
type: 'data', type: 'data',
@ -444,6 +513,13 @@ const activeAgents = computed(() => {
} }
}) })
}) })
function convertToDeal() {
lead.data.status = 'Qualified'
lead.data.is_deal = 1
updateLead()
router.push({ name: 'Deal', params: { dealId: lead.data.name } })
}
</script> </script>
<style scoped> <style scoped>
@ -453,4 +529,8 @@ const activeAgents = computed(() => {
border-color: transparent; border-color: transparent;
background: white; background: white;
} }
:deep(.form-control button svg) {
color: white;
}
</style> </style>

View File

@ -96,6 +96,10 @@ function getFilter() {
} }
} }
function getSortBy() {
return getOrderBy() || 'modified desc'
}
const leads = createListResource({ const leads = createListResource({
type: 'list', type: 'list',
doctype: 'CRM Lead', doctype: 'CRM Lead',
@ -113,7 +117,7 @@ const leads = createListResource({
'modified', 'modified',
], ],
filters: getFilter(), filters: getFilter(),
orderBy: 'modified desc', orderBy: getSortBy(),
pageLength: 20, pageLength: 20,
auto: true, auto: true,
}) })
@ -122,7 +126,7 @@ watch(
() => getOrderBy(), () => getOrderBy(),
(value, old_value) => { (value, old_value) => {
if (!value && !old_value) return if (!value && !old_value) return
leads.orderBy = getOrderBy() || 'modified desc' leads.orderBy = getSortBy()
leads.reload() leads.reload()
}, },
{ immediate: true } { immediate: true }
@ -143,37 +147,43 @@ const columns = [
label: 'Name', label: 'Name',
key: 'lead_name', key: 'lead_name',
type: 'avatar', type: 'avatar',
size: 'w-44', size: 'w-48',
}, },
{ {
label: 'Organization', label: 'Organization',
key: 'organization_name', key: 'organization_name',
type: 'logo', type: 'logo',
size: 'w-44', size: 'w-40',
}, },
{ {
label: 'Status', label: 'Status',
key: 'status', key: 'status',
type: 'indicator', type: 'indicator',
size: 'w-44', size: 'w-36',
}, },
{ {
label: 'Email', label: 'Email',
key: 'email', key: 'email',
type: 'email', type: 'email',
size: 'w-44', size: 'w-40',
}, },
{ {
label: 'Mobile no', label: 'Mobile no',
key: 'mobile_no', key: 'mobile_no',
type: 'phone', type: 'phone',
size: 'w-44', size: 'w-32',
}, },
{ {
label: 'Lead owner', label: 'Lead owner',
key: 'lead_owner', key: 'lead_owner',
type: 'avatar', type: 'avatar',
size: 'w-44', size: 'w-36',
},
{
label: 'Last modified',
key: 'modified',
type: 'pretty_date',
size: 'w-28',
}, },
] ]
@ -197,6 +207,7 @@ const rows = computed(() => {
email: lead.email, email: lead.email,
mobile_no: lead.mobile_no, mobile_no: lead.mobile_no,
lead_owner: lead.lead_owner && getUser(lead.lead_owner), lead_owner: lead.lead_owner && getUser(lead.lead_owner),
modified: lead.modified,
} }
}) })
}) })

View File

@ -14,25 +14,58 @@ export function timeAgo(date) {
export const dateTooltipFormat = 'ddd, MMM D, YYYY h:mm A' export const dateTooltipFormat = 'ddd, MMM D, YYYY h:mm A'
export const leadStatuses = { export const leadStatuses = {
Open: { label: 'Open', color: '!text-gray-600' }, Open: { label: 'Open', color: '!text-gray-600', bgColor: '!bg-gray-200' },
Contacted: { label: 'Contacted', color: '!text-orange-600' }, Contacted: {
Nurture: { label: 'Nurture', color: '!text-blue-600' }, label: 'Contacted',
Qualified: { label: 'Qualified', color: '!text-green-600' }, color: '!text-orange-600',
Unqualified: { label: 'Unqualified', color: '!text-red-600' }, bgColor: '!bg-orange-200',
Junk: { label: 'Junk', color: '!text-purple-600' }, },
Nurture: {
label: 'Nurture',
color: '!text-blue-600',
bgColor: '!bg-blue-200',
},
Qualified: {
label: 'Qualified',
color: '!text-green-600',
bgColor: '!bg-green-200',
},
Unqualified: {
label: 'Unqualified',
color: '!text-red-600',
bgColor: '!bg-red-200',
},
Junk: { label: 'Junk', color: '!text-purple-600', bgColor: '!bg-purple-200' },
} }
export const dealStatuses = { export const dealStatuses = {
Qualification: { label: 'Qualification', color: '!text-gray-600' }, Qualification: {
'Demo/Making': { label: 'Demo/Making', color: '!text-orange-600' }, label: 'Qualification',
color: '!text-gray-600',
bgColor: '!bg-gray-200',
},
'Demo/Making': {
label: 'Demo/Making',
color: '!text-orange-600',
bgColor: '!bg-orange-200',
},
'Proposal/Quotation': { 'Proposal/Quotation': {
label: 'Proposal/Quotation', label: 'Proposal/Quotation',
color: '!text-blue-600', color: '!text-blue-600',
bgColor: '!bg-blue-200',
}, },
Negotiation: { label: 'Negotiation', color: '!text-yellow-600' }, Negotiation: {
'Ready to Close': { label: 'Ready to Close', color: '!text-purple-600' }, label: 'Negotiation',
Won: { label: 'Won', color: '!text-green-600' }, color: '!text-yellow-600',
Lost: { label: 'Lost', color: '!text-red-600' }, bgColor: '!bg-yellow-100',
},
'Ready to Close': {
label: 'Ready to Close',
color: '!text-purple-600',
bgColor: '!bg-purple-200',
},
Won: { label: 'Won', color: '!text-green-600', bgColor: '!bg-green-200' },
Lost: { label: 'Lost', color: '!text-red-600', bgColor: '!bg-red-200' },
} }
export function statusDropdownOptions(data, doctype) { export function statusDropdownOptions(data, doctype) {
@ -56,3 +89,7 @@ export function statusDropdownOptions(data, doctype) {
} }
return options return options
} }
export function openWebsite(url) {
window.open(url, '_blank')
}