fix: if contact email is updated it is updating previously opened contact

(cherry picked from commit 50708ebe32df130a4c6c5f678b5b9d0e85ccfa66)

# Conflicts:
#	frontend/components.d.ts
#	frontend/src/components/SidePanelLayout.vue
#	frontend/src/pages/Contact.vue
This commit is contained in:
Shariq Ansari 2025-09-05 15:34:36 +05:30 committed by Mergify
parent 8ab6da55de
commit afadf9f8f4
5 changed files with 179 additions and 99 deletions

View File

@ -84,8 +84,11 @@ declare module 'vue' {
DoubleCheckIcon: typeof import('./src/components/Icons/DoubleCheckIcon.vue')['default']
DragIcon: typeof import('./src/components/Icons/DragIcon.vue')['default']
DragVerticalIcon: typeof import('./src/components/Icons/DragVerticalIcon.vue')['default']
<<<<<<< HEAD
Dropdown: typeof import('./src/components/frappe-ui/Dropdown.vue')['default']
DropdownItem: typeof import('./src/components/DropdownItem.vue')['default']
=======
>>>>>>> 50708ebe (fix: if contact email is updated it is updating previously opened contact)
DuplicateIcon: typeof import('./src/components/Icons/DuplicateIcon.vue')['default']
DurationIcon: typeof import('./src/components/Icons/DurationIcon.vue')['default']
EditEmailTemplate: typeof import('./src/components/Settings/EmailTemplate/EditEmailTemplate.vue')['default']
@ -201,7 +204,12 @@ declare module 'vue' {
PlaybackSpeedIcon: typeof import('./src/components/Icons/PlaybackSpeedIcon.vue')['default']
PlaybackSpeedOption: typeof import('./src/components/Activities/PlaybackSpeedOption.vue')['default']
PlayIcon: typeof import('./src/components/Icons/PlayIcon.vue')['default']
<<<<<<< HEAD
Popover: typeof import('./src/components/frappe-ui/Popover.vue')['default']
=======
PrimaryDropdown: typeof import('./src/components/PrimaryDropdown.vue')['default']
PrimaryDropdownItem: typeof import('./src/components/PrimaryDropdownItem.vue')['default']
>>>>>>> 50708ebe (fix: if contact email is updated it is updating previously opened contact)
ProfileSettings: typeof import('./src/components/Settings/ProfileSettings.vue')['default']
QuickEntryModal: typeof import('./src/components/Modals/QuickEntryModal.vue')['default']
QuickFilterField: typeof import('./src/components/QuickFilterField.vue')['default']

View File

@ -0,0 +1,69 @@
<template>
<Popover>
<template #target="{ isOpen, togglePopover }">
<Button
:label="value"
class="dropdown-button flex items-center justify-between bg-surface-white !px-2.5 py-1.5 text-base text-ink-gray-8 placeholder-ink-gray-4 transition-colors hover:bg-surface-white focus:bg-surface-white focus:shadow-sm focus:outline-none focus:ring-0"
@click="togglePopover"
>
<div v-if="value" class="truncate">{{ value }}</div>
<div v-else class="text-base leading-5 text-ink-gray-4 truncate">
{{ placeholder }}
</div>
<template #suffix>
<FeatherIcon
:name="isOpen ? 'chevron-up' : 'chevron-down'"
class="h-4 text-ink-gray-5"
/>
</template>
</Button>
</template>
<template #body>
<div
class="my-2 p-1.5 min-w-40 space-y-1.5 divide-y divide-outline-gray-1 rounded-lg bg-surface-modal shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none"
>
<div>
<PrimaryDropdownItem
v-for="option in options"
:key="option.name || option.value"
:option="option"
/>
<div v-if="!options?.length">
<div class="p-1.5 pl-3 pr-4 text-base text-ink-gray-4">
{{ __('No {0} Available', [label]) }}
</div>
</div>
</div>
<div class="pt-1.5">
<Button
variant="ghost"
class="w-full !justify-start"
:label="__('Create New')"
iconLeft="plus"
@click="create && create()"
/>
</div>
</div>
</template>
</Popover>
</template>
<script setup>
import PrimaryDropdownItem from '@/components/PrimaryDropdownItem.vue'
import { Popover } from 'frappe-ui'
const props = defineProps({
value: { type: [String, Number], default: '' },
placeholder: { type: String, default: '' },
options: { type: Array, default: [] },
create: { type: Function },
label: { type: String, default: '' },
})
</script>
<style scoped>
.dropdown-button {
border-color: transparent;
background: transparent;
}
</style>

View File

@ -56,7 +56,7 @@
<script setup>
import SuccessIcon from '@/components/Icons/SuccessIcon.vue'
import EditIcon from '@/components/Icons/EditIcon.vue'
import { TextInput, Tooltip } from 'frappe-ui'
import { TextInput } from 'frappe-ui'
import { nextTick, ref, onMounted } from 'vue'
const props = defineProps({

View File

@ -79,70 +79,14 @@
<div>{{ doc[field.fieldname] }}</div>
</Tooltip>
</div>
<div v-else-if="field.fieldtype === 'Dropdown'">
<Popover>
<template #target="{ isOpen, togglePopover }">
<Button
:label="doc[field.fieldname]"
class="dropdown-button flex items-center justify-between bg-surface-white !px-2.5 py-1.5 text-base text-ink-gray-8 placeholder-ink-gray-4 transition-colors hover:bg-surface-white focus:bg-surface-white focus:shadow-sm focus:outline-none focus:ring-0"
@click="togglePopover"
>
<div
v-if="doc[field.fieldname]"
class="truncate"
>
{{ doc[field.fieldname] }}
</div>
<div
v-else
class="text-base leading-5 text-ink-gray-4 truncate"
>
{{ field.placeholder }}
</div>
<template #suffix>
<FeatherIcon
:name="
isOpen ? 'chevron-up' : 'chevron-down'
"
class="h-4 text-ink-gray-5"
/>
</template>
</Button>
</template>
<template #body>
<div
class="my-2 p-1.5 min-w-40 space-y-1.5 divide-y divide-outline-gray-1 rounded-lg bg-surface-modal shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none"
>
<div>
<DropdownItem
v-if="field.options?.length"
v-for="option in field.options"
:key="option.name"
:option="option"
/>
<div v-else>
<div
class="p-1.5 pl-3 pr-4 text-base text-ink-gray-4"
>
{{
__('No {0} Available', [field.label])
}}
</div>
</div>
</div>
<div class="pt-1.5">
<Button
variant="ghost"
class="w-full !justify-start"
:label="__('Create New')"
iconLeft="plus"
@click="field.create()"
/>
</div>
</div>
</template>
</Popover>
</div>
<PrimaryDropdown
v-else-if="field.fieldtype === 'Dropdown'"
:value="doc[field.fieldname]"
:placeholder="field.placeholder"
:options="field.options"
:create="field.create"
:label="field.label"
/>
<FormControl
v-else-if="field.fieldtype == 'Check'"
class="form-control"
@ -366,7 +310,7 @@
import Password from '@/components/Controls/Password.vue'
import FormattedInput from '@/components/Controls/FormattedInput.vue'
import Section from '@/components/Section.vue'
import DropdownItem from '@/components/DropdownItem.vue'
import PrimaryDropdown from '@/components/PrimaryDropdown.vue'
import FadedScrollableDiv from '@/components/FadedScrollableDiv.vue'
import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue'
import EditIcon from '@/components/Icons/EditIcon.vue'
@ -378,7 +322,11 @@ import { usersStore } from '@/stores/users'
import { isMobileView } from '@/composables/settings'
import { getFormat, evaluateDependsOnValue } from '@/utils'
import { flt } from '@/utils/numberFormat.js'
<<<<<<< HEAD
import { Tooltip, DateTimePicker, DatePicker, Popover } from 'frappe-ui'
=======
import { Tooltip, DateTimePicker, DatePicker, TimePicker } from 'frappe-ui'
>>>>>>> 50708ebe (fix: if contact email is updated it is updating previously opened contact)
import { useDocument } from '@/data/document'
import { ref, computed, getCurrentInstance } from 'vue'

View File

@ -113,7 +113,7 @@
class="flex flex-1 flex-col justify-between overflow-hidden"
>
<SidePanelLayout
:sections="sections.data"
:sections="parsedSections"
doctype="Contact"
:docname="contact.doc.name"
@reload="sections.reload"
@ -293,9 +293,7 @@ const tabs = [
const deals = createResource({
url: 'crm.api.contact.get_linked_deals',
cache: ['deals', props.contactId],
params: {
contact: props.contactId,
},
params: { contact: props.contactId },
auto: true,
})
@ -310,18 +308,21 @@ const sections = createResource({
cache: ['sidePanelSections', 'Contact'],
params: { doctype: 'Contact' },
auto: true,
transform: (data) => computed(() => getParsedSections(data)),
})
function getParsedSections(_sections) {
return _sections.map((section) => {
section.columns = section.columns.map((column) => {
column.fields = column.fields.map((field) => {
const parsedSections = computed(() => {
if (!sections.data) return []
return sections.data.map((section) => ({
...section,
columns: section.columns.map((column) => ({
...column,
fields: column.fields.map((field) => {
if (field.fieldname === 'email_id') {
return {
...field,
read_only: false,
fieldtype: 'Dropdown',
<<<<<<< HEAD
options:
contact.doc?.email_ids?.map((email) => {
return {
@ -352,20 +353,50 @@ function getParsedSections(_sections) {
},
}
}) || [],
=======
options: (contact.doc?.email_ids || []).map((email) => ({
name: email.name,
value: email.email_id,
selected: email.email_id === contact.doc.email_id,
placeholder: 'john@doe.com',
onClick: () => setAsPrimary('email', email.email_id),
onSave: (option, isNew) =>
isNew
? createNew('email', option.value)
: editOption(
'Contact Email',
option.name,
'email_id',
option.value,
),
onDelete: async (option, isNew) => {
contact.doc.email_ids = contact.doc.email_ids.filter(
(e) => e.name !== option.name,
)
if (!isNew) await deleteOption('Contact Email', option.name)
},
})),
>>>>>>> 50708ebe (fix: if contact email is updated it is updating previously opened contact)
create: () => {
contact.doc?.email_ids?.push({
name: 'new-1',
value: '',
selected: false,
isNew: true,
})
// Add a temporary new option locally (mirrors original behavior)
contact.doc.email_ids = [
...(contact.doc.email_ids || []),
{
name: 'new-1',
value: '',
selected: false,
isNew: true,
},
]
},
}
} else if (field.fieldname === 'mobile_no') {
}
if (field.fieldname === 'mobile_no') {
return {
...field,
read_only: false,
fieldtype: 'Dropdown',
<<<<<<< HEAD
options:
contact.doc?.phone_nos?.map((phone) => {
return {
@ -395,33 +426,57 @@ function getParsedSections(_sections) {
},
}
}) || [],
=======
options: (contact.doc?.phone_nos || []).map((phone) => ({
name: phone.name,
value: phone.phone,
selected: phone.phone === contact.doc.mobile_no,
onClick: () => setAsPrimary('mobile_no', phone.phone),
onSave: (option, isNew) =>
isNew
? createNew('phone', option.value)
: editOption(
'Contact Phone',
option.name,
'phone',
option.value,
),
onDelete: async (option, isNew) => {
contact.doc.phone_nos = contact.doc.phone_nos.filter(
(p) => p.name !== option.name,
)
if (!isNew) await deleteOption('Contact Phone', option.name)
},
})),
>>>>>>> 50708ebe (fix: if contact email is updated it is updating previously opened contact)
create: () => {
contact.doc?.phone_nos?.push({
name: 'new-1',
value: '',
selected: false,
isNew: true,
})
contact.doc.phone_nos = [
...(contact.doc.phone_nos || []),
{
name: 'new-1',
value: '',
selected: false,
isNew: true,
},
]
},
}
} else if (field.fieldname === 'address') {
}
if (field.fieldname === 'address') {
return {
...field,
create: (value, close) => {
create: (_value, close) => {
openAddressModal()
close()
close && close()
},
edit: (address) => openAddressModal(address),
}
} else {
return field
}
})
return column
})
return section
})
}
return field
}),
})),
}))
})
async function setAsPrimary(field, value) {
let d = await call('crm.api.contact.set_as_primary', {