Merge pull request #32 from shariquerik/custom-list-columns
This commit is contained in:
commit
190ea095e3
@ -12,6 +12,7 @@ def sort_options(doctype: str):
|
||||
|
||||
return c.sort_options()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_filterable_fields(doctype: str):
|
||||
DocField = frappe.qb.DocType("DocField")
|
||||
@ -46,6 +47,63 @@ def get_filterable_fields(doctype: str):
|
||||
res.extend(from_doc_fields)
|
||||
return res
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_list_data(doctype: str, filters: dict, order_by: str):
|
||||
columns = []
|
||||
rows = []
|
||||
|
||||
if frappe.db.exists("CRM List View Settings", doctype):
|
||||
list_view_settings = frappe.get_doc("CRM List View Settings", doctype)
|
||||
columns = frappe.parse_json(list_view_settings.columns)
|
||||
rows = frappe.parse_json(list_view_settings.rows)
|
||||
else:
|
||||
list = get_controller(doctype)
|
||||
|
||||
if hasattr(list, "default_list_data"):
|
||||
columns = list.default_list_data().get("columns")
|
||||
rows = list.default_list_data().get("rows")
|
||||
|
||||
# check if rows has all keys from columns if not add them
|
||||
for column in columns:
|
||||
if column.get("key") not in rows:
|
||||
rows.append(column.get("key"))
|
||||
|
||||
data = frappe.get_all(
|
||||
doctype,
|
||||
fields=rows,
|
||||
filters=filters,
|
||||
order_by=order_by,
|
||||
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 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'},
|
||||
]
|
||||
|
||||
for field in std_fields:
|
||||
if field.get('value') not in rows:
|
||||
rows.append(field.get('value'))
|
||||
if field not in fields:
|
||||
fields.append(field)
|
||||
|
||||
return {'data': data, 'columns': columns, 'rows': rows, 'fields': fields}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_doctype_fields(doctype):
|
||||
not_allowed_fieldtypes = [
|
||||
@ -87,6 +145,7 @@ def get_doctype_fields(doctype):
|
||||
|
||||
return all_fields
|
||||
|
||||
|
||||
def get_field_obj(field):
|
||||
obj = {
|
||||
"label": field.label,
|
||||
@ -107,6 +166,7 @@ def get_field_obj(field):
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
def get_type(field):
|
||||
if field.fieldtype == "Data" and field.options == "Phone":
|
||||
return "phone"
|
||||
@ -120,4 +180,4 @@ def get_type(field):
|
||||
return "textarea"
|
||||
elif field.read_only:
|
||||
return "read_only"
|
||||
return field.fieldtype.lower()
|
||||
return field.fieldtype.lower()
|
||||
|
||||
@ -57,6 +57,57 @@ class CRMDeal(Document):
|
||||
{ "label": 'Mobile no', "value": 'mobile_no' },
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def default_list_data():
|
||||
columns = [
|
||||
{
|
||||
'label': 'Organization',
|
||||
'key': 'organization',
|
||||
'width': '11rem',
|
||||
},
|
||||
{
|
||||
'label': 'Amount',
|
||||
'key': 'annual_revenue',
|
||||
'width': '9rem',
|
||||
},
|
||||
{
|
||||
'label': 'Status',
|
||||
'key': 'status',
|
||||
'width': '10rem',
|
||||
},
|
||||
{
|
||||
'label': 'Email',
|
||||
'key': 'email',
|
||||
'width': '12rem',
|
||||
},
|
||||
{
|
||||
'label': 'Mobile no',
|
||||
'key': 'mobile_no',
|
||||
'width': '11rem',
|
||||
},
|
||||
{
|
||||
'label': 'Deal owner',
|
||||
'key': 'deal_owner',
|
||||
'width': '10rem',
|
||||
},
|
||||
{
|
||||
'label': 'Last modified',
|
||||
'key': 'modified',
|
||||
'width': '8rem',
|
||||
},
|
||||
]
|
||||
rows = [
|
||||
"name",
|
||||
"organization",
|
||||
"annual_revenue",
|
||||
"status",
|
||||
"email",
|
||||
"mobile_no",
|
||||
"deal_owner",
|
||||
"modified",
|
||||
]
|
||||
return {'columns': columns, 'rows': rows}
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_contact(deal, contact):
|
||||
if not frappe.has_permission("CRM Deal", "write", deal):
|
||||
|
||||
@ -136,6 +136,59 @@ class CRMLead(Document):
|
||||
{ "label": 'Mobile no', "value": 'mobile_no' },
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def default_list_data():
|
||||
columns = [
|
||||
{
|
||||
'label': 'Name',
|
||||
'key': 'lead_name',
|
||||
'width': '12rem',
|
||||
},
|
||||
{
|
||||
'label': 'Organization',
|
||||
'key': 'organization',
|
||||
'width': '10rem',
|
||||
},
|
||||
{
|
||||
'label': 'Status',
|
||||
'key': 'status',
|
||||
'width': '8rem',
|
||||
},
|
||||
{
|
||||
'label': 'Email',
|
||||
'key': 'email',
|
||||
'width': '12rem',
|
||||
},
|
||||
{
|
||||
'label': 'Mobile no',
|
||||
'key': 'mobile_no',
|
||||
'width': '11rem',
|
||||
},
|
||||
{
|
||||
'label': 'Lead owner',
|
||||
'key': 'lead_owner',
|
||||
'width': '10rem',
|
||||
},
|
||||
{
|
||||
'label': 'Last modified',
|
||||
'key': 'modified',
|
||||
'width': '8rem',
|
||||
},
|
||||
]
|
||||
rows = [
|
||||
"name",
|
||||
"lead_name",
|
||||
"organization",
|
||||
"status",
|
||||
"email",
|
||||
"mobile_no",
|
||||
"lead_owner",
|
||||
"first_name",
|
||||
"modified",
|
||||
"image",
|
||||
]
|
||||
return {'columns': columns, 'rows': rows}
|
||||
|
||||
@frappe.whitelist()
|
||||
def convert_to_deal(lead):
|
||||
if not frappe.has_permission("CRM Lead", "write", lead):
|
||||
|
||||
0
crm/fcrm/doctype/crm_list_view_settings/__init__.py
Normal file
0
crm/fcrm/doctype/crm_list_view_settings/__init__.py
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("CRM List View Settings", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -0,0 +1,58 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "prompt",
|
||||
"creation": "2023-11-27 16:29:10.993403",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"user",
|
||||
"columns",
|
||||
"rows"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "columns",
|
||||
"fieldtype": "Code",
|
||||
"label": "Columns"
|
||||
},
|
||||
{
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"label": "User",
|
||||
"options": "User"
|
||||
},
|
||||
{
|
||||
"fieldname": "rows",
|
||||
"fieldtype": "Code",
|
||||
"label": "Rows"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-28 00:17:42.675332",
|
||||
"modified_by": "Administrator",
|
||||
"module": "FCRM",
|
||||
"name": "CRM List View Settings",
|
||||
"naming_rule": "Set by user",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"read_only": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
import json
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class CRMListViewSettings(Document):
|
||||
pass
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update(doctype, columns, 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.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.save()
|
||||
|
||||
def remove_duplicates(l):
|
||||
return list(dict.fromkeys(l))
|
||||
@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestCRMListViewSettings(FrappeTestCase):
|
||||
pass
|
||||
@ -20,7 +20,8 @@
|
||||
"tailwindcss": "^3.3.3",
|
||||
"vite": "^4.4.9",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.2"
|
||||
"vue-router": "^4.2.2",
|
||||
"vuedraggable": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
|
||||
15
frontend/src/components/Icons/SettingsIcon.vue
Normal file
15
frontend/src/components/Icons/SettingsIcon.vue
Normal file
@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7.48069 2.93167L7.48066 2.93173C7.22113 3.4979 6.81542 3.9782 6.27003 4.29118C5.72492 4.60401 5.10392 4.71317 4.48225 4.65362L4.47831 4.65324C4.38554 4.6436 4.29136 4.65661 4.20417 4.69129C4.11752 4.72575 4.04086 4.78039 3.98066 4.8501C3.49267 5.42823 3.10928 6.08487 2.84733 6.79073C2.8166 6.876 2.80739 6.96674 2.82005 7.05518C2.83278 7.14407 2.86727 7.22846 2.9207 7.30108L2.92304 7.30425L2.92302 7.30426C3.28725 7.80753 3.50281 8.3954 3.50112 9.02032L3.50112 9.02058C3.49912 9.64243 3.28219 10.2276 2.9195 10.7294L2.87923 10.7905C2.79807 10.9301 2.77389 11.0964 2.8317 11.2526L2.83206 11.2536C3.09058 11.9565 3.46969 12.6094 3.95288 13.1838C4.01152 13.2522 4.08662 13.3057 4.1721 13.3394L3.98882 13.8046L4.1721 13.3394C4.25807 13.3733 4.35179 13.3859 4.44503 13.3758L4.45431 13.3747L4.46362 13.3741L4.64024 13.3615L4.64849 13.3609L4.6485 13.3609C5.20037 13.3306 5.7493 13.4566 6.23089 13.725L6.23468 13.7271L6.23467 13.7272C6.77958 14.0371 7.18389 14.516 7.44043 15.0816L7.44206 15.0852L7.44205 15.0852C7.47849 15.1672 7.53486 15.2395 7.60669 15.2955C7.67817 15.3512 7.76294 15.3892 7.85391 15.4056C8.60087 15.5332 9.36556 15.5315 10.1139 15.4003C10.2057 15.3833 10.2919 15.3444 10.3651 15.2872C10.4386 15.2297 10.4964 15.1557 10.5338 15.0722L10.5356 15.0682L10.5356 15.0683C10.7952 14.502 11.2015 14.0214 11.7462 13.7088C12.2912 13.3961 12.9124 13.288 13.5331 13.3463L13.5381 13.3468L13.5381 13.3468C13.6309 13.3564 13.725 13.3434 13.8121 13.3088L13.9969 13.7734L13.8121 13.3088C13.8988 13.2743 13.9754 13.2197 14.0356 13.15C14.524 12.5713 14.9076 11.9141 15.1696 11.2075L15.1697 11.2071C15.2363 11.028 15.1964 10.8383 15.0932 10.6957C14.7292 10.1927 14.5131 9.60571 14.5152 8.97941L14.5152 8.97906C14.5176 8.35901 14.7334 7.77374 15.0963 7.27232L15.1364 7.21067C15.2181 7.07086 15.2425 6.90403 15.1846 6.7474L15.1842 6.74645C14.9254 6.04291 14.5459 5.38941 14.0621 4.81462L14.062 4.81449C13.939 4.66831 13.7495 4.60609 13.5677 4.62461L13.5602 4.62538L13.5526 4.62591L13.376 4.63854L13.3685 4.63907L13.361 4.63938C12.81 4.66211 12.2647 4.54912 11.7813 4.27262C11.2354 3.96277 10.832 3.4832 10.5758 2.91842L10.5754 2.91743C10.5042 2.75965 10.3588 2.62823 10.1641 2.59472L7.48069 2.93167ZM7.48069 2.93167C7.5554 2.76862 7.70513 2.63444 7.9003 2.60005L7.48069 2.93167ZM7.90047 2.60002C8.64996 2.46846 9.41582 2.46669 10.1639 2.59468L7.90047 2.60002Z"
|
||||
stroke="currentColor"
|
||||
/>
|
||||
<circle cx="9.00781" cy="9" r="2.125" stroke="currentColor" />
|
||||
</svg>
|
||||
</template>
|
||||
@ -44,7 +44,7 @@
|
||||
<PhoneIcon class="h-4 w-4" />
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="column.key === 'modified'" class="truncate text-base">
|
||||
<div v-if="['modified', 'creation'].includes(column.key)" class="truncate text-base">
|
||||
{{ item.timeAgo }}
|
||||
</div>
|
||||
</ListRowItem>
|
||||
|
||||
@ -53,7 +53,7 @@
|
||||
<PhoneIcon class="h-4 w-4" />
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="column.key === 'modified'" class="truncate text-base">
|
||||
<div v-if="['modified', 'creation'].includes(column.key)" class="truncate text-base">
|
||||
{{ item.timeAgo }}
|
||||
</div>
|
||||
</ListRowItem>
|
||||
|
||||
218
frontend/src/components/ViewSettings.vue
Normal file
218
frontend/src/components/ViewSettings.vue
Normal file
@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<NestedPopover>
|
||||
<template #target>
|
||||
<Button label="View Settings">
|
||||
<template #prefix>
|
||||
<SettingsIcon class="h-4" />
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
<template #body>
|
||||
<div
|
||||
class="my-2 rounded-lg border border-gray-100 bg-white p-1.5 shadow-xl"
|
||||
>
|
||||
<div v-if="!edit">
|
||||
<Draggable
|
||||
:list="columns"
|
||||
@end="updateColumnDetails"
|
||||
item-key="key"
|
||||
class="list-group"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div
|
||||
class="flex cursor-grab items-center justify-between gap-6 rounded px-2 py-1.5 text-base text-gray-800 hover:bg-gray-100"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<DragIcon class="h-3.5" />
|
||||
<div>{{ element.label }}</div>
|
||||
</div>
|
||||
<div class="flex cursor-pointer items-center gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="!h-5 w-5 !p-1"
|
||||
@click="editColumn(element)"
|
||||
>
|
||||
<EditIcon class="h-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="!h-5 w-5 !p-1"
|
||||
@click="removeColumn(element)"
|
||||
>
|
||||
<FeatherIcon name="x" class="h-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Draggable>
|
||||
<div class="mt-1.5 border-t pt-1.5">
|
||||
<Autocomplete
|
||||
value=""
|
||||
:options="fields"
|
||||
@change="(e) => addColumn(e)"
|
||||
>
|
||||
<template #target="{ togglePopover }">
|
||||
<Button
|
||||
class="w-full !justify-start !text-gray-600"
|
||||
variant="ghost"
|
||||
@click="togglePopover()"
|
||||
label="Add Column"
|
||||
>
|
||||
<template #prefix>
|
||||
<FeatherIcon name="plus" class="h-4" />
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
</Autocomplete>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div
|
||||
class="flex flex-col items-center justify-between gap-2 rounded px-2 py-1.5 text-base text-gray-800"
|
||||
>
|
||||
<div class="flex flex-col items-center gap-3">
|
||||
<FormControl
|
||||
type="text"
|
||||
size="md"
|
||||
label="Label"
|
||||
v-model="column.label"
|
||||
class="w-full"
|
||||
placeholder="Column Label"
|
||||
/>
|
||||
<FormControl
|
||||
type="text"
|
||||
size="md"
|
||||
label="Width"
|
||||
class="w-full"
|
||||
v-model="column.width"
|
||||
placeholder="Column Width"
|
||||
description="Width can be in number, pixel or rem (eg. 3, 30px, 10rem)"
|
||||
:debounce="500"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex w-full gap-2 border-t pt-2">
|
||||
<Button
|
||||
variant="subtle"
|
||||
label="Cancel"
|
||||
class="w-full flex-1"
|
||||
@click="cancelUpdate"
|
||||
/>
|
||||
<Button
|
||||
variant="solid"
|
||||
label="Update"
|
||||
class="w-full flex-1"
|
||||
@click="updateColumn(column)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</NestedPopover>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import SettingsIcon from '@/components/Icons/SettingsIcon.vue'
|
||||
import EditIcon from '@/components/Icons/EditIcon.vue'
|
||||
import DragIcon from '@/components/Icons/DragIcon.vue'
|
||||
import NestedPopover from '@/components/NestedPopover.vue'
|
||||
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
|
||||
import Draggable from 'vuedraggable'
|
||||
import { computed, defineModel, ref } from 'vue'
|
||||
import { FeatherIcon, FormControl, call } from 'frappe-ui'
|
||||
|
||||
const props = defineProps({
|
||||
doctype: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const list = defineModel()
|
||||
const edit = ref(false)
|
||||
const column = ref({
|
||||
old: {},
|
||||
label: '',
|
||||
key: '',
|
||||
width: '10rem',
|
||||
})
|
||||
|
||||
const columns = computed({
|
||||
get: () => list.value?.data?.columns,
|
||||
set: (val) => {
|
||||
list.value.data.columns = val
|
||||
},
|
||||
})
|
||||
|
||||
const rows = computed({
|
||||
get: () => list.value?.data?.rows,
|
||||
set: (val) => {
|
||||
list.value.data.rows = val
|
||||
},
|
||||
})
|
||||
|
||||
const fields = computed(() => {
|
||||
let allFields = list.value?.data?.fields
|
||||
if (!allFields) return []
|
||||
|
||||
return allFields.filter((field) => {
|
||||
return !columns.value.find((column) => column.key === field.value)
|
||||
})
|
||||
})
|
||||
|
||||
async function addColumn(c) {
|
||||
let _column = {
|
||||
label: c.label,
|
||||
key: c.value,
|
||||
width: '10rem',
|
||||
}
|
||||
columns.value.push(_column)
|
||||
rows.value.push(c.value)
|
||||
await updateColumnDetails()
|
||||
list.value.reload()
|
||||
}
|
||||
|
||||
function removeColumn(c) {
|
||||
columns.value = columns.value.filter((column) => column.key !== c.key)
|
||||
if (c.key !== 'name') {
|
||||
rows.value = rows.value.filter((row) => row !== c.key)
|
||||
}
|
||||
updateColumnDetails()
|
||||
}
|
||||
|
||||
function editColumn(c) {
|
||||
edit.value = true
|
||||
column.value = c
|
||||
column.value.old = { ...c }
|
||||
}
|
||||
|
||||
function updateColumn(c) {
|
||||
edit.value = false
|
||||
let index = columns.value.findIndex((column) => column.key === c.key)
|
||||
columns.value[index].label = c.label
|
||||
columns.value[index].width = c.width
|
||||
|
||||
if (columns.value[index].old) {
|
||||
delete columns.value[index].old
|
||||
}
|
||||
updateColumnDetails()
|
||||
}
|
||||
|
||||
function cancelUpdate() {
|
||||
edit.value = false
|
||||
column.value.label = column.value.old.label
|
||||
column.value.width = column.value.old.width
|
||||
delete column.value.old
|
||||
}
|
||||
|
||||
async function updateColumnDetails() {
|
||||
await call(
|
||||
'crm.fcrm.doctype.crm_list_view_settings.crm_list_view_settings.update',
|
||||
{
|
||||
doctype: props.doctype,
|
||||
columns: columns.value,
|
||||
rows: rows.value,
|
||||
}
|
||||
)
|
||||
}
|
||||
</script>
|
||||
@ -30,10 +30,10 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<Filter doctype="CRM Deal" />
|
||||
<SortBy doctype="CRM Deal" />
|
||||
<Button icon="more-horizontal" />
|
||||
<ViewSettings doctype="CRM Deal" v-model="deals"/>
|
||||
</div>
|
||||
</div>
|
||||
<DealsListView :rows="rows" :columns="columns" />
|
||||
<DealsListView v-if="deals.data" :rows="rows" :columns="deals.data.columns" />
|
||||
<Dialog
|
||||
v-model="showNewDialog"
|
||||
:options="{
|
||||
@ -59,6 +59,7 @@ import DealsListView from '@/components/ListViews/DealsListView.vue'
|
||||
import NewDeal from '@/components/NewDeal.vue'
|
||||
import SortBy from '@/components/SortBy.vue'
|
||||
import Filter from '@/components/Filter.vue'
|
||||
import ViewSettings from '@/components/ViewSettings.vue'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { organizationsStore } from '@/stores/organizations'
|
||||
import { useOrderBy } from '@/composables/orderby'
|
||||
@ -76,7 +77,6 @@ import {
|
||||
Dialog,
|
||||
Button,
|
||||
Dropdown,
|
||||
createListResource,
|
||||
createResource,
|
||||
Breadcrumbs,
|
||||
} from 'frappe-ui'
|
||||
@ -95,26 +95,20 @@ const currentView = ref({
|
||||
icon: 'list',
|
||||
})
|
||||
|
||||
function getFilter() {
|
||||
return getArgs() || {}
|
||||
function getParams() {
|
||||
const filters = getArgs() || {}
|
||||
const order_by = getOrderBy() || 'modified desc'
|
||||
|
||||
return {
|
||||
doctype: 'CRM Deal',
|
||||
filters: filters,
|
||||
order_by: order_by,
|
||||
}
|
||||
}
|
||||
|
||||
const deals = createListResource({
|
||||
type: 'list',
|
||||
doctype: 'CRM Deal',
|
||||
fields: [
|
||||
'name',
|
||||
'organization',
|
||||
'annual_revenue',
|
||||
'status',
|
||||
'email',
|
||||
'mobile_no',
|
||||
'deal_owner',
|
||||
'modified',
|
||||
],
|
||||
filters: getFilter(),
|
||||
orderBy: 'modified desc',
|
||||
pageLength: 20,
|
||||
const deals = createResource({
|
||||
url: 'crm.api.doc.get_list_data',
|
||||
params: getParams(),
|
||||
auto: true,
|
||||
})
|
||||
|
||||
@ -122,7 +116,7 @@ watch(
|
||||
() => getOrderBy(),
|
||||
(value, old_value) => {
|
||||
if (!value && !old_value) return
|
||||
deals.orderBy = getOrderBy() || 'modified desc'
|
||||
deals.params = getParams()
|
||||
deals.reload()
|
||||
},
|
||||
{ immediate: true }
|
||||
@ -132,75 +126,49 @@ watch(
|
||||
storage,
|
||||
useDebounceFn((value, old_value) => {
|
||||
if (JSON.stringify([...value]) === JSON.stringify([...old_value])) return
|
||||
deals.filters = getFilter()
|
||||
deals.params = getParams()
|
||||
deals.reload()
|
||||
}, 300),
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const columns = [
|
||||
{
|
||||
label: 'Organization',
|
||||
key: 'organization',
|
||||
width: '11rem',
|
||||
},
|
||||
{
|
||||
label: 'Amount',
|
||||
key: 'annual_revenue',
|
||||
width: '9rem',
|
||||
},
|
||||
{
|
||||
label: 'Status',
|
||||
key: 'status',
|
||||
width: '10rem',
|
||||
},
|
||||
{
|
||||
label: 'Email',
|
||||
key: 'email',
|
||||
width: '12rem',
|
||||
},
|
||||
{
|
||||
label: 'Mobile no',
|
||||
key: 'mobile_no',
|
||||
width: '11rem',
|
||||
},
|
||||
{
|
||||
label: 'Deal owner',
|
||||
key: 'deal_owner',
|
||||
width: '10rem',
|
||||
},
|
||||
{
|
||||
label: 'Last modified',
|
||||
key: 'modified',
|
||||
width: '8rem',
|
||||
},
|
||||
]
|
||||
|
||||
const rows = computed(() => {
|
||||
if (!deals.data) return []
|
||||
return deals.data.map((deal) => {
|
||||
return {
|
||||
name: deal.name,
|
||||
organization: {
|
||||
label: deal.organization,
|
||||
logo: getOrganization(deal.organization)?.organization_logo,
|
||||
},
|
||||
annual_revenue: formatNumberIntoCurrency(deal.annual_revenue),
|
||||
status: {
|
||||
label: deal.status,
|
||||
color: dealStatuses[deal.status]?.color,
|
||||
},
|
||||
email: deal.email,
|
||||
mobile_no: deal.mobile_no,
|
||||
deal_owner: {
|
||||
label: deal.deal_owner && getUser(deal.deal_owner).full_name,
|
||||
...(deal.deal_owner && getUser(deal.deal_owner)),
|
||||
},
|
||||
modified: {
|
||||
label: dateFormat(deal.modified, dateTooltipFormat),
|
||||
timeAgo: timeAgo(deal.modified),
|
||||
},
|
||||
}
|
||||
if (!deals.data?.data) return []
|
||||
return deals.data.data.map((deal) => {
|
||||
let _rows = {}
|
||||
deals.data.rows.forEach((row) => {
|
||||
_rows[row] = deal[row]
|
||||
|
||||
if (row == 'organization') {
|
||||
_rows[row] = {
|
||||
label: deal.organization,
|
||||
logo: getOrganization(deal.organization)?.organization_logo,
|
||||
}
|
||||
} else if (row == 'annual_revenue') {
|
||||
_rows[row] = formatNumberIntoCurrency(deal.annual_revenue)
|
||||
} else if (row == 'status') {
|
||||
_rows[row] = {
|
||||
label: deal.status,
|
||||
color: dealStatuses[deal.status]?.color,
|
||||
}
|
||||
} else if (row == 'deal_owner') {
|
||||
_rows[row] = {
|
||||
label: deal.deal_owner && getUser(deal.deal_owner).full_name,
|
||||
...(deal.deal_owner && getUser(deal.deal_owner)),
|
||||
}
|
||||
} else if (row == 'modified') {
|
||||
_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),
|
||||
}
|
||||
}
|
||||
})
|
||||
return _rows
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -29,10 +29,14 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<Filter doctype="CRM Lead" />
|
||||
<SortBy doctype="CRM Lead" />
|
||||
<Button icon="more-horizontal" />
|
||||
<ViewSettings doctype="CRM Lead" v-model="leads"/>
|
||||
</div>
|
||||
</div>
|
||||
<LeadsListView :rows="rows" :columns="columns" />
|
||||
<LeadsListView
|
||||
v-if="leads.data"
|
||||
:rows="rows"
|
||||
:columns="leads.data.columns"
|
||||
/>
|
||||
<Dialog
|
||||
v-model="showNewDialog"
|
||||
:options="{
|
||||
@ -58,6 +62,7 @@ import LeadsListView from '@/components/ListViews/LeadsListView.vue'
|
||||
import NewLead from '@/components/NewLead.vue'
|
||||
import SortBy from '@/components/SortBy.vue'
|
||||
import Filter from '@/components/Filter.vue'
|
||||
import ViewSettings from '@/components/ViewSettings.vue'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { organizationsStore } from '@/stores/organizations'
|
||||
import { useOrderBy } from '@/composables/orderby'
|
||||
@ -69,7 +74,6 @@ import {
|
||||
Dialog,
|
||||
Button,
|
||||
Dropdown,
|
||||
createListResource,
|
||||
createResource,
|
||||
Breadcrumbs,
|
||||
} from 'frappe-ui'
|
||||
@ -88,35 +92,24 @@ const currentView = ref({
|
||||
icon: 'list',
|
||||
})
|
||||
|
||||
function getFilter() {
|
||||
return {
|
||||
...(getArgs() || {}),
|
||||
function getParams() {
|
||||
const filters = {
|
||||
converted: 0,
|
||||
...(getArgs() || {}),
|
||||
}
|
||||
|
||||
const order_by = getOrderBy() || 'modified desc'
|
||||
|
||||
return {
|
||||
doctype: 'CRM Lead',
|
||||
filters: filters,
|
||||
order_by: order_by,
|
||||
}
|
||||
}
|
||||
|
||||
function getSortBy() {
|
||||
return getOrderBy() || 'modified desc'
|
||||
}
|
||||
|
||||
const leads = createListResource({
|
||||
type: 'list',
|
||||
doctype: 'CRM Lead',
|
||||
fields: [
|
||||
'name',
|
||||
'first_name',
|
||||
'lead_name',
|
||||
'image',
|
||||
'organization',
|
||||
'status',
|
||||
'email',
|
||||
'mobile_no',
|
||||
'lead_owner',
|
||||
'modified',
|
||||
],
|
||||
filters: getFilter(),
|
||||
orderBy: getSortBy(),
|
||||
pageLength: 20,
|
||||
const leads = createResource({
|
||||
url: 'crm.api.doc.get_list_data',
|
||||
params: getParams(),
|
||||
auto: true,
|
||||
})
|
||||
|
||||
@ -124,7 +117,7 @@ watch(
|
||||
() => getOrderBy(),
|
||||
(value, old_value) => {
|
||||
if (!value && !old_value) return
|
||||
leads.orderBy = getSortBy()
|
||||
leads.params = getParams()
|
||||
leads.reload()
|
||||
},
|
||||
{ immediate: true }
|
||||
@ -134,79 +127,54 @@ watch(
|
||||
storage,
|
||||
useDebounceFn((value, old_value) => {
|
||||
if (JSON.stringify([...value]) === JSON.stringify([...old_value])) return
|
||||
leads.filters = getFilter()
|
||||
leads.params = getParams()
|
||||
leads.reload()
|
||||
}, 300),
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const columns = [
|
||||
{
|
||||
label: 'Name',
|
||||
key: 'lead_name',
|
||||
width: '12rem',
|
||||
},
|
||||
{
|
||||
label: 'Organization',
|
||||
key: 'organization',
|
||||
width: '10rem',
|
||||
},
|
||||
{
|
||||
label: 'Status',
|
||||
key: 'status',
|
||||
width: '8rem',
|
||||
},
|
||||
{
|
||||
label: 'Email',
|
||||
key: 'email',
|
||||
width: '12rem',
|
||||
},
|
||||
{
|
||||
label: 'Mobile no',
|
||||
key: 'mobile_no',
|
||||
width: '11rem',
|
||||
},
|
||||
{
|
||||
label: 'Lead owner',
|
||||
key: 'lead_owner',
|
||||
width: '10rem',
|
||||
},
|
||||
{
|
||||
label: 'Last modified',
|
||||
key: 'modified',
|
||||
width: '8rem',
|
||||
},
|
||||
]
|
||||
|
||||
const rows = computed(() => {
|
||||
if (!leads.data) return []
|
||||
return leads.data.map((lead) => {
|
||||
return {
|
||||
name: lead.name,
|
||||
lead_name: {
|
||||
label: lead.lead_name,
|
||||
image: lead.image,
|
||||
image_label: lead.first_name,
|
||||
},
|
||||
organization: {
|
||||
label: lead.organization,
|
||||
logo: getOrganization(lead.organization)?.organization_logo,
|
||||
},
|
||||
status: {
|
||||
label: lead.status,
|
||||
color: leadStatuses[lead.status]?.color,
|
||||
},
|
||||
email: lead.email,
|
||||
mobile_no: lead.mobile_no,
|
||||
lead_owner: {
|
||||
label: lead.lead_owner && getUser(lead.lead_owner).full_name,
|
||||
...(lead.lead_owner && getUser(lead.lead_owner)),
|
||||
},
|
||||
modified: {
|
||||
label: dateFormat(lead.modified, dateTooltipFormat),
|
||||
timeAgo: timeAgo(lead.modified),
|
||||
},
|
||||
}
|
||||
if (!leads.data?.data) return []
|
||||
return leads.data.data.map((lead) => {
|
||||
let _rows = {}
|
||||
|
||||
leads.data.rows.forEach((row) => {
|
||||
_rows[row] = lead[row]
|
||||
|
||||
if (row == 'lead_name') {
|
||||
_rows[row] = {
|
||||
label: lead.lead_name,
|
||||
image: lead.image,
|
||||
image_label: lead.first_name,
|
||||
}
|
||||
} else if (row == 'organization') {
|
||||
_rows[row] = {
|
||||
label: lead.organization,
|
||||
logo: getOrganization(lead.organization)?.organization_logo,
|
||||
}
|
||||
} else if (row == 'status') {
|
||||
_rows[row] = {
|
||||
label: lead.status,
|
||||
color: leadStatuses[lead.status]?.color,
|
||||
}
|
||||
} else if (row == 'lead_owner') {
|
||||
_rows[row] = {
|
||||
label: lead.lead_owner && getUser(lead.lead_owner).full_name,
|
||||
...(lead.lead_owner && getUser(lead.lead_owner)),
|
||||
}
|
||||
} else if (row == 'modified') {
|
||||
_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),
|
||||
}
|
||||
}
|
||||
})
|
||||
return _rows
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
12
yarn.lock
12
yarn.lock
@ -3030,6 +3030,11 @@ socket.io-parser@~4.2.4:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.1"
|
||||
|
||||
sortablejs@1.14.0:
|
||||
version "1.14.0"
|
||||
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.14.0.tgz#6d2e17ccbdb25f464734df621d4f35d4ab35b3d8"
|
||||
integrity sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==
|
||||
|
||||
sortablejs@^1.15.0:
|
||||
version "1.15.0"
|
||||
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.15.0.tgz#53230b8aa3502bb77a29e2005808ffdb4a5f7e2a"
|
||||
@ -3387,6 +3392,13 @@ vue@^3.2.45, vue@^3.3.4, vue@^3.3.6:
|
||||
"@vue/server-renderer" "3.3.7"
|
||||
"@vue/shared" "3.3.7"
|
||||
|
||||
vuedraggable@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/vuedraggable/-/vuedraggable-4.1.0.tgz#edece68adb8a4d9e06accff9dfc9040e66852270"
|
||||
integrity sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==
|
||||
dependencies:
|
||||
sortablejs "1.14.0"
|
||||
|
||||
w3c-keyname@^2.2.0, w3c-keyname@^2.2.4:
|
||||
version "2.2.8"
|
||||
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user