refactor: removed contact store from contact & contact modal

This commit is contained in:
Shariq Ansari 2024-03-13 12:17:11 +05:30
parent afb561110a
commit 95661987e8
3 changed files with 91 additions and 55 deletions

View File

@ -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"""

View File

@ -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
} }

View File

@ -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"
> >
&middot; &middot;
</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"
> >
&middot; &middot;
</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"
> >
&middot; &middot;
</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],