feat: Quick List Block for Workspace (#16887)
This commit is contained in:
parent
a5995b2a10
commit
78489cd700
20 changed files with 786 additions and 36 deletions
150
cypress/integration/workspace_blocks.js
Normal file
150
cypress/integration/workspace_blocks.js
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
context('Workspace Blocks', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app');
|
||||
});
|
||||
|
||||
it('Create Test Page', () => {
|
||||
cy.intercept({
|
||||
method: 'POST',
|
||||
url: 'api/method/frappe.desk.doctype.workspace.workspace.new_page'
|
||||
}).as('new_page');
|
||||
|
||||
cy.get('.codex-editor__redactor .ce-block');
|
||||
cy.get('.custom-actions button[data-label="Create%20Workspace"]').click();
|
||||
cy.fill_field('title', 'Test Block Page', 'Data');
|
||||
cy.fill_field('icon', 'edit', 'Icon');
|
||||
cy.get_open_dialog().find('.modal-header').click();
|
||||
cy.get_open_dialog().find('.btn-primary').click();
|
||||
|
||||
// check if sidebar item is added in private section
|
||||
cy.get('.sidebar-item-container[item-name="Test Block Page"]').should('have.attr', 'item-public', '0');
|
||||
|
||||
cy.get('.standard-actions .btn-primary[data-label="Save"]').click();
|
||||
cy.wait(300);
|
||||
cy.get('.sidebar-item-container[item-name="Test Block Page"]').should('have.attr', 'item-public', '0');
|
||||
|
||||
cy.wait('@new_page');
|
||||
});
|
||||
|
||||
it('Quick List Block', () => {
|
||||
cy.create_records([
|
||||
{
|
||||
doctype: 'ToDo',
|
||||
description: 'Quick List ToDo 1',
|
||||
status: 'Open'
|
||||
},
|
||||
{
|
||||
doctype: 'ToDo',
|
||||
description: 'Quick List ToDo 2',
|
||||
status: 'Open'
|
||||
},
|
||||
{
|
||||
doctype: 'ToDo',
|
||||
description: 'Quick List ToDo 3',
|
||||
status: 'Open'
|
||||
},
|
||||
{
|
||||
doctype: 'ToDo',
|
||||
description: 'Quick List ToDo 4',
|
||||
status: 'Open'
|
||||
},
|
||||
{
|
||||
doctype: 'ToDo',
|
||||
description: 'Quick List ToDo 5',
|
||||
status: 'Closed'
|
||||
},
|
||||
{
|
||||
doctype: 'ToDo',
|
||||
description: 'Quick List ToDo 6',
|
||||
status: 'Closed'
|
||||
},
|
||||
{
|
||||
doctype: 'ToDo',
|
||||
description: 'Quick List ToDo 7',
|
||||
status: 'Closed'
|
||||
},
|
||||
{
|
||||
doctype: 'ToDo',
|
||||
description: 'Quick List ToDo 8',
|
||||
status: 'Closed'
|
||||
}
|
||||
]);
|
||||
|
||||
cy.get('.codex-editor__redactor .ce-block');
|
||||
cy.get('.standard-actions .btn-secondary[data-label=Edit]').click();
|
||||
|
||||
// test quick list creation
|
||||
cy.get('.ce-block').first().click({force: true}).type('{enter}');
|
||||
cy.get('.block-list-container .block-list-item').contains('Quick List').click();
|
||||
|
||||
cy.get_open_dialog().find('.modal-header').click();
|
||||
|
||||
cy.fill_field('document_type', 'ToDo', 'Link').blur();
|
||||
cy.fill_field('label', 'ToDo', 'Data').blur();
|
||||
|
||||
cy.get_open_dialog().find('.filter-edit-area').should('contain', 'No filters selected');
|
||||
cy.get_open_dialog().find('.filter-area .add-filter').click();
|
||||
|
||||
cy.get_open_dialog().find('.fieldname-select-area input').type('Status{enter}').blur();
|
||||
cy.get_open_dialog().find('select.input-with-feedback').select('Open');
|
||||
|
||||
cy.get_open_dialog().find('.modal-header').click();
|
||||
cy.get_open_dialog().find('.btn-primary').click();
|
||||
|
||||
cy.get('.standard-actions .btn-primary[data-label="Save"]').click();
|
||||
|
||||
|
||||
cy.get('.codex-editor__redactor .ce-block');
|
||||
|
||||
cy.get('.ce-block .quick-list-widget-box').first().as('todo-quick-list');
|
||||
|
||||
cy.get('@todo-quick-list').find('.quick-list-item .status').should('contain', 'Open');
|
||||
|
||||
// test filter-list
|
||||
cy.get('@todo-quick-list').find('.widget-control .filter-list').click();
|
||||
|
||||
cy.get_open_dialog().find('select.input-with-feedback').select('Closed');
|
||||
cy.get_open_dialog().find('.modal-header').click();
|
||||
cy.get_open_dialog().find('.btn-primary').click();
|
||||
|
||||
cy.get('@todo-quick-list').find('.quick-list-item .status').should('contain', 'Closed');
|
||||
|
||||
|
||||
// test refresh-list
|
||||
cy.intercept({
|
||||
method: 'POST',
|
||||
url: 'api/method/frappe.desk.reportview.get'
|
||||
}).as('refresh-list');
|
||||
|
||||
cy.get('@todo-quick-list').find('.widget-control .refresh-list').click();
|
||||
cy.wait('@refresh-list');
|
||||
|
||||
|
||||
// test add-new
|
||||
cy.get('@todo-quick-list').find('.widget-control .add-new').click();
|
||||
cy.url().should('include', `/todo/new-todo-1`);
|
||||
cy.go('back');
|
||||
|
||||
|
||||
// test quick-list-item
|
||||
cy.get('@todo-quick-list').find('.quick-list-item .title')
|
||||
.first()
|
||||
.invoke('attr', 'title')
|
||||
.then(title => {
|
||||
cy.get('@todo-quick-list').find('.quick-list-item').contains(title).click();
|
||||
cy.get_field('description', 'Text Editor').should('contain', title);
|
||||
});
|
||||
cy.go('back');
|
||||
|
||||
|
||||
// test see-all
|
||||
cy.get('@todo-quick-list').find('.widget-footer .see-all').click();
|
||||
|
||||
cy.get('.standard-filter-section select[data-fieldname="status"]')
|
||||
.invoke('val')
|
||||
.should('eq', 'Open');
|
||||
cy.go('back');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -166,6 +166,8 @@ class Workspace:
|
|||
|
||||
self.onboardings = {"items": self.get_onboardings()}
|
||||
|
||||
self.quick_lists = {"items": self.get_quick_lists()}
|
||||
|
||||
def _doctype_contains_a_record(self, name):
|
||||
exists = self.table_counts.get(name, False)
|
||||
|
||||
|
|
@ -284,6 +286,21 @@ class Workspace:
|
|||
|
||||
return items
|
||||
|
||||
@handle_not_exist
|
||||
def get_quick_lists(self):
|
||||
items = []
|
||||
quick_lists = self.doc.quick_lists
|
||||
|
||||
for item in quick_lists:
|
||||
new_item = item.as_dict().copy()
|
||||
|
||||
# Translate label
|
||||
new_item["label"] = _(item.label) if item.label else _(item.document_type)
|
||||
|
||||
items.append(new_item)
|
||||
|
||||
return items
|
||||
|
||||
@handle_not_exist
|
||||
def get_onboardings(self):
|
||||
if self.onboarding_list:
|
||||
|
|
@ -336,6 +353,7 @@ def get_desktop_page(page):
|
|||
"shortcuts": workspace.shortcuts,
|
||||
"cards": workspace.cards,
|
||||
"onboardings": workspace.onboardings,
|
||||
"quick_lists": workspace.quick_lists,
|
||||
}
|
||||
except DoesNotExistError:
|
||||
frappe.log_error("Workspace Missing")
|
||||
|
|
@ -452,6 +470,8 @@ def save_new_widget(doc, page, blocks, new_widgets):
|
|||
doc.charts.extend(new_widget(widgets.chart, "Workspace Chart", "charts"))
|
||||
if widgets.shortcut:
|
||||
doc.shortcuts.extend(new_widget(widgets.shortcut, "Workspace Shortcut", "shortcuts"))
|
||||
if widgets.quick_list:
|
||||
doc.quick_lists.extend(new_widget(widgets.quick_list, "Workspace Quick List", "quick_lists"))
|
||||
if widgets.card:
|
||||
doc.build_links_table_from_card(widgets.card)
|
||||
|
||||
|
|
@ -481,12 +501,12 @@ def save_new_widget(doc, page, blocks, new_widgets):
|
|||
def clean_up(original_page, blocks):
|
||||
page_widgets = {}
|
||||
|
||||
for wid in ["shortcut", "card", "chart"]:
|
||||
for wid in ["shortcut", "card", "chart", "quick_list"]:
|
||||
# get list of widget's name from blocks
|
||||
page_widgets[wid] = [x["data"][wid + "_name"] for x in loads(blocks) if x["type"] == wid]
|
||||
|
||||
# shortcut & chart cleanup
|
||||
for wid in ["shortcut", "chart"]:
|
||||
# shortcut, chart & quick_list cleanup
|
||||
for wid in ["shortcut", "chart", "quick_list"]:
|
||||
updated_widgets = []
|
||||
original_page.get(wid + "s").reverse()
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@
|
|||
"shortcuts",
|
||||
"tab_break_18",
|
||||
"links",
|
||||
"quick_lists_tab",
|
||||
"quick_lists",
|
||||
"roles_tab",
|
||||
"roles"
|
||||
],
|
||||
|
|
@ -155,11 +157,22 @@
|
|||
"fieldname": "roles_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Roles"
|
||||
},
|
||||
{
|
||||
"fieldname": "quick_lists_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Quick Lists"
|
||||
},
|
||||
{
|
||||
"fieldname": "quick_lists",
|
||||
"fieldtype": "Table",
|
||||
"label": "Quick Lists",
|
||||
"options": "Workspace Quick List"
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2022-01-27 12:06:13.111743",
|
||||
"modified": "2022-05-12 13:00:03.925605",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Workspace",
|
||||
|
|
@ -189,5 +202,6 @@
|
|||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
0
frappe/desk/doctype/workspace_quick_list/__init__.py
Normal file
0
frappe/desk/doctype/workspace_quick_list/__init__.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2022-05-12 12:58:41.824496",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"document_type",
|
||||
"column_break_1",
|
||||
"label",
|
||||
"section_break_4",
|
||||
"quick_list_filter"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "document_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "DocType",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_1",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Label",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "quick_list_filter",
|
||||
"fieldtype": "Code",
|
||||
"in_list_view": 1,
|
||||
"label": "Quick List Filter",
|
||||
"options": "JSON"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-05-12 13:48:40.617623",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Workspace Quick List",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# Copyright (c) 2022, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class WorkspaceQuickList(Document):
|
||||
pass
|
||||
|
|
@ -80,6 +80,7 @@ def sync_for(app_name, force=0, reset_permissions=False):
|
|||
"workspace_link",
|
||||
"workspace_chart",
|
||||
"workspace_shortcut",
|
||||
"workspace_quick_list",
|
||||
"workspace",
|
||||
]:
|
||||
files.append(os.path.join(FRAPPE_PATH, "desk", "doctype", desk_module, f"{desk_module}.json"))
|
||||
|
|
|
|||
|
|
@ -324,7 +324,7 @@ frappe.ui.FilterGroup = class {
|
|||
}
|
||||
|
||||
add_filters_to_filter_group(filters) {
|
||||
if (filters.length) {
|
||||
if (filters && filters.length) {
|
||||
this.toggle_empty_filters(false);
|
||||
filters.forEach((filter) => {
|
||||
this.add_filter(filter[0], filter[1], filter[2], filter[3]);
|
||||
|
|
|
|||
|
|
@ -1486,5 +1486,47 @@ Object.assign(frappe.utils, {
|
|||
case "f": case "false": case "n": case "no": case "0": case null: return false;
|
||||
default: return string;
|
||||
}
|
||||
},
|
||||
|
||||
get_filter_as_json(filters) {
|
||||
// convert filter array to json
|
||||
let filter = null;
|
||||
|
||||
if (filters.length) {
|
||||
filter = {};
|
||||
filters.forEach(arr => {
|
||||
filter[arr[1]] = [arr[2], arr[3]];
|
||||
});
|
||||
filter = JSON.stringify(filter);
|
||||
}
|
||||
|
||||
return filter;
|
||||
},
|
||||
|
||||
get_filter_from_json(filter_json, doctype) {
|
||||
// convert json to filter array
|
||||
if (filter_json) {
|
||||
if (!filter_json.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const filters_json = new Function(`return ${filter_json}`)();
|
||||
if (!doctype) {
|
||||
// e.g. return {
|
||||
// priority: (2) ['=', 'Medium'],
|
||||
// status: (2) ['=', 'Open']
|
||||
// }
|
||||
return filters_json || [];
|
||||
}
|
||||
|
||||
// e.g. return [
|
||||
// ['ToDo', 'status', '=', 'Open', false],
|
||||
// ['ToDo', 'priority', '=', 'Medium', false]
|
||||
// ]
|
||||
return Object.keys(filters_json).map(filter => {
|
||||
let val = filters_json[filter];
|
||||
return [doctype, filter, val[0], val[1], false];
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import Chart from "./chart";
|
|||
import Shortcut from "./shortcut";
|
||||
import Spacer from "./spacer";
|
||||
import Onboarding from "./onboarding";
|
||||
import QuickList from "./quick_list";
|
||||
|
||||
// import tunes
|
||||
import HeaderSize from "./header_size";
|
||||
|
|
@ -20,6 +21,7 @@ frappe.workspace_block.blocks = {
|
|||
shortcut: Shortcut,
|
||||
spacer: Spacer,
|
||||
onboarding: Onboarding,
|
||||
quick_list: QuickList,
|
||||
};
|
||||
|
||||
frappe.workspace_block.tunes = {
|
||||
|
|
|
|||
63
frappe/public/js/frappe/views/workspace/blocks/quick_list.js
Normal file
63
frappe/public/js/frappe/views/workspace/blocks/quick_list.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import Block from "./block.js";
|
||||
export default class QuickList extends Block {
|
||||
static get toolbox() {
|
||||
return {
|
||||
title: 'Quick List',
|
||||
icon: frappe.utils.icon('list', 'sm')
|
||||
};
|
||||
}
|
||||
|
||||
static get isReadOnlySupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
constructor({ data, api, config, readOnly, block }) {
|
||||
super({ data, api, config, readOnly, block });
|
||||
this.col = this.data.col ? this.data.col : "4";
|
||||
this.allow_customization = !this.readOnly;
|
||||
this.options = {
|
||||
allow_sorting: this.allow_customization,
|
||||
allow_create: this.allow_customization,
|
||||
allow_delete: this.allow_customization,
|
||||
allow_hiding: false,
|
||||
allow_edit: true,
|
||||
allow_resize: true,
|
||||
min_width: 4,
|
||||
max_widget_count: 2
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
this.wrapper = document.createElement('div');
|
||||
this.new('quick_list');
|
||||
|
||||
if (this.data && this.data.quick_list_name) {
|
||||
let has_data = this.make('quick_list', this.data.quick_list_name);
|
||||
if (!has_data) return;
|
||||
}
|
||||
|
||||
if (!this.readOnly) {
|
||||
$(this.wrapper).find('.widget').addClass('quick_list edit-mode');
|
||||
this.add_settings_button();
|
||||
this.add_new_block_button();
|
||||
}
|
||||
|
||||
return this.wrapper;
|
||||
}
|
||||
|
||||
validate(savedData) {
|
||||
if (!savedData.quick_list_name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
save() {
|
||||
return {
|
||||
quick_list_name: this.wrapper.getAttribute('quick_list_name'),
|
||||
col: this.get_col(),
|
||||
new: this.new_block_widget
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -325,6 +325,7 @@ frappe.views.Workspace = class Workspace {
|
|||
this.editor.configuration.tools.shortcut.config.page_data = this.page_data;
|
||||
this.editor.configuration.tools.card.config.page_data = this.page_data;
|
||||
this.editor.configuration.tools.onboarding.config.page_data = this.page_data;
|
||||
this.editor.configuration.tools.quick_list.config.page_data = this.page_data;
|
||||
this.editor.render({ blocks: this.content || [] });
|
||||
});
|
||||
} else {
|
||||
|
|
@ -1121,6 +1122,12 @@ frappe.views.Workspace = class Workspace {
|
|||
page_data: this.page_data || []
|
||||
}
|
||||
},
|
||||
quick_list: {
|
||||
class: this.blocks['quick_list'],
|
||||
config: {
|
||||
page_data: this.page_data || []
|
||||
}
|
||||
},
|
||||
spacer: this.blocks['spacer'],
|
||||
HeaderSize: frappe.workspace_block.tunes['header_size'],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export default class Widget {
|
|||
this.set_actions();
|
||||
this.set_body();
|
||||
this.setup_events();
|
||||
this.set_footer();
|
||||
}
|
||||
|
||||
get_config() {
|
||||
|
|
@ -196,4 +197,8 @@ export default class Widget {
|
|||
set_body() {
|
||||
//
|
||||
}
|
||||
|
||||
set_footer() {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
|
|||
247
frappe/public/js/frappe/widgets/quick_list_widget.js
Normal file
247
frappe/public/js/frappe/widgets/quick_list_widget.js
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
import Widget from "./base_widget.js";
|
||||
|
||||
frappe.provide("frappe.utils");
|
||||
|
||||
export default class QuickListWidget extends Widget {
|
||||
constructor(opts) {
|
||||
opts.shadow = true;
|
||||
super(opts);
|
||||
}
|
||||
|
||||
get_config() {
|
||||
return {
|
||||
document_type: this.document_type,
|
||||
label: this.label,
|
||||
quick_list_filter: this.quick_list_filter
|
||||
};
|
||||
}
|
||||
|
||||
set_actions() {
|
||||
if (this.in_customize_mode) return;
|
||||
|
||||
this.setup_add_new_button();
|
||||
this.setup_refresh_list_button();
|
||||
this.setup_filter_list_button();
|
||||
}
|
||||
|
||||
setup_add_new_button() {
|
||||
this.add_new_button = $(
|
||||
`<div class="add-new btn btn-xs pull-right" title="${__("Add New " + this.document_type)}">
|
||||
${frappe.utils.icon('add', 'sm')}
|
||||
</div>`
|
||||
);
|
||||
|
||||
this.add_new_button.appendTo(this.action_area);
|
||||
this.add_new_button.on("click", () => {
|
||||
frappe.set_route(
|
||||
frappe.utils.generate_route({type: 'doctype', name: this.document_type, doc_view: 'New'})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
setup_refresh_list_button() {
|
||||
this.refresh_list = $(
|
||||
`<div class="refresh-list btn btn-xs pull-right" title="${__("Refresh List")}">
|
||||
${frappe.utils.icon('refresh', 'sm')}
|
||||
</div>`
|
||||
);
|
||||
|
||||
this.refresh_list.appendTo(this.action_area);
|
||||
this.refresh_list.on("click", () => {
|
||||
this.body.empty();
|
||||
this.set_body();
|
||||
});
|
||||
}
|
||||
|
||||
setup_filter_list_button() {
|
||||
this.filter_list = $(
|
||||
`<div class="filter-list btn btn-xs pull-right" title="${__("Add/Update Filter")}">
|
||||
${frappe.utils.icon('filter', 'sm')}
|
||||
</div>`
|
||||
);
|
||||
|
||||
this.filter_list.appendTo(this.action_area);
|
||||
this.filter_list.on("click", () => this.setup_filter_dialog());
|
||||
}
|
||||
|
||||
setup_filter(doctype) {
|
||||
if (this.filter_group) {
|
||||
this.filter_group.wrapper.empty();
|
||||
delete this.filter_group;
|
||||
}
|
||||
|
||||
this.filters = frappe.utils.get_filter_from_json(this.quick_list_filter, doctype);
|
||||
|
||||
this.filter_group = new frappe.ui.FilterGroup({
|
||||
parent: this.dialog.get_field("filter_area").$wrapper,
|
||||
doctype: doctype,
|
||||
on_change: () => {},
|
||||
});
|
||||
|
||||
frappe.model.with_doctype(doctype, () => {
|
||||
this.filter_group.add_filters_to_filter_group(this.filters);
|
||||
this.dialog.set_df_property("filter_area", "hidden", false);
|
||||
});
|
||||
}
|
||||
|
||||
setup_filter_dialog() {
|
||||
let fields = [
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "filter_area"
|
||||
}
|
||||
];
|
||||
let me = this;
|
||||
this.dialog = new frappe.ui.Dialog({
|
||||
title: __("Set Filters for {0}", [this.document_type]),
|
||||
fields: fields,
|
||||
primary_action: function() {
|
||||
let old_filter = me.quick_list_filter;
|
||||
let filters = me.filter_group.get_filters();
|
||||
me.quick_list_filter = frappe.utils.get_filter_as_json(filters);
|
||||
|
||||
this.hide();
|
||||
|
||||
if (old_filter != me.quick_list_filter) {
|
||||
me.body.empty();
|
||||
me.set_body();
|
||||
}
|
||||
},
|
||||
primary_action_label: "Set"
|
||||
});
|
||||
|
||||
this.dialog.show();
|
||||
this.setup_filter(this.document_type);
|
||||
}
|
||||
|
||||
render_loading_state() {
|
||||
this.body.empty();
|
||||
this.loading = $(
|
||||
`<div class="list-loading-state text-muted">${__(
|
||||
"Loading..."
|
||||
)}</div>`
|
||||
);
|
||||
this.loading.appendTo(this.body);
|
||||
}
|
||||
|
||||
render_no_data_state() {
|
||||
this.loading = $(
|
||||
`<div class="list-no-data-state text-muted">${__(
|
||||
"No Data..."
|
||||
)}</div>`
|
||||
);
|
||||
this.loading.appendTo(this.body);
|
||||
}
|
||||
|
||||
setup_quick_list_item(doc) {
|
||||
const indicator = frappe.get_indicator(doc, this.document_type);
|
||||
|
||||
let $quick_list_item = $(`
|
||||
<div class="quick-list-item">
|
||||
<div class="ellipsis left">
|
||||
<div class="ellipsis title"
|
||||
title="${strip_html(doc[this.title_field_name])}">
|
||||
${strip_html(doc[this.title_field_name])}
|
||||
</div>
|
||||
<div class="timestamp text-muted">
|
||||
${frappe.datetime.prettyDate(doc.modified)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
if (indicator) {
|
||||
$(`
|
||||
<div class="status indicator-pill ${indicator[1]} ellipsis">
|
||||
${__(indicator[0])}
|
||||
</div>
|
||||
`).appendTo($quick_list_item);
|
||||
}
|
||||
|
||||
$(`<div class="right-arrow">${frappe.utils.icon('right', 'xs')}</div>`).appendTo($quick_list_item);
|
||||
|
||||
$quick_list_item.click(() => {
|
||||
frappe.set_route(`${frappe.utils.get_form_link(this.document_type, doc.name)}`);
|
||||
});
|
||||
|
||||
return $quick_list_item;
|
||||
}
|
||||
|
||||
set_body() {
|
||||
this.widget.addClass("quick-list-widget-box");
|
||||
|
||||
this.render_loading_state();
|
||||
|
||||
frappe.model.with_doctype(this.document_type, () => {
|
||||
let fields = ['name'];
|
||||
|
||||
// get name of title field
|
||||
if (!this.title_field_name) {
|
||||
let meta = frappe.get_meta(this.document_type);
|
||||
this.title_field_name = meta && meta.title_field || 'name';
|
||||
}
|
||||
|
||||
if (this.title_field_name && this.title_field_name != 'name') {
|
||||
fields.push(this.title_field_name);
|
||||
}
|
||||
|
||||
// check doctype has status field
|
||||
this.has_status_field = frappe.meta.has_field(this.document_type, 'status');
|
||||
|
||||
if (this.has_status_field) {
|
||||
fields.push('status');
|
||||
fields.push('docstatus');
|
||||
}
|
||||
|
||||
fields.push('modified');
|
||||
|
||||
let quick_list_filter = frappe.utils.get_filter_from_json(this.quick_list_filter);
|
||||
|
||||
let args = {
|
||||
method: 'frappe.desk.reportview.get',
|
||||
args: {
|
||||
doctype: this.document_type,
|
||||
fields: fields,
|
||||
filters: quick_list_filter,
|
||||
order_by: 'modified desc',
|
||||
start: 0,
|
||||
page_length: 4
|
||||
}
|
||||
};
|
||||
|
||||
frappe.call(args).then((r) => {
|
||||
if (!r.message) return;
|
||||
let data = r.message;
|
||||
|
||||
this.body.empty();
|
||||
data = !Array.isArray(data)
|
||||
? frappe.utils.dict(data.keys, data.values)
|
||||
: data;
|
||||
|
||||
if (!data.length) {
|
||||
this.render_no_data_state();
|
||||
return;
|
||||
}
|
||||
|
||||
this.quick_list = data.map(doc => this.setup_quick_list_item(doc));
|
||||
this.quick_list.forEach($quick_list_item => $quick_list_item.appendTo(this.body));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
set_footer() {
|
||||
if (!this.see_all_button) {
|
||||
this.see_all_button = $(`
|
||||
<div class="see-all btn">See all</div>
|
||||
`).appendTo(this.footer);
|
||||
|
||||
this.see_all_button.click(() => {
|
||||
let filters = frappe.utils.get_filter_from_json(this.quick_list_filter);
|
||||
if (filters) {
|
||||
frappe.route_options = filters;
|
||||
}
|
||||
frappe.set_route(frappe.utils.generate_route({type: 'doctype', name: this.document_type}));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ export default class ShortcutWidget extends Widget {
|
|||
doc_view: this.doc_view
|
||||
});
|
||||
|
||||
let filters = this.get_doctype_filter();
|
||||
let filters = frappe.utils.get_filter_from_json(this.stats_filter);
|
||||
if (this.type == "DocType" && filters) {
|
||||
frappe.route_options = filters;
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@ export default class ShortcutWidget extends Widget {
|
|||
|
||||
this.widget.addClass("shortcut-widget-box");
|
||||
|
||||
let filters = this.get_doctype_filter();
|
||||
let filters = frappe.utils.get_filter_from_json(this.stats_filter);
|
||||
if (this.type == "DocType" && filters) {
|
||||
frappe.db
|
||||
.count(this.link_to, {
|
||||
|
|
@ -59,15 +59,6 @@ export default class ShortcutWidget extends Widget {
|
|||
}
|
||||
}
|
||||
|
||||
get_doctype_filter() {
|
||||
let count_filter = new Function(`return ${this.stats_filter}`)();
|
||||
if (count_filter) {
|
||||
return count_filter;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
set_count(count) {
|
||||
const get_label = () => {
|
||||
if (this.format) {
|
||||
|
|
|
|||
|
|
@ -75,13 +75,7 @@ class WidgetDialog {
|
|||
|
||||
this.filters = [];
|
||||
|
||||
if (this.values && this.values.stats_filter) {
|
||||
const filters_json = new Function(`return ${this.values.stats_filter}`)();
|
||||
this.filters = Object.keys(filters_json).map((filter) => {
|
||||
let val = filters_json[filter];
|
||||
return [this.values.link_to, filter, val[0], val[1], false];
|
||||
});
|
||||
}
|
||||
this.generate_filter_from_json();
|
||||
|
||||
this.filter_group = new frappe.ui.FilterGroup({
|
||||
parent: this.dialog.get_field("filter_area").$wrapper,
|
||||
|
|
@ -124,6 +118,74 @@ class ChartDialog extends WidgetDialog {
|
|||
return data;
|
||||
}
|
||||
}
|
||||
class QuickListDialog extends WidgetDialog {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
}
|
||||
|
||||
get_fields() {
|
||||
return [
|
||||
{
|
||||
fieldtype: "Link",
|
||||
fieldname: "document_type",
|
||||
label: "DocType",
|
||||
options: "DocType",
|
||||
reqd: 1,
|
||||
onchange: () => {
|
||||
this.document_type = this.dialog.get_value("document_type");
|
||||
this.document_type && this.setup_filter(this.document_type);
|
||||
},
|
||||
get_query: () => {
|
||||
return {
|
||||
filters: {
|
||||
issingle: 0,
|
||||
istable: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldtype: "Column Break",
|
||||
fieldname: "column_break_4",
|
||||
},
|
||||
{
|
||||
fieldtype: "Data",
|
||||
fieldname: "label",
|
||||
label: "Label",
|
||||
},
|
||||
{
|
||||
fieldtype: "Section Break",
|
||||
fieldname: "filter_section",
|
||||
label: __('Add Filters'),
|
||||
depends_on: 'eval: doc.document_type'
|
||||
},
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "filter_area_loading",
|
||||
},
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "filter_area"
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
generate_filter_from_json() {
|
||||
if (this.values && this.values.quick_list_filter) {
|
||||
this.filters = frappe.utils.get_filter_from_json(this.values.quick_list_filter, this.values.document_type);
|
||||
}
|
||||
}
|
||||
|
||||
process_data(data) {
|
||||
if (this.filter_group) {
|
||||
let filters = this.filter_group.get_filters();
|
||||
data.quick_list_filter = frappe.utils.get_filter_as_json(filters);
|
||||
}
|
||||
|
||||
data.label = data.label ? data.label : data.document_type;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class OnboardingDialog extends WidgetDialog {
|
||||
constructor(opts) {
|
||||
|
|
@ -410,20 +472,17 @@ class ShortcutDialog extends WidgetDialog {
|
|||
});
|
||||
}
|
||||
|
||||
generate_filter_from_json() {
|
||||
if (this.values && this.values.stats_filter) {
|
||||
this.filters = frappe.utils.get_filter_from_json(this.values.stats_filter, this.values.link_to);
|
||||
}
|
||||
}
|
||||
|
||||
process_data(data) {
|
||||
|
||||
if (this.dialog.get_value("type") == "DocType" && this.filter_group) {
|
||||
let filters = this.filter_group.get_filters();
|
||||
let stats_filter = null;
|
||||
|
||||
if (filters.length) {
|
||||
stats_filter = {};
|
||||
filters.forEach((arr) => {
|
||||
stats_filter[arr[1]] = [arr[2], arr[3]];
|
||||
});
|
||||
stats_filter = JSON.stringify(stats_filter);
|
||||
}
|
||||
data.stats_filter = stats_filter;
|
||||
data.stats_filter = frappe.utils.get_filter_as_json(filters);
|
||||
}
|
||||
|
||||
data.label = data.label
|
||||
|
|
@ -579,7 +638,8 @@ export default function get_dialog_constructor(type) {
|
|||
shortcut: ShortcutDialog,
|
||||
number_card: NumberCardDialog,
|
||||
links: CardDialog,
|
||||
onboarding: OnboardingDialog
|
||||
onboarding: OnboardingDialog,
|
||||
quick_list: QuickListDialog
|
||||
};
|
||||
|
||||
return widget_map[type] || WidgetDialog;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import LinksWidget from "../widgets/links_widget";
|
|||
import OnboardingWidget from "../widgets/onboarding_widget";
|
||||
import NewWidget from "../widgets/new_widget";
|
||||
import NumberCardWidget from "../widgets/number_card_widget";
|
||||
import QuickListWidget from "../widgets/quick_list_widget";
|
||||
|
||||
frappe.provide("frappe.widget");
|
||||
|
||||
|
|
@ -15,6 +16,7 @@ frappe.widget.widget_factory = {
|
|||
links: LinksWidget,
|
||||
onboarding: OnboardingWidget,
|
||||
number_card: NumberCardWidget,
|
||||
quick_list: QuickListWidget
|
||||
};
|
||||
|
||||
frappe.widget.make_widget = (opts) => {
|
||||
|
|
|
|||
|
|
@ -218,6 +218,10 @@
|
|||
|
||||
--awesomplete-hover-bg: var(--control-bg);
|
||||
|
||||
// Button Colors
|
||||
--btn-default-bg: var(--gray-100);
|
||||
--btn-default-hover-bg: var(--gray-300);
|
||||
|
||||
// Other Colors
|
||||
--sidebar-select-color: var(--gray-200);
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@
|
|||
--toast-bg: var(--modal-bg);
|
||||
--popover-bg: var(--bg-color);
|
||||
|
||||
// Button Colors
|
||||
--btn-default-bg: var(--gray-700);
|
||||
--btn-default-hover-bg: var(--gray-500);
|
||||
|
||||
// Background Text Color Pairs
|
||||
--bg-blue: var(--blue-600);
|
||||
--bg-light-blue: var(--blue-400);
|
||||
|
|
|
|||
|
|
@ -716,6 +716,75 @@ body {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.quick-list-widget-box {
|
||||
.list-loading-state, .list-no-data-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 202px;
|
||||
}
|
||||
|
||||
.refresh-list, .filter-list, .add-new {
|
||||
background-color: var(--btn-default-bg);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--btn-default-hover-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.widget-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 6px 0px;
|
||||
min-height: 202px;
|
||||
|
||||
.quick-list-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 3px 0px;
|
||||
padding: 3px 6px;
|
||||
border-radius: var(--border-radius);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--btn-default-bg);
|
||||
}
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
margin-left: 3px;
|
||||
|
||||
.timestamp {
|
||||
font-size: smaller;
|
||||
}
|
||||
}
|
||||
|
||||
.status {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.right-arrow {
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.widget-footer {
|
||||
.see-all {
|
||||
background-color: var(--btn-default-bg);
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--btn-default-hover-bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.onboarding-success {
|
||||
|
|
@ -1002,7 +1071,7 @@ body {
|
|||
transition: visibility 0s, opacity 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.link-item {
|
||||
.link-item, .quick-list-item, .see-all {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue