Revert "fix: add PrimaryDropdown and PrimaryDropdownItem components for enhanced dropdown functionality in forms"

This reverts commit 816bc700ede63c2e9d12f70cea1e90c26f11acc1.
This commit is contained in:
Shariq Ansari 2025-09-05 15:42:51 +05:30
parent 816bc700ed
commit af6970569f
5 changed files with 166 additions and 164 deletions

View File

@ -99,6 +99,7 @@ declare module 'vue' {
DoubleCheckIcon: typeof import('./src/components/Icons/DoubleCheckIcon.vue')['default'] DoubleCheckIcon: typeof import('./src/components/Icons/DoubleCheckIcon.vue')['default']
DragIcon: typeof import('./src/components/Icons/DragIcon.vue')['default'] DragIcon: typeof import('./src/components/Icons/DragIcon.vue')['default']
DragVerticalIcon: typeof import('./src/components/Icons/DragVerticalIcon.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'] DuplicateIcon: typeof import('./src/components/Icons/DuplicateIcon.vue')['default']
DurationIcon: typeof import('./src/components/Icons/DurationIcon.vue')['default'] DurationIcon: typeof import('./src/components/Icons/DurationIcon.vue')['default']
EditEmailTemplate: typeof import('./src/components/Settings/EmailTemplate/EditEmailTemplate.vue')['default'] EditEmailTemplate: typeof import('./src/components/Settings/EmailTemplate/EditEmailTemplate.vue')['default']
@ -223,8 +224,6 @@ declare module 'vue' {
PlaybackSpeedIcon: typeof import('./src/components/Icons/PlaybackSpeedIcon.vue')['default'] PlaybackSpeedIcon: typeof import('./src/components/Icons/PlaybackSpeedIcon.vue')['default']
PlaybackSpeedOption: typeof import('./src/components/Activities/PlaybackSpeedOption.vue')['default'] PlaybackSpeedOption: typeof import('./src/components/Activities/PlaybackSpeedOption.vue')['default']
PlayIcon: typeof import('./src/components/Icons/PlayIcon.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'] ProfileSettings: typeof import('./src/components/Settings/ProfileSettings.vue')['default']
QuickEntryModal: typeof import('./src/components/Modals/QuickEntryModal.vue')['default'] QuickEntryModal: typeof import('./src/components/Modals/QuickEntryModal.vue')['default']
QuickFilterField: typeof import('./src/components/QuickFilterField.vue')['default'] QuickFilterField: typeof import('./src/components/QuickFilterField.vue')['default']

View File

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

View File

@ -1,69 +0,0 @@
<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

@ -79,14 +79,70 @@
<div>{{ doc[field.fieldname] }}</div> <div>{{ doc[field.fieldname] }}</div>
</Tooltip> </Tooltip>
</div> </div>
<PrimaryDropdown <div v-else-if="field.fieldtype === 'Dropdown'">
v-else-if="field.fieldtype === 'Dropdown'" <Popover>
:value="doc[field.fieldname]" <template #target="{ isOpen, togglePopover }">
:placeholder="field.placeholder" <Button
:options="field.options" :label="doc[field.fieldname]"
:create="field.create" 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"
:label="field.label" @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>
<FormControl <FormControl
v-else-if="field.fieldtype == 'Check'" v-else-if="field.fieldtype == 'Check'"
class="form-control" class="form-control"
@ -315,7 +371,7 @@
import Password from '@/components/Controls/Password.vue' import Password from '@/components/Controls/Password.vue'
import FormattedInput from '@/components/Controls/FormattedInput.vue' import FormattedInput from '@/components/Controls/FormattedInput.vue'
import Section from '@/components/Section.vue' import Section from '@/components/Section.vue'
import PrimaryDropdown from '@/components/PrimaryDropdown.vue' import DropdownItem from '@/components/DropdownItem.vue'
import FadedScrollableDiv from '@/components/FadedScrollableDiv.vue' import FadedScrollableDiv from '@/components/FadedScrollableDiv.vue'
import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue' import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue'
import EditIcon from '@/components/Icons/EditIcon.vue' import EditIcon from '@/components/Icons/EditIcon.vue'
@ -327,7 +383,13 @@ import { usersStore } from '@/stores/users'
import { isMobileView } from '@/composables/settings' import { isMobileView } from '@/composables/settings'
import { getFormat, evaluateDependsOnValue } from '@/utils' import { getFormat, evaluateDependsOnValue } from '@/utils'
import { flt } from '@/utils/numberFormat.js' import { flt } from '@/utils/numberFormat.js'
import { Tooltip, DateTimePicker, DatePicker, TimePicker } from 'frappe-ui' import {
Tooltip,
DateTimePicker,
DatePicker,
TimePicker,
Popover,
} from 'frappe-ui'
import { useDocument } from '@/data/document' import { useDocument } from '@/data/document'
import { ref, computed, getCurrentInstance } from 'vue' import { ref, computed, getCurrentInstance } from 'vue'

View File

@ -113,7 +113,7 @@
class="flex flex-1 flex-col justify-between overflow-hidden" class="flex flex-1 flex-col justify-between overflow-hidden"
> >
<SidePanelLayout <SidePanelLayout
:sections="parsedSections" :sections="sections.data"
doctype="Contact" doctype="Contact"
:docname="contact.doc.name" :docname="contact.doc.name"
@reload="sections.reload" @reload="sections.reload"
@ -293,7 +293,9 @@ const tabs = [
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],
params: { contact: props.contactId }, params: {
contact: props.contactId,
},
auto: true, auto: true,
}) })
@ -308,110 +310,118 @@ const sections = createResource({
cache: ['sidePanelSections', 'Contact'], cache: ['sidePanelSections', 'Contact'],
params: { doctype: 'Contact' }, params: { doctype: 'Contact' },
auto: true, auto: true,
transform: (data) => computed(() => getParsedSections(data)),
}) })
const parsedSections = computed(() => { function getParsedSections(_sections) {
if (!sections.data) return [] return _sections.map((section) => {
return sections.data.map((section) => ({ section.columns = section.columns.map((column) => {
...section, column.fields = column.fields.map((field) => {
columns: section.columns.map((column) => ({
...column,
fields: column.fields.map((field) => {
if (field.fieldname === 'email_id') { if (field.fieldname === 'email_id') {
return { return {
...field, ...field,
read_only: false, read_only: false,
fieldtype: 'Dropdown', fieldtype: 'Dropdown',
options: (contact.doc?.email_ids || []).map((email) => ({ options:
name: email.name, contact.doc?.email_ids?.map((email) => {
value: email.email_id, return {
selected: email.email_id === contact.doc.email_id, name: email.name,
placeholder: 'john@doe.com', value: email.email_id,
onClick: () => setAsPrimary('email', email.email_id), selected: email.email_id === contact.doc.email_id,
onSave: (option, isNew) => placeholder: 'john@doe.com',
isNew onClick: () => {
? createNew('email', option.value) setAsPrimary('email', email.email_id)
: editOption( },
'Contact Email', onSave: (option, isNew) => {
option.name, if (isNew) {
'email_id', createNew('email', option.value)
option.value, } else {
), editOption(
onDelete: async (option, isNew) => { 'Contact Email',
contact.doc.email_ids = contact.doc.email_ids.filter( option.name,
(e) => e.name !== option.name, 'email_id',
) option.value,
if (!isNew) await deleteOption('Contact Email', option.name) )
}, }
})), },
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))
},
}
}) || [],
create: () => { create: () => {
// Add a temporary new option locally (mirrors original behavior) contact.doc?.email_ids?.push({
contact.doc.email_ids = [ name: 'new-1',
...(contact.doc.email_ids || []), value: '',
{ selected: false,
name: 'new-1', isNew: true,
value: '', })
selected: false,
isNew: true,
},
]
}, },
} }
} } else if (field.fieldname === 'mobile_no') {
if (field.fieldname === 'mobile_no') {
return { return {
...field, ...field,
read_only: false, read_only: false,
fieldtype: 'Dropdown', fieldtype: 'Dropdown',
options: (contact.doc?.phone_nos || []).map((phone) => ({ options:
name: phone.name, contact.doc?.phone_nos?.map((phone) => {
value: phone.phone, return {
selected: phone.phone === contact.doc.mobile_no, name: phone.name,
onClick: () => setAsPrimary('mobile_no', phone.phone), value: phone.phone,
onSave: (option, isNew) => selected: phone.phone === contact.doc.mobile_no,
isNew onClick: () => {
? createNew('phone', option.value) setAsPrimary('mobile_no', phone.phone)
: editOption( },
'Contact Phone', onSave: (option, isNew) => {
option.name, if (isNew) {
'phone', createNew('phone', option.value)
option.value, } else {
), editOption(
onDelete: async (option, isNew) => { 'Contact Phone',
contact.doc.phone_nos = contact.doc.phone_nos.filter( option.name,
(p) => p.name !== option.name, 'phone',
) option.value,
if (!isNew) await deleteOption('Contact Phone', option.name) )
}, }
})), },
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))
},
}
}) || [],
create: () => { create: () => {
contact.doc.phone_nos = [ contact.doc?.phone_nos?.push({
...(contact.doc.phone_nos || []), name: 'new-1',
{ value: '',
name: 'new-1', selected: false,
value: '', isNew: true,
selected: false, })
isNew: true,
},
]
}, },
} }
} } else if (field.fieldname === 'address') {
if (field.fieldname === 'address') {
return { return {
...field, ...field,
create: (_value, close) => { create: (value, close) => {
openAddressModal() openAddressModal()
close && close() close()
}, },
edit: (address) => openAddressModal(address), edit: (address) => openAddressModal(address),
} }
} else {
return field
} }
return field })
}), return column
})), })
})) return section
}) })
}
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', {