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 />
</DesktopLayout>
</CallUI>
<Toasts />
</template>
<script setup>
import DesktopLayout from '@/components/DesktopLayout.vue'
import { sessionStore as session } from '@/stores/session'
import CallUI from './components/CallUI.vue'
import { Toasts } from 'frappe-ui'
</script>

View File

@ -7,7 +7,7 @@
<Autocomplete
:options="activeAgents"
:value="getUser(deal.data.lead_owner).full_name"
@change="(option) => (deal.data.lead_owner = option.email)"
@change="(option) => updateAssignedAgent(option.email)"
placeholder="Deal owner"
>
<template #prefix>
@ -17,7 +17,7 @@
<UserAvatar class="mr-2" :user="option.email" size="sm" />
</template>
</Autocomplete>
<Dropdown :options="statusDropdownOptions(deal.data, 'deal')">
<Dropdown :options="statusDropdownOptions(deal.data, 'deal', updateDeal)">
<template #default="{ open }">
<Button
:label="deal.data.deal_status"
@ -36,7 +36,6 @@
</Button>
</template>
</Dropdown>
<Button label="Save" variant="solid" @click="() => updateDeal()" />
</template>
</LayoutHeader>
<TabGroup v-slot="{ selectedIndex }" v-if="deal.data" @change="onTabChange">
@ -179,7 +178,11 @@
v-if="field.type === 'select'"
type="select"
: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"
>
<template #prefix>
@ -192,17 +195,29 @@
v-else-if="field.type === 'email'"
type="email"
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
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"
:value="getUser(deal.data[field.name]).full_name"
@change="
(option) => (deal.data[field.name] = option.email)
(option) => updateAssignedAgent(option.email)
"
class="form-control"
placeholder="Deal owner"
:placeholder="deal.placeholder"
>
<template #target="{ togglePopover }">
<Button
@ -229,7 +244,9 @@
</Autocomplete>
<Dropdown
v-else-if="field.type === 'dropdown'"
:options="statusDropdownOptions(deal.data, 'deal')"
:options="
statusDropdownOptions(deal.data, 'deal', updateDeal)
"
class="w-full flex-1"
>
<template #default="{ open }">
@ -259,25 +276,41 @@
<FormControl
v-else-if="field.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"
/>
<FormControl
v-else-if="field.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"
/>
<FormControl
v-else-if="field.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"
/>
<FormControl
v-else
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"
/>
</div>
@ -330,6 +363,7 @@ import {
statusDropdownOptions,
openWebsite,
secondsToDuration,
createToast,
} from '@/utils'
import { usersStore } from '@/stores/users'
import { contactsStore } from '@/stores/contacts'
@ -379,10 +413,34 @@ const uDeal = createDocumentResource({
},
})
function updateDeal() {
let dealCopy = { ...deal.data }
delete dealCopy.activities
uDeal.setValue.submit({ ...dealCopy })
function updateDeal(fieldname, value) {
createResource({
url: 'frappe.client.set_value',
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(() => {
@ -439,7 +497,8 @@ function all_activities() {
}
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) {
@ -506,6 +565,27 @@ const detailSections = computed(() => {
label: 'Contacts',
opened: true,
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',
type: 'data',
@ -654,6 +734,11 @@ const calls = createListResource({
return docs
},
})
function updateAssignedAgent(email) {
deal.data.lead_owner = email
updateDeal('lead_owner', email)
}
</script>
<style scoped>
@ -663,4 +748,8 @@ const calls = createListResource({
border-color: transparent;
background: white;
}
:deep(.form-control button svg) {
color: white;
}
</style>

View File

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

View File

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