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/list/base_list.js b/frappe/public/js/frappe/list/base_list.js
index 414f7feb8a..e7d54d4fb0 100644
--- a/frappe/public/js/frappe/list/base_list.js
+++ b/frappe/public/js/frappe/list/base_list.js
@@ -289,10 +289,6 @@ frappe.views.BaseList = class BaseList {
});
}
- 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);
@@ -701,7 +697,7 @@ class FilterArea {
this.user_setting_fields =
frappe.get_user_settings(this.list_view.doctype)?.group_by_fields || [];
- if (["assigned_to", "owner"].some((v) => this.user_setting_fields.includes(v))) {
+ if (["assigned_to", "owner", "tags"].some((v) => this.user_setting_fields.includes(v))) {
this.render_non_standard_fields_filter();
}
}
@@ -823,6 +819,8 @@ class FilterArea {
label = __("Assigned To");
} else if (fieldname === "owner") {
label = __("Created By");
+ } else if (fieldname === "tags") {
+ label = __("Tags");
}
return `
@@ -848,6 +846,10 @@ class FilterArea {
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();
@@ -863,11 +865,21 @@ class FilterArea {
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) {
- let applied_filter = this.list_view.get_filter_value(
- fieldname == "assigned_to" ? "_assign" : fieldname
- );
+ 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,
@@ -896,7 +908,13 @@ class FilterArea {
typeof $target.data("value") === "string"
? decodeURIComponent($target.data("value").trim())
: $target.data("value");
- fieldname = fieldname === "assigned_to" ? "_assign" : fieldname;
+
+ 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;
@@ -905,20 +923,6 @@ class FilterArea {
});
}
- 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);
- }
-
render_dropdown_items(fields, fieldtype, $dropdown, applied_filter) {
let standard_html = `
@@ -945,18 +949,6 @@ class FilterArea {
$dropdown.html(dropdown_html);
}
- 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")}
-
`
- );
- }
-
get_dropdown_html(field, fieldtype, applied = false) {
let label;
if (field.name == null) {
@@ -985,18 +977,61 @@ class FilterArea {
`;
}
- set_dropdown_loading_state($dropdown) {
- $dropdown.html(`
-
- ${__("Loading...")}
-
- `);
+ 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();
- // remove filter of the current field
current_filters = current_filters.filter(
(f_arr) => !f_arr.includes(field === "assigned_to" ? "_assign" : field)
);
@@ -1020,6 +1055,40 @@ class FilterArea {
});
}
+ 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]);
diff --git a/frappe/public/js/frappe/list/list_sidebar.html b/frappe/public/js/frappe/list/list_sidebar.html
index 0a4f41294d..7a37c0f793 100644
--- a/frappe/public/js/frappe/list/list_sidebar.html
+++ b/frappe/public/js/frappe/list/list_sidebar.html
@@ -28,44 +28,6 @@
-
-