From a1e3fe96ed74bfa469d013087b40b0254a51f0b9 Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Mon, 25 May 2026 17:31:33 +0000 Subject: [PATCH] feat(list,grid): period filter for date columns + dialog row fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - list-view: replace single-date "=" standard filter with a DateRange "Between" control when the doctype carries posting_date as Date/Datetime. If posting_date isn't in standard_filter, inject the period control after the ID filter so it's always reachable. - shared.css: size the period filter to fit "DD/MM/YYYY to DD/MM/YYYY" without being squashed by the surrounding flex layout. - shared.css: let rows in the grid "Configure Columns" dialog grow with content. Frappe hardcodes height:32px inline on each .fields_order row in grid_row.js, which clips long translated labels (eg. Azerbaijani "Əsas Tarif (Anbar Ölçü Vahidinə görə)") so the wrapped portion overflows into the next row and the help text. Switch to flex centering with min-height; reset inline padding-tops that were calibrated for fixed 32px rows. - shared.css: hide the ERPNext desktop icon by default (same treatment as Organization). Co-Authored-By: Claude Opus 4.7 (1M context) --- jey_theme/public/css/shared.css | 34 ++++- jey_theme/public/js/jey_theme.js | 209 +++++++++++++++++++++++++++++++ 2 files changed, 242 insertions(+), 1 deletion(-) diff --git a/jey_theme/public/css/shared.css b/jey_theme/public/css/shared.css index bf8e49e..2fe8c13 100644 --- a/jey_theme/public/css/shared.css +++ b/jey_theme/public/css/shared.css @@ -13,7 +13,8 @@ } /* Hide Organization desktop icon by default */ -.desktop-icon[data-id="Organization"] { +.desktop-icon[data-id="Organization"], +.desktop-icon[data-id="ERPNext"] { display: none !important; } @@ -1375,3 +1376,34 @@ textarea.form-control:focus, border-color: transparent !important; box-shadow: none !important; } + +/* List view — period (Between) filter sized to fit "DD/MM/YYYY to DD/MM/YYYY". */ +.standard-filter-section .frappe-control.jey-period-filter { + flex: 0 0 auto; + min-width: 200px; +} +.standard-filter-section .frappe-control.jey-period-filter input { + min-width: 200px; +} + +/* Grid "Configure Columns" dialog — let rows grow when label wraps. + Frappe hardcodes height:32px inline on each .fields_order row in + grid_row.js, which clips long translated labels (eg. Azerbaijani + "Əsas Tarif (Anbar Ölçü Vahidinə görə)") so the wrapped portion + overflows into the next row and into the help text below. Switch + to flex centering with min-height so rows grow with content. */ +.control-input.fields_order { + height: auto !important; + min-height: 32px; + display: flex; + align-items: center; +} +.control-input.fields_order > .row { + width: 100%; + align-items: center; + margin: 0; +} +.control-input.fields_order > .row > [class*="col-"] { + padding-top: 0 !important; + margin-top: 0 !important; +} diff --git a/jey_theme/public/js/jey_theme.js b/jey_theme/public/js/jey_theme.js index c43a0cd..eaab694 100644 --- a/jey_theme/public/js/jey_theme.js +++ b/jey_theme/public/js/jey_theme.js @@ -1921,6 +1921,215 @@ } })(); +/* ========================================================================== + List View — Period filter (Between) for date fields like posting_date + Replaces the single-date "=" standard filter with a DateRange "Between" + control, or injects one after the ID filter if it's not in standard_filter. + ========================================================================== */ + +(function () { + "use strict"; + + /* Trigger fields: if doctype has any of these as Date/Datetime, we upgrade. + Order matters — first match wins. Extend freely. */ + var TRIGGER_FIELDS = ["posting_date"]; + + function findTriggerDf(listview) { + if (!listview || !listview.meta || !Array.isArray(listview.meta.fields)) return null; + var fields = listview.meta.fields; + for (var i = 0; i < TRIGGER_FIELDS.length; i++) { + var name = TRIGGER_FIELDS[i]; + for (var j = 0; j < fields.length; j++) { + var f = fields[j]; + if (f.fieldname === name && (f.fieldtype === "Date" || f.fieldtype === "Datetime")) { + return f; + } + } + } + return null; + } + + function patchFilterAreaProto(filter_area) { + var proto = Object.getPrototypeOf(filter_area); + if (!proto || proto.__jeyBetweenPatched) return; + proto.__jeyBetweenPatched = true; + + var origSetStandard = proto.set_standard_filter; + proto.set_standard_filter = function (filters) { + if (!filters || filters.length === 0) return origSetStandard.call(this, filters); + + var fields_dict = this.list_view.page.fields_dict || {}; + var handled = []; + var passthrough = []; + + filters.forEach(function (filter) { + var fieldname = filter[1]; + var condition = filter[2]; + var value = filter[3]; + var ctrl = fields_dict[fieldname]; + if ( + condition === "Between" && + ctrl && + ctrl.df && + ctrl.df.fieldtype === "DateRange" + ) { + handled.push({ ctrl: ctrl, value: value }); + } else { + passthrough.push(filter); + } + }); + + var result = origSetStandard.call(this, passthrough); + + if (handled.length) { + result.promise = (result.promise || Promise.resolve()).then(function () { + handled.forEach(function (h) { + h.ctrl.df.match_type = "Between"; + h.ctrl.df.condition = "Between"; + return h.ctrl.set_value(h.value); + }); + }); + } + return result; + }; + } + + function buildDateRangeField(listview, df) { + var page = listview.page; + var $section = page.page_form.find(".standard-filter-section"); + if (!$section.length) $section = page.page_form; + + var existing = page.fields_dict[df.fieldname]; + var $placeholder = null; + + if (existing && existing.$wrapper) { + var $existingWrap = $(existing.$wrapper); + if ($existingWrap.parent().length) { + $placeholder = $(''); + $existingWrap.before($placeholder); + } + $existingWrap.remove(); + delete page.fields_dict[df.fieldname]; + } + + var newField = page.add_field( + { + fieldtype: "DateRange", + label: df.label || "Posting Date", + fieldname: df.fieldname, + condition: "Between", + is_filter: 1, + onchange: function () { + if ( + listview && + listview.filter_area && + typeof listview.filter_area.refresh_list_view === "function" + ) { + listview.filter_area.trigger_refresh = true; + listview.filter_area.refresh_list_view(); + } + }, + }, + $section + ); + + if (!newField) return null; + + newField.df.match_type = "Between"; + newField.df.condition = "Between"; + + var $newWrap = $(newField.$wrapper || newField.wrapper); + $newWrap.addClass("jey-period-filter"); + + if ($placeholder && $placeholder.length) { + $placeholder.replaceWith($newWrap); + } else { + var $idWrap = $section.find('[data-fieldname="name"]').closest(".frappe-control"); + if ($idWrap.length) { + $idWrap.after($newWrap); + } else { + $section.prepend($newWrap); + } + } + + return newField; + } + + function reflectExistingFilter(listview, df, field) { + try { + if ( + !listview.filter_area || + !listview.filter_area.filter_list || + typeof listview.filter_area.filter_list.get_filters !== "function" + ) { + return; + } + var filters = listview.filter_area.filter_list.get_filters() || []; + var match = null; + for (var i = 0; i < filters.length; i++) { + var f = filters[i]; + if (f[1] === df.fieldname && f[2] === "Between") { + match = f; + break; + } + } + if (!match) return; + + var sidebarFilter = listview.filter_area.filter_list.get_filter(df.fieldname); + if (sidebarFilter && typeof sidebarFilter.remove === "function") { + sidebarFilter.remove(); + } + + field.df.match_type = "Between"; + field.df.condition = "Between"; + field.set_value(match[3]); + } catch (e) { + /* noop */ + } + } + + function setup(listview) { + if (!listview || listview.view_name !== "List") return; + if (listview.__jeyPeriodSet) return; + if (!listview.page || !listview.page.page_form) return; + + var df = findTriggerDf(listview); + if (!df) return; + + if (listview.filter_area) patchFilterAreaProto(listview.filter_area); + + var field = buildDateRangeField(listview, df); + if (!field) return; + + reflectExistingFilter(listview, df, field); + + listview.__jeyPeriodSet = true; + } + + function wrapRenderList() { + if (!window.frappe || !frappe.views || !frappe.views.ListView) return false; + var proto = frappe.views.ListView.prototype; + if (proto.__jeyPeriodWrapped) return true; + proto.__jeyPeriodWrapped = true; + + var originalRender = proto.render_list; + proto.render_list = function () { + var result = originalRender.apply(this, arguments); + try { setup(this); } catch (e) { /* noop */ } + return result; + }; + return true; + } + + if (!wrapRenderList()) { + var attempts = 0; + var iv = setInterval(function () { + attempts++; + if (wrapRenderList() || attempts > 100) clearInterval(iv); + }, 200); + } +})(); + /* ========================================================================== List View — Override SVG sprite icons Replaces content in Frappe's SVG sprite sheets so every