fix: auto save lead/deal

This commit is contained in:
Shariq Ansari 2023-08-31 17:05:37 +05:30
parent 65bafc1d05
commit 67480656a3
4 changed files with 178 additions and 41 deletions

View File

@ -5,10 +5,12 @@
<router-view /> <router-view />
</DesktopLayout> </DesktopLayout>
</CallUI> </CallUI>
<Toasts />
</template> </template>
<script setup> <script setup>
import DesktopLayout from '@/components/DesktopLayout.vue' import DesktopLayout from '@/components/DesktopLayout.vue'
import { sessionStore as session } from '@/stores/session' import { sessionStore as session } from '@/stores/session'
import CallUI from './components/CallUI.vue' import CallUI from './components/CallUI.vue'
import { Toasts } from 'frappe-ui'
</script> </script>

View File

@ -7,7 +7,7 @@
<Autocomplete <Autocomplete
:options="activeAgents" :options="activeAgents"
:value="getUser(deal.data.lead_owner).full_name" :value="getUser(deal.data.lead_owner).full_name"
@change="(option) => (deal.data.lead_owner = option.email)" @change="(option) => updateAssignedAgent(option.email)"
placeholder="Deal owner" placeholder="Deal owner"
> >
<template #prefix> <template #prefix>
@ -17,7 +17,7 @@
<UserAvatar class="mr-2" :user="option.email" size="sm" /> <UserAvatar class="mr-2" :user="option.email" size="sm" />
</template> </template>
</Autocomplete> </Autocomplete>
<Dropdown :options="statusDropdownOptions(deal.data, 'deal')"> <Dropdown :options="statusDropdownOptions(deal.data, 'deal', updateDeal)">
<template #default="{ open }"> <template #default="{ open }">
<Button <Button
:label="deal.data.deal_status" :label="deal.data.deal_status"
@ -36,7 +36,6 @@
</Button> </Button>
</template> </template>
</Dropdown> </Dropdown>
<Button label="Save" variant="solid" @click="() => updateDeal()" />
</template> </template>
</LayoutHeader> </LayoutHeader>
<TabGroup v-slot="{ selectedIndex }" v-if="deal.data" @change="onTabChange"> <TabGroup v-slot="{ selectedIndex }" v-if="deal.data" @change="onTabChange">
@ -179,7 +178,11 @@
v-if="field.type === 'select'" v-if="field.type === 'select'"
type="select" type="select"
:options="field.options" :options="field.options"
v-model="deal.data[field.name]" :value="deal.data[field.name]"
@change.stop="
updateDeal(field.name, $event.target.value)
"
:debounce="500"
class="form-control cursor-pointer [&_select]:cursor-pointer" class="form-control cursor-pointer [&_select]:cursor-pointer"
> >
<template #prefix> <template #prefix>
@ -192,17 +195,29 @@
v-else-if="field.type === 'email'" v-else-if="field.type === 'email'"
type="email" type="email"
class="form-control" class="form-control"
v-model="deal.data[field.name]" :value="deal.data[field.name]"
@change.stop="
updateDeal(field.name, $event.target.value)
"
:debounce="500"
/> />
<Autocomplete <Autocomplete
v-else-if="field.type === 'link'" v-else-if="field.type === 'link'"
:value="deal.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(deal.data[field.name]).full_name" :value="getUser(deal.data[field.name]).full_name"
@change=" @change="
(option) => (deal.data[field.name] = option.email) (option) => updateAssignedAgent(option.email)
" "
class="form-control" class="form-control"
placeholder="Deal owner" :placeholder="deal.placeholder"
> >
<template #target="{ togglePopover }"> <template #target="{ togglePopover }">
<Button <Button
@ -229,7 +244,9 @@
</Autocomplete> </Autocomplete>
<Dropdown <Dropdown
v-else-if="field.type === 'dropdown'" v-else-if="field.type === 'dropdown'"
:options="statusDropdownOptions(deal.data, 'deal')" :options="
statusDropdownOptions(deal.data, 'deal', updateDeal)
"
class="w-full flex-1" class="w-full flex-1"
> >
<template #default="{ open }"> <template #default="{ open }">
@ -259,25 +276,41 @@
<FormControl <FormControl
v-else-if="field.type === 'date'" v-else-if="field.type === 'date'"
type="date" type="date"
v-model="deal.data[field.name]" :value="deal.data[field.name]"
@change.stop="
updateDeal(field.name, $event.target.value)
"
:debounce="500"
class="form-control" class="form-control"
/> />
<FormControl <FormControl
v-else-if="field.type === 'number'" v-else-if="field.type === 'number'"
type="number" type="number"
v-model="deal.data[field.name]" :value="deal.data[field.name]"
@change.stop="
updateDeal(field.name, $event.target.value)
"
:debounce="500"
class="form-control" class="form-control"
/> />
<FormControl <FormControl
v-else-if="field.type === 'tel'" v-else-if="field.type === 'tel'"
type="tel" type="tel"
v-model="deal.data[field.name]" :value="deal.data[field.name]"
@change.stop="
updateDeal(field.name, $event.target.value)
"
:debounce="500"
class="form-control" class="form-control"
/> />
<FormControl <FormControl
v-else v-else
type="text" type="text"
v-model="deal.data[field.name]" :value="deal.data[field.name]"
@change.stop="
updateDeal(field.name, $event.target.value)
"
:debounce="500"
class="form-control" class="form-control"
/> />
</div> </div>
@ -330,6 +363,7 @@ import {
statusDropdownOptions, statusDropdownOptions,
openWebsite, openWebsite,
secondsToDuration, secondsToDuration,
createToast,
} from '@/utils' } from '@/utils'
import { usersStore } from '@/stores/users' import { usersStore } from '@/stores/users'
import { contactsStore } from '@/stores/contacts' import { contactsStore } from '@/stores/contacts'
@ -379,10 +413,34 @@ const uDeal = createDocumentResource({
}, },
}) })
function updateDeal() { function updateDeal(fieldname, value) {
let dealCopy = { ...deal.data } createResource({
delete dealCopy.activities url: 'frappe.client.set_value',
uDeal.setValue.submit({ ...dealCopy }) params: {
doctype: 'CRM Lead',
name: props.dealId,
fieldname,
value,
},
auto: true,
onSuccess: () => {
deal.reload()
contacts.reload()
createToast({
title: 'Deal updated',
icon: 'check',
iconClasses: 'text-green-600',
})
},
onError: (err) => {
createToast({
title: 'Error updating deal',
text: err.messages?.[0],
icon: 'x',
iconClasses: 'text-red-600',
})
},
})
} }
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
@ -439,7 +497,8 @@ function all_activities() {
} }
function changeDealImage(file) { function changeDealImage(file) {
uDeal.setValue.submit({ organization_logo: file.file_url }) deal.data.organization_logo = file.file_url
updateDeal('organization_logo', file.file_url)
} }
function validateFile(file) { function validateFile(file) {
@ -506,6 +565,27 @@ const detailSections = computed(() => {
label: 'Contacts', label: 'Contacts',
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) => {
deal.data.salutation = data.value
updateDeal('salutation', data.value)
},
},
{ {
label: 'First name', label: 'First name',
type: 'data', type: 'data',
@ -654,6 +734,11 @@ const calls = createListResource({
return docs return docs
}, },
}) })
function updateAssignedAgent(email) {
deal.data.lead_owner = email
updateDeal('lead_owner', email)
}
</script> </script>
<style scoped> <style scoped>
@ -663,4 +748,8 @@ const calls = createListResource({
border-color: transparent; border-color: transparent;
background: white; background: white;
} }
:deep(.form-control button svg) {
color: white;
}
</style> </style>

View File

@ -7,7 +7,7 @@
<Autocomplete <Autocomplete
:options="activeAgents" :options="activeAgents"
:value="getUser(lead.data.lead_owner).full_name" :value="getUser(lead.data.lead_owner).full_name"
@change="(option) => (lead.data.lead_owner = option.email)" @change="(option) => updateAssignedAgent(option.email)"
placeholder="Lead owner" placeholder="Lead owner"
> >
<template #prefix> <template #prefix>
@ -17,7 +17,7 @@
<UserAvatar class="mr-2" :user="option.email" size="sm" /> <UserAvatar class="mr-2" :user="option.email" size="sm" />
</template> </template>
</Autocomplete> </Autocomplete>
<Dropdown :options="statusDropdownOptions(lead.data)"> <Dropdown :options="statusDropdownOptions(lead.data, 'lead', updateLead)">
<template #default="{ open }"> <template #default="{ open }">
<Button <Button
:label="lead.data.status" :label="lead.data.status"
@ -34,7 +34,6 @@
</Button> </Button>
</template> </template>
</Dropdown> </Dropdown>
<Button label="Save" variant="solid" @click="updateLead()" />
<Button <Button
label="Convert to deal" label="Convert to deal"
variant="solid" variant="solid"
@ -166,7 +165,7 @@
<div v-if="opened" class="flex flex-col gap-1.5"> <div v-if="opened" class="flex flex-col gap-1.5">
<div <div
v-for="field in section.fields" v-for="field in section.fields"
:key="field.label" :key="field.name"
class="flex items-center px-3 gap-2 text-base leading-5 first:mt-3" class="flex items-center px-3 gap-2 text-base leading-5 first:mt-3"
> >
<div class="text-gray-600 w-[106px]"> <div class="text-gray-600 w-[106px]">
@ -177,7 +176,11 @@
v-if="field.type === 'select'" v-if="field.type === 'select'"
type="select" type="select"
:options="field.options" :options="field.options"
v-model="lead.data[field.name]" :value="lead.data[field.name]"
@change.stop="
updateLead(field.name, $event.target.value)
"
:debounce="500"
class="form-control cursor-pointer [&_select]:cursor-pointer" class="form-control cursor-pointer [&_select]:cursor-pointer"
> >
<template #prefix> <template #prefix>
@ -190,7 +193,11 @@
v-else-if="field.type === 'email'" v-else-if="field.type === 'email'"
type="email" type="email"
class="form-control" class="form-control"
v-model="lead.data[field.name]" :value="lead.data[field.name]"
@change.stop="
updateLead(field.name, $event.target.value)
"
:debounce="500"
/> />
<Autocomplete <Autocomplete
v-else-if="field.type === 'link'" v-else-if="field.type === 'link'"
@ -205,10 +212,10 @@
:options="activeAgents" :options="activeAgents"
:value="getUser(lead.data[field.name]).full_name" :value="getUser(lead.data[field.name]).full_name"
@change=" @change="
(option) => (lead.data[field.name] = option.email) (option) => updateAssignedAgent(option.email)
" "
class="form-control" class="form-control"
placeholder="Lead owner" :placeholder="field.placeholder"
> >
<template #target="{ togglePopover }"> <template #target="{ togglePopover }">
<Button <Button
@ -235,7 +242,9 @@
</Autocomplete> </Autocomplete>
<Dropdown <Dropdown
v-else-if="field.type === 'dropdown'" v-else-if="field.type === 'dropdown'"
:options="statusDropdownOptions(lead.data)" :options="
statusDropdownOptions(lead.data, 'lead', updateLead)
"
class="w-full flex-1" class="w-full flex-1"
> >
<template #default="{ open }"> <template #default="{ open }">
@ -265,8 +274,12 @@
<FormControl <FormControl
v-else v-else
type="text" type="text"
v-model="lead.data[field.name]" :value="lead.data[field.name]"
@change.stop="
updateLead(field.name, $event.target.value)
"
class="form-control" class="form-control"
:debounce="500"
/> />
</div> </div>
</div> </div>
@ -318,6 +331,7 @@ import {
statusDropdownOptions, statusDropdownOptions,
openWebsite, openWebsite,
secondsToDuration, secondsToDuration,
createToast,
} from '@/utils' } from '@/utils'
import { usersStore } from '@/stores/users' import { usersStore } from '@/stores/users'
import { contactsStore } from '@/stores/contacts' import { contactsStore } from '@/stores/contacts'
@ -358,21 +372,34 @@ const lead = createResource({
auto: true, auto: true,
}) })
const uLead = createDocumentResource({ function updateLead(fieldname, value) {
doctype: 'CRM Lead', createResource({
name: props.leadId, url: 'frappe.client.set_value',
setValue: { params: {
doctype: 'CRM Lead',
name: props.leadId,
fieldname,
value,
},
auto: true,
onSuccess: () => { onSuccess: () => {
lead.reload() lead.reload()
contacts.reload() contacts.reload()
createToast({
title: 'Lead updated',
icon: 'check',
iconClasses: 'text-green-600',
})
}, },
}, onError: (err) => {
}) createToast({
title: 'Error updating lead',
function updateLead() { text: err.messages?.[0],
let leadCopy = { ...lead.data } icon: 'x',
delete leadCopy.activities iconClasses: 'text-red-600',
uLead.setValue.submit({ ...leadCopy }) })
},
})
} }
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
@ -429,7 +456,8 @@ function all_activities() {
} }
function changeLeadImage(file) { function changeLeadImage(file) {
uLead.setValue.submit({ image: file.file_url }) lead.data.image = file.file_url
updateLead('image', file.file_url)
} }
function validateFile(file) { function validateFile(file) {
@ -487,6 +515,7 @@ const detailSections = computed(() => {
], ],
change: (data) => { change: (data) => {
lead.data.source = data.value lead.data.source = data.value
updateLead('source', data.value)
}, },
}, },
{ {
@ -502,6 +531,7 @@ const detailSections = computed(() => {
], ],
change: (data) => { change: (data) => {
lead.data.industry = data.value lead.data.industry = data.value
updateLead('industry', data.value)
}, },
}, },
], ],
@ -528,6 +558,7 @@ const detailSections = computed(() => {
], ],
change: (data) => { change: (data) => {
lead.data.salutation = data.value lead.data.salutation = data.value
updateLead('salutation', data.value)
}, },
}, },
{ {
@ -572,7 +603,7 @@ const activeAgents = computed(() => {
function convertToDeal() { function convertToDeal() {
lead.data.status = 'Qualified' lead.data.status = 'Qualified'
lead.data.is_deal = 1 lead.data.is_deal = 1
updateLead() updateLead('is_deal', 1)
router.push({ name: 'Deal', params: { dealId: lead.data.name } }) router.push({ name: 'Deal', params: { dealId: lead.data.name } })
} }
@ -685,6 +716,11 @@ const calls = createListResource({
return docs return docs
}, },
}) })
function updateAssignedAgent(email) {
lead.data.lead_owner = email
updateLead('lead_owner', email)
}
</script> </script>
<style scoped> <style scoped>

View File

@ -1,7 +1,15 @@
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue' import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import { useDateFormat, useTimeAgo } from '@vueuse/core' import { useDateFormat, useTimeAgo } from '@vueuse/core'
import { toast } from 'frappe-ui'
import { h } from 'vue' import { h } from 'vue'
export function createToast(options) {
toast({
position: 'bottom-right',
...options,
})
}
export function dateFormat(date, format) { export function dateFormat(date, format) {
const _format = format || 'DD-MM-YYYY HH:mm:ss' const _format = format || 'DD-MM-YYYY HH:mm:ss'
return useDateFormat(date, _format).value return useDateFormat(date, _format).value
@ -68,7 +76,7 @@ export const dealStatuses = {
Lost: { label: 'Lost', color: '!text-red-600', bgColor: '!bg-red-200' }, Lost: { label: 'Lost', color: '!text-red-600', bgColor: '!bg-red-200' },
} }
export function statusDropdownOptions(data, doctype) { export function statusDropdownOptions(data, doctype, action) {
let statuses = leadStatuses let statuses = leadStatuses
if (doctype == 'deal') { if (doctype == 'deal') {
statuses = dealStatuses statuses = dealStatuses
@ -84,6 +92,8 @@ export function statusDropdownOptions(data, doctype) {
} else { } else {
data.status = statuses[status].label data.status = statuses[status].label
} }
let field = doctype == 'deal' ? 'deal_status' : 'status'
action && action(field, statuses[status].label)
}, },
}) })
} }