Add custom date range filter UI for grid date columns
Introduced a custom date range filter menu for date columns in BandGridBase<TItem> using DevExpress Blazor grids. The new UI provides "from" and "to" date pickers, applies filters immediately on selection, and hides the default filter dropdown footer for a smoother user experience. State management and filter criteria logic were added to support this feature.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using DbFirst.BlazorWebApp.Models.Grid;
|
||||
using DbFirst.BlazorWebApp.Services;
|
||||
using DevExpress.Blazor;
|
||||
using DevExpress.Data.Filtering;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
@@ -24,6 +25,15 @@ public abstract class BandGridBase<TItem> : ComponentBase
|
||||
protected bool gridLayoutApplied;
|
||||
protected IGrid? gridRef;
|
||||
|
||||
// --- Datumsfilter-Zustand ---
|
||||
private readonly Dictionary<string, DateTime?> _filterFrom = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, DateTime?> _filterTo = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Stabile Referenzen: werden einmal pro FieldName erstellt und wiederverwendet
|
||||
private readonly Dictionary<string, EventCallback<DateTime?>> _fromCallbacks = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, EventCallback<DateTime?>> _toCallbacks = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, RenderFragment<GridDataColumnFilterMenuTemplateContext>> _dateFilterTemplates = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// --- SizeMode ---
|
||||
protected SizeMode _sizeMode = SizeMode.Medium;
|
||||
protected static readonly List<SizeMode> _sizeModes = Enum.GetValues<SizeMode>().ToList();
|
||||
@@ -224,9 +234,97 @@ public abstract class BandGridBase<TItem> : ComponentBase
|
||||
builder.AddAttribute(seq++, "DisplayFormat", column.DisplayFormat);
|
||||
if (column.ReadOnly)
|
||||
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||
if (column.FilterType == ColumnFilterType.Date)
|
||||
builder.AddAttribute(seq++, "FilterMenuTemplate", GetOrCreateDateFilterTemplate(column.FieldName));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
|
||||
private RenderFragment<GridDataColumnFilterMenuTemplateContext> GetOrCreateDateFilterTemplate(string fieldName)
|
||||
{
|
||||
if (!_dateFilterTemplates.TryGetValue(fieldName, out var template))
|
||||
{
|
||||
// EventCallbacks einmalig erstellen – stabile Referenzen über alle Renders
|
||||
_fromCallbacks[fieldName] = EventCallback.Factory.Create<DateTime?>(this, (DateTime? v) => OnFilterFromChanged(fieldName, v));
|
||||
_toCallbacks[fieldName] = EventCallback.Factory.Create<DateTime?>(this, (DateTime? v) => OnFilterToChanged(fieldName, v));
|
||||
template = BuildDateFilterTemplate(fieldName);
|
||||
_dateFilterTemplates[fieldName] = template;
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
private RenderFragment<GridDataColumnFilterMenuTemplateContext> BuildDateFilterTemplate(string fieldName) =>
|
||||
_ => b =>
|
||||
{
|
||||
int s = 0;
|
||||
b.OpenElement(s++, "div");
|
||||
b.AddAttribute(s++, "class", "date-filter-menu p-2");
|
||||
|
||||
// Ab Datum
|
||||
b.OpenElement(s++, "div");
|
||||
b.AddAttribute(s++, "class", "mb-2");
|
||||
b.OpenElement(s++, "label");
|
||||
b.AddAttribute(s++, "class", "form-label small fw-semibold");
|
||||
b.AddContent(s++, "Ab Datum");
|
||||
b.CloseElement();
|
||||
b.OpenComponent<DxDateEdit<DateTime?>>(s++);
|
||||
b.AddAttribute(s++, "Date", _filterFrom.GetValueOrDefault(fieldName));
|
||||
b.AddAttribute(s++, "DateChanged", _fromCallbacks[fieldName]);
|
||||
b.AddAttribute(s++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto);
|
||||
b.AddAttribute(s++, "NullText", "Kein Startdatum");
|
||||
b.AddAttribute(s++, "Width", "100%");
|
||||
b.CloseComponent();
|
||||
b.CloseElement();
|
||||
|
||||
// Bis Datum
|
||||
b.OpenElement(s++, "div");
|
||||
b.AddAttribute(s++, "class", "mb-0");
|
||||
b.OpenElement(s++, "label");
|
||||
b.AddAttribute(s++, "class", "form-label small fw-semibold");
|
||||
b.AddContent(s++, "Bis Datum");
|
||||
b.CloseElement();
|
||||
b.OpenComponent<DxDateEdit<DateTime?>>(s++);
|
||||
b.AddAttribute(s++, "Date", _filterTo.GetValueOrDefault(fieldName));
|
||||
b.AddAttribute(s++, "DateChanged", _toCallbacks[fieldName]);
|
||||
b.AddAttribute(s++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto);
|
||||
b.AddAttribute(s++, "NullText", "Kein Enddatum");
|
||||
b.AddAttribute(s++, "Width", "100%");
|
||||
b.CloseComponent();
|
||||
b.CloseElement();
|
||||
|
||||
b.CloseElement();
|
||||
};
|
||||
|
||||
private void OnFilterFromChanged(string fieldName, DateTime? value)
|
||||
{
|
||||
_filterFrom[fieldName] = value;
|
||||
ApplyDateFilter(fieldName);
|
||||
}
|
||||
|
||||
private void OnFilterToChanged(string fieldName, DateTime? value)
|
||||
{
|
||||
_filterTo[fieldName] = value;
|
||||
ApplyDateFilter(fieldName);
|
||||
}
|
||||
|
||||
private void ApplyDateFilter(string fieldName)
|
||||
{
|
||||
var ops = new List<CriteriaOperator>();
|
||||
if (_filterFrom.TryGetValue(fieldName, out var from) && from.HasValue)
|
||||
ops.Add(new BinaryOperator(fieldName, from.Value.Date, BinaryOperatorType.GreaterOrEqual));
|
||||
if (_filterTo.TryGetValue(fieldName, out var to) && to.HasValue)
|
||||
ops.Add(new BinaryOperator(fieldName, to.Value.Date.AddDays(1), BinaryOperatorType.Less));
|
||||
|
||||
CriteriaOperator? criteria = ops.Count switch
|
||||
{
|
||||
0 => null,
|
||||
1 => ops[0],
|
||||
_ => new GroupOperator(GroupOperatorType.And, ops)
|
||||
};
|
||||
|
||||
gridRef?.SetFieldFilterCriteria(fieldName, criteria);
|
||||
_ = InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected void SetEditContext(EditContext context)
|
||||
{
|
||||
if (editContext == context) return;
|
||||
|
||||
Reference in New Issue
Block a user