diff --git a/crm/api/doc.py b/crm/api/doc.py index 9a89fbd6..fd5f6172 100644 --- a/crm/api/doc.py +++ b/crm/api/doc.py @@ -1,5 +1,6 @@ import frappe from frappe.model.document import get_controller +from frappe.model import no_value_fields from pypika import Criterion @@ -50,8 +51,11 @@ def get_filterable_fields(doctype: str): @frappe.whitelist() def get_list_data(doctype: str, filters: dict, order_by: str): - columns = [] - rows = [] + columns = [ + {"label": "Name", "type": "Data", "key": "name", "width": "16rem"}, + {"label": "Last Modified", "type": "Datetime", "key": "modified", "width": "8rem"}, + ] + rows = ["name"] if frappe.db.exists("CRM List View Settings", doctype): list_view_settings = frappe.get_doc("CRM List View Settings", doctype) @@ -77,22 +81,30 @@ def get_list_data(doctype: str, filters: dict, order_by: str): page_length=20, ) or [] - not_allowed_fieldtypes = [ - "Section Break", - "Column Break", - "Tab Break", + fields = frappe.get_meta(doctype).fields + fields = [field for field in fields if field.fieldtype not in no_value_fields] + fields = [ + { + "label": field.label, + "type": field.fieldtype, + "value": field.fieldname, + "options": field.options, + } + for field in fields + if field.label and field.fieldname ] - fields = frappe.get_meta(doctype).fields - fields = [field for field in fields if field.fieldtype not in not_allowed_fieldtypes] - fields = [{"label": field.label, "value": field.fieldname} for field in fields if field.label and field.fieldname] - std_fields = [ - {'label': 'Name', 'value': 'name'}, - {'label': 'Created On', 'value': 'creation'}, - {'label': 'Last Modified', 'value': 'modified'}, - {'label': 'Modified By', 'value': 'modified_by'}, - {'label': 'Owner', 'value': 'owner'}, + {"label": "Name", "type": "Data", "value": "name"}, + {"label": "Created On", "type": "Datetime", "value": "creation"}, + {"label": "Last Modified", "type": "Datetime", "value": "modified"}, + { + "label": "Modified By", + "type": "Link", + "value": "modified_by", + "options": "User", + }, + {"label": "Owner", "type": "Link", "value": "owner", "options": "User"}, ] for field in std_fields: diff --git a/crm/fcrm/doctype/crm_call_log/crm_call_log.py b/crm/fcrm/doctype/crm_call_log/crm_call_log.py index 27549c3b..8e0c1841 100644 --- a/crm/fcrm/doctype/crm_call_log/crm_call_log.py +++ b/crm/fcrm/doctype/crm_call_log/crm_call_log.py @@ -6,7 +6,86 @@ from frappe.model.document import Document class CRMCallLog(Document): - pass + @staticmethod + def sort_options(): + return [ + { "label": 'Created', "value": 'creation' }, + { "label": 'Modified', "value": 'modified' }, + { "label": 'Status', "value": 'status' }, + { "label": 'Type', "value": 'type' }, + { "label": 'Duration', "value": 'duration' }, + { "label": 'From', "value": 'from' }, + { "label": 'To', "value": 'to' }, + { "label": 'Caller', "value": 'caller' }, + { "label": 'Receiver', "value": 'receiver' }, + ] + + @staticmethod + def default_list_data(): + columns = [ + { + 'label': 'From', + 'type': 'Link', + 'key': 'caller', + 'options': 'User', + 'width': '9rem', + }, + { + 'label': 'To', + 'type': 'Link', + 'key': 'receiver', + 'options': 'User', + 'width': '9rem', + }, + { + 'label': 'Type', + 'type': 'Select', + 'key': 'type', + 'width': '9rem', + }, + { + 'label': 'Status', + 'type': 'Select', + 'key': 'status', + 'width': '9rem', + }, + { + 'label': 'Duration', + 'type': 'Duration', + 'key': 'duration', + 'width': '6rem', + }, + { + 'label': 'From (number)', + 'type': 'Data', + 'key': 'from', + 'width': '9rem', + }, + { + 'label': 'To (number)', + 'type': 'Data', + 'key': 'to', + 'width': '9rem', + }, + { + 'label': 'Created on', + 'type': 'Datetime', + 'key': 'creation', + 'width': '8rem', + }, + ] + rows = [ + "name", + "caller", + "receiver", + "type", + "status", + "duration", + "from", + "to", + "creation", + ] + return {'columns': columns, 'rows': rows} @frappe.whitelist() def get_call_log(name): diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.py b/crm/fcrm/doctype/crm_deal/crm_deal.py index 05280a14..7774eb3c 100644 --- a/crm/fcrm/doctype/crm_deal/crm_deal.py +++ b/crm/fcrm/doctype/crm_deal/crm_deal.py @@ -62,36 +62,45 @@ class CRMDeal(Document): columns = [ { 'label': 'Organization', + 'type': 'Link', 'key': 'organization', + 'options': 'CRM Organization', 'width': '11rem', }, { 'label': 'Amount', + 'type': 'Currency', 'key': 'annual_revenue', 'width': '9rem', }, { 'label': 'Status', + 'type': 'Select', 'key': 'status', 'width': '10rem', }, { 'label': 'Email', + 'type': 'Data', 'key': 'email', 'width': '12rem', }, { 'label': 'Mobile no', + 'type': 'Data', 'key': 'mobile_no', 'width': '11rem', }, { 'label': 'Deal owner', + 'type': 'Link', 'key': 'deal_owner', + 'options': 'User', 'width': '10rem', }, { 'label': 'Last modified', + 'type': 'Datetime', 'key': 'modified', 'width': '8rem', }, diff --git a/crm/fcrm/doctype/crm_lead/crm_lead.py b/crm/fcrm/doctype/crm_lead/crm_lead.py index 53cc570e..98c8e4c4 100644 --- a/crm/fcrm/doctype/crm_lead/crm_lead.py +++ b/crm/fcrm/doctype/crm_lead/crm_lead.py @@ -141,36 +141,45 @@ class CRMLead(Document): columns = [ { 'label': 'Name', + 'type': 'Data', 'key': 'lead_name', 'width': '12rem', }, { 'label': 'Organization', + 'type': 'Link', 'key': 'organization', + 'options': 'CRM Organization', 'width': '10rem', }, { 'label': 'Status', + 'type': 'Select', 'key': 'status', 'width': '8rem', }, { 'label': 'Email', + 'type': 'Data', 'key': 'email', 'width': '12rem', }, { 'label': 'Mobile no', + 'type': 'Data', 'key': 'mobile_no', 'width': '11rem', }, { 'label': 'Lead owner', + 'type': 'Link', 'key': 'lead_owner', + 'options': 'User', 'width': '10rem', }, { 'label': 'Last modified', + 'type': 'Datetime', 'key': 'modified', 'width': '8rem', }, diff --git a/crm/fcrm/doctype/crm_list_view_settings/crm_list_view_settings.py b/crm/fcrm/doctype/crm_list_view_settings/crm_list_view_settings.py index fe9ff687..ea591728 100644 --- a/crm/fcrm/doctype/crm_list_view_settings/crm_list_view_settings.py +++ b/crm/fcrm/doctype/crm_list_view_settings/crm_list_view_settings.py @@ -2,7 +2,7 @@ # For license information, please see license.txt import json import frappe -from frappe.model.document import Document +from frappe.model.document import Document, get_controller class CRMListViewSettings(Document): @@ -11,19 +11,35 @@ class CRMListViewSettings(Document): @frappe.whitelist() def update(doctype, columns, rows): + default_rows = sync_default_list_rows(doctype) + + if default_rows: + rows = rows + default_rows + + rows = remove_duplicates(rows) + if not frappe.db.exists("CRM List View Settings", doctype): # create new CRM List View Settings doc = frappe.new_doc("CRM List View Settings") doc.name = doctype doc.columns = json.dumps(columns) - doc.rows = json.dumps(remove_duplicates(rows)) + doc.rows = json.dumps(rows) doc.insert() else: # update existing CRM List View Settings doc = frappe.get_doc("CRM List View Settings", doctype) doc.columns = json.dumps(columns) - doc.rows = json.dumps(remove_duplicates(rows)) + doc.rows = json.dumps(rows) doc.save() def remove_duplicates(l): - return list(dict.fromkeys(l)) \ No newline at end of file + return list(dict.fromkeys(l)) + +def sync_default_list_rows(doctype): + list = get_controller(doctype) + rows = [] + + if hasattr(list, "default_list_data"): + rows = list.default_list_data().get("rows") + + return rows diff --git a/crm/fcrm/doctype/crm_organization/crm_organization.py b/crm/fcrm/doctype/crm_organization/crm_organization.py index 5933eabf..1cdae09c 100644 --- a/crm/fcrm/doctype/crm_organization/crm_organization.py +++ b/crm/fcrm/doctype/crm_organization/crm_organization.py @@ -6,4 +6,59 @@ from frappe.model.document import Document class CRMOrganization(Document): - pass + @staticmethod + def sort_options(): + return [ + { "label": 'Created', "value": 'creation' }, + { "label": 'Modified', "value": 'modified' }, + { "label": 'Name', "value": 'name' }, + { "label": 'Website', "value": 'website' }, + { "label": 'Amount', "value": 'annual_revenue' }, + { "label": 'Industry', "value": 'industry' }, + ] + + @staticmethod + def default_list_data(): + columns = [ + { + 'label': 'Organization', + 'type': 'Data', + 'key': 'organization_name', + 'width': '16rem', + }, + { + 'label': 'Website', + 'type': 'Data', + 'key': 'website', + 'width': '14rem', + }, + { + 'label': 'Industry', + 'type': 'Link', + 'key': 'industry', + 'options': 'CRM Industry', + 'width': '14rem', + }, + { + 'label': 'Annual Revenue', + 'type': 'Currency', + 'key': 'annual_revenue', + 'width': '14rem', + }, + { + 'label': 'Last modified', + 'type': 'Datetime', + 'key': 'modified', + 'width': '8rem', + }, + ] + rows = [ + "name", + "organization_name", + "organization_logo", + "website", + "industry", + "annual_revenue", + "modified", + ] + return {'columns': columns, 'rows': rows} diff --git a/crm/hooks.py b/crm/hooks.py index fdaef00f..abfc9d0e 100644 --- a/crm/hooks.py +++ b/crm/hooks.py @@ -117,9 +117,9 @@ website_route_rules = [ # --------------- # Override standard doctype classes -# override_doctype_class = { -# "ToDo": "custom_app.overrides.CustomToDo" -# } +override_doctype_class = { + "Contact": "crm.overrides.contact.CustomContact" +} # Document Events # --------------- diff --git a/crm/overrides/contact.py b/crm/overrides/contact.py new file mode 100644 index 00000000..64c2cc8b --- /dev/null +++ b/crm/overrides/contact.py @@ -0,0 +1,63 @@ +# import frappe +from frappe import _ +from frappe.contacts.doctype.contact.contact import Contact + + +class CustomContact(Contact): + @staticmethod + def sort_options(): + return [ + { "label": 'Created', "value": 'creation' }, + { "label": 'Modified', "value": 'modified' }, + { "label": 'Organization', "value": 'company_name' }, + { "label": 'Full Name', "value": 'full_name' }, + { "label": 'First Name', "value": 'first_name' }, + { "label": 'Last Name', "value": 'last_name' }, + { "label": 'Email', "value": 'email' }, + { "label": 'Mobile no', "value": 'mobile_no' }, + ] + + @staticmethod + def default_list_data(): + columns = [ + { + 'label': 'Name', + 'type': 'Data', + 'key': 'full_name', + 'width': '17rem', + }, + { + 'label': 'Email', + 'type': 'Data', + 'key': 'email_id', + 'width': '12rem', + }, + { + 'label': 'Phone', + 'type': 'Data', + 'key': 'mobile_no', + 'width': '12rem', + }, + { + 'label': 'Organization', + 'type': 'Data', + 'key': 'company_name', + 'width': '12rem', + }, + { + 'label': 'Last modified', + 'type': 'Datetime', + 'key': 'modified', + 'width': '8rem', + }, + ] + rows = [ + "name", + "full_name", + "company_name", + "email_id", + "mobile_no", + "modified", + "image", + ] + return {'columns': columns, 'rows': rows} diff --git a/frontend/src/components/ListViews/CallLogsListView.vue b/frontend/src/components/ListViews/CallLogsListView.vue new file mode 100644 index 00000000..4bbf2441 --- /dev/null +++ b/frontend/src/components/ListViews/CallLogsListView.vue @@ -0,0 +1,96 @@ + + diff --git a/frontend/src/components/ListViews/ContactsListView.vue b/frontend/src/components/ListViews/ContactsListView.vue index 9540cc05..e73701bd 100644 --- a/frontend/src/components/ListViews/ContactsListView.vue +++ b/frontend/src/components/ListViews/ContactsListView.vue @@ -3,7 +3,10 @@ :columns="columns" :rows="rows" :options="{ - getRowRoute: (row) => ({ name: 'Contact', params: { contactId: row.name } }), + getRowRoute: (row) => ({ + name: 'Contact', + params: { contactId: row.name }, + }), selectable: options.selectable, }" row-key="name" @@ -41,9 +44,20 @@ -
+
{{ item.timeAgo }}
+
+ +
@@ -60,6 +74,7 @@ import { ListRow, ListSelectBanner, ListRowItem, + FormControl, } from 'frappe-ui' const props = defineProps({ diff --git a/frontend/src/components/ListViews/DealsListView.vue b/frontend/src/components/ListViews/DealsListView.vue index 93d7cf41..1b5618b4 100644 --- a/frontend/src/components/ListViews/DealsListView.vue +++ b/frontend/src/components/ListViews/DealsListView.vue @@ -47,6 +47,14 @@
{{ item.timeAgo }}
+
+ +
@@ -65,6 +73,7 @@ import { ListRow, ListRowItem, ListSelectBanner, + FormControl, } from 'frappe-ui' const props = defineProps({ diff --git a/frontend/src/components/ListViews/LeadsListView.vue b/frontend/src/components/ListViews/LeadsListView.vue index e4eb06e1..83601dde 100644 --- a/frontend/src/components/ListViews/LeadsListView.vue +++ b/frontend/src/components/ListViews/LeadsListView.vue @@ -56,6 +56,14 @@
{{ item.timeAgo }}
+
+ +
@@ -74,6 +82,7 @@ import { ListRow, ListSelectBanner, ListRowItem, + FormControl, } from 'frappe-ui' const props = defineProps({ diff --git a/frontend/src/components/ListViews/OrganizationsListView.vue b/frontend/src/components/ListViews/OrganizationsListView.vue index 82e824e2..488438e6 100644 --- a/frontend/src/components/ListViews/OrganizationsListView.vue +++ b/frontend/src/components/ListViews/OrganizationsListView.vue @@ -22,7 +22,7 @@ > -
+
{{ item.timeAgo }}
+
+ +
@@ -50,6 +58,7 @@ import { ListRow, ListSelectBanner, ListRowItem, + FormControl, } from 'frappe-ui' const props = defineProps({ diff --git a/frontend/src/components/ViewSettings.vue b/frontend/src/components/ViewSettings.vue index 6334a350..a678be04 100644 --- a/frontend/src/components/ViewSettings.vue +++ b/frontend/src/components/ViewSettings.vue @@ -163,6 +163,7 @@ const fields = computed(() => { async function addColumn(c) { let _column = { label: c.label, + type: c.type, key: c.value, width: '10rem', } diff --git a/frontend/src/pages/CallLogs.vue b/frontend/src/pages/CallLogs.vue index cfc2dec7..ee430ce0 100644 --- a/frontend/src/pages/CallLogs.vue +++ b/frontend/src/pages/CallLogs.vue @@ -6,74 +6,26 @@
- - + +
-
- - - - - - -
- {{ item.timeAgo }} -
-
- -
-
-
-
- -
+ :columns="callLogs.data.columns" + /> diff --git a/frontend/src/pages/Contacts.vue b/frontend/src/pages/Contacts.vue index f94464f1..fca3ae82 100644 --- a/frontend/src/pages/Contacts.vue +++ b/frontend/src/pages/Contacts.vue @@ -30,10 +30,14 @@
-
- + @@ -43,21 +47,25 @@ import ContactModal from '@/components/Modals/ContactModal.vue' import ContactsListView from '@/components/ListViews/ContactsListView.vue' import SortBy from '@/components/SortBy.vue' import Filter from '@/components/Filter.vue' -import { FeatherIcon, Breadcrumbs, Dropdown } from 'frappe-ui' -import { contactsStore } from '@/stores/contacts.js' +import ViewSettings from '@/components/ViewSettings.vue' +import { FeatherIcon, Breadcrumbs, Dropdown, createResource } from 'frappe-ui' import { organizationsStore } from '@/stores/organizations.js' +import { useOrderBy } from '@/composables/orderby' +import { useFilter } from '@/composables/filter' import { dateFormat, dateTooltipFormat, timeAgo } from '@/utils' -import { ref, computed, onMounted } from 'vue' +import { useDebounceFn } from '@vueuse/core' +import { ref, computed, watch } from 'vue' import { useRoute } from 'vue-router' -const { contacts } = contactsStore() const { getOrganization } = organizationsStore() +const { get: getOrderBy } = useOrderBy() +const { getArgs, storage } = useFilter() const route = useRoute() const showContactModal = ref(false) const currentContact = computed(() => { - return contacts.data.find( + return contacts.data?.data?.find( (contact) => contact.name === route.params.contactId ) }) @@ -80,6 +88,72 @@ const currentView = ref({ icon: 'list', }) +function getParams() { + const filters = getArgs() || {} + const order_by = getOrderBy() || 'modified desc' + + return { + doctype: 'Contact', + filters: filters, + order_by: order_by, + } +} + +const contacts = createResource({ + url: 'crm.api.doc.get_list_data', + params: getParams(), + auto: true, +}) + +watch( + () => getOrderBy(), + (value, old_value) => { + if (!value && !old_value) return + contacts.params = getParams() + contacts.reload() + }, + { immediate: true } +) + +watch( + storage, + useDebounceFn((value, old_value) => { + if (JSON.stringify([...value]) === JSON.stringify([...old_value])) return + contacts.params = getParams() + contacts.reload() + }, 300), + { deep: true } +) + +const rows = computed(() => { + if (!contacts.data?.data) return [] + return contacts.data.data.map((contact) => { + let _rows = {} + contacts.data.rows.forEach((row) => { + _rows[row] = contact[row] + + if (row == 'full_name') { + _rows[row] = { + label: contact.full_name, + image_label: contact.full_name, + image: contact.image, + } + } else if (row == 'company_name') { + _rows[row] = { + label: contact.company_name, + logo: getOrganization(contact.company_name)?.organization_logo, + } + } else if (['modified', 'creation'].includes(row)) { + _rows[row] = { + label: dateFormat(contact[row], dateTooltipFormat), + timeAgo: timeAgo(contact[row]), + } + } + }) + return _rows + }) +}) + const viewsDropdownOptions = [ { label: 'List', @@ -122,55 +196,4 @@ const viewsDropdownOptions = [ }, }, ] - -const rows = computed(() => { - return contacts.data.map((contact) => { - return { - name: contact.name, - full_name: { - label: contact.full_name, - image_label: contact.full_name, - image: contact.image, - }, - email: contact.email_id, - mobile_no: contact.mobile_no, - company_name: { - label: contact.company_name, - logo: getOrganization(contact.company_name)?.organization_logo, - }, - modified: { - label: dateFormat(contact.modified, dateTooltipFormat), - timeAgo: timeAgo(contact.modified), - }, - } - }) -}) - -const columns = [ - { - label: 'Name', - key: 'full_name', - width: '17rem', - }, - { - label: 'Email', - key: 'email', - width: '12rem', - }, - { - label: 'Phone', - key: 'mobile_no', - width: '12rem', - }, - { - label: 'Organization', - key: 'company_name', - width: '12rem', - }, - { - label: 'Last modified', - key: 'modified', - width: '8rem', - }, -] diff --git a/frontend/src/pages/Deals.vue b/frontend/src/pages/Deals.vue index ef095b2a..a5b7629d 100644 --- a/frontend/src/pages/Deals.vue +++ b/frontend/src/pages/Deals.vue @@ -156,15 +156,10 @@ const rows = computed(() => { label: deal.deal_owner && getUser(deal.deal_owner).full_name, ...(deal.deal_owner && getUser(deal.deal_owner)), } - } else if (row == 'modified') { + } else if (['modified', 'creation'].includes(row)) { _rows[row] = { - label: dateFormat(deal.modified, dateTooltipFormat), - timeAgo: timeAgo(deal.modified), - } - } else if (row == 'creation') { - _rows[row] = { - label: dateFormat(deal.creation, dateTooltipFormat), - timeAgo: timeAgo(deal.creation), + label: dateFormat(deal[row], dateTooltipFormat), + timeAgo: timeAgo(deal[row]), } } }) diff --git a/frontend/src/pages/Leads.vue b/frontend/src/pages/Leads.vue index a721e964..d3e62503 100644 --- a/frontend/src/pages/Leads.vue +++ b/frontend/src/pages/Leads.vue @@ -137,7 +137,6 @@ const rows = computed(() => { if (!leads.data?.data) return [] return leads.data.data.map((lead) => { let _rows = {} - leads.data.rows.forEach((row) => { _rows[row] = lead[row] @@ -162,15 +161,10 @@ const rows = computed(() => { label: lead.lead_owner && getUser(lead.lead_owner).full_name, ...(lead.lead_owner && getUser(lead.lead_owner)), } - } else if (row == 'modified') { + } else if (['modified', 'creation'].includes(row)) { _rows[row] = { - label: dateFormat(lead.modified, dateTooltipFormat), - timeAgo: timeAgo(lead.modified), - } - } else if (row == 'creation') { - _rows[row] = { - label: dateFormat(lead.creation, dateTooltipFormat), - timeAgo: timeAgo(lead.creation), + label: dateFormat(lead[row], dateTooltipFormat), + timeAgo: timeAgo(lead[row]), } } }) diff --git a/frontend/src/pages/Organizations.vue b/frontend/src/pages/Organizations.vue index e9cd1df8..49433a06 100644 --- a/frontend/src/pages/Organizations.vue +++ b/frontend/src/pages/Organizations.vue @@ -34,14 +34,15 @@
-
- - +