fix: if contact email is updated it is updating previously opened contact
This commit is contained in:
parent
181439be1d
commit
50708ebe32
3
frontend/components.d.ts
vendored
3
frontend/components.d.ts
vendored
@ -99,7 +99,6 @@ 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']
|
||||
DropdownItem: typeof import('./src/components/DropdownItem.vue')['default']
|
||||
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']
|
||||
@ -224,6 +223,8 @@ 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']
|
||||
PrimaryDropdown: typeof import('./src/components/PrimaryDropdown.vue')['default']
|
||||
PrimaryDropdownItem: typeof import('./src/components/PrimaryDropdownItem.vue')['default']
|
||||
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']
|
||||
|
||||
69
frontend/src/components/PrimaryDropdown.vue
Normal file
69
frontend/src/components/PrimaryDropdown.vue
Normal 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>
|
||||
@ -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({
|
||||
@ -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"
|
||||
@ -371,7 +315,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'
|
||||
@ -383,13 +327,7 @@ import { usersStore } from '@/stores/users'
|
||||
import { isMobileView } from '@/composables/settings'
|
||||
import { getFormat, evaluateDependsOnValue } from '@/utils'
|
||||
import { flt } from '@/utils/numberFormat.js'
|
||||
import {
|
||||
Tooltip,
|
||||
DateTimePicker,
|
||||
DatePicker,
|
||||
TimePicker,
|
||||
Popover,
|
||||
} from 'frappe-ui'
|
||||
import { Tooltip, DateTimePicker, DatePicker, TimePicker } from 'frappe-ui'
|
||||
import { useDocument } from '@/data/document'
|
||||
import { ref, computed, getCurrentInstance } from 'vue'
|
||||
|
||||
|
||||
@ -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,118 +308,110 @@ 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',
|
||||
options:
|
||||
contact.doc?.email_ids?.map((email) => {
|
||||
return {
|
||||
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) => {
|
||||
if (isNew) {
|
||||
createNew('email', option.value)
|
||||
} else {
|
||||
editOption(
|
||||
'Contact Email',
|
||||
option.name,
|
||||
'email_id',
|
||||
option.value,
|
||||
)
|
||||
}
|
||||
},
|
||||
onDelete: async (option, isNew) => {
|
||||
contact.doc.email_ids = contact.doc.email_ids.filter(
|
||||
(email) => email.name !== option.name,
|
||||
)
|
||||
!isNew && (await deleteOption('Contact Email', option.name))
|
||||
},
|
||||
}
|
||||
}) || [],
|
||||
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)
|
||||
},
|
||||
})),
|
||||
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',
|
||||
options:
|
||||
contact.doc?.phone_nos?.map((phone) => {
|
||||
return {
|
||||
name: phone.name,
|
||||
value: phone.phone,
|
||||
selected: phone.phone === contact.doc.mobile_no,
|
||||
onClick: () => {
|
||||
setAsPrimary('mobile_no', phone.phone)
|
||||
},
|
||||
onSave: (option, isNew) => {
|
||||
if (isNew) {
|
||||
createNew('phone', option.value)
|
||||
} else {
|
||||
editOption(
|
||||
'Contact Phone',
|
||||
option.name,
|
||||
'phone',
|
||||
option.value,
|
||||
)
|
||||
}
|
||||
},
|
||||
onDelete: async (option, isNew) => {
|
||||
contact.doc.phone_nos = contact.doc.phone_nos.filter(
|
||||
(phone) => phone.name !== option.name,
|
||||
)
|
||||
!isNew && (await deleteOption('Contact Phone', option.name))
|
||||
},
|
||||
}
|
||||
}) || [],
|
||||
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)
|
||||
},
|
||||
})),
|
||||
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', {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user