feat: added global create document modal for link field
(cherry picked from commit 76aaf7f37d38e293dbb247badd92782a59309d67)
This commit is contained in:
parent
ba003e8453
commit
20e970d90f
2
frontend/components.d.ts
vendored
2
frontend/components.d.ts
vendored
@ -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']
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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>
|
||||
|
||||
146
frontend/src/components/Modals/CreateDocumentModal.vue
Normal file
146
frontend/src/components/Modals/CreateDocumentModal.vue
Normal 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>
|
||||
32
frontend/src/components/Modals/GlobalModals.vue
Normal file
32
frontend/src/components/Modals/GlobalModals.vue
Normal 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>
|
||||
14
frontend/src/composables/document.js
Normal file
14
frontend/src/composables/document.js
Normal 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
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user