Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Default view #595

Merged
merged 4 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions crm/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ def get_views(doctype):
query = (
frappe.qb.from_(View)
.select("*")
.where(Criterion.any([View.user == '', View.user == frappe.session.user]))
.where(Criterion.any([View.user == "", View.user == frappe.session.user]))
)
if doctype:
query = query.where(View.dt == doctype)
views = query.run(as_dict=True)
return views
return views


@frappe.whitelist()
def get_default_view():
return frappe.db.get_single_value("FCRM Settings", "default_view") or None
51 changes: 31 additions & 20 deletions crm/fcrm/doctype/crm_view_settings/crm_view_settings.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# 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, get_controller
from frappe.utils import parse_json
Expand All @@ -9,15 +10,16 @@
class CRMViewSettings(Document):
pass


@frappe.whitelist()
def create(view):
view = frappe._dict(view)

view.filters = parse_json(view.filters) or {}
view.columns = parse_json(view.columns or '[]')
view.rows = parse_json(view.rows or '[]')
view.kanban_columns = parse_json(view.kanban_columns or '[]')
view.kanban_fields = parse_json(view.kanban_fields or '[]')
view.columns = parse_json(view.columns or "[]")
view.rows = parse_json(view.rows or "[]")
view.kanban_columns = parse_json(view.kanban_columns or "[]")
view.kanban_fields = parse_json(view.kanban_fields or "[]")

default_rows = sync_default_rows(view.doctype)
view.rows = view.rows + default_rows if default_rows else view.rows
Expand All @@ -31,7 +33,7 @@ def create(view):
doc = frappe.new_doc("CRM View Settings")
doc.name = view.label
doc.label = view.label
doc.type = view.type or 'list'
doc.type = view.type or "list"
doc.icon = view.icon
doc.dt = view.doctype
doc.user = frappe.session.user
Expand All @@ -49,6 +51,7 @@ def create(view):
doc.insert()
return doc


@frappe.whitelist()
def update(view):
view = frappe._dict(view)
Expand All @@ -65,7 +68,7 @@ def update(view):

doc = frappe.get_doc("CRM View Settings", view.name)
doc.label = view.label
doc.type = view.type or 'list'
doc.type = view.type or "list"
doc.icon = view.icon
doc.route_name = view.route_name or ""
doc.load_default_columns = view.load_default_columns or False
Expand All @@ -81,11 +84,13 @@ def update(view):
doc.save()
return doc


@frappe.whitelist()
def delete(name):
if frappe.db.exists("CRM View Settings", name):
frappe.delete_doc("CRM View Settings", name)


@frappe.whitelist()
def public(name, value):
if frappe.session.user != "Administrator" and "Sales Manager" not in frappe.get_roles():
Expand All @@ -98,15 +103,18 @@ def public(name, value):
doc.user = "" if value else frappe.session.user
doc.save()


@frappe.whitelist()
def pin(name, value):
doc = frappe.get_doc("CRM View Settings", name)
doc.pinned = value
doc.save()


def remove_duplicates(l):
return list(dict.fromkeys(l))


def sync_default_rows(doctype, type="list"):
list = get_controller(doctype)
rows = []
Expand All @@ -116,6 +124,7 @@ def sync_default_rows(doctype, type="list"):

return rows


def sync_default_columns(view):
list = get_controller(view.doctype)
columns = []
Expand All @@ -136,15 +145,22 @@ def sync_default_columns(view):
return columns


@frappe.whitelist()
def set_as_default(name=None, type=None, doctype=None):
if not name:
name = type + "_" + doctype
frappe.db.set_single_value("FCRM Settings", "default_view", name)


@frappe.whitelist()
def create_or_update_default_view(view):
view = frappe._dict(view)

filters = parse_json(view.filters) or {}
columns = parse_json(view.columns or '[]')
rows = parse_json(view.rows or '[]')
kanban_columns = parse_json(view.kanban_columns or '[]')
kanban_fields = parse_json(view.kanban_fields or '[]')
columns = parse_json(view.columns or "[]")
rows = parse_json(view.rows or "[]")
kanban_columns = parse_json(view.kanban_columns or "[]")
kanban_fields = parse_json(view.kanban_fields or "[]")

default_rows = sync_default_rows(view.doctype, view.type)
rows = rows + default_rows if default_rows else rows
Expand All @@ -157,17 +173,12 @@ def create_or_update_default_view(view):

doc = frappe.db.exists(
"CRM View Settings",
{
"dt": view.doctype,
"type": view.type or 'list',
"is_default": True,
"user": frappe.session.user
},
{"dt": view.doctype, "type": view.type or "list", "is_default": True, "user": frappe.session.user},
)
if doc:
doc = frappe.get_doc("CRM View Settings", doc)
doc.label = view.label
doc.type = view.type or 'list'
doc.type = view.type or "list"
doc.route_name = view.route_name or ""
doc.load_default_columns = view.load_default_columns or False
doc.filters = json.dumps(filters)
Expand All @@ -182,10 +193,10 @@ def create_or_update_default_view(view):
doc.save()
else:
doc = frappe.new_doc("CRM View Settings")
label = 'Group By View' if view.type == 'group_by' else 'List View'
label = "Group By View" if view.type == "group_by" else "List View"
doc.name = view.label or label
doc.label = view.label or label
doc.type = view.type or 'list'
doc.type = view.type or "list"
doc.dt = view.doctype
doc.user = frappe.session.user
doc.route_name = view.route_name or ""
Expand All @@ -200,4 +211,4 @@ def create_or_update_default_view(view):
doc.columns = json.dumps(columns)
doc.rows = json.dumps(rows)
doc.is_default = True
doc.insert()
doc.insert()
13 changes: 12 additions & 1 deletion crm/fcrm/doctype/fcrm_settings/fcrm_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"engine": "InnoDB",
"field_order": [
"defaults_tab",
"default_view",
"column_break_jeeh",
"restore_defaults",
"branding_tab",
"brand_name",
Expand Down Expand Up @@ -56,12 +58,21 @@
"fieldname": "favicon",
"fieldtype": "Attach",
"label": "Favicon"
},
{
"fieldname": "default_view",
"fieldtype": "Data",
"label": "Default View"
},
{
"fieldname": "column_break_jeeh",
"fieldtype": "Column Break"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2025-01-19 14:23:05.981355",
"modified": "2025-02-18 17:05:39.440396",
"modified_by": "Administrator",
"module": "FCRM",
"name": "FCRM Settings",
Expand Down
49 changes: 46 additions & 3 deletions frontend/src/components/ViewControls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ import QuickFilterField from '@/components/QuickFilterField.vue'
import RefreshIcon from '@/components/Icons/RefreshIcon.vue'
import EditIcon from '@/components/Icons/EditIcon.vue'
import DuplicateIcon from '@/components/Icons/DuplicateIcon.vue'
import CheckIcon from '@/components/Icons/CheckIcon.vue'
import PinIcon from '@/components/Icons/PinIcon.vue'
import UnpinIcon from '@/components/Icons/UnpinIcon.vue'
import ViewModal from '@/components/Modals/ViewModal.vue'
Expand Down Expand Up @@ -263,7 +264,7 @@ const props = defineProps({

const { brand } = getSettings()
const { $dialog } = globalStore()
const { reload: reloadView, getView } = viewsStore()
const { reload: reloadView, getDefaultView, getView } = viewsStore()
const { isManager } = usersStore()

const list = defineModel()
Expand Down Expand Up @@ -887,9 +888,20 @@ function updatePageLength(value, loadMore = false) {

// View Actions
const viewActions = (view) => {
let isDefault = typeof view.name === 'string'
let isStandard = typeof view.name === 'string'
let _view = getView(view.name)

if (isStandard) {
_view = getView(null, view.name, props.doctype)
}

if (!_view) {
_view = {
type: view.name,
dt: props.doctype,
}
}

let actions = [
{
group: __('Default Views'),
Expand All @@ -904,7 +916,15 @@ const viewActions = (view) => {
},
]

if (!isDefault && (!_view.public || isManager())) {
if (!isStandardView(_view, isStandard)) {
actions[0].items.unshift({
label: __('Set as default'),
icon: () => h(CheckIcon, { class: 'h-4 w-4' }),
onClick: () => setAsDefault(_view),
})
}

if (!isStandard && (!_view.public || isManager())) {
actions[0].items.push({
label: __('Edit'),
icon: () => h(EditIcon, { class: 'h-4 w-4' }),
Expand Down Expand Up @@ -961,6 +981,18 @@ const viewActions = (view) => {
return actions
}

function isStandardView(v, isStandard) {
let defaultView = getDefaultView()

if (!defaultView) return false

if (isStandard && !v.name) {
return defaultView == v.type + '_' + v.dt
}

return defaultView == v.name
}

const viewModalObj = ref({})

function createView() {
Expand All @@ -972,6 +1004,17 @@ function createView() {
showViewModal.value = true
}

function setAsDefault(v) {
call('crm.fcrm.doctype.crm_view_settings.crm_view_settings.set_as_default', {
name: v.name,
type: v.type,
doctype: v.dt,
}).then(() => {
reloadView()
list.value.reload()
})
}

function duplicateView(v) {
v.label = v.label + __(' (New)')
viewModalObj.value = v
Expand Down
14 changes: 12 additions & 2 deletions frontend/src/router.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { createRouter, createWebHistory } from 'vue-router'
import { userResource } from '@/stores/user'
import { sessionStore } from '@/stores/session'
import { viewsStore } from '@/stores/views'

const routes = [
{
path: '/',
redirect: { name: 'Leads' },
name: 'Home',
},
{
Expand Down Expand Up @@ -113,7 +113,17 @@ router.beforeEach(async (to, from, next) => {
isLoggedIn && (await userResource.promise)

if (to.name === 'Home' && isLoggedIn) {
next({ name: 'Leads' })
const { getDefaultView, defaultView } = viewsStore()
await defaultView.promise

let { name, type, view } = getDefaultView(true)
name = name || 'Leads'

if (view) {
next({ name, params: { viewType: type }, query: { view } })
} else {
next({ name, params: { viewType: type } })
}
} else if (!isLoggedIn) {
window.location.href = '/login?redirect-to=/crm'
} else if (to.matched.length === 0) {
Expand Down
Loading
Loading