refactor: update mobile lead/deal components
This commit is contained in:
parent
c38c190d42
commit
7e42599b49
@ -335,6 +335,7 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import DeleteLinkedDocModal from '@/components/DeleteLinkedDocModal.vue'
|
||||||
import ErrorPage from '@/components/ErrorPage.vue'
|
import ErrorPage from '@/components/ErrorPage.vue'
|
||||||
import Icon from '@/components/Icon.vue'
|
import Icon from '@/components/Icon.vue'
|
||||||
import Resizer from '@/components/Resizer.vue'
|
import Resizer from '@/components/Resizer.vue'
|
||||||
@ -417,6 +418,7 @@ const props = defineProps({
|
|||||||
|
|
||||||
const errorTitle = ref('')
|
const errorTitle = ref('')
|
||||||
const errorMessage = ref('')
|
const errorMessage = ref('')
|
||||||
|
const showDeleteLinkedDocModal = ref(false)
|
||||||
|
|
||||||
const { triggerOnChange, assignees, document, scripts, error } = useDocument(
|
const { triggerOnChange, assignees, document, scripts, error } = useDocument(
|
||||||
'CRM Deal',
|
'CRM Deal',
|
||||||
@ -451,7 +453,7 @@ watch(
|
|||||||
toast,
|
toast,
|
||||||
updateField,
|
updateField,
|
||||||
createToast: toast.create,
|
createToast: toast.create,
|
||||||
deleteDoc: deleteDealWithModal,
|
deleteDoc: deleteDeal,
|
||||||
call,
|
call,
|
||||||
})
|
})
|
||||||
document._actions = s.actions || []
|
document._actions = s.actions || []
|
||||||
@ -493,7 +495,6 @@ const reload = ref(false)
|
|||||||
const showOrganizationModal = ref(false)
|
const showOrganizationModal = ref(false)
|
||||||
const showFilesUploader = ref(false)
|
const showFilesUploader = ref(false)
|
||||||
const _organization = ref({})
|
const _organization = ref({})
|
||||||
const showDeleteLinkedDocModal = ref(false)
|
|
||||||
|
|
||||||
const breadcrumbs = computed(() => {
|
const breadcrumbs = computed(() => {
|
||||||
let items = [{ label: __('Deals'), route: { name: 'Deals' } }]
|
let items = [{ label: __('Deals'), route: { name: 'Deals' } }]
|
||||||
@ -739,7 +740,7 @@ function updateField(name, value) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteDealWithModal() {
|
function deleteDeal() {
|
||||||
showDeleteLinkedDocModal.value = true
|
showDeleteLinkedDocModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -183,7 +183,7 @@
|
|||||||
<Tooltip :text="__('Delete')">
|
<Tooltip :text="__('Delete')">
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
@click="deleteLeadWithModal"
|
@click="deleteLead"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
theme="red"
|
theme="red"
|
||||||
icon="trash-2"
|
icon="trash-2"
|
||||||
@ -245,6 +245,7 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import DeleteLinkedDocModal from '@/components/DeleteLinkedDocModal.vue'
|
||||||
import ErrorPage from '@/components/ErrorPage.vue'
|
import ErrorPage from '@/components/ErrorPage.vue'
|
||||||
import Icon from '@/components/Icon.vue'
|
import Icon from '@/components/Icon.vue'
|
||||||
import Resizer from '@/components/Resizer.vue'
|
import Resizer from '@/components/Resizer.vue'
|
||||||
@ -294,7 +295,7 @@ import {
|
|||||||
usePageMeta,
|
usePageMeta,
|
||||||
toast,
|
toast,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { ref, computed, onMounted, watch, nextTick } from 'vue'
|
import { ref, computed, watch, nextTick } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { useActiveTabManager } from '@/composables/useActiveTabManager'
|
import { useActiveTabManager } from '@/composables/useActiveTabManager'
|
||||||
|
|
||||||
@ -351,7 +352,7 @@ watch(
|
|||||||
toast,
|
toast,
|
||||||
updateField,
|
updateField,
|
||||||
createToast: toast.create,
|
createToast: toast.create,
|
||||||
deleteDoc: deleteLeadWithModal,
|
deleteDoc: deleteLead,
|
||||||
call,
|
call,
|
||||||
})
|
})
|
||||||
document._actions = s.actions || []
|
document._actions = s.actions || []
|
||||||
@ -501,7 +502,7 @@ function updateField(name, value) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteLeadWithModal() {
|
function deleteLead() {
|
||||||
showDeleteLinkedDocModal.value = true
|
showDeleteLinkedDocModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<LayoutHeader v-if="deal.data">
|
<LayoutHeader>
|
||||||
<header
|
<header
|
||||||
class="relative flex h-10.5 items-center justify-between gap-2 py-2.5 pl-2"
|
class="relative flex h-10.5 items-center justify-between gap-2 py-2.5 pl-2"
|
||||||
>
|
>
|
||||||
@ -10,23 +10,21 @@
|
|||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
<div class="absolute right-0">
|
<div class="absolute right-0">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
v-if="document.doc"
|
v-if="doc"
|
||||||
:options="
|
:options="
|
||||||
statusOptions(
|
statusOptions(
|
||||||
'deal',
|
'deal',
|
||||||
document.statuses?.length
|
document.statuses?.length
|
||||||
? document.statuses
|
? document.statuses
|
||||||
: deal.data._customStatuses,
|
: document._statuses,
|
||||||
triggerStatusChange,
|
triggerStatusChange,
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<template #default="{ open }">
|
<template #default="{ open }">
|
||||||
<Button :label="document.doc.status">
|
<Button v-if="doc.status" :label="doc.status">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<IndicatorIcon
|
<IndicatorIcon :class="getDealStatus(doc.status).color" />
|
||||||
:class="getDealStatus(document.doc.status).color"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<FeatherIcon
|
<FeatherIcon
|
||||||
@ -41,18 +39,14 @@
|
|||||||
</header>
|
</header>
|
||||||
</LayoutHeader>
|
</LayoutHeader>
|
||||||
<div
|
<div
|
||||||
v-if="deal.data"
|
v-if="doc.name"
|
||||||
class="flex h-12 items-center justify-between gap-2 border-b px-3 py-2.5"
|
class="flex h-12 items-center justify-between gap-2 border-b px-3 py-2.5"
|
||||||
>
|
>
|
||||||
<AssignTo
|
<AssignTo v-model="assignees.data" :data="doc" doctype="CRM Deal" />
|
||||||
v-model="assignees.data"
|
|
||||||
:data="document.doc"
|
|
||||||
doctype="CRM Deal"
|
|
||||||
/>
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<CustomActions
|
<CustomActions
|
||||||
v-if="deal.data._customActions?.length"
|
v-if="document._actions?.length"
|
||||||
:actions="deal.data._customActions"
|
:actions="document._actions"
|
||||||
/>
|
/>
|
||||||
<CustomActions
|
<CustomActions
|
||||||
v-if="document.actions?.length"
|
v-if="document.actions?.length"
|
||||||
@ -60,14 +54,14 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="deal.data" class="flex h-full overflow-hidden">
|
<div v-if="doc.name" class="flex h-full overflow-hidden">
|
||||||
<Tabs as="div" v-model="tabIndex" :tabs="tabs" class="overflow-auto">
|
<Tabs as="div" v-model="tabIndex" :tabs="tabs" class="overflow-auto">
|
||||||
<TabList class="!px-3" />
|
<TabList class="!px-3" />
|
||||||
<TabPanel v-slot="{ tab }">
|
<TabPanel v-slot="{ tab }">
|
||||||
<div v-if="tab.name == 'Details'">
|
<div v-if="tab.name == 'Details'">
|
||||||
<SLASection
|
<SLASection
|
||||||
v-if="deal.data.sla_status"
|
v-if="doc.sla_status"
|
||||||
v-model="deal.data"
|
v-model="doc"
|
||||||
@updateField="updateField"
|
@updateField="updateField"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
@ -77,7 +71,7 @@
|
|||||||
<SidePanelLayout
|
<SidePanelLayout
|
||||||
:sections="sections.data"
|
:sections="sections.data"
|
||||||
doctype="CRM Deal"
|
doctype="CRM Deal"
|
||||||
:docname="deal.data.name"
|
:docname="dealId"
|
||||||
@reload="sections.reload"
|
@reload="sections.reload"
|
||||||
@beforeFieldChange="beforeStatusChange"
|
@beforeFieldChange="beforeStatusChange"
|
||||||
@afterFieldChange="reloadAssignees"
|
@afterFieldChange="reloadAssignees"
|
||||||
@ -92,7 +86,7 @@
|
|||||||
(value, close) => {
|
(value, close) => {
|
||||||
_contact = {
|
_contact = {
|
||||||
first_name: value,
|
first_name: value,
|
||||||
company_name: deal.data.organization,
|
company_name: doc.organization,
|
||||||
}
|
}
|
||||||
showContactModal = true
|
showContactModal = true
|
||||||
close()
|
close()
|
||||||
@ -220,23 +214,28 @@
|
|||||||
<Activities
|
<Activities
|
||||||
v-else
|
v-else
|
||||||
doctype="CRM Deal"
|
doctype="CRM Deal"
|
||||||
|
:docname="dealId"
|
||||||
:tabs="tabs"
|
:tabs="tabs"
|
||||||
v-model:reload="reload"
|
v-model:reload="reload"
|
||||||
v-model:tabIndex="tabIndex"
|
v-model:tabIndex="tabIndex"
|
||||||
v-model="deal"
|
|
||||||
@beforeSave="beforeStatusChange"
|
@beforeSave="beforeStatusChange"
|
||||||
@afterSave="reloadAssignees"
|
@afterSave="reloadAssignees"
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
<ErrorPage
|
||||||
|
v-else-if="errorTitle"
|
||||||
|
:errorTitle="errorTitle"
|
||||||
|
:errorMessage="errorMessage"
|
||||||
|
/>
|
||||||
<OrganizationModal
|
<OrganizationModal
|
||||||
v-if="showOrganizationModal"
|
v-if="showOrganizationModal"
|
||||||
v-model="showOrganizationModal"
|
v-model="showOrganizationModal"
|
||||||
:data="_organization"
|
:data="_organization"
|
||||||
:options="{
|
:options="{
|
||||||
redirect: false,
|
redirect: false,
|
||||||
afterInsert: (doc) => updateField('organization', doc.name),
|
afterInsert: (_doc) => updateField('organization', _doc.name),
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<ContactModal
|
<ContactModal
|
||||||
@ -245,9 +244,16 @@
|
|||||||
:contact="_contact"
|
:contact="_contact"
|
||||||
:options="{
|
:options="{
|
||||||
redirect: false,
|
redirect: false,
|
||||||
afterInsert: (doc) => addContact(doc.name),
|
afterInsert: (_doc) => addContact(_doc.name),
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
|
<DeleteLinkedDocModal
|
||||||
|
v-if="showDeleteLinkedDocModal"
|
||||||
|
v-model="showDeleteLinkedDocModal"
|
||||||
|
:doctype="'CRM Deal'"
|
||||||
|
:docname="dealId"
|
||||||
|
name="Deals"
|
||||||
|
/>
|
||||||
<LostReasonModal
|
<LostReasonModal
|
||||||
v-if="showLostReasonModal"
|
v-if="showLostReasonModal"
|
||||||
v-model="showLostReasonModal"
|
v-model="showLostReasonModal"
|
||||||
@ -255,6 +261,8 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import DeleteLinkedDocModal from '@/components/DeleteLinkedDocModal.vue'
|
||||||
|
import ErrorPage from '@/components/ErrorPage.vue'
|
||||||
import Icon from '@/components/Icon.vue'
|
import Icon from '@/components/Icon.vue'
|
||||||
import DetailsIcon from '@/components/Icons/DetailsIcon.vue'
|
import DetailsIcon from '@/components/Icons/DetailsIcon.vue'
|
||||||
import LoadingIndicator from '@/components/Icons/LoadingIndicator.vue'
|
import LoadingIndicator from '@/components/Icons/LoadingIndicator.vue'
|
||||||
@ -306,7 +314,7 @@ import {
|
|||||||
usePageMeta,
|
usePageMeta,
|
||||||
toast,
|
toast,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { ref, computed, h, onMounted } from 'vue'
|
import { ref, computed, h, watch } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
const { brand } = getSettings()
|
const { brand } = getSettings()
|
||||||
@ -323,86 +331,57 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const deal = createResource({
|
const errorTitle = ref('')
|
||||||
url: 'crm.fcrm.doctype.crm_deal.api.get_deal',
|
const errorMessage = ref('')
|
||||||
params: { name: props.dealId },
|
const showDeleteLinkedDocModal = ref(false)
|
||||||
cache: ['deal', props.dealId],
|
|
||||||
onSuccess: (data) => {
|
const { triggerOnChange, assignees, document, scripts, error } = useDocument(
|
||||||
if (data.organization) {
|
'CRM Deal',
|
||||||
organization.update({
|
props.dealId,
|
||||||
params: { doctype: 'CRM Organization', name: data.organization },
|
)
|
||||||
|
|
||||||
|
const doc = computed(() => document.doc || {})
|
||||||
|
|
||||||
|
watch(error, (err) => {
|
||||||
|
if (err) {
|
||||||
|
errorTitle.value = __(
|
||||||
|
err.exc_type == 'DoesNotExistError'
|
||||||
|
? 'Document not found'
|
||||||
|
: 'Error occurred',
|
||||||
|
)
|
||||||
|
errorMessage.value = __(err.messages?.[0] || 'An error occurred')
|
||||||
|
} else {
|
||||||
|
errorTitle.value = ''
|
||||||
|
errorMessage.value = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => document.doc,
|
||||||
|
async (_doc) => {
|
||||||
|
if (scripts.data?.length) {
|
||||||
|
let s = await setupCustomizations(scripts.data, {
|
||||||
|
doc: _doc,
|
||||||
|
$dialog,
|
||||||
|
$socket,
|
||||||
|
router,
|
||||||
|
toast,
|
||||||
|
updateField,
|
||||||
|
createToast: toast.create,
|
||||||
|
deleteDoc: deleteDeal,
|
||||||
|
call,
|
||||||
})
|
})
|
||||||
organization.fetch()
|
document._actions = s.actions || []
|
||||||
|
document._statuses = s.statuses || []
|
||||||
}
|
}
|
||||||
|
|
||||||
setupCustomizations(deal, {
|
|
||||||
doc: data,
|
|
||||||
$dialog,
|
|
||||||
$socket,
|
|
||||||
router,
|
|
||||||
toast,
|
|
||||||
updateField,
|
|
||||||
createToast: toast.create,
|
|
||||||
deleteDoc: deleteDeal,
|
|
||||||
resource: {
|
|
||||||
deal,
|
|
||||||
dealContacts,
|
|
||||||
sections,
|
|
||||||
},
|
|
||||||
call,
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
})
|
{ once: true },
|
||||||
|
)
|
||||||
const organization = createResource({
|
|
||||||
url: 'frappe.client.get',
|
|
||||||
onSuccess: (data) => (deal.data._organizationObj = data),
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (deal.data) return
|
|
||||||
deal.fetch()
|
|
||||||
})
|
|
||||||
|
|
||||||
const reload = ref(false)
|
const reload = ref(false)
|
||||||
const showOrganizationModal = ref(false)
|
const showOrganizationModal = ref(false)
|
||||||
const _organization = ref({})
|
const _organization = ref({})
|
||||||
|
|
||||||
function updateDeal(fieldname, value, callback) {
|
|
||||||
value = Array.isArray(fieldname) ? '' : value
|
|
||||||
|
|
||||||
if (validateRequired(fieldname, value)) return
|
|
||||||
|
|
||||||
createResource({
|
|
||||||
url: 'frappe.client.set_value',
|
|
||||||
params: {
|
|
||||||
doctype: 'CRM Deal',
|
|
||||||
name: props.dealId,
|
|
||||||
fieldname,
|
|
||||||
value,
|
|
||||||
},
|
|
||||||
auto: true,
|
|
||||||
onSuccess: () => {
|
|
||||||
deal.reload()
|
|
||||||
reload.value = true
|
|
||||||
toast.success(__('Deal updated'))
|
|
||||||
callback?.()
|
|
||||||
},
|
|
||||||
onError: (err) => {
|
|
||||||
toast.error(err.messages?.[0] || __('Error updating deal'))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateRequired(fieldname, value) {
|
|
||||||
let meta = deal.data.fields_meta || {}
|
|
||||||
if (meta[fieldname]?.reqd && !value) {
|
|
||||||
toast.error(__('{0} is a required field', [meta[fieldname].label]))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const breadcrumbs = computed(() => {
|
const breadcrumbs = computed(() => {
|
||||||
let items = [{ label: __('Deals'), route: { name: 'Deals' } }]
|
let items = [{ label: __('Deals'), route: { name: 'Deals' } }]
|
||||||
|
|
||||||
@ -423,14 +402,14 @@ const breadcrumbs = computed(() => {
|
|||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
label: title.value,
|
label: title.value,
|
||||||
route: { name: 'Deal', params: { dealId: deal.data.name } },
|
route: { name: 'Deal', params: { dealId: props.dealId } },
|
||||||
})
|
})
|
||||||
return items
|
return items
|
||||||
})
|
})
|
||||||
|
|
||||||
const title = computed(() => {
|
const title = computed(() => {
|
||||||
let t = doctypeMeta['CRM Deal']?.title_field || 'name'
|
let t = doctypeMeta['CRM Deal']?.title_field || 'name'
|
||||||
return deal.data?.[t] || props.dealId
|
return doc.value?.[t] || props.dealId
|
||||||
})
|
})
|
||||||
|
|
||||||
usePageMeta(() => {
|
usePageMeta(() => {
|
||||||
@ -614,26 +593,33 @@ const dealContacts = createResource({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
function updateField(name, value, callback) {
|
function updateField(name, value) {
|
||||||
updateDeal(name, value, () => {
|
value = Array.isArray(name) ? '' : value
|
||||||
deal.data[name] = value
|
let oldValues = Array.isArray(name) ? {} : doc.value[name]
|
||||||
callback?.()
|
|
||||||
|
if (Array.isArray(name)) {
|
||||||
|
name.forEach((field) => (doc.value[field] = value))
|
||||||
|
} else {
|
||||||
|
doc.value[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
document.save.submit(null, {
|
||||||
|
onSuccess: () => (reload.value = true),
|
||||||
|
onError: (err) => {
|
||||||
|
if (Array.isArray(name)) {
|
||||||
|
name.forEach((field) => (doc.value[field] = oldValues[field]))
|
||||||
|
} else {
|
||||||
|
doc.value[name] = oldValues
|
||||||
|
}
|
||||||
|
toast.error(err.messages?.[0] || __('Error updating field'))
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteDeal(name) {
|
function deleteDeal() {
|
||||||
await call('frappe.client.delete', {
|
showDeleteLinkedDocModal.value = true
|
||||||
doctype: 'CRM Deal',
|
|
||||||
name,
|
|
||||||
})
|
|
||||||
router.push({ name: 'Deals' })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { assignees, document, triggerOnChange } = useDocument(
|
|
||||||
'CRM Deal',
|
|
||||||
props.dealId,
|
|
||||||
)
|
|
||||||
|
|
||||||
async function triggerStatusChange(value) {
|
async function triggerStatusChange(value) {
|
||||||
await triggerOnChange('status', value)
|
await triggerOnChange('status', value)
|
||||||
setLostReason()
|
setLostReason()
|
||||||
@ -643,9 +629,9 @@ const showLostReasonModal = ref(false)
|
|||||||
|
|
||||||
function setLostReason() {
|
function setLostReason() {
|
||||||
if (
|
if (
|
||||||
getDealStatus(document.doc.status).type !== 'Lost' ||
|
getDealStatus(doc.status).type !== 'Lost' ||
|
||||||
(document.doc.lost_reason && document.doc.lost_reason !== 'Other') ||
|
(doc.lost_reason && doc.lost_reason !== 'Other') ||
|
||||||
(document.doc.lost_reason === 'Other' && document.doc.lost_notes)
|
(doc.lost_reason === 'Other' && doc.lost_notes)
|
||||||
) {
|
) {
|
||||||
document.save.submit()
|
document.save.submit()
|
||||||
return
|
return
|
||||||
@ -655,7 +641,10 @@ function setLostReason() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function beforeStatusChange(data) {
|
function beforeStatusChange(data) {
|
||||||
if (data?.hasOwnProperty('status') && getDealStatus(data.status).type == 'Lost') {
|
if (
|
||||||
|
data?.hasOwnProperty('status') &&
|
||||||
|
getDealStatus(data.status).type == 'Lost'
|
||||||
|
) {
|
||||||
setLostReason()
|
setLostReason()
|
||||||
} else {
|
} else {
|
||||||
document.save.submit(null, {
|
document.save.submit(null, {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<LayoutHeader v-if="lead.data">
|
<LayoutHeader>
|
||||||
<header
|
<header
|
||||||
class="relative flex h-10.5 items-center justify-between gap-2 py-2.5 pl-2"
|
class="relative flex h-10.5 items-center justify-between gap-2 py-2.5 pl-2"
|
||||||
>
|
>
|
||||||
@ -10,23 +10,21 @@
|
|||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
<div class="absolute right-0">
|
<div class="absolute right-0">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
v-if="document.doc"
|
v-if="doc"
|
||||||
:options="
|
:options="
|
||||||
statusOptions(
|
statusOptions(
|
||||||
'lead',
|
'lead',
|
||||||
document.statuses?.length
|
document.statuses?.length
|
||||||
? document.statuses
|
? document.statuses
|
||||||
: lead.data._customStatuses,
|
: document._statuses,
|
||||||
triggerStatusChange,
|
triggerStatusChange,
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<template #default="{ open }">
|
<template #default="{ open }">
|
||||||
<Button :label="document.doc.status">
|
<Button v-if="doc.status" :label="doc.status">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<IndicatorIcon
|
<IndicatorIcon :class="getLeadStatus(doc.status).color" />
|
||||||
:class="getLeadStatus(document.doc.status).color"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<FeatherIcon
|
<FeatherIcon
|
||||||
@ -41,18 +39,14 @@
|
|||||||
</header>
|
</header>
|
||||||
</LayoutHeader>
|
</LayoutHeader>
|
||||||
<div
|
<div
|
||||||
v-if="lead.data"
|
v-if="doc.name"
|
||||||
class="flex h-12 items-center justify-between gap-2 border-b px-3 py-2.5"
|
class="flex h-12 items-center justify-between gap-2 border-b px-3 py-2.5"
|
||||||
>
|
>
|
||||||
<AssignTo
|
<AssignTo v-model="assignees.data" :data="doc" doctype="CRM Lead" />
|
||||||
v-model="assignees.data"
|
|
||||||
:data="document.doc"
|
|
||||||
doctype="CRM Lead"
|
|
||||||
/>
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<CustomActions
|
<CustomActions
|
||||||
v-if="lead.data._customActions?.length"
|
v-if="document._actions?.length"
|
||||||
:actions="lead.data._customActions"
|
:actions="document._actions"
|
||||||
/>
|
/>
|
||||||
<CustomActions
|
<CustomActions
|
||||||
v-if="document.actions?.length"
|
v-if="document.actions?.length"
|
||||||
@ -65,14 +59,14 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="lead?.data" class="flex h-full overflow-hidden">
|
<div v-if="doc.name" class="flex h-full overflow-hidden">
|
||||||
<Tabs as="div" v-model="tabIndex" :tabs="tabs" class="overflow-auto">
|
<Tabs as="div" v-model="tabIndex" :tabs="tabs" class="overflow-auto">
|
||||||
<TabList class="!px-3" />
|
<TabList class="!px-3" />
|
||||||
<TabPanel v-slot="{ tab }">
|
<TabPanel v-slot="{ tab }">
|
||||||
<div v-if="tab.name == 'Details'">
|
<div v-if="tab.name == 'Details'">
|
||||||
<SLASection
|
<SLASection
|
||||||
v-if="lead.data.sla_status"
|
v-if="doc.sla_status"
|
||||||
v-model="lead.data"
|
v-model="doc"
|
||||||
@updateField="updateField"
|
@updateField="updateField"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
@ -82,7 +76,7 @@
|
|||||||
<SidePanelLayout
|
<SidePanelLayout
|
||||||
:sections="sections.data"
|
:sections="sections.data"
|
||||||
doctype="CRM Lead"
|
doctype="CRM Lead"
|
||||||
:docname="lead.data.name"
|
:docname="leadId"
|
||||||
@reload="sections.reload"
|
@reload="sections.reload"
|
||||||
@afterFieldChange="reloadAssignees"
|
@afterFieldChange="reloadAssignees"
|
||||||
/>
|
/>
|
||||||
@ -91,16 +85,21 @@
|
|||||||
<Activities
|
<Activities
|
||||||
v-else
|
v-else
|
||||||
doctype="CRM Lead"
|
doctype="CRM Lead"
|
||||||
|
:docname="leadId"
|
||||||
:tabs="tabs"
|
:tabs="tabs"
|
||||||
v-model:reload="reload"
|
v-model:reload="reload"
|
||||||
v-model:tabIndex="tabIndex"
|
v-model:tabIndex="tabIndex"
|
||||||
v-model="lead"
|
|
||||||
@beforeSave="saveChanges"
|
@beforeSave="saveChanges"
|
||||||
@afterSave="reloadAssignees"
|
@afterSave="reloadAssignees"
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
<ErrorPage
|
||||||
|
v-else-if="errorTitle"
|
||||||
|
:errorTitle="errorTitle"
|
||||||
|
:errorMessage="errorMessage"
|
||||||
|
/>
|
||||||
<Dialog
|
<Dialog
|
||||||
v-model="showConvertToDealModal"
|
v-model="showConvertToDealModal"
|
||||||
:options="{
|
:options="{
|
||||||
@ -167,8 +166,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
<DeleteLinkedDocModal
|
||||||
|
v-if="showDeleteLinkedDocModal"
|
||||||
|
v-model="showDeleteLinkedDocModal"
|
||||||
|
:doctype="'CRM Lead'"
|
||||||
|
:docname="leadId"
|
||||||
|
name="Leads"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import DeleteLinkedDocModal from '@/components/DeleteLinkedDocModal.vue'
|
||||||
|
import ErrorPage from '@/components/ErrorPage.vue'
|
||||||
import Icon from '@/components/Icon.vue'
|
import Icon from '@/components/Icon.vue'
|
||||||
import DetailsIcon from '@/components/Icons/DetailsIcon.vue'
|
import DetailsIcon from '@/components/Icons/DetailsIcon.vue'
|
||||||
import ActivityIcon from '@/components/Icons/ActivityIcon.vue'
|
import ActivityIcon from '@/components/Icons/ActivityIcon.vue'
|
||||||
@ -215,7 +223,7 @@ import {
|
|||||||
usePageMeta,
|
usePageMeta,
|
||||||
toast,
|
toast,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { ref, computed, onMounted, watch } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
|
||||||
const { brand } = getSettings()
|
const { brand } = getSettings()
|
||||||
@ -232,71 +240,55 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const lead = createResource({
|
const errorTitle = ref('')
|
||||||
url: 'crm.fcrm.doctype.crm_lead.api.get_lead',
|
const errorMessage = ref('')
|
||||||
params: { name: props.leadId },
|
const showDeleteLinkedDocModal = ref(false)
|
||||||
cache: ['lead', props.leadId],
|
|
||||||
onSuccess: (data) => {
|
const { triggerOnChange, assignees, document, scripts, error } = useDocument(
|
||||||
setupCustomizations(lead, {
|
'CRM Lead',
|
||||||
doc: data,
|
props.leadId,
|
||||||
$dialog,
|
)
|
||||||
$socket,
|
|
||||||
router,
|
const doc = computed(() => document.doc || {})
|
||||||
toast,
|
|
||||||
updateField,
|
watch(error, (err) => {
|
||||||
createToast: toast.create,
|
if (err) {
|
||||||
deleteDoc: deleteLead,
|
errorTitle.value = __(
|
||||||
resource: {
|
err.exc_type == 'DoesNotExistError'
|
||||||
lead,
|
? 'Document not found'
|
||||||
sections,
|
: 'Error occurred',
|
||||||
},
|
)
|
||||||
call,
|
errorMessage.value = __(err.messages?.[0] || 'An error occurred')
|
||||||
})
|
} else {
|
||||||
},
|
errorTitle.value = ''
|
||||||
|
errorMessage.value = ''
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
watch(
|
||||||
if (lead.data) return
|
() => document.doc,
|
||||||
lead.fetch()
|
async (_doc) => {
|
||||||
})
|
if (scripts.data?.length) {
|
||||||
|
let s = await setupCustomizations(scripts.data, {
|
||||||
|
doc: _doc,
|
||||||
|
$dialog,
|
||||||
|
$socket,
|
||||||
|
router,
|
||||||
|
toast,
|
||||||
|
updateField,
|
||||||
|
createToast: toast.create,
|
||||||
|
deleteDoc: deleteLead,
|
||||||
|
call,
|
||||||
|
})
|
||||||
|
document._actions = s.actions || []
|
||||||
|
document._statuses = s.statuses || []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ once: true },
|
||||||
|
)
|
||||||
|
|
||||||
const reload = ref(false)
|
const reload = ref(false)
|
||||||
|
|
||||||
function updateLead(fieldname, value, callback) {
|
|
||||||
value = Array.isArray(fieldname) ? '' : value
|
|
||||||
|
|
||||||
if (!Array.isArray(fieldname) && validateRequired(fieldname, value)) return
|
|
||||||
|
|
||||||
createResource({
|
|
||||||
url: 'frappe.client.set_value',
|
|
||||||
params: {
|
|
||||||
doctype: 'CRM Lead',
|
|
||||||
name: props.leadId,
|
|
||||||
fieldname,
|
|
||||||
value,
|
|
||||||
},
|
|
||||||
auto: true,
|
|
||||||
onSuccess: () => {
|
|
||||||
lead.reload()
|
|
||||||
reload.value = true
|
|
||||||
toast.success(__('Lead updated successfully'))
|
|
||||||
callback?.()
|
|
||||||
},
|
|
||||||
onError: (err) => {
|
|
||||||
toast.error(__(err.messages?.[0] || 'Error updating lead'))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateRequired(fieldname, value) {
|
|
||||||
let meta = lead.data.fields_meta || {}
|
|
||||||
if (meta[fieldname]?.reqd && !value) {
|
|
||||||
toast.error(__('{0} is a required field', [meta[fieldname].label]))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const breadcrumbs = computed(() => {
|
const breadcrumbs = computed(() => {
|
||||||
let items = [{ label: __('Leads'), route: { name: 'Leads' } }]
|
let items = [{ label: __('Leads'), route: { name: 'Leads' } }]
|
||||||
|
|
||||||
@ -317,14 +309,14 @@ const breadcrumbs = computed(() => {
|
|||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
label: title.value,
|
label: title.value,
|
||||||
route: { name: 'Lead', params: { leadId: lead.data.name } },
|
route: { name: 'Lead', params: { leadId: props.leadId } },
|
||||||
})
|
})
|
||||||
return items
|
return items
|
||||||
})
|
})
|
||||||
|
|
||||||
const title = computed(() => {
|
const title = computed(() => {
|
||||||
let t = doctypeMeta['CRM Lead']?.title_field || 'name'
|
let t = doctypeMeta['CRM Lead']?.title_field || 'name'
|
||||||
return lead.data?.[t] || props.leadId
|
return doc.value?.[t] || props.leadId
|
||||||
})
|
})
|
||||||
|
|
||||||
usePageMeta(() => {
|
usePageMeta(() => {
|
||||||
@ -412,19 +404,31 @@ const sections = createResource({
|
|||||||
auto: true,
|
auto: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
function updateField(name, value, callback) {
|
function updateField(name, value) {
|
||||||
updateLead(name, value, () => {
|
value = Array.isArray(name) ? '' : value
|
||||||
lead.data[name] = value
|
let oldValues = Array.isArray(name) ? {} : doc.value[name]
|
||||||
callback?.()
|
|
||||||
|
if (Array.isArray(name)) {
|
||||||
|
name.forEach((field) => (doc.value[field] = value))
|
||||||
|
} else {
|
||||||
|
doc.value[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
document.save.submit(null, {
|
||||||
|
onSuccess: () => (reload.value = true),
|
||||||
|
onError: (err) => {
|
||||||
|
if (Array.isArray(name)) {
|
||||||
|
name.forEach((field) => (doc.value[field] = oldValues[field]))
|
||||||
|
} else {
|
||||||
|
doc.value[name] = oldValues
|
||||||
|
}
|
||||||
|
toast.error(err.messages?.[0] || __('Error updating field'))
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteLead(name) {
|
function deleteLead() {
|
||||||
await call('frappe.client.delete', {
|
showDeleteLinkedDocModal.value = true
|
||||||
doctype: 'CRM Lead',
|
|
||||||
name,
|
|
||||||
})
|
|
||||||
router.push({ name: 'Leads' })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to Deal
|
// Convert to Deal
|
||||||
@ -455,7 +459,7 @@ async function convertToDeal() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let deal = await call('crm.fcrm.doctype.crm_lead.crm_lead.convert_to_deal', {
|
let deal = await call('crm.fcrm.doctype.crm_lead.crm_lead.convert_to_deal', {
|
||||||
lead: lead.data.name,
|
lead: props.leadId,
|
||||||
deal: {},
|
deal: {},
|
||||||
existing_contact: existingContact.value,
|
existing_contact: existingContact.value,
|
||||||
existing_organization: existingOrganization.value,
|
existing_organization: existingOrganization.value,
|
||||||
@ -471,11 +475,6 @@ async function convertToDeal() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { assignees, document, triggerOnChange } = useDocument(
|
|
||||||
'CRM Lead',
|
|
||||||
props.leadId,
|
|
||||||
)
|
|
||||||
|
|
||||||
async function triggerStatusChange(value) {
|
async function triggerStatusChange(value) {
|
||||||
await triggerOnChange('status', value)
|
await triggerOnChange('status', value)
|
||||||
document.save.submit()
|
document.save.submit()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user