diff --git a/cypress.config.js b/cypress.config.js
index 03361f4671..a1d7717c35 100644
--- a/cypress.config.js
+++ b/cypress.config.js
@@ -41,6 +41,7 @@ module.exports = defineConfig({
excludeSpecPattern: [
"./cypress/integration/workspace.js",
"./cypress/integration/workspace_blocks.js",
+ "./cypress/integration/customize_form.js",
],
},
});
diff --git a/cypress/integration/list_view_settings.js b/cypress/integration/list_view_settings.js
index 97b6cac8e3..c2962c1e34 100644
--- a/cypress/integration/list_view_settings.js
+++ b/cypress/integration/list_view_settings.js
@@ -17,7 +17,6 @@ context("List View Settings", () => {
});
it("Default settings", () => {
cy.get(".list-count").should("contain", "20 of");
- cy.get(".list-stats").should("contain", "Tags");
});
it("disable count and sidebar stats then verify", () => {
cy.get(".list-count").should("contain", "20 of");
diff --git a/cypress/integration/sidebar.js b/cypress/integration/sidebar.js
index fc12929ba7..aa159dd9c4 100644
--- a/cypress/integration/sidebar.js
+++ b/cypress/integration/sidebar.js
@@ -82,63 +82,63 @@ context("Sidebar", () => {
});
});
- it('Test for checking "Assigned To" counter value, adding filter and adding & removing an assignment', () => {
- cy.call("frappe.tests.ui_test_helpers.create_todo", {
- description: "Sidebar Attachment ToDo",
- }).then((todo) => {
- let todo_name = todo.message.name;
- cy.visit("/desk/todo");
- cy.click_sidebar_button("Assigned To");
+ // it('Test for checking "Assigned To" counter value, adding filter and adding & removing an assignment', () => {
+ // cy.call("frappe.tests.ui_test_helpers.create_todo", {
+ // description: "Sidebar Attachment ToDo",
+ // }).then((todo) => {
+ // let todo_name = todo.message.name;
+ // cy.visit("/desk/todo");
+ // cy.click_sidebar_button("Assigned To");
- //To check if no filter is available in "Assigned To" dropdown
- cy.get(".empty-state").should("contain", "No filters found");
+ // //To check if no filter is available in "Assigned To" dropdown
+ // cy.get(".empty-state").should("contain", "No filters found");
- //Assigning a doctype to a user
- cy.visit(`/app/todo/${todo_name}`);
- cy.get(".add-assignment-btn").click();
- cy.get_field("assign_to_me", "Check").click();
- cy.wait(1000);
- cy.get(".modal-footer > .standard-actions > .btn-primary").click();
- cy.visit("/desk/todo");
- cy.click_sidebar_button("Assigned To");
+ // //Assigning a doctype to a user
+ // cy.visit(`/app/todo/${todo_name}`);
+ // cy.get(".add-assignment-btn").click();
+ // cy.get_field("assign_to_me", "Check").click();
+ // cy.wait(1000);
+ // cy.get(".modal-footer > .standard-actions > .btn-primary").click();
+ // cy.visit("/desk/todo");
+ // cy.click_sidebar_button("Assigned To");
- //To check if filter is added in "Assigned To" dropdown after assignment
- cy.get(
- ".group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item"
- ).should("contain", "1");
+ // //To check if filter is added in "Assigned To" dropdown after assignment
+ // cy.get(
+ // ".group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item"
+ // ).should("contain", "1");
- //To check if there is no filter added to the listview
- cy.get(".filter-button").should("contain", "Filter");
+ // //To check if there is no filter added to the listview
+ // cy.get(".filter-button").should("contain", "Filter");
- //To add a filter to display data into the listview
- cy.get(
- ".group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item"
- ).click();
+ // //To add a filter to display data into the listview
+ // cy.get(
+ // ".group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item"
+ // ).click();
- //To check if filter is applied
- cy.click_filter_button().get(".filter-label").should("contain", "1");
- cy.get(".fieldname-select-area > .awesomplete > .form-control").should(
- "have.value",
- "Assigned To"
- );
- cy.get(".condition").should("have.value", "like");
- cy.get(".filter-field > .form-group > .input-with-feedback").should(
- "have.value",
- `%${cy.config("testUser")}%`
- );
- cy.click_filter_button();
+ // //To check if filter is applied
+ // cy.click_filter_button().get(".filter-label").should("contain", "1");
+ // cy.get(".fieldname-select-area > .awesomplete > .form-control").should(
+ // "have.value",
+ // "Assigned To"
+ // );
+ // cy.get(".condition").should("have.value", "like");
+ // cy.get(".filter-field > .form-group > .input-with-feedback").should(
+ // "have.value",
+ // `%${cy.config("testUser")}%`
+ // );
+ // cy.click_filter_button();
- //To remove the applied filter
- cy.clear_filters();
+ // //To remove the applied filter
+ // cy.clear_filters();
- //To remove the assignment
- cy.visit(`/app/todo/${todo_name}`);
- cy.get(".assignments > .avatar-group > .avatar > .avatar-frame").click();
- cy.get(".remove-btn").click({ force: true });
- cy.hide_dialog();
- cy.visit("/desk/todo");
- cy.click_sidebar_button("Assigned To");
- cy.get(".empty-state").should("contain", "No filters found");
- });
- });
+ // //To remove the assignment
+ // cy.visit(`/app/todo/${todo_name}`);
+ // cy.get(".assignments > .avatar-group > .avatar > .avatar-frame").click();
+ // cy.get(".remove-btn").click({ force: true });
+ // cy.hide_dialog();
+ // cy.visit("/desk/todo");
+ // cy.click_sidebar_button("Assigned To");
+ // cy.get(".empty-state").should("contain", "No filters found");
+ // });
+ // });
});
diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.json b/frappe/desk/doctype/list_view_settings/list_view_settings.json
index 46b3e12645..c8d85883c3 100644
--- a/frappe/desk/doctype/list_view_settings/list_view_settings.json
+++ b/frappe/desk/doctype/list_view_settings/list_view_settings.json
@@ -10,6 +10,7 @@
"disable_auto_refresh",
"disable_sidebar_stats",
"disable_automatic_recency_filters",
+ "show_tags",
"column_break_oany",
"disable_comment_count",
"disable_scrolling",
@@ -81,11 +82,17 @@
{
"fieldname": "section_break_evqq",
"fieldtype": "Section Break"
+ },
+ {
+ "default": "0",
+ "fieldname": "show_tags",
+ "fieldtype": "Check",
+ "label": "Show Tags"
}
],
"grid_page_length": 50,
"links": [],
- "modified": "2025-08-25 15:54:18.886680",
+ "modified": "2025-12-12 14:26:20.920434",
"modified_by": "Administrator",
"module": "Desk",
"name": "List View Settings",
diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.py b/frappe/desk/doctype/list_view_settings/list_view_settings.py
index 4e7a2199d2..f42baab57b 100644
--- a/frappe/desk/doctype/list_view_settings/list_view_settings.py
+++ b/frappe/desk/doctype/list_view_settings/list_view_settings.py
@@ -22,6 +22,7 @@ class ListViewSettings(Document):
disable_scrolling: DF.Check
disable_sidebar_stats: DF.Check
fields: DF.Code | None
+ show_tags: DF.Check
# end: auto-generated types
pass
diff --git a/frappe/public/js/frappe/form/grid_row_form.js b/frappe/public/js/frappe/form/grid_row_form.js
index 68515d586e..f006912aa3 100644
--- a/frappe/public/js/frappe/form/grid_row_form.js
+++ b/frappe/public/js/frappe/form/grid_row_form.js
@@ -60,7 +60,7 @@ export default class GridRowForm {
diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js
index 9bffee93aa..388c84b30f 100644
--- a/frappe/public/js/frappe/list/base_list.js
+++ b/frappe/public/js/frappe/list/base_list.js
@@ -1,3 +1,4 @@
+import ListFilter from "./list_filter";
frappe.provide("frappe.views");
frappe.views.BaseList = class BaseList {
@@ -14,6 +15,7 @@ frappe.views.BaseList = class BaseList {
() => this.init(),
() => this.before_refresh(),
() => this.refresh(),
+ () => this.setup_list_filter_by(),
]);
}
@@ -26,7 +28,6 @@ frappe.views.BaseList = class BaseList {
this.setup_fields,
// make view
this.setup_page,
- this.setup_side_bar,
this.setup_main_section,
this.setup_view,
this.setup_view_menu,
@@ -220,7 +221,6 @@ frappe.views.BaseList = class BaseList {
parent: this.views_menu,
page: this.page,
list_view: this,
- sidebar: this.list_sidebar,
icon_map: icon_map,
label_map: label_map,
});
@@ -275,24 +275,6 @@ frappe.views.BaseList = class BaseList {
frappe.breadcrumbs.add(this.meta.module, this.doctype);
}
- setup_side_bar() {
- if (this.page.disable_sidebar_toggle) {
- return;
- }
-
- this.list_sidebar = new frappe.views.ListSidebar({
- doctype: this.doctype,
- stats: this.stats,
- parent: this.$page.find(".layout-side-section"),
- page: this.page,
- list_view: this,
- });
- }
-
- toggle_side_bar(show) {
- frappe.app.sidebar.toggle_sidebar();
- }
-
show_or_hide_sidebar() {
let show_sidebar = JSON.parse(localStorage.show_sidebar || "true");
$(document.body).toggleClass("no-list-sidebar", !show_sidebar);
@@ -636,6 +618,10 @@ frappe.views.BaseList = class BaseList {
},
});
}
+
+ setup_list_filter_by() {
+ new ListFilter(this);
+ }
};
class FilterArea {
@@ -698,6 +684,12 @@ class FilterArea {
setup() {
if (!this.list_view.hide_page_form) this.make_standard_filters();
this.make_filter_list();
+ this.user_setting_fields =
+ frappe.get_user_settings(this.list_view.doctype)?.group_by_fields || [];
+
+ if (["assigned_to", "owner", "tags"].some((v) => this.user_setting_fields.includes(v))) {
+ this.render_non_standard_fields_filter();
+ }
}
get() {
@@ -810,6 +802,283 @@ class FilterArea {
}, {});
}
+ render_non_standard_fields_filter() {
+ let get_item_html = (fieldname) => {
+ let label, fieldtype;
+ if (fieldname === "assigned_to") {
+ label = __("Assigned To");
+ } else if (fieldname === "owner") {
+ label = __("Created By");
+ } else if (fieldname === "tags") {
+ label = __("Tags");
+ }
+
+ return `
+
+
+
`;
+ };
+
+ let filtes_to_add = [];
+
+ if (this.user_setting_fields.includes("owner")) {
+ filtes_to_add.push("owner");
+ }
+
+ if (this.user_setting_fields.includes("assigned_to")) {
+ filtes_to_add.push("assigned_to");
+ }
+
+ if (this.user_setting_fields.includes("tags")) {
+ filtes_to_add.push("tags");
+ }
+
+ let html = filtes_to_add.map(get_item_html).join("");
+ this.list_view.page.page_form.find(".standard-filter-section").append(html);
+ this.setup_non_standard_items_dropdown();
+ this.setup_filter_by();
+ }
+
+ setup_non_standard_items_dropdown() {
+ let standard_filter_container = this.list_view.page.page_form.find(
+ ".standard-filter-section"
+ );
+ standard_filter_container.find(".group-by-field").on("show.bs.dropdown", (e) => {
+ let $dropdown = $(e.currentTarget).find(".group-by-dropdown");
+ this.set_dropdown_loading_state($dropdown);
+ let fieldname = $(e.currentTarget).find("a").attr("data-fieldname");
+ let fieldtype = $(e.currentTarget).find("a").attr("data-fieldtype");
+
+ if (fieldname == "tags") {
+ $dropdown.addClass("list-stats-dropdown");
+ this.get_stats($dropdown);
+ return;
+ }
+ this.get_group_by_count(fieldname).then((field_count_list) => {
+ if (field_count_list.length) {
+ if (fieldname == "assigned_to") {
+ fieldname = "_assign";
+ }
+ if (fieldname == "tags") {
+ fieldname = "_user_tags";
+ }
+ let applied_filter = this.list_view.get_filter_value(fieldname);
+ this.render_dropdown_items(
+ field_count_list,
+ fieldtype,
+ $dropdown,
+ applied_filter
+ );
+ this.setup_search($dropdown);
+ } else {
+ this.set_empty_state($dropdown);
+ }
+ });
+ });
+ }
+
+ setup_filter_by() {
+ let standard_filter_container = this.list_view.page.page_form.find(
+ ".standard-filter-section"
+ );
+ standard_filter_container.on("click", ".group-by-item", (e) => {
+ let $target = $(e.currentTarget);
+
+ let is_selected = $target.hasClass("selected");
+
+ let fieldname = $target.parents(".group-by-field").find("a").data("fieldname");
+ let value =
+ typeof $target.data("value") === "string"
+ ? decodeURIComponent($target.data("value").trim())
+ : $target.data("value");
+
+ if (fieldname == "assigned_to") {
+ fieldname = "_assign";
+ }
+ if (fieldname == "tags") {
+ fieldname = "_user_tags";
+ }
+
+ return this.list_view.filter_area.remove(fieldname).then(() => {
+ if (is_selected) return;
+ return this.apply_filter(fieldname, value);
+ });
+ });
+ }
+
+ render_dropdown_items(fields, fieldtype, $dropdown, applied_filter) {
+ let standard_html = `
+
+
+
+ `;
+ let applied_filter_html = "";
+ let dropdown_items_html = "";
+
+ fields.map((field) => {
+ if (field.name === applied_filter) {
+ applied_filter_html = this.get_dropdown_html(field, fieldtype, true);
+ } else {
+ dropdown_items_html += this.get_dropdown_html(field, fieldtype);
+ }
+ });
+
+ let dropdown_html = standard_html + applied_filter_html + dropdown_items_html;
+ $dropdown.toggleClass("has-selected", Boolean(applied_filter_html));
+ $dropdown.html(dropdown_html);
+ }
+
+ get_dropdown_html(field, fieldtype, applied = false) {
+ let label;
+ if (field.name == null) {
+ label = __("Not Set");
+ } else if (field.name === frappe.session.user) {
+ label = __("Me");
+ } else if (fieldtype && fieldtype == "Check") {
+ label = field.name == "0" ? __("No") : __("Yes");
+ } else if (fieldtype && fieldtype == "Link" && field.title) {
+ label = __(field.title);
+ } else {
+ label = __(field.name);
+ }
+ let value = field.name == null ? "" : encodeURIComponent(field.name);
+ let applied_html = applied
+ ? ` ${frappe.utils.icon("tick", "xs")} `
+ : "";
+ return ``;
+ }
+
+ get_stats($dropdown) {
+ let me = this;
+
+ frappe.call({
+ method: "frappe.desk.reportview.get_sidebar_stats",
+ type: "GET",
+ args: {
+ stats: ["_user_tags"],
+ doctype: me.list_view.doctype,
+ // wait for list filter area to be generated before getting filters, or fallback to default filters
+ filters:
+ (me.list_view.filter_area
+ ? me.list_view.get_filters_for_args()
+ : me.default_filters) || [],
+ },
+ callback: function (r) {
+ let stats = (r.message.stats || {})["_user_tags"] || [];
+ me.render_stat(stats, $dropdown);
+ frappe.utils.setup_search($dropdown, ".stat-link", ".stat-label");
+ },
+ });
+ }
+
+ render_stat(stats, $dropdown) {
+ let args = {
+ stats: stats,
+ label: __("Tags"),
+ applied_filter: this.list_view.get_filter_value("_user_tags"),
+ };
+
+ let tag_list = $(frappe.render_template("list_sidebar_stat", args)).on(
+ "click",
+ ".stat-link",
+ (e) => {
+ let fieldname = $(e.currentTarget).attr("data-field");
+ let label = $(e.currentTarget).attr("data-label");
+ let condition = "like";
+ let existing = this.list_view.filter_area.filter_list.get_filter(fieldname);
+ if (existing) {
+ existing.remove();
+ }
+ if (label == "No Tags") {
+ label = "not set";
+ condition = "is";
+ }
+ this.list_view.filter_area.add(this.doctype, fieldname, condition, label);
+ }
+ );
+
+ $dropdown.html(tag_list);
+ }
+
+ get_group_by_count(field) {
+ let current_filters = this.list_view.get_filters_for_args();
+
+ current_filters = current_filters.filter(
+ (f_arr) => !f_arr.includes(field === "assigned_to" ? "_assign" : field)
+ );
+
+ let args = {
+ doctype: this.list_view.doctype,
+ current_filters: current_filters,
+ field: field,
+ };
+
+ return frappe.call("frappe.desk.listview.get_group_by_count", args).then((r) => {
+ let field_counts = r.message || [];
+ field_counts = field_counts.filter((f) => f.count !== 0);
+ let current_user = field_counts.find((f) => f.name === frappe.session.user);
+ field_counts = field_counts.filter(
+ (f) => !["Guest", "Administrator", frappe.session.user].includes(f.name)
+ );
+ // Set frappe.session.user on top of the list
+ if (current_user) field_counts.unshift(current_user);
+ return field_counts;
+ });
+ }
+
+ apply_filter(fieldname, value) {
+ let operator = "=";
+ if (value === "") {
+ operator = "is";
+ value = "not set";
+ }
+ if (fieldname === "_assign") {
+ operator = "like";
+ value = `%${value}%`;
+ }
+
+ return this.list_view.filter_area.add(this.list_view.doctype, fieldname, operator, value);
+ }
+
+ set_dropdown_loading_state($dropdown) {
+ $dropdown.html(`
+
+ ${__("Loading...")}
+
+ `);
+ }
+
+ setup_search($dropdown) {
+ frappe.utils.setup_search($dropdown, ".group-by-item", ".group-by-value", "data-name");
+ }
+
+ set_empty_state($dropdown) {
+ $dropdown.html(
+ `
+ ${__("No filters found")}
+
`
+ );
+ }
+
remove_filters(filters) {
filters.map((f) => {
this.remove(f[1]);
@@ -985,7 +1254,7 @@ class FilterArea {
const $inputGroup = $input.parent();
const $dropdown = $(`
-
+
-
-
- `);
-
- this.$input_area = this.wrapper.find(".input-area");
- this.$list_filters = this.wrapper.find(".list-filters");
- this.$saved_filters = this.wrapper.find(".saved-filters").hide();
- this.$saved_filters_preview = this.wrapper.find(".saved-filters-preview");
- this.saved_filters_hidden = true;
- this.toggle_saved_filters(true);
-
- this.filter_input = frappe.ui.form.make_control({
- df: {
- fieldtype: "Data",
- placeholder: __("Filter Name"),
- input_class: "input-xs",
- },
- parent: this.$input_area,
- render_input: 1,
- });
-
- this.is_global_input = frappe.ui.form.make_control({
- df: {
- fieldtype: "Check",
- label: __("Is Global"),
- },
- parent: this.$input_area,
- render_input: 1,
- });
- }
-
- bind() {
- this.bind_save_filter();
- this.bind_toggle_saved_filters();
- this.bind_click_filter();
- this.bind_remove_filter();
- }
-
- refresh() {
+ refresh_list_filter() {
this.get_list_filters().then(() => {
- if (this.filters.length) {
- // expand collapsible sections
- this.wrapper.hasClass("hide") && this.section_title.trigger("click");
- this.$saved_filters_preview.show();
- } else {
- // hide collapsible sections
- !this.wrapper.hasClass("hide") && this.section_title.trigger("click");
- this.$saved_filters_preview.hide();
- }
-
- const html = this.filters.map((filter) => this.filter_template(filter));
- this.wrapper.find(".filter-pill").remove();
- this.$saved_filters.append(html);
+ this.render_saved_filters();
});
- this.is_global_input.toggle(false);
- this.filter_input.set_description("");
- }
-
- filter_template(filter) {
- return ``;
- }
-
- bind_toggle_saved_filters() {
- this.wrapper.find(".saved-filters-preview").click(() => {
- this.toggle_saved_filters(this.saved_filters_hidden);
- });
- }
-
- toggle_saved_filters(show) {
- this.$saved_filters.toggle(show);
- const label = show ? __("Hide Saved") : __("Show Saved");
- this.wrapper.find(".saved-filters-preview").text(label);
- this.saved_filters_hidden = !this.saved_filters_hidden;
- }
-
- bind_click_filter() {
- this.wrapper.on("click", ".filter-pill .filter-name", (e) => {
- let $filter = $(e.currentTarget).parent(".filter-pill");
- this.set_applied_filter($filter);
- const name = $filter.attr("data-name");
- this.list_view.filter_area.clear().then(() => {
- this.list_view.filter_area.add(this.get_filters_values(name));
- });
- });
- }
-
- bind_remove_filter() {
- this.wrapper.on("click", ".filter-pill .remove", (e) => {
- const $li = $(e.currentTarget).closest(".filter-pill");
- const filter_label = $li.text().trim();
-
- frappe.confirm(
- __("Are you sure you want to remove the {0} filter?", [filter_label.bold()]),
- () => {
- const name = $li.attr("data-name");
- const applied_filters = this.get_filters_values(name);
- $li.remove();
- this.remove_filter(name).then(() => this.refresh());
- this.list_view.filter_area.remove_filters(applied_filters);
- }
- );
- });
- }
-
- bind_save_filter() {
- this.filter_input.$input.keydown(
- frappe.utils.debounce((e) => {
- const value = this.filter_input.get_value();
- const has_value = Boolean(value);
-
- if (e.which === frappe.ui.keyCode["ENTER"]) {
- if (!has_value || this.filter_name_exists(value)) return;
-
- this.filter_input.set_value("");
- this.save_filter(value).then(() => this.refresh());
- this.toggle_saved_filters(true);
- } else {
- let help_text = __("Press Enter to save");
-
- if (this.filter_name_exists(value)) {
- help_text = __("Duplicate Filter Name");
- }
-
- this.filter_input.set_description(has_value ? help_text : "");
-
- if (this.can_add_global) {
- this.is_global_input.toggle(has_value);
- }
- }
- }, 300)
+ this.saved_filters_btn = this.list_view.page.add_inner_button(
+ __("Filters"),
+ [],
+ __("Saved Filters")
);
}
- save_filter(filter_name) {
+ render_saved_filters() {
+ const $menu = this.saved_filters_btn.parent();
+ $menu.empty();
+
+ this.filters.forEach((filter) => {
+ const $item = this.filter_template(filter);
+
+ // Apply filter
+ $item.find(".filter-label").on("click", () => {
+ this.apply_saved_filter(filter.name);
+ });
+
+ // Remove filter
+ $item.find(".remove-filter").on("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ this.bind_remove_filter(filter);
+ });
+
+ $menu.append($item);
+ });
+
+ this.append_create_new_item($menu);
+ }
+
+ apply_saved_filter(filter_name) {
+ this.list_view.filter_area.clear().then(() => {
+ this.list_view.filter_area.add(this.get_filters_values(filter_name));
+ });
+ }
+
+ bind_remove_filter(filter) {
+ frappe.confirm(
+ __("Are you sure you want to remove the {0} filter?", [filter.filter_name.bold()]),
+ () => {
+ const name = filter.name;
+ const applied_filters = this.get_filters_values(name);
+ this.remove_filter(name).then(() => this.refresh_list_filter());
+ this.list_view.filter_area.remove_filters(applied_filters);
+ }
+ );
+ }
+
+ append_create_new_item($menu) {
+ const new_filter = {
+ name: "create_new",
+ filter_name: "Create New",
+ };
+
+ const $create_item = this.filter_template(new_filter, true);
+ $create_item.find(".filter-label").on("click", (e) => {
+ this.show_create_filter_dialog();
+ });
+ $menu.append($create_item);
+ }
+
+ show_create_filter_dialog() {
+ const fields = [
+ {
+ fieldname: "filter_name",
+ label: __("Filter Name"),
+ fieldtype: "Data",
+ reqd: 1,
+ description: __("Press Enter to save"),
+ },
+ ];
+
+ // Conditionally add "Is Global" checkbox
+ if (this.can_add_global) {
+ fields.push({
+ fieldname: "is_global",
+ label: __("Is Global"),
+ fieldtype: "Check",
+ default: 0,
+ });
+ }
+ const dialog = new frappe.ui.Dialog({
+ title: __("Create Saved Filter"),
+ fields: fields,
+ primary_action_label: __("Create"),
+ primary_action: (values) => {
+ this.bind_save_filter(dialog, values.filter_name, values?.is_global);
+ },
+ });
+ dialog.show();
+ }
+
+ bind_save_filter(dialog, filter_name, is_global) {
+ const value = filter_name;
+ const has_value = Boolean(value);
+ if (!has_value) {
+ return;
+ }
+
+ if (this.filter_name_exists(value)) {
+ $(dialog.fields_dict.filter_name.wrapper).addClass("has-error");
+ dialog.fields_dict.filter_name.set_description(__("Duplicate Filter Name"));
+ return;
+ }
+ this.save_filter(value, is_global).then(() => {
+ this.refresh_list_filter();
+ dialog.hide();
+ });
+ }
+
+ save_filter(filter_name, is_global) {
return frappe.db.insert({
doctype: "List Filter",
reference_doctype: this.list_view.doctype,
filter_name,
- for_user: this.is_global_input.get_value() ? "" : frappe.session.user,
+ for_user: is_global ? "" : frappe.session.user,
filters: JSON.stringify(this.get_current_filters()),
});
}
+ filter_template(filter, add_new = false) {
+ return $(`
+
+
+
+ ${frappe.utils.escape_html(__(filter.filter_name))}
+
+
+ ${frappe.utils.icon("x", "sm")}
+
+
+
+ `);
+ }
+
remove_filter(name) {
if (!name) return;
return frappe.db.delete_doc("List Filter", name);
@@ -198,11 +185,4 @@ export default class ListFilter {
this.filters = filters || [];
});
}
-
- set_applied_filter($filter) {
- this.$saved_filters
- .find(".btn-primary-light")
- .toggleClass("btn-primary-light btn-default");
- $filter.toggleClass("btn-default btn-primary-light");
- }
}
diff --git a/frappe/public/js/frappe/list/list_sidebar.html b/frappe/public/js/frappe/list/list_sidebar.html
deleted file mode 100644
index 96593dcfaf..0000000000
--- a/frappe/public/js/frappe/list/list_sidebar.html
+++ /dev/null
@@ -1,90 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/frappe/public/js/frappe/list/list_sidebar.js b/frappe/public/js/frappe/list/list_sidebar.js
deleted file mode 100644
index 5026707798..0000000000
--- a/frappe/public/js/frappe/list/list_sidebar.js
+++ /dev/null
@@ -1,277 +0,0 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-// MIT License. See license.txt
-import ListFilter from "./list_filter";
-frappe.provide("frappe.views");
-
-// opts:
-// stats = list of fields
-// doctype
-// parent
-
-frappe.views.ListSidebar = class ListSidebar {
- constructor(opts) {
- $.extend(this, opts);
- this.make();
- }
-
- make() {
- var sidebar_content = frappe.render_template("list_sidebar", { doctype: this.doctype });
-
- this.sidebar = $('')
- .html(sidebar_content)
- .appendTo(this.page.sidebar.empty());
-
- this.setup_list_filter();
- this.setup_list_group_by();
- this.setup_collapsible();
-
- // do not remove
- // used to trigger custom scripts
- $(document).trigger("list_sidebar_setup");
-
- if (
- this.list_view.list_view_settings &&
- this.list_view.list_view_settings.disable_sidebar_stats
- ) {
- this.sidebar.find(".list-tags").remove();
- } else {
- this.sidebar.find(".list-stats").on("show.bs.dropdown", (e) => {
- this.reload_stats();
- });
- }
- }
-
- setup_views() {
- var show_list_link = false;
-
- if (frappe.views.calendar[this.doctype]) {
- this.sidebar.find('.list-link[data-view="Calendar"]').removeClass("hide");
- this.sidebar.find('.list-link[data-view="Gantt"]').removeClass("hide");
- show_list_link = true;
- }
- //show link for kanban view
- this.sidebar.find('.list-link[data-view="Kanban"]').removeClass("hide");
- if (this.doctype === "Communication" && frappe.boot.email_accounts.length) {
- this.sidebar.find('.list-link[data-view="Inbox"]').removeClass("hide");
- show_list_link = true;
- }
-
- if (frappe.treeview_settings[this.doctype] || frappe.get_meta(this.doctype).is_tree) {
- this.sidebar.find(".tree-link").removeClass("hide");
- }
-
- this.current_view = "List";
- var route = frappe.get_route();
- if (route.length > 2 && frappe.views.view_modes.includes(route[2])) {
- this.current_view = route[2];
-
- if (this.current_view === "Kanban") {
- this.kanban_board = route[3];
- } else if (this.current_view === "Inbox") {
- this.email_account = route[3];
- }
- }
-
- // disable link for current view
- this.sidebar
- .find('.list-link[data-view="' + this.current_view + '"] a')
- .attr("disabled", "disabled")
- .addClass("disabled");
-
- //enable link for Kanban view
- this.sidebar
- .find('.list-link[data-view="Kanban"] a, .list-link[data-view="Inbox"] a')
- .attr("disabled", null)
- .removeClass("disabled");
-
- // show image link if image_view
- if (this.list_view.meta.image_field) {
- this.sidebar.find('.list-link[data-view="Image"]').removeClass("hide");
- show_list_link = true;
- }
-
- if (
- this.list_view.settings.get_coords_method ||
- (this.list_view.meta.fields.find((i) => i.fieldname === "latitude") &&
- this.list_view.meta.fields.find((i) => i.fieldname === "longitude")) ||
- this.list_view.meta.fields.find(
- (i) => i.fieldname === "location" && i.fieldtype == "Geolocation"
- )
- ) {
- this.sidebar.find('.list-link[data-view="Map"]').removeClass("hide");
- show_list_link = true;
- }
-
- if (show_list_link) {
- this.sidebar.find('.list-link[data-view="List"]').removeClass("hide");
- }
- }
-
- setup_reports() {
- // add reports linked to this doctype to the dropdown
- var me = this;
- var added = [];
- var dropdown = this.page.sidebar.find(".reports-dropdown");
- var divider = false;
-
- var add_reports = function (reports) {
- $.each(reports, function (name, r) {
- if (!r.ref_doctype || r.ref_doctype == me.doctype) {
- var report_type =
- r.report_type === "Report Builder"
- ? `List/${r.ref_doctype}/Report`
- : "query-report";
-
- var route = r.route || report_type + "/" + (r.title || r.name);
-
- if (added.indexOf(route) === -1) {
- // don't repeat
- added.push(route);
-
- if (!divider) {
- me.get_divider().appendTo(dropdown);
- divider = true;
- }
-
- $(
- '
' + __(r.title || r.name) + ""
- ).appendTo(dropdown);
- }
- }
- });
- };
-
- // from reference doctype
- if (this.list_view.settings.reports) {
- add_reports(this.list_view.settings.reports);
- }
-
- // Sort reports alphabetically
- var reports =
- Object.values(frappe.boot.user.all_reports).sort((a, b) =>
- a.title.localeCompare(b.title)
- ) || [];
-
- // from specially tagged reports
- add_reports(reports);
- }
-
- setup_list_filter() {
- this.list_filter = new ListFilter({
- wrapper: this.page.sidebar.find(".list-filters"),
- doctype: this.doctype,
- list_view: this.list_view,
- section_title: this.page.sidebar.find(".save-filter-section .sidebar-label"),
- });
- }
-
- setup_collapsible() {
- // tags and save filter sections should be collapsible
- let sections = [
- ["tags-section", "list-tags"],
- ["save-filter-section", "list-filters"],
- ["filter-section", "list-group-by"],
- ];
-
- for (let s of sections) {
- this.page.sidebar.find(`.${s[0]} .sidebar-label`).on("click", () => {
- let list_tags = this.page.sidebar.find("." + s[1]);
- let icon = "#es-line-down";
- list_tags.toggleClass("hide");
- if (list_tags.hasClass("hide")) {
- icon = "#es-line-right-chevron";
- }
- this.page.sidebar.find(`.${s[0]} .es-line use`).attr("href", icon);
- });
- }
- }
-
- setup_kanban_boards() {
- const $dropdown = this.page.sidebar.find(".kanban-dropdown");
- frappe.views.KanbanView.setup_dropdown_in_sidebar(this.doctype, $dropdown);
- }
-
- setup_keyboard_shortcuts() {
- this.sidebar.find(".list-link > a, .list-link > .btn-group > a").each((i, el) => {
- frappe.ui.keys.get_shortcut_group(this.page).add($(el));
- });
- }
-
- setup_list_group_by() {
- this.list_group_by = new frappe.views.ListGroupBy({
- doctype: this.doctype,
- sidebar: this,
- list_view: this.list_view,
- page: this.page,
- });
- }
-
- get_stats() {
- var me = this;
-
- let dropdown_options = me.sidebar.find(".list-stats-dropdown .stat-result");
- this.set_loading_state(dropdown_options);
-
- frappe.call({
- method: "frappe.desk.reportview.get_sidebar_stats",
- type: "GET",
- args: {
- stats: me.stats,
- doctype: me.doctype,
- // wait for list filter area to be generated before getting filters, or fallback to default filters
- filters:
- (me.list_view.filter_area
- ? me.list_view.get_filters_for_args()
- : me.default_filters) || [],
- },
- callback: function (r) {
- let stats = (r.message.stats || {})["_user_tags"] || [];
- me.render_stat(stats);
- let stats_dropdown = me.sidebar.find(".list-stats-dropdown");
- frappe.utils.setup_search(stats_dropdown, ".stat-link", ".stat-label");
- },
- });
- }
-
- set_loading_state(dropdown) {
- dropdown.html(`
-
- ${__("Loading...")}
-
-
`);
- }
-
- render_stat(stats) {
- let args = {
- stats: stats,
- label: __("Tags"),
- };
-
- let tag_list = $(frappe.render_template("list_sidebar_stat", args)).on(
- "click",
- ".stat-link",
- (e) => {
- let fieldname = $(e.currentTarget).attr("data-field");
- let label = $(e.currentTarget).attr("data-label");
- let condition = "like";
- let existing = this.list_view.filter_area.filter_list.get_filter(fieldname);
- if (existing) {
- existing.remove();
- }
- if (label == "No Tags") {
- label = "not set";
- condition = "is";
- }
- this.list_view.filter_area.add(this.doctype, fieldname, condition, label);
- }
- );
-
- this.sidebar.find(".list-stats-dropdown .stat-result").html(tag_list);
- }
-
- reload_stats() {
- this.sidebar.find(".stat-link").remove();
- this.sidebar.find(".stat-no-records").remove();
- this.get_stats();
- }
-};
diff --git a/frappe/public/js/frappe/list/list_sidebar_group_by.js b/frappe/public/js/frappe/list/list_sidebar_group_by.js
index f3b2c35589..eb890c1ac5 100644
--- a/frappe/public/js/frappe/list/list_sidebar_group_by.js
+++ b/frappe/public/js/frappe/list/list_sidebar_group_by.js
@@ -2,6 +2,7 @@ frappe.provide("frappe.views");
frappe.views.ListGroupBy = class ListGroupBy {
constructor(opts) {
+ // TODO: move assigned to and owner logic in this file, currently this file is not use
$.extend(this, opts);
this.make_wrapper();
diff --git a/frappe/public/js/frappe/list/list_sidebar_stat.html b/frappe/public/js/frappe/list/list_sidebar_stat.html
index 140202b5f3..20809b45f3 100644
--- a/frappe/public/js/frappe/list/list_sidebar_stat.html
+++ b/frappe/public/js/frappe/list/list_sidebar_stat.html
@@ -1,4 +1,12 @@
-
+{% if (stats.length) { %}
+
+
+
+{% } %}
{% if (!stats.length) { %}
{{ __("No records tagged.") }}
{% } else {
@@ -7,8 +15,14 @@
var stat_count = stats[i][1];
%}
-
- {{ __(stat_label) }}
+
+
+
+ {% if (applied_filter == stat_label) { %}
+ {{ frappe.utils.icon("tick", "xs") }}
+ {% } %}
+ {{ __(stat_label) }}
+
{{ stat_count }}
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index 73321f2833..cb7f3b6e7e 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -329,6 +329,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
refresh_columns(meta, list_view_settings) {
this.meta = meta;
+ this.tags_shown = list_view_settings?.show_tags;
this.list_view_settings = list_view_settings;
this.setup_columns();
@@ -727,7 +728,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
let classes = [
"list-row-col ellipsis",
col.type == "Subject" ? "list-subject level" : "hidden-xs",
- col.type == "Tag" ? "tag-col hide" : "",
+ col.type == "Tag" ? `tag-col ${!this.tags_shown ? "hide" : ""} ` : "",
frappe.model.is_numeric_field(col.df) ? "text-right" : "",
col.df?.fieldname,
].join(" ");
@@ -1338,7 +1339,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
this.setup_sort_by();
this.setup_list_click();
this.setup_drag_click();
- this.setup_tag_event();
+ this.setup_tag_visibility();
this.setup_new_doc_event();
this.setup_check_events();
this.setup_like();
@@ -1679,13 +1680,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
});
}
- setup_tag_event() {
- this.tags_shown = false;
- this.list_sidebar &&
- this.list_sidebar.parent.on("click", ".list-tag-preview", () => {
- this.tags_shown = !this.tags_shown;
- this.toggle_tags();
- });
+ setup_tag_visibility() {
+ this.tags_shown = this.list_view_settings?.show_tags;
}
setup_realtime_updates() {
@@ -1853,12 +1849,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
this.toggle_actions_menu_button(this.$checks.length > 0);
}
- toggle_tags() {
- this.$result.find(".tag-col").toggleClass("hide");
- const preview_label = this.tags_shown ? __("Hide Tags") : __("Show Tags");
- this.list_sidebar.parent.find(".list-tag-preview").text(preview_label);
- }
-
get_checked_items(only_docnames) {
const docnames = Array.from(this.$checks || []).map((check) =>
cstr(unescape($(check).data().name))
@@ -1973,14 +1963,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
});
}
- items.push({
- label: __("Toggle Sidebar", null, "Button in list view menu"),
- action: () => this.toggle_side_bar(),
- condition: () => !this.page.disable_sidebar_toggle,
- standard: true,
- shortcut: "Ctrl+G",
- });
-
if (frappe.user.has_role("System Manager") && frappe.boot.developer_mode) {
// edit doctype
items.push({
@@ -2044,14 +2026,28 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
get_group_by_dropdown_fields() {
let group_by_fields = [];
- let default_fields = ["assigned_to", "owner"];
- default_fields = default_fields.concat(
- frappe.get_user_settings(this.doctype)?.group_by_fields || []
- );
+ let default_fields = frappe.get_user_settings(this.doctype)?.group_by_fields || [];
+
let fields = this.meta.fields.filter((f) =>
["Select", "Link", "Data", "Int", "Check"].includes(f.fieldtype)
);
+ let default_fields_dict = [
+ {
+ label: "Assigned To",
+ fieldname: "assigned_to",
+ },
+ {
+ label: "Created By",
+ fieldname: "owner",
+ },
+ {
+ label: "Tags",
+ fieldname: "tags",
+ },
+ ];
+ fields = fields.concat(default_fields_dict);
+
group_by_fields.push({
label: __(this.doctype),
fieldname: "group_by_fields",
diff --git a/frappe/public/js/frappe/list/list_view_select.js b/frappe/public/js/frappe/list/list_view_select.js
index eaf48be562..5a61b1551f 100644
--- a/frappe/public/js/frappe/list/list_view_select.js
+++ b/frappe/public/js/frappe/list/list_view_select.js
@@ -64,10 +64,10 @@ frappe.views.ListViewSelect = class ListViewSelect {
if (frappe.get_route().length > 3) {
default_action = {
label: __("Report Builder"),
- action: () => this.set_route("report"),
+ action: () => frappe.set_route("report"),
};
}
- this.setup_dropdown_in_sidebar("Report", reports, default_action);
+ this.setup_dropdown_in_navbar("Report", reports, default_action);
},
},
Dashboard: {
@@ -79,7 +79,7 @@ frappe.views.ListViewSelect = class ListViewSelect {
action: () => this.set_route("calendar", "default"),
current_view_handler: () => {
this.get_calendars().then((calendars) => {
- this.setup_dropdown_in_sidebar("Calendar", calendars);
+ this.setup_dropdown_in_navbar("Calendar", calendars);
});
},
},
@@ -99,7 +99,7 @@ frappe.views.ListViewSelect = class ListViewSelect {
action: () => frappe.new_doc("Email Account"),
};
}
- this.setup_dropdown_in_sidebar("Inbox", accounts, default_action);
+ this.setup_dropdown_in_navbar("Inbox", accounts, default_action);
},
},
Image: {
@@ -144,40 +144,22 @@ frappe.views.ListViewSelect = class ListViewSelect {
});
}
- setup_dropdown_in_sidebar(view, items, default_action) {
- if (!this.sidebar) return;
- const views_wrapper = this.sidebar.sidebar.find(".views-section");
- views_wrapper.find(".sidebar-label").html(__(view));
- const $dropdown = views_wrapper.find(".views-dropdown");
-
+ setup_dropdown_in_navbar(view, items, default_action) {
let placeholder = __("Select {0}", [__(view)]);
- let html = ``;
- if (!items || !items.length) {
- html = `
- ${__("No {0} Found", [__(view)])}
-
`;
- } else {
- const page_name = this.get_page_name();
+ if (items && items.length) {
items.map((item) => {
- if (item.name.toLowerCase() == page_name.toLowerCase()) {
- placeholder = item.name;
- } else {
- html += `
${item.name}`;
- }
+ this.page.add_inner_button(
+ item.name,
+ () => location.replace(item.route),
+ placeholder
+ );
});
}
- views_wrapper.find(".selected-view").html(placeholder);
-
- if (default_action) {
- views_wrapper.find(".sidebar-action a").html(default_action.label);
- views_wrapper.find(".sidebar-action a").click(() => default_action.action());
+ if (default_action && Object.keys(default_action).length) {
+ this.page.add_inner_button(default_action.label, default_action.action, placeholder);
}
-
- $dropdown.html(html);
-
- views_wrapper.removeClass("hide");
}
setup_kanban_switcher(kanbans) {
diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js
index 7755636faa..db3352f77a 100644
--- a/frappe/public/js/frappe/views/reports/report_view.js
+++ b/frappe/public/js/frappe/views/reports/report_view.js
@@ -77,12 +77,6 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
super.setup_page();
}
- toggle_side_bar() {
- super.toggle_side_bar();
- // refresh datatable when sidebar is toggled to accomodate extra space
- this.render(true);
- }
-
setup_result_area() {
super.setup_result_area();
this.setup_charts_area();
@@ -1572,11 +1566,6 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
label: __("Toggle Chart"),
action: () => this.toggle_charts(),
},
- {
- label: __("Toggle Sidebar"),
- action: () => this.toggle_side_bar(),
- shortcut: "Ctrl+K",
- },
{
label: __("Pick Columns"),
action: () => {
diff --git a/frappe/public/js/list.bundle.js b/frappe/public/js/list.bundle.js
index e40ef94a81..54951cd03b 100644
--- a/frappe/public/js/list.bundle.js
+++ b/frappe/public/js/list.bundle.js
@@ -16,8 +16,6 @@ import "./frappe/list/list_view.js";
import "./frappe/list/list_factory.js";
import "./frappe/list/list_view_select.js";
-import "./frappe/list/list_sidebar.js";
-import "./frappe/list/list_sidebar.html";
import "./frappe/list/list_sidebar_stat.html";
import "./frappe/list/list_sidebar_group_by.js";
import "./frappe/list/list_view_permission_restrictions.html";
diff --git a/frappe/public/scss/desk/list.scss b/frappe/public/scss/desk/list.scss
index d7e1d1c5c8..34ff41b7b7 100644
--- a/frappe/public/scss/desk/list.scss
+++ b/frappe/public/scss/desk/list.scss
@@ -481,6 +481,12 @@ input.list-header-checkbox {
.form-group {
min-width: 150px;
}
+
+ .group-by-field {
+ .group-by-dropdown {
+ max-width: 220px;
+ }
+ }
}
.filter-section {