refactor: removed contact store from contact & contact modal
This commit is contained in:
parent
afb561110a
commit
95661987e8
@ -1,4 +1,5 @@
|
|||||||
import frappe
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
|
||||||
def validate(doc, method):
|
def validate(doc, method):
|
||||||
@ -24,6 +25,31 @@ def set_primary_mobile_no(doc):
|
|||||||
doc.phone_nos[0].is_primary_mobile_no = 1
|
doc.phone_nos[0].is_primary_mobile_no = 1
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_contact(name):
|
||||||
|
Contact = frappe.qb.DocType("Contact")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(Contact)
|
||||||
|
.select("*")
|
||||||
|
.where(Contact.name == name)
|
||||||
|
.limit(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
contact = query.run(as_dict=True)
|
||||||
|
if not len(contact):
|
||||||
|
frappe.throw(_("Contact not found"), frappe.DoesNotExistError)
|
||||||
|
contact = contact.pop()
|
||||||
|
|
||||||
|
contact["doctype"] = "Contact"
|
||||||
|
contact["email_ids"] = frappe.get_all(
|
||||||
|
"Contact Email", filters={"parent": name}, fields=["name", "email_id", "is_primary"]
|
||||||
|
)
|
||||||
|
contact["phone_nos"] = frappe.get_all(
|
||||||
|
"Contact Phone", filters={"parent": name}, fields=["name", "phone", "is_primary_mobile_no"]
|
||||||
|
)
|
||||||
|
return contact
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_linked_deals(contact):
|
def get_linked_deals(contact):
|
||||||
"""Get linked deals for a contact"""
|
"""Get linked deals for a contact"""
|
||||||
|
|||||||
@ -40,10 +40,10 @@
|
|||||||
<template #default="{ open }">
|
<template #default="{ open }">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
:label="contact[field.name]"
|
:label="contact.data[field.name]"
|
||||||
class="dropdown-button w-full justify-between truncate hover:bg-white"
|
class="dropdown-button w-full justify-between truncate hover:bg-white"
|
||||||
>
|
>
|
||||||
<div class="truncate">{{ contact[field.name] }}</div>
|
<div class="truncate">{{ contact.data[field.name] }}</div>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<FeatherIcon
|
<FeatherIcon
|
||||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
:name="open ? 'chevron-up' : 'chevron-down'"
|
||||||
@ -166,7 +166,6 @@ import CertificateIcon from '@/components/Icons/CertificateIcon.vue'
|
|||||||
import EditIcon from '@/components/Icons/EditIcon.vue'
|
import EditIcon from '@/components/Icons/EditIcon.vue'
|
||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
import Dropdown from '@/components/frappe-ui/Dropdown.vue'
|
import Dropdown from '@/components/frappe-ui/Dropdown.vue'
|
||||||
import { contactsStore } from '@/stores/contacts'
|
|
||||||
import { call } from 'frappe-ui'
|
import { call } from 'frappe-ui'
|
||||||
import { ref, nextTick, watch, computed, h } from 'vue'
|
import { ref, nextTick, watch, computed, h } from 'vue'
|
||||||
import { createToast } from '@/utils'
|
import { createToast } from '@/utils'
|
||||||
@ -189,7 +188,6 @@ const props = defineProps({
|
|||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const show = defineModel()
|
const show = defineModel()
|
||||||
const { contacts } = contactsStore()
|
|
||||||
|
|
||||||
const detailMode = ref(false)
|
const detailMode = ref(false)
|
||||||
const editMode = ref(false)
|
const editMode = ref(false)
|
||||||
@ -211,7 +209,7 @@ async function updateContact() {
|
|||||||
async function callSetValue(values) {
|
async function callSetValue(values) {
|
||||||
const d = await call('frappe.client.set_value', {
|
const d = await call('frappe.client.set_value', {
|
||||||
doctype: 'Contact',
|
doctype: 'Contact',
|
||||||
name: props.contact.name,
|
name: props.contact.data.name,
|
||||||
fieldname: values,
|
fieldname: values,
|
||||||
})
|
})
|
||||||
return d.name
|
return d.name
|
||||||
@ -238,7 +236,7 @@ async function callInsertDoc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleContactUpdate(doc) {
|
function handleContactUpdate(doc) {
|
||||||
contacts.reload()
|
props.contact.reload()
|
||||||
if (doc.name && props.options.redirect) {
|
if (doc.name && props.options.redirect) {
|
||||||
router.push({
|
router.push({
|
||||||
name: 'Contact',
|
name: 'Contact',
|
||||||
@ -345,14 +343,14 @@ const sections = computed(() => {
|
|||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
label: 'Email',
|
label: 'Email',
|
||||||
type: props.contact.name ? 'dropdown' : 'data',
|
type: props.contact.data.name ? 'dropdown' : 'data',
|
||||||
name: 'email_id',
|
name: 'email_id',
|
||||||
options:
|
options:
|
||||||
props.contact?.email_ids?.map((email) => {
|
props.contact.data?.email_ids?.map((email) => {
|
||||||
return {
|
return {
|
||||||
name: email.name,
|
name: email.name,
|
||||||
value: email.email_id,
|
value: email.email_id,
|
||||||
selected: email.email_id === props.contact.email_id,
|
selected: email.email_id === props.contact.data.email_id,
|
||||||
placeholder: 'john@doe.com',
|
placeholder: 'john@doe.com',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
_contact.value.email_id = email.email_id
|
_contact.value.email_id = email.email_id
|
||||||
@ -366,7 +364,7 @@ const sections = computed(() => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDelete: (option, isNew) => {
|
onDelete: (option, isNew) => {
|
||||||
props.contact.email_ids = props.contact.email_ids.filter(
|
props.contact.data.email_ids = props.contact.data.email_ids.filter(
|
||||||
(email) => email.name !== option.name
|
(email) => email.name !== option.name
|
||||||
)
|
)
|
||||||
!isNew && deleteOption('Contact Email', option.name)
|
!isNew && deleteOption('Contact Email', option.name)
|
||||||
@ -374,7 +372,7 @@ const sections = computed(() => {
|
|||||||
}
|
}
|
||||||
}) || [],
|
}) || [],
|
||||||
create: () => {
|
create: () => {
|
||||||
props.contact?.email_ids?.push({
|
props.contact.data?.email_ids?.push({
|
||||||
name: 'new-1',
|
name: 'new-1',
|
||||||
value: '',
|
value: '',
|
||||||
selected: false,
|
selected: false,
|
||||||
@ -388,14 +386,14 @@ const sections = computed(() => {
|
|||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
label: 'Mobile No.',
|
label: 'Mobile No.',
|
||||||
type: props.contact.name ? 'dropdown' : 'data',
|
type: props.contact.data.name ? 'dropdown' : 'data',
|
||||||
name: 'actual_mobile_no',
|
name: 'actual_mobile_no',
|
||||||
options:
|
options:
|
||||||
props.contact?.phone_nos?.map((phone) => {
|
props.contact.data?.phone_nos?.map((phone) => {
|
||||||
return {
|
return {
|
||||||
name: phone.name,
|
name: phone.name,
|
||||||
value: phone.phone,
|
value: phone.phone,
|
||||||
selected: phone.phone === props.contact.actual_mobile_no,
|
selected: phone.phone === props.contact.data.actual_mobile_no,
|
||||||
placeholder: '+91 1234567890',
|
placeholder: '+91 1234567890',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
_contact.value.actual_mobile_no = phone.phone
|
_contact.value.actual_mobile_no = phone.phone
|
||||||
@ -410,7 +408,7 @@ const sections = computed(() => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDelete: (option, isNew) => {
|
onDelete: (option, isNew) => {
|
||||||
props.contact.phone_nos = props.contact.phone_nos.filter(
|
props.contact.data.phone_nos = props.contact.data.phone_nos.filter(
|
||||||
(phone) => phone.name !== option.name
|
(phone) => phone.name !== option.name
|
||||||
)
|
)
|
||||||
!isNew && deleteOption('Contact Phone', option.name)
|
!isNew && deleteOption('Contact Phone', option.name)
|
||||||
@ -418,7 +416,7 @@ const sections = computed(() => {
|
|||||||
}
|
}
|
||||||
}) || [],
|
}) || [],
|
||||||
create: () => {
|
create: () => {
|
||||||
props.contact?.phone_nos?.push({
|
props.contact.data?.phone_nos?.push({
|
||||||
name: 'new-1',
|
name: 'new-1',
|
||||||
value: '',
|
value: '',
|
||||||
selected: false,
|
selected: false,
|
||||||
@ -486,12 +484,12 @@ const sections = computed(() => {
|
|||||||
|
|
||||||
async function setAsPrimary(field, value) {
|
async function setAsPrimary(field, value) {
|
||||||
let d = await call('crm.api.contact.set_as_primary', {
|
let d = await call('crm.api.contact.set_as_primary', {
|
||||||
contact: props.contact.name,
|
contact: props.contact.data.name,
|
||||||
field,
|
field,
|
||||||
value,
|
value,
|
||||||
})
|
})
|
||||||
if (d) {
|
if (d) {
|
||||||
contacts.reload()
|
props.contact.reload()
|
||||||
createToast({
|
createToast({
|
||||||
title: 'Contact updated',
|
title: 'Contact updated',
|
||||||
icon: 'check',
|
icon: 'check',
|
||||||
@ -502,12 +500,12 @@ async function setAsPrimary(field, value) {
|
|||||||
|
|
||||||
async function createNew(field, value) {
|
async function createNew(field, value) {
|
||||||
let d = await call('crm.api.contact.create_new', {
|
let d = await call('crm.api.contact.create_new', {
|
||||||
contact: props.contact.name,
|
contact: props.contact.data.name,
|
||||||
field,
|
field,
|
||||||
value,
|
value,
|
||||||
})
|
})
|
||||||
if (d) {
|
if (d) {
|
||||||
contacts.reload()
|
props.contact.reload()
|
||||||
createToast({
|
createToast({
|
||||||
title: 'Contact updated',
|
title: 'Contact updated',
|
||||||
icon: 'check',
|
icon: 'check',
|
||||||
@ -524,7 +522,7 @@ async function editOption(doctype, name, value) {
|
|||||||
value,
|
value,
|
||||||
})
|
})
|
||||||
if (d) {
|
if (d) {
|
||||||
contacts.reload()
|
props.contact.reload()
|
||||||
createToast({
|
createToast({
|
||||||
title: 'Contact updated',
|
title: 'Contact updated',
|
||||||
icon: 'check',
|
icon: 'check',
|
||||||
@ -539,7 +537,7 @@ async function deleteOption(doctype, name) {
|
|||||||
name,
|
name,
|
||||||
})
|
})
|
||||||
if (d) {
|
if (d) {
|
||||||
contacts.reload()
|
props.contact.reload()
|
||||||
createToast({
|
createToast({
|
||||||
title: 'Contact updated',
|
title: 'Contact updated',
|
||||||
icon: 'check',
|
icon: 'check',
|
||||||
@ -549,7 +547,7 @@ async function deleteOption(doctype, name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dirty = computed(() => {
|
const dirty = computed(() => {
|
||||||
return JSON.stringify(props.contact) !== JSON.stringify(_contact.value)
|
return JSON.stringify(props.contact.data) !== JSON.stringify(_contact.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@ -559,7 +557,7 @@ watch(
|
|||||||
detailMode.value = props.options.detailMode
|
detailMode.value = props.options.detailMode
|
||||||
editMode.value = false
|
editMode.value = false
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
_contact.value = { ...props.contact }
|
_contact.value = { ...props.contact.data }
|
||||||
if (_contact.value.name) {
|
if (_contact.value.name) {
|
||||||
editMode.value = true
|
editMode.value = true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<LayoutHeader v-if="contact">
|
<LayoutHeader v-if="contact.data">
|
||||||
<template #left-header>
|
<template #left-header>
|
||||||
<Breadcrumbs :items="breadcrumbs" />
|
<Breadcrumbs :items="breadcrumbs" />
|
||||||
</template>
|
</template>
|
||||||
</LayoutHeader>
|
</LayoutHeader>
|
||||||
<div class="flex h-full flex-col overflow-hidden">
|
<div v-if="contact.data" class="flex h-full flex-col overflow-hidden">
|
||||||
<FileUploader @success="changeContactImage" :validateFile="validateFile">
|
<FileUploader @success="changeContactImage" :validateFile="validateFile">
|
||||||
<template #default="{ openFileSelector, error }">
|
<template #default="{ openFileSelector, error }">
|
||||||
<div class="flex items-center justify-start gap-6 p-5">
|
<div class="flex items-center justify-start gap-6 p-5">
|
||||||
@ -12,18 +12,18 @@
|
|||||||
<Avatar
|
<Avatar
|
||||||
size="3xl"
|
size="3xl"
|
||||||
class="h-24 w-24"
|
class="h-24 w-24"
|
||||||
:label="contact.full_name"
|
:label="contact.data.full_name"
|
||||||
:image="contact.image"
|
:image="contact.data.image"
|
||||||
/>
|
/>
|
||||||
<component
|
<component
|
||||||
:is="contact.image ? Dropdown : 'div'"
|
:is="contact.data.image ? Dropdown : 'div'"
|
||||||
v-bind="
|
v-bind="
|
||||||
contact.image
|
contact.data.image
|
||||||
? {
|
? {
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
icon: 'upload',
|
icon: 'upload',
|
||||||
label: contact.image
|
label: contact.data.image
|
||||||
? 'Change image'
|
? 'Change image'
|
||||||
: 'Upload image',
|
: 'Upload image',
|
||||||
onClick: openFileSelector,
|
onClick: openFileSelector,
|
||||||
@ -51,62 +51,62 @@
|
|||||||
</component>
|
</component>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-0.5 truncate">
|
<div class="flex flex-col gap-0.5 truncate">
|
||||||
<Tooltip :text="contact.full_name">
|
<Tooltip :text="contact.data.full_name">
|
||||||
<div class="truncate text-3xl font-semibold">
|
<div class="truncate text-3xl font-semibold">
|
||||||
<span v-if="contact.salutation">
|
<span v-if="contact.data.salutation">
|
||||||
{{ contact.salutation + '. ' }}
|
{{ contact.data.salutation + '. ' }}
|
||||||
</span>
|
</span>
|
||||||
<span>{{ contact.full_name }}</span>
|
<span>{{ contact.data.full_name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<div class="flex items-center gap-2 text-base text-gray-700">
|
<div class="flex items-center gap-2 text-base text-gray-700">
|
||||||
<div v-if="contact.email_id" class="flex items-center gap-1.5">
|
<div v-if="contact.data.email_id" class="flex items-center gap-1.5">
|
||||||
<EmailIcon class="h-4 w-4" />
|
<EmailIcon class="h-4 w-4" />
|
||||||
<span class="">{{ contact.email_id }}</span>
|
<span class="">{{ contact.data.email_id }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
v-if="contact.email_id"
|
v-if="contact.data.email_id"
|
||||||
class="text-3xl leading-[0] text-gray-600"
|
class="text-3xl leading-[0] text-gray-600"
|
||||||
>
|
>
|
||||||
·
|
·
|
||||||
</span>
|
</span>
|
||||||
<Tooltip text="Make Call" v-if="contact.actual_mobile_no">
|
<Tooltip text="Make Call" v-if="contact.data.actual_mobile_no">
|
||||||
<div
|
<div
|
||||||
class="flex cursor-pointer items-center gap-1.5"
|
class="flex cursor-pointer items-center gap-1.5"
|
||||||
@click="makeCall(contact.actual_mobile_no)"
|
@click="makeCall(contact.data.actual_mobile_no)"
|
||||||
>
|
>
|
||||||
<PhoneIcon class="h-4 w-4" />
|
<PhoneIcon class="h-4 w-4" />
|
||||||
<span class="">{{ contact.actual_mobile_no }}</span>
|
<span class="">{{ contact.data.actual_mobile_no }}</span>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<span
|
<span
|
||||||
v-if="contact.actual_mobile_no"
|
v-if="contact.data.actual_mobile_no"
|
||||||
class="text-3xl leading-[0] text-gray-600"
|
class="text-3xl leading-[0] text-gray-600"
|
||||||
>
|
>
|
||||||
·
|
·
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
v-if="contact.company_name"
|
v-if="contact.data.company_name"
|
||||||
class="flex items-center gap-1.5"
|
class="flex items-center gap-1.5"
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
size="xs"
|
size="xs"
|
||||||
:label="contact.company_name"
|
:label="contact.data.company_name"
|
||||||
:image="
|
:image="
|
||||||
getOrganization(contact.company_name)?.organization_logo
|
getOrganization(contact.data.company_name)?.organization_logo
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<span class="">{{ contact.company_name }}</span>
|
<span class="">{{ contact.data.company_name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
v-if="contact.company_name"
|
v-if="contact.data.company_name"
|
||||||
class="text-3xl leading-[0] text-gray-600"
|
class="text-3xl leading-[0] text-gray-600"
|
||||||
>
|
>
|
||||||
·
|
·
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
v-if="
|
v-if="
|
||||||
contact.email_id || contact.mobile_no || contact.company_name
|
contact.data.email_id || contact.data.mobile_no || contact.data.company_name
|
||||||
"
|
"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
label="More"
|
label="More"
|
||||||
@ -223,7 +223,6 @@ import {
|
|||||||
} from '@/utils'
|
} from '@/utils'
|
||||||
import { globalStore } from '@/stores/global.js'
|
import { globalStore } from '@/stores/global.js'
|
||||||
import { usersStore } from '@/stores/users.js'
|
import { usersStore } from '@/stores/users.js'
|
||||||
import { contactsStore } from '@/stores/contacts.js'
|
|
||||||
import { organizationsStore } from '@/stores/organizations.js'
|
import { organizationsStore } from '@/stores/organizations.js'
|
||||||
import { statusesStore } from '@/stores/statuses'
|
import { statusesStore } from '@/stores/statuses'
|
||||||
import { ref, computed, h } from 'vue'
|
import { ref, computed, h } from 'vue'
|
||||||
@ -231,7 +230,6 @@ import { useRouter } from 'vue-router'
|
|||||||
|
|
||||||
const { $dialog, makeCall } = globalStore()
|
const { $dialog, makeCall } = globalStore()
|
||||||
|
|
||||||
const { getContactByName, contacts } = contactsStore()
|
|
||||||
const { getUser } = usersStore()
|
const { getUser } = usersStore()
|
||||||
const { getOrganization } = organizationsStore()
|
const { getOrganization } = organizationsStore()
|
||||||
const { getDealStatus } = statusesStore()
|
const { getDealStatus } = statusesStore()
|
||||||
@ -245,16 +243,14 @@ const props = defineProps({
|
|||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const contact = computed(() => getContactByName(props.contactId))
|
|
||||||
|
|
||||||
const showContactModal = ref(false)
|
const showContactModal = ref(false)
|
||||||
const detailMode = ref(false)
|
const detailMode = ref(false)
|
||||||
|
|
||||||
const breadcrumbs = computed(() => {
|
const breadcrumbs = computed(() => {
|
||||||
let items = [{ label: 'Contacts', route: { name: 'Contacts' } }]
|
let items = [{ label: 'Contacts', route: { name: 'Contacts' } }]
|
||||||
items.push({
|
items.push({
|
||||||
label: contact.value.full_name,
|
label: contact.data?.full_name,
|
||||||
route: { name: 'Contact', params: { contactId: contact.value.name } },
|
route: { name: 'Contact', params: { contactId: props.contactId } },
|
||||||
})
|
})
|
||||||
return items
|
return items
|
||||||
})
|
})
|
||||||
@ -273,7 +269,7 @@ async function changeContactImage(file) {
|
|||||||
fieldname: 'image',
|
fieldname: 'image',
|
||||||
value: file?.file_url || '',
|
value: file?.file_url || '',
|
||||||
})
|
})
|
||||||
contacts.reload()
|
contact.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteContact() {
|
async function deleteContact() {
|
||||||
@ -307,6 +303,22 @@ const tabs = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const contact = createResource({
|
||||||
|
url: 'crm.api.contact.get_contact',
|
||||||
|
cache: ['contact', props.contactId],
|
||||||
|
params: {
|
||||||
|
name: props.contactId,
|
||||||
|
},
|
||||||
|
auto: true,
|
||||||
|
transform: (data) => {
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
actual_mobile_no: data.mobile_no,
|
||||||
|
mobile_no: data.mobile_no,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const deals = createResource({
|
const deals = createResource({
|
||||||
url: 'crm.api.contact.get_linked_deals',
|
url: 'crm.api.contact.get_linked_deals',
|
||||||
cache: ['deals', props.contactId],
|
cache: ['deals', props.contactId],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user