1
0
forked from test/crm

feat: added global create document modal for link field

(cherry picked from commit 76aaf7f37d38e293dbb247badd92782a59309d67)
This commit is contained in:
Shariq Ansari 2025-05-12 19:27:41 +05:30 committed by Mergify
parent ba003e8453
commit 20e970d90f
7 changed files with 211 additions and 0 deletions

View File

@ -53,6 +53,7 @@ declare module 'vue' {
ContactsListView: typeof import('./src/components/ListViews/ContactsListView.vue')['default']
ConvertIcon: typeof import('./src/components/Icons/ConvertIcon.vue')['default']
CountUpTimer: typeof import('./src/components/CountUpTimer.vue')['default']
CreateDocumentModal: typeof import('./src/components/Modals/CreateDocumentModal.vue')['default']
CRMLogo: typeof import('./src/components/Icons/CRMLogo.vue')['default']
CustomActions: typeof import('./src/components/CustomActions.vue')['default']
DashboardIcon: typeof import('./src/components/Icons/DashboardIcon.vue')['default']
@ -116,6 +117,7 @@ declare module 'vue' {
FrappeCloudIcon: typeof import('./src/components/Icons/FrappeCloudIcon.vue')['default']
GenderIcon: typeof import('./src/components/Icons/GenderIcon.vue')['default']
GeneralSettings: typeof import('./src/components/Settings/GeneralSettings.vue')['default']
GlobalModals: typeof import('./src/components/Modals/GlobalModals.vue')['default']
GoogleIcon: typeof import('./src/components/Icons/GoogleIcon.vue')['default']
Grid: typeof import('./src/components/Controls/Grid.vue')['default']
GridFieldsEditorModal: typeof import('./src/components/Controls/GridFieldsEditorModal.vue')['default']

View File

@ -126,6 +126,7 @@
"
:filters="field.filters"
@change="(v) => fieldChange(v, field, row)"
:onCreate="field.create"
/>
<Link
v-else-if="field.fieldtype === 'User'"
@ -321,6 +322,7 @@ import { getRandom, getFormat, isTouchScreenDevice } from '@/utils'
import { flt } from '@/utils/numberFormat.js'
import { usersStore } from '@/stores/users'
import { getMeta } from '@/stores/meta'
import { createDocument } from '@/composables/document'
import {
FeatherIcon,
FormControl,
@ -399,6 +401,12 @@ const allFields = computed(() => {
})
function getFieldObj(field) {
if (field.fieldtype === 'Link' && field.options !== 'User') {
if (!field.create) {
field.create = (obj, close) => createDocument(field.options, obj, close)
}
}
return {
...field,
filters: field.link_filters && JSON.parse(field.link_filters),

View File

@ -215,6 +215,7 @@ import UserAvatar from '@/components/UserAvatar.vue'
import TableMultiselectInput from '@/components/Controls/TableMultiselectInput.vue'
import Link from '@/components/Controls/Link.vue'
import Grid from '@/components/Controls/Grid.vue'
import { createDocument } from '@/composables/document'
import { getFormat, evaluateDependsOnValue } from '@/utils'
import { flt } from '@/utils/numberFormat.js'
import { getMeta } from '@/stores/meta'
@ -272,6 +273,12 @@ const field = computed(() => {
field.fieldtype = 'User'
}
if (field.fieldtype === 'Link' && field.options !== 'User') {
if (!field.create) {
field.create = (obj, close) => createDocument(field.options, obj, close)
}
}
let _field = {
...field,
filters: field.link_filters && JSON.parse(field.link_filters),

View File

@ -7,9 +7,11 @@
<AppHeader />
<slot />
</div>
<GlobalModals />
</div>
</template>
<script setup>
import AppSidebar from '@/components/Layouts/AppSidebar.vue'
import AppHeader from '@/components/Layouts/AppHeader.vue'
import GlobalModals from '@/components/Modals/GlobalModals.vue'
</script>

View File

@ -0,0 +1,146 @@
<template>
<Dialog v-model="show" :options="dialogOptions">
<template #body>
<div class="bg-surface-modal px-4 pb-6 pt-5 sm:px-6">
<div class="mb-5 flex items-center justify-between">
<div>
<h3 class="text-2xl font-semibold leading-6 text-ink-gray-9">
{{ __(dialogOptions.title) || __('Untitled') }}
</h3>
</div>
<div class="flex items-center gap-1">
<Button
v-if="isManager() && !isMobileView"
variant="ghost"
class="w-7"
@click="openQuickEntryModal"
>
<EditIcon class="h-4 w-4" />
</Button>
<Button variant="ghost" class="w-7" @click="show = false">
<FeatherIcon name="x" class="h-4 w-4" />
</Button>
</div>
</div>
<div v-if="tabs.data">
<FieldLayout :tabs="tabs.data" :data="_data" :doctype="doctype" />
<ErrorMessage class="mt-2" :message="error" />
</div>
</div>
<div class="px-4 pb-7 pt-4 sm:px-6">
<div class="space-y-2">
<Button
class="w-full"
v-for="action in dialogOptions.actions"
:key="action.label"
v-bind="action"
:label="__(action.label)"
:loading="loading"
/>
</div>
</div>
</template>
</Dialog>
</template>
<script setup>
import FieldLayout from '@/components/FieldLayout/FieldLayout.vue'
import EditIcon from '@/components/Icons/EditIcon.vue'
import { usersStore } from '@/stores/users'
import { isMobileView } from '@/composables/settings'
import { FeatherIcon, createResource, ErrorMessage, call } from 'frappe-ui'
import { ref, nextTick, watch, computed } from 'vue'
const props = defineProps({
doctype: {
type: String,
required: true,
},
data: {
type: Object,
default: () => ({}),
},
})
const emit = defineEmits(['showQuickEntryModal'])
const { isManager } = usersStore()
const show = defineModel()
const loading = ref(false)
const error = ref(null)
let _data = ref({})
const dialogOptions = computed(() => {
let doctype = props.doctype
if (doctype.startsWith('CRM ') || doctype.startsWith('FCRM ')) {
doctype = doctype.replace(/^(CRM |FCRM )/, '')
}
let title = __('New {0}', [doctype])
let size = 'xl'
let actions = [
{
label: __('Create'),
variant: 'solid',
onClick: () => create(),
},
]
return { title, size, actions }
})
const tabs = createResource({
url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_fields_layout',
cache: ['QuickEntry', props.doctype],
params: { doctype: props.doctype, type: 'Quick Entry' },
auto: true,
})
async function create(close) {
loading.value = true
error.value = null
await call(
'frappe.client.insert',
{
doc: {
doctype: props.doctype,
..._data.value,
},
},
{
onError: (err) => {
loading.value = false
if (err.error) {
error.value = err.error.messages?.[0]
}
},
},
)
loading.value = false
show.value = false
}
watch(
() => show.value,
(value) => {
if (!value) return
nextTick(() => {
_data.value = { ...props.data }
})
},
)
function openQuickEntryModal() {
emit('showQuickEntryModal', props.doctype)
nextTick(() => {
show.value = false
})
}
</script>

View File

@ -0,0 +1,32 @@
<template>
<CreateDocumentModal
v-if="showCreateDocumentModal"
v-model="showCreateDocumentModal"
:doctype="createDocumentDoctype"
:data="createDocumentData"
@showQuickEntryModal="(dt) => openQuickEntryModal(dt)"
/>
<QuickEntryModal
v-if="showQuickEntryModal"
v-model="showQuickEntryModal"
:doctype="quickEntryDoctype"
/>
</template>
<script setup>
import CreateDocumentModal from '@/components/Modals/CreateDocumentModal.vue'
import QuickEntryModal from '@/components/Modals/QuickEntryModal.vue'
import {
showCreateDocumentModal,
createDocumentDoctype,
createDocumentData,
} from '@/composables/document'
import { ref } from 'vue'
const showQuickEntryModal = ref(false)
const quickEntryDoctype = ref('')
function openQuickEntryModal(dt) {
showQuickEntryModal.value = true
quickEntryDoctype.value = dt
}
</script>

View File

@ -0,0 +1,14 @@
import { ref } from 'vue'
export const showCreateDocumentModal = ref(false)
export const createDocumentDoctype = ref('')
export const createDocumentData = ref({})
export function createDocument(doctype, obj, close) {
if (doctype) {
close()
createDocumentDoctype.value = doctype
createDocumentData.value = obj || {}
showCreateDocumentModal.value = true
}
}