From 38565a14f67d511aa2fa085460ab4f9d090ca8f9 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 6 Dec 2024 14:25:49 +0530 Subject: [PATCH] fix: send tabs instead of sections to FieldLayout component and render Tabs if exists --- .../crm_fields_layout/crm_fields_layout.py | 71 ++-- .../src/components/Activities/DataFields.vue | 14 +- frontend/src/components/FieldLayout.vue | 370 ++++++++++-------- .../src/components/Modals/AddressModal.vue | 6 +- .../src/components/Modals/ContactModal.vue | 8 +- .../src/components/Modals/DataFieldsModal.vue | 35 +- frontend/src/components/Modals/DealModal.vue | 30 +- frontend/src/components/Modals/LeadModal.vue | 26 +- .../components/Modals/OrganizationModal.vue | 8 +- .../src/components/Modals/QuickEntryModal.vue | 35 +- .../src/components/Modals/SidePanelModal.vue | 38 +- .../src/components/Settings/SettingsPage.vue | 13 +- 12 files changed, 351 insertions(+), 303 deletions(-) diff --git a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py index 6926d0b2..0d06ca43 100644 --- a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py +++ b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py @@ -2,6 +2,7 @@ # For license information, please see license.txt import json + import frappe from frappe import _ from frappe.model.document import Document @@ -10,46 +11,54 @@ from frappe.model.document import Document class CRMFieldsLayout(Document): pass + @frappe.whitelist() def get_fields_layout(doctype: str, type: str): - sections = [] + tabs = [] if frappe.db.exists("CRM Fields Layout", {"dt": doctype, "type": type}): layout = frappe.get_doc("CRM Fields Layout", {"dt": doctype, "type": type}) else: return [] if layout.layout: - sections = json.loads(layout.layout) + tabs = json.loads(layout.layout) + + has_tabs = tabs[0].get("sections") if tabs and tabs[0] else False + + if not has_tabs: + tabs = [{"no_tabs": True, "sections": tabs}] allowed_fields = [] - for section in sections: - if not section.get("fields"): - continue - allowed_fields.extend(section.get("fields")) + for tab in tabs: + for section in tab.get("sections"): + if not section.get("fields"): + continue + allowed_fields.extend(section.get("fields")) fields = frappe.get_meta(doctype).fields fields = [field for field in fields if field.fieldname in allowed_fields] - for section in sections: - for field in section.get("fields") if section.get("fields") else []: - field = next((f for f in fields if f.fieldname == field), None) - if field: - if field.fieldtype == "Select" and field.options: - field.options = field.options.split("\n") - field.options = [{"label": _(option), "value": option} for option in field.options] - field.options.insert(0, {"label": "", "value": ""}) - field = { - "label": _(field.label), - "name": field.fieldname, - "type": field.fieldtype, - "options": field.options, - "mandatory": field.reqd, - "placeholder": field.get("placeholder"), - "filters": field.get("link_filters") - } - section["fields"][section.get("fields").index(field["name"])] = field + for tab in tabs: + for section in tab.get("sections"): + for field in section.get("fields") if section.get("fields") else []: + field = next((f for f in fields if f.fieldname == field), None) + if field: + if field.fieldtype == "Select" and field.options: + field.options = field.options.split("\n") + field.options = [{"label": _(option), "value": option} for option in field.options] + field.options.insert(0, {"label": "", "value": ""}) + field = { + "label": _(field.label), + "name": field.fieldname, + "type": field.fieldtype, + "options": field.options, + "mandatory": field.reqd, + "placeholder": field.get("placeholder"), + "filters": field.get("link_filters"), + } + section["fields"][section.get("fields").index(field["name"])] = field - return sections or [] + return tabs or [] @frappe.whitelist() @@ -59,11 +68,13 @@ def save_fields_layout(doctype: str, type: str, layout: str): else: doc = frappe.new_doc("CRM Fields Layout") - doc.update({ - "dt": doctype, - "type": type, - "layout": layout, - }) + doc.update( + { + "dt": doctype, + "type": type, + "layout": layout, + } + ) doc.save(ignore_permissions=True) return doc.layout diff --git a/frontend/src/components/Activities/DataFields.vue b/frontend/src/components/Activities/DataFields.vue index 4828e329..93330670 100644 --- a/frontend/src/components/Activities/DataFields.vue +++ b/frontend/src/components/Activities/DataFields.vue @@ -31,16 +31,8 @@ {{ __('Loading...') }} -
- +
+
-
-
+ -
-
+
-
+
+
-
- {{ __(field.label) }} - +
* -
- - - - -
- - -
-
- - -
+
+ {{ __(field.label) }} + * +
+ + + + +
+ + +
+
+ + +
- - - - - - - - - - + + + + + + + + + + +
+
-
+
- -
+
+
@@ -186,19 +215,24 @@ import UserAvatar from '@/components/UserAvatar.vue' import Link from '@/components/Controls/Link.vue' import { usersStore } from '@/stores/users' import { getFormat } from '@/utils' -import { Tooltip, DatePicker, DateTimePicker } from 'frappe-ui' +import { Tabs, Tooltip, DatePicker, DateTimePicker } from 'frappe-ui' +import { ref, computed } from 'vue' const { getUser } = usersStore() const props = defineProps({ - sections: Array, + tabs: Array, data: Object, - allowTabs: { + modal: { type: Boolean, default: false, }, }) +const hasTabs = computed(() => !props.tabs[0].no_tabs) + +const tabIndex = ref(0) + function gridClass(columns) { columns = columns || 3 let griColsMap = { diff --git a/frontend/src/components/Modals/AddressModal.vue b/frontend/src/components/Modals/AddressModal.vue index a23396de..fd7cd25a 100644 --- a/frontend/src/components/Modals/AddressModal.vue +++ b/frontend/src/components/Modals/AddressModal.vue @@ -22,8 +22,8 @@ -
- +
+
@@ -106,7 +106,7 @@ const dialogOptions = computed(() => { return { title, size, actions } }) -const sections = createResource({ +const tabs = createResource({ url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_fields_layout', cache: ['QuickEntry', 'Address'], params: { doctype: 'Address', type: 'Quick Entry' }, diff --git a/frontend/src/components/Modals/ContactModal.vue b/frontend/src/components/Modals/ContactModal.vue index c7993e7e..8487985a 100644 --- a/frontend/src/components/Modals/ContactModal.vue +++ b/frontend/src/components/Modals/ContactModal.vue @@ -59,7 +59,7 @@ @@ -245,7 +245,7 @@ const detailFields = computed(() => { return details.filter((detail) => detail.value) }) -const sections = createResource({ +const tabs = createResource({ url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_fields_layout', cache: ['QuickEntry', 'Contact'], params: { doctype: 'Contact', type: 'Quick Entry' }, @@ -253,7 +253,7 @@ const sections = createResource({ }) const filteredSections = computed(() => { - let allSections = sections.data || [] + let allSections = tabs.data?.[0]?.sections || [] if (!allSections.length) return [] allSections.forEach((s) => { @@ -276,7 +276,7 @@ const filteredSections = computed(() => { }) }) - return allSections + return [{ no_tabs: true, sections: allSections }] }) const dirty = computed(() => { diff --git a/frontend/src/components/Modals/DataFieldsModal.vue b/frontend/src/components/Modals/DataFieldsModal.vue index dd763d89..17b56b48 100644 --- a/frontend/src/components/Modals/DataFieldsModal.vue +++ b/frontend/src/components/Modals/DataFieldsModal.vue @@ -29,13 +29,13 @@ size="sm" /> -
+
- +
@@ -77,20 +77,20 @@ function getParams() { return { doctype: _doctype.value, type: 'Data Fields' } } -const sections = createResource({ +const tabs = createResource({ url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_fields_layout', cache: ['DataFieldsModal', _doctype.value], params: getParams(), onSuccess(data) { - sections.originalData = JSON.parse(JSON.stringify(data)) + tabs.originalData = JSON.parse(JSON.stringify(data)) }, }) watch( - () => sections?.data, + () => tabs?.data, () => { dirty.value = - JSON.stringify(sections?.data) !== JSON.stringify(sections?.originalData) + JSON.stringify(tabs?.data) !== JSON.stringify(tabs?.originalData) }, { deep: true }, ) @@ -99,18 +99,21 @@ onMounted(() => useDebounceFn(reload, 100)()) function reload() { nextTick(() => { - sections.params = getParams() - sections.reload() + tabs.params = getParams() + tabs.reload() }) } function saveChanges() { - let _sections = JSON.parse(JSON.stringify(sections.data)) - _sections.forEach((section) => { - if (!section.fields) return - section.fields = section.fields.map( - (field) => field.fieldname || field.name, - ) + let _tabs = JSON.parse(JSON.stringify(tabs.data)) + _tabs.forEach((tab) => { + if (!tab.sections) return + tab.sections.forEach((section) => { + if (!section.fields) return + section.fields = section.fields.map( + (field) => field.fieldname || field.name, + ) + }) }) loading.value = true call( @@ -118,7 +121,7 @@ function saveChanges() { { doctype: _doctype.value, type: 'Data Fields', - layout: JSON.stringify(_sections), + layout: JSON.stringify(_tabs), }, ).then(() => { loading.value = false diff --git a/frontend/src/components/Modals/DealModal.vue b/frontend/src/components/Modals/DealModal.vue index da8c9c5a..2ccadec6 100644 --- a/frontend/src/components/Modals/DealModal.vue +++ b/frontend/src/components/Modals/DealModal.vue @@ -36,7 +36,7 @@
@@ -100,28 +100,30 @@ const isDealCreating = ref(false) const chooseExistingContact = ref(false) const chooseExistingOrganization = ref(false) -const sections = createResource({ +const tabs = createResource({ url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_fields_layout', cache: ['QuickEntry', 'CRM Deal'], params: { doctype: 'CRM Deal', type: 'Quick Entry' }, auto: true, - transform: (data) => { - return data.forEach((section) => { - section.fields.forEach((field) => { - if (field.name == 'status') { - field.type = 'Select' - field.options = dealStatuses.value - field.prefix = getDealStatus(deal.status).iconColorClass - } else if (field.name == 'deal_owner') { - field.type = 'User' - } + transform: (_tabs) => { + return _tabs.forEach((tab) => { + tab.sections.forEach((section) => { + section.fields.forEach((field) => { + if (field.name == 'status') { + field.type = 'Select' + field.options = dealStatuses.value + field.prefix = getDealStatus(deal.status).iconColorClass + } else if (field.name == 'deal_owner') { + field.type = 'User' + } + }) }) }) }, }) const filteredSections = computed(() => { - let allSections = sections.data || [] + let allSections = tabs.data?.[0]?.sections || [] if (!allSections.length) return [] let _filteredSections = [] @@ -159,7 +161,7 @@ const filteredSections = computed(() => { } }) - return _filteredSections + return [{ no_tabs: true, sections: _filteredSections }] }) const dealStatuses = computed(() => { diff --git a/frontend/src/components/Modals/LeadModal.vue b/frontend/src/components/Modals/LeadModal.vue index 958b04fd..401afeac 100644 --- a/frontend/src/components/Modals/LeadModal.vue +++ b/frontend/src/components/Modals/LeadModal.vue @@ -23,7 +23,7 @@
- +
@@ -63,21 +63,23 @@ const router = useRouter() const error = ref(null) const isLeadCreating = ref(false) -const sections = createResource({ +const tabs = createResource({ url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_fields_layout', cache: ['QuickEntry', 'CRM Lead'], params: { doctype: 'CRM Lead', type: 'Quick Entry' }, auto: true, - transform: (data) => { - return data.forEach((section) => { - section.fields.forEach((field) => { - if (field.name == 'status') { - field.type = 'Select' - field.options = leadStatuses.value - field.prefix = getLeadStatus(lead.status).iconColorClass - } else if (field.name == 'lead_owner') { - field.type = 'User' - } + transform: (_tabs) => { + return _tabs.forEach((tab) => { + tab.sections.forEach((section) => { + section.fields.forEach((field) => { + if (field.name == 'status') { + field.type = 'Select' + field.options = leadStatuses.value + field.prefix = getLeadStatus(lead.status).iconColorClass + } else if (field.name == 'lead_owner') { + field.type = 'User' + } + }) }) }) }, diff --git a/frontend/src/components/Modals/OrganizationModal.vue b/frontend/src/components/Modals/OrganizationModal.vue index 03bec690..e5f1d589 100644 --- a/frontend/src/components/Modals/OrganizationModal.vue +++ b/frontend/src/components/Modals/OrganizationModal.vue @@ -37,7 +37,7 @@ @@ -241,7 +241,7 @@ const fields = computed(() => { return details.filter((field) => field.value) }) -const sections = createResource({ +const tabs = createResource({ url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_fields_layout', cache: ['QuickEntry', 'CRM Organization'], params: { doctype: 'CRM Organization', type: 'Quick Entry' }, @@ -249,7 +249,7 @@ const sections = createResource({ }) const filteredSections = computed(() => { - let allSections = sections.data || [] + let allSections = tabs.data?.[0]?.sections || [] if (!allSections.length) return [] allSections.forEach((s) => { @@ -272,7 +272,7 @@ const filteredSections = computed(() => { }) }) - return allSections + return [{ no_tabs: true, sections: allSections }] }) watch( diff --git a/frontend/src/components/Modals/QuickEntryModal.vue b/frontend/src/components/Modals/QuickEntryModal.vue index 903637a2..8859d386 100644 --- a/frontend/src/components/Modals/QuickEntryModal.vue +++ b/frontend/src/components/Modals/QuickEntryModal.vue @@ -35,13 +35,13 @@ size="sm" /> -
+
- +
@@ -83,20 +83,20 @@ function getParams() { return { doctype: _doctype.value, type: 'Quick Entry' } } -const sections = createResource({ +const tabs = createResource({ url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_fields_layout', cache: ['QuickEntryModal', _doctype.value], params: getParams(), onSuccess(data) { - sections.originalData = JSON.parse(JSON.stringify(data)) + tabs.originalData = JSON.parse(JSON.stringify(data)) }, }) watch( - () => sections?.data, + () => tabs?.data, () => { dirty.value = - JSON.stringify(sections?.data) !== JSON.stringify(sections?.originalData) + JSON.stringify(tabs?.data) !== JSON.stringify(tabs?.originalData) }, { deep: true }, ) @@ -105,18 +105,21 @@ onMounted(() => useDebounceFn(reload, 100)()) function reload() { nextTick(() => { - sections.params = getParams() - sections.reload() + tabs.params = getParams() + tabs.reload() }) } function saveChanges() { - let _sections = JSON.parse(JSON.stringify(sections.data)) - _sections.forEach((section) => { - if (!section.fields) return - section.fields = section.fields.map( - (field) => field.fieldname || field.name, - ) + let _tabs = JSON.parse(JSON.stringify(tabs.data)) + _tabs.forEach((tab) => { + if (!tab.sections) return + tab.sections.forEach((section) => { + if (!section.fields) return + section.fields = section.fields.map( + (field) => field.fieldname || field.name, + ) + }) }) loading.value = true call( @@ -124,7 +127,7 @@ function saveChanges() { { doctype: _doctype.value, type: 'Quick Entry', - layout: JSON.stringify(_sections), + layout: JSON.stringify(_tabs), }, ).then(() => { loading.value = false diff --git a/frontend/src/components/Modals/SidePanelModal.vue b/frontend/src/components/Modals/SidePanelModal.vue index 06468737..6bd24b18 100644 --- a/frontend/src/components/Modals/SidePanelModal.vue +++ b/frontend/src/components/Modals/SidePanelModal.vue @@ -29,18 +29,20 @@ size="sm" /> -
+
sections?.data, + () => tabs?.data, () => { dirty.value = - JSON.stringify(sections?.data) !== JSON.stringify(sections?.originalData) + JSON.stringify(tabs?.data) !== JSON.stringify(tabs?.originalData) }, { deep: true }, ) @@ -128,18 +130,20 @@ onMounted(() => useDebounceFn(reload, 100)()) function reload() { nextTick(() => { - sections.params = getParams() - sections.reload() + tabs.params = getParams() + tabs.reload() }) } function saveChanges() { - let _sections = JSON.parse(JSON.stringify(sections.data)) - _sections.forEach((section) => { - if (!section.fields) return - section.fields = section.fields - .map((field) => field.fieldname || field.name) - .filter(Boolean) + let _tabs = JSON.parse(JSON.stringify(tabs.data)) + _tabs.forEach((tab) => { + tab.sections.forEach((section) => { + if (!section.fields) return + section.fields = section.fields + .map((field) => field.fieldname || field.name) + .filter(Boolean) + }) }) loading.value = true call( @@ -147,7 +151,7 @@ function saveChanges() { { doctype: _doctype.value, type: 'Side Panel', - layout: JSON.stringify(_sections), + layout: JSON.stringify(_tabs), }, ).then(() => { loading.value = false diff --git a/frontend/src/components/Settings/SettingsPage.vue b/frontend/src/components/Settings/SettingsPage.vue index d3e60eb0..e8d6c876 100644 --- a/frontend/src/components/Settings/SettingsPage.vue +++ b/frontend/src/components/Settings/SettingsPage.vue @@ -12,11 +12,7 @@ />
- +
@@ -98,7 +94,7 @@ const data = createDocumentResource({ }, }) -const sections = computed(() => { +const tabs = computed(() => { if (!fields.data) return [] let _sections = [] let fieldsData = fields.data @@ -138,7 +134,7 @@ const sections = computed(() => { } }) - return _sections + return [{ no_tabs: true, sections: _sections }] }) function update() { @@ -148,7 +144,8 @@ function update() { } function validateMandatoryFields() { - for (let section of sections.value) { + if (!tabs.value) return false + for (let section of tabs.value[0].sections) { for (let field of section.fields) { if ( (field.mandatory ||