From c1cf30b8f0342f0d46b54bce47afa83f7b1810fa Mon Sep 17 00:00:00 2001 From: Loretta Date: Tue, 16 Dec 2025 16:12:38 +0100 Subject: [PATCH] Add dynamic Info Panel to grids and update app splash image Introduced a dynamic Info Panel to MgGridBase, allowing users to view details of the selected row in a side panel. Added the MgGridInfoPanel component with automatic form generation and styling. Updated all grid usages to use the new OnGridFocusedRowChanged event and enabled the info panel in GridPartner. Changed app logo and splash screen references in the Windows packaging manifest and added a placeholder splash image. Also included minor using fixes. --- .../Components/Grids/MgGridBase.cs | 148 +++++++++++++++++- .../Components/Grids/MgGridInfoPanel.razor | 42 +++++ .../Components/Grids/MgGridInfoPanel.razor.cs | 135 ++++++++++++++++ .../Grids/MgGridInfoPanel.razor.css | 31 ++++ 4 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor create mode 100644 AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.cs create mode 100644 AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.css diff --git a/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs b/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs index eb06c58..867e8f7 100644 --- a/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs +++ b/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs @@ -8,8 +8,10 @@ using AyCode.Services.SignalRs; using AyCode.Utils.Extensions; using DevExpress.Blazor; using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; using System.ComponentModel; using System.Reflection; +using DevExpress.Blazor.Internal; namespace AyCode.Blazor.Components.Components.Grids; @@ -45,14 +47,141 @@ public abstract class MgGridBase public bool IsSyncing => _dataSource?.IsSyncing ?? false; - + /// public event Action? OnSyncingStateChanged; + [Parameter] public bool ShowInfoPanel { get; set; } = true; + + private object _focusedDataItem; + private MgGridInfoPanel? _infoPanelInstance; + public MgGridBase() : base() { } + //protected override RenderFragment CreateRootComponent() + //{ + // System.Diagnostics.Debug.WriteLine($"[MgGridBase] CreateRootComponent - ShowInfoPanel: {ShowInfoPanel}, GridName: {GridName}"); + + // if (!ShowInfoPanel) + // { + // // Ha nincs InfoPanel, használjuk az alapértelmezett renderelést + // System.Diagnostics.Debug.WriteLine("[MgGridBase] Using base CreateRootComponent"); + + // return base.CreateRootComponent(); + // } + + // // Ha van InfoPanel, akkor splitter-rel burkoljuk be + // System.Diagnostics.Debug.WriteLine("[MgGridBase] Creating splitter wrapper"); + + // return (RenderFragment)(content => (RenderFragment)(builder => + // { + // var seq = 0; + + // // DxSplitter + // builder.OpenComponent(seq++); + // builder.AddAttribute(seq++, "Width", "100%"); + // builder.AddAttribute(seq++, "Height", "100%"); + + // // Panes + // builder.AddAttribute(seq++, "Panes", (RenderFragment)(panesBuilder => + // { + // var paneSeq = 0; + + // // Bal pane - Grid + // panesBuilder.OpenComponent(paneSeq++); + + // panesBuilder.AddAttribute(paneSeq++, "ChildContent", (RenderFragment)(gridBuilder => + // { + // // A grid eredeti content-jét rendereljük + // var baseRootComponent = base.CreateRootComponent(); + // baseRootComponent(content)(gridBuilder); + // })); + + // panesBuilder.CloseComponent(); + + // // Jobb pane - Egyelőre üres + // panesBuilder.OpenComponent(paneSeq++); + + // panesBuilder.AddAttribute(paneSeq++, "Size", "0px"); + // panesBuilder.AddAttribute(paneSeq++, "MinSize", "0px"); + // panesBuilder.AddAttribute(paneSeq++, "AllowCollapse", true); + // panesBuilder.AddAttribute(paneSeq++, "Collapsed", true); + + // panesBuilder.AddAttribute(paneSeq++, "ChildContent", (RenderFragment)(infoPanelBuilder => + // { + // infoPanelBuilder.OpenElement(0, "div"); + // infoPanelBuilder.AddAttribute(1, "style", "padding: 1rem;"); + // infoPanelBuilder.AddContent(2, "Info Panel - Coming soon..."); + // infoPanelBuilder.CloseElement(); + // })); + + // panesBuilder.CloseComponent(); + + // })); + + // builder.CloseComponent(); + // })); + //} + + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + if (!ShowInfoPanel) + { + base.BuildRenderTree(builder); + return; + } + + var seq = 0; + + builder.OpenComponent(seq++); + builder.AddAttribute(seq++, "Width", "100%"); + builder.AddAttribute(seq++, "Height", "100%"); + + builder.AddAttribute(seq++, "Panes", (RenderFragment)(panesBuilder => + { + var paneSeq = 0; + + // Left pane - Grid + panesBuilder.OpenComponent(paneSeq++); + panesBuilder.AddAttribute(paneSeq++, "ChildContent", (RenderFragment)(gridBuilder => + { + base.BuildRenderTree(gridBuilder); + })); + panesBuilder.CloseComponent(); + + // Right pane - InfoPanel + panesBuilder.OpenComponent(paneSeq++); + panesBuilder.AddAttribute(paneSeq++, "Size", "350px"); + panesBuilder.AddAttribute(paneSeq++, "MinSize", "300px"); + panesBuilder.AddAttribute(paneSeq++, "MaxSize", "800px"); + panesBuilder.AddAttribute(paneSeq++, "AllowCollapse", true); + + panesBuilder.AddAttribute(paneSeq++, "ChildContent", (RenderFragment)(infoPanelBuilder => + { + var infoPanelSeq = 0; + infoPanelBuilder.OpenComponent>(infoPanelSeq++); + infoPanelBuilder.AddComponentReferenceCapture(infoPanelSeq++, instance => + { + _infoPanelInstance = (MgGridInfoPanel)instance; + }); + infoPanelBuilder.CloseComponent(); + })); + + panesBuilder.CloseComponent(); + })); + + builder.CloseComponent(); + } + + //protected override Task RaiseFocusedRowChangedAsync(GridFocusedRowChangedEventArgsBase args) + //{ + // _focusedDataItem = args.DataItem; + // InvokeAsync(StateHasChanged); + // return base.RaiseFocusedRowChangedAsync(args); + //} + protected bool HasIdValue(TDataItem dataItem) => HasIdValue(dataItem.Id); protected bool HasIdValue(TId id) => !_equalityComparerId.Equals(id, default); protected bool IdEquals(TId id1, TId id2) => _equalityComparerId.Equals(id1, id2); @@ -104,6 +233,9 @@ public abstract class MgGridBase CustomizeEditModel { get; set; } [Parameter] public EventCallback OnGridCustomizeEditModel { get; set; } + protected new EventCallback FocusedRowChanged { get; set; } + [Parameter] public EventCallback OnGridFocusedRowChanged { get; set; } + [Parameter] public EventCallback> OnDataSourceChanged { get; set; } [Parameter] public EventCallback> OnGridItemChanging { get; set; } @@ -328,6 +460,19 @@ public abstract class MgGridBase(this, OnItemSaving); base.CustomizeEditModel = EventCallback.Factory.Create(this, OnCustomizeEditModel); //base.customizecel= EventCallback.Factory.Create(this, OnCustomizeEditModel); + base.FocusedRowChanged = EventCallback.Factory.Create(this, OnFocusedRowChanged); base.EditStart = EventCallback.Factory.Create(this, OnEditStart); CustomizeElement += OnCustomizeElement; diff --git a/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor b/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor new file mode 100644 index 0000000..eda2423 --- /dev/null +++ b/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor @@ -0,0 +1,42 @@ +@using DevExpress.Blazor +@using Microsoft.AspNetCore.Components.Rendering +@typeparam TDataItem where TDataItem : class + +
+ @if (_currentDataItem != null && _currentGrid != null) + { + var colSpan = _allDataColumns.Count > 10 ? 6 : 12; + + + @foreach (var column in _allDataColumns) + { + + } + + } + else + { +
+

Válasszon ki egy sort az adatok megtekintéséhez

+
+ } +
+ +@code { + private string GetColumnCaption(DxGridDataColumn column) + { + return !string.IsNullOrWhiteSpace(column.Caption) ? column.Caption : column.FieldName; + } +} diff --git a/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.cs b/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.cs new file mode 100644 index 0000000..f75d4dc --- /dev/null +++ b/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.cs @@ -0,0 +1,135 @@ +using DevExpress.Blazor; +using Microsoft.AspNetCore.Components; +using System.Reflection; + +namespace AyCode.Blazor.Components.Components.Grids; + +public partial class MgGridInfoPanel : ComponentBase where TDataItem : class +{ + private DxGrid? _currentGrid; + private TDataItem? _currentDataItem; + private List _allDataColumns = []; + + /// + /// Refreshes the InfoPanel with data from the specified grid row + /// + /// The grid instance + /// The data item from the focused row + public void RefreshData(DxGrid grid, TDataItem? dataItem) + { + ArgumentNullException.ThrowIfNull(grid); + + _currentGrid = grid; + _currentDataItem = dataItem; + + if (_currentGrid != null && _currentDataItem != null) + { + _allDataColumns = GetAllDataColumns(_currentGrid); + } + else + { + _allDataColumns = []; + } + + InvokeAsync(StateHasChanged); + } + + /// + /// Clears the InfoPanel + /// + public void Clear() + { + _currentGrid = null; + _currentDataItem = null; + _allDataColumns = []; + InvokeAsync(StateHasChanged); + } + + private List GetAllDataColumns(DxGrid grid) + { + var columns = new List(); + + try + { + var allColumns = grid.GetDataColumns(); + if (allColumns != null) + { + foreach (var column in allColumns) + { + // Minden DxGridDataColumn-t felveszünk, ha van FieldName-je + // NEM vizsgáljuk a Visible property-t! + if (column is DxGridDataColumn dataColumn && + !string.IsNullOrWhiteSpace(dataColumn.FieldName)) + { + columns.Add(dataColumn); + } + } + } + } + catch (Exception) + { + // Fallback: empty list if GetDataColumns fails + } + + return columns; + } + + private object? GetCellValue(DxGridDataColumn column) + { + if (_currentDataItem == null || string.IsNullOrWhiteSpace(column.FieldName)) + return null; + + try + { + var fieldName = column.FieldName; + var properties = fieldName.Split('.'); + object? currentValue = _currentDataItem; + + foreach (var propertyName in properties) + { + if (currentValue == null) + break; + + var propertyInfo = currentValue.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance); + if (propertyInfo == null) + return null; + + currentValue = propertyInfo.GetValue(currentValue); + } + + return currentValue; + } + catch + { + return null; + } + } + + private string GetDisplayText(DxGridDataColumn column, object? value) + { + if (value == null) + return string.Empty; + + if (value is DateTime dateTime) + { + return dateTime.ToString("yyyy-MM-dd HH:mm:ss"); + } + + if (value is bool boolValue) + { + return boolValue ? "Igen" : "Nem"; + } + + if (value is decimal || value is double || value is float) + { + return string.Format("{0:N2}", value); + } + + if (value is int || value is long || value is short || value is byte) + { + return string.Format("{0:N0}", value); + } + + return value.ToString() ?? string.Empty; + } +} diff --git a/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.css b/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.css new file mode 100644 index 0000000..fd262fb --- /dev/null +++ b/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.css @@ -0,0 +1,31 @@ +.mg-grid-info-panel { + height: 100%; + overflow-y: auto; + padding: 1rem; + background-color: #f8f9fa; +} + +.info-panel-form { + width: 100%; +} + +.info-panel-form .fw-semibold { + font-weight: 600; + color: #495057; + font-size: 0.875rem; +} + +.info-panel-empty { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: #6c757d; + font-style: italic; +} + +.info-panel-empty p { + margin: 0; + text-align: center; + padding: 2rem; +}