From ed5c4dfd9542f0c24adecadf782a803c4353751e Mon Sep 17 00:00:00 2001 From: kunhimohamed Date: Tue, 1 Jul 2025 06:33:28 +0400 Subject: [PATCH 1/2] feat(report summary): categorize and display in card view within the report summary section --- .../js/frappe/views/reports/query_report.js | 76 ++++++++++++++++++- frappe/public/scss/desk/report.scss | 21 +++++ 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index b16c274749e2..4a6edbb1d52d 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -770,13 +770,85 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { } render_summary(data) { - data.forEach((summary) => { - frappe.utils.build_summary_item(summary).appendTo(this.$summary); + this.$summary.empty(); + + // Check if all items in data have card property set to true + let is_it_build_card = data.every((item) => { + return item.card === true; }); + if (is_it_build_card) { + let html = `
`; + + let section_label = ''; + let section_rows = []; + + data.forEach((item, index) => { + if (item.datatype === "Section Break") { + // render last section if exists + if (section_rows.length > 0) { + html += this.build_card(section_label, section_rows); + section_rows = []; + } + section_label = item.label; + } else { + section_rows.push(item); + } + }); + + // render final section + if (section_rows.length > 0) { + html += this.build_card(section_label, section_rows); + } + html += `
`; + this.$summary.html(html); + + // Remove border-bottom from summary section + $('.report-summary').css('border-bottom', 'none'); + } + else { + data.forEach((summary) => { + frappe.utils.build_summary_item(summary).appendTo(this.$summary); + }); + } this.$summary.show(); } + // Add this helper method + build_card(title, rows) { + let card = ` +
+

${title}

+ + + `; + + rows.forEach(item => { + const value = item.datatype === "Percent" ? `${item.value}%` : frappe.format(item.value, { fieldtype: item.datatype }); + const color = { + green: "#28a745", + red: "#dc3545", + blue: "#007bff", + orange: "#fd7e14", + purple: "#6f42c1" + }[item.indicator] || "#333"; + + card += ` + + + + `; + }); + + card += ` + +
${item.label}${value}
+
+ `; + + return card; + } + get_query_params() { const query_string = frappe.utils.get_query_string(frappe.get_route_str()); return frappe.utils.get_query_params(query_string); diff --git a/frappe/public/scss/desk/report.scss b/frappe/public/scss/desk/report.scss index f064f89e5bac..dc2f19c60710 100644 --- a/frappe/public/scss/desk/report.scss +++ b/frappe/public/scss/desk/report.scss @@ -268,6 +268,27 @@ } } +.report-summary-card-container { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + width: 100%; + gap: 16px; +} + +.report-summary-card { + flex: 1 1 23%; + box-sizing: border-box; + margin-bottom: 16px; + border: 1px solid #ddd; + border-radius: 8px; + box-shadow: 0 1px 4px rgba(0,0,0,0.05); + padding: 12px 16px; + background-color: #fff; + min-width: 250px; +} + + .report-footer { border-top: 1px solid var(--border-color); @include get_textstyle("sm", "regular"); From 542c91fe0856fce24fdd6523671b1f1a7c7d3df6 Mon Sep 17 00:00:00 2001 From: kunhimohamed Date: Fri, 4 Jul 2025 14:09:11 +0400 Subject: [PATCH 2/2] feat(reprot): card view at total summary section --- frappe/core/doctype/report/report.json | 11 +++++++++-- frappe/core/doctype/report/report.py | 2 +- frappe/public/js/frappe/views/reports/query_report.js | 8 ++------ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/frappe/core/doctype/report/report.json b/frappe/core/doctype/report/report.json index 7a38bbb742a7..4bdcb8774d63 100644 --- a/frappe/core/doctype/report/report.json +++ b/frappe/core/doctype/report/report.json @@ -15,6 +15,7 @@ "report_type", "letter_head", "add_total_row", + "enable_card_view_in_summary", "disabled", "prepared_report", "timeout", @@ -190,12 +191,18 @@ "fieldname": "timeout", "fieldtype": "Int", "label": "Timeout (In Seconds)" + }, + { + "default": "0", + "fieldname": "enable_card_view_in_summary", + "fieldtype": "Check", + "label": "Enable Card View in Summary" } ], "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2024-08-31 20:34:10.018811", + "modified": "2025-07-04 14:04:43.497377", "modified_by": "Administrator", "module": "Core", "name": "Report", @@ -248,4 +255,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} +} \ No newline at end of file diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index e2d4de08fd27..12eeca7b7548 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -31,6 +31,7 @@ class Report(Document): add_total_row: DF.Check columns: DF.Table[ReportColumn] disabled: DF.Check + enable_card_view_in_summary: DF.Check filters: DF.Table[ReportFilter] is_standard: DF.Literal["No", "Yes"] javascript: DF.Code | None @@ -46,7 +47,6 @@ class Report(Document): report_type: DF.Literal["Report Builder", "Query Report", "Script Report", "Custom Report"] roles: DF.Table[HasRole] timeout: DF.Int - # end: auto-generated types def validate(self): """only administrator can save standard report""" diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 4a6edbb1d52d..e10eb7934a4f 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -772,12 +772,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { render_summary(data) { this.$summary.empty(); - // Check if all items in data have card property set to true - let is_it_build_card = data.every((item) => { - return item.card === true; - }); - - if (is_it_build_card) { + // Check if the report enable card view + if (this.report_doc.enable_card_view_in_summary) { let html = `
`; let section_label = '';