fix: auto save lead/deal
This commit is contained in:
parent
65bafc1d05
commit
67480656a3
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user