diff --git a/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs b/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs index 2a03c8c..e033bea 100644 --- a/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs +++ b/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs @@ -23,6 +23,7 @@ public interface IMgGridBase : IGrid bool IsSyncing { get; } string Caption { get; set; } + /// /// Current edit state of the grid (None, New, Edit) /// @@ -47,16 +48,11 @@ public interface IMgGridBase : IGrid /// Navigates to the next row in the grid /// void StepNextRow(); - - /// - /// Whether this grid shows an InfoPanel - /// - bool ShowInfoPanel { get; set; } /// - /// InfoPanel instance for displaying row details + /// InfoPanel instance for displaying row details (from wrapper) /// - IInfoPanelBase? InfoPanelInstance { get; set; } + IInfoPanelBase? InfoPanelInstance { get; } } public abstract class MgGridBase : DxGrid, IMgGridBase, IAsyncDisposable @@ -97,90 +93,27 @@ public abstract class MgGridBase - /// Custom InfoPanel component type. Must inherit from MgInfoPanelTemplateBase. - /// If not set, the default MgGridInfoPanel is used. + /// Reference to the wrapper component for grid-InfoPanel communication /// - [Parameter] public Type? InfoPanelType { get; set; } + [CascadingParameter] + public MgGridWithInfoPanel? GridWrapper { get; set; } private object _focusedDataItem; /// - /// InfoPanel instance for displaying row details + /// InfoPanel instance for displaying row details (from wrapper or direct) /// - public IInfoPanelBase? InfoPanelInstance { get; set; } + public IInfoPanelBase? InfoPanelInstance + { + get => GridWrapper?.InfoPanelInstance; + set { /* Set through wrapper */ } + } 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) { var seq = 0; @@ -190,70 +123,11 @@ public abstract class MgGridBase { - // Nested grids or root without InfoPanel: render base grid only - if (ParentGrid != null || !ShowInfoPanel) - { - base.BuildRenderTree(contentBuilder); - return; - } - - // Root grid with InfoPanel enabled: render splitter with grid + InfoPanel - var innerSeq = 0; - - contentBuilder.OpenComponent(innerSeq++); - contentBuilder.AddAttribute(innerSeq++, "Width", "100%"); - contentBuilder.AddAttribute(innerSeq++, "CssClass", "mg-grid-splitter"); - contentBuilder.AddAttribute(innerSeq++, "Orientation", Orientation.Horizontal); - - contentBuilder.AddAttribute(innerSeq++, "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", "400px"); - panesBuilder.AddAttribute(paneSeq++, "MinSize", "0px"); - panesBuilder.AddAttribute(paneSeq++, "MaxSize", "100%"); - panesBuilder.AddAttribute(paneSeq++, "AllowCollapse", true); - panesBuilder.AddAttribute(paneSeq++, "CssClass", "mg-info-panel-pane"); - - panesBuilder.AddAttribute(paneSeq++, "ChildContent", (RenderFragment)(infoPanelBuilder => - { - var infoPanelSeq = 0; - - // Use custom InfoPanel type if provided, otherwise use default MgGridInfoPanel - var panelType = InfoPanelType ?? typeof(MgGridInfoPanel); - infoPanelBuilder.OpenComponent(infoPanelSeq++, panelType); - infoPanelBuilder.AddComponentReferenceCapture(infoPanelSeq++, instance => - { - InfoPanelInstance = instance as IInfoPanelBase; - }); - infoPanelBuilder.CloseComponent(); - })); - - panesBuilder.CloseComponent(); - })); - - contentBuilder.CloseComponent(); + base.BuildRenderTree(contentBuilder); })); - builder.CloseComponent(); // Close CascadingValue + 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); @@ -373,12 +247,10 @@ public abstract class MgGridBase(SignalRClient, crudTags, ContextIds) { FilterText = FilterText }; SetGridData(_dataSource.GetReferenceInnerList()); @@ -514,11 +386,7 @@ public abstract class MgGridBase - @((_currentGrid as IMgGridBase)?.Caption ?? "") + @(_currentGrid?.Caption ?? "") + } - @* Toolbar *@ - @if (_currentGrid != null) - { -
- -
- } + @* Toolbar *@ + @if (_currentGrid != null) + { +
+ +
} @* Content *@
@if (GetActiveDataItem() != null && _currentGrid != null) { - var dataItem = GetActiveDataItem()!; - var dataItemType = dataItem.GetType(); + @* Before Columns *@ + @if (BeforeColumnsTemplate != null) + { + @BeforeColumnsTemplate(GetActiveDataItem()) + } + @* Columns *@ @if (ColumnsTemplate != null) { - @ColumnsTemplate + @ColumnsTemplate(GetActiveDataItem()) } else { -
- @foreach (var column in GetVisibleColumns()) - { - var displayText = GetDisplayTextFromGrid(column); - var value = GetCellValue(column); - var settingsType = GetEditSettingsType(column); - var isReadOnly = !_isEditMode || column.ReadOnly; + @RenderDefaultColumns() + } -
-
- -
- @if (_isEditMode && !column.ReadOnly) - { - @RenderEditableCell(column, dataItem, dataItemType, value, displayText, settingsType) - } - else - { - @RenderCellContent(column, value, displayText, settingsType) - } -
-
-
- } -
+ @* After Columns *@ + @if (AfterColumnsTemplate != null) + { + @AfterColumnsTemplate(GetActiveDataItem()) } } else @@ -76,24 +60,82 @@ @* Footer *@ @if (FooterTemplate != null) { - @FooterTemplate + @FooterTemplate(GetActiveDataItem()) }
@code { /// - /// Gets the columns to display based on edit mode and ShowReadOnlyFieldsInEditMode setting + /// Custom header template. Receives the current data item. /// - private IEnumerable GetVisibleColumns() + [Parameter] public RenderFragment? HeaderTemplate { get; set; } + + /// + /// Content to render before the columns. + /// + [Parameter] public RenderFragment? BeforeColumnsTemplate { get; set; } + + /// + /// Custom columns template. If not set, columns are auto-generated. + /// + [Parameter] public RenderFragment? ColumnsTemplate { get; set; } + + /// + /// Content to render after the columns. + /// + [Parameter] public RenderFragment? AfterColumnsTemplate { get; set; } + + /// + /// Custom footer template. + /// + [Parameter] public RenderFragment? FooterTemplate { get; set; } + + private RenderFragment RenderDefaultColumns() => builder => { - if (!_isEditMode || ShowReadOnlyFieldsInEditMode) + var dataItem = GetActiveDataItem()!; + var dataItemType = dataItem.GetType(); + var seq = 0; + + builder.OpenElement(seq++, "div"); + builder.AddAttribute(seq++, "class", "mg-info-panel-grid"); + + foreach (var column in GetVisibleColumns()) { - return _allDataColumns; + var displayText = GetDisplayTextFromGrid(column); + var value = GetCellValue(column); + var settingsType = GetEditSettingsType(column); + var isReadOnly = !_isEditMode || column.ReadOnly; + + builder.OpenElement(seq++, "div"); + builder.AddAttribute(seq++, "class", "mg-info-panel-item"); + + builder.OpenElement(seq++, "div"); + builder.AddAttribute(seq++, "class", "dxbl-form-layout-item"); + + builder.OpenElement(seq++, "label"); + builder.AddAttribute(seq++, "class", $"dxbl-fl-lc {GetCaptionCssClass(isReadOnly)} d-block mb-1 small"); + builder.AddContent(seq++, GetColumnCaption(column)); + builder.CloseElement(); + + builder.OpenElement(seq++, "div"); + builder.AddAttribute(seq++, "class", "dxbl-fl-ec"); + + if (_isEditMode && !column.ReadOnly) + { + RenderEditableCell(column, dataItem, dataItemType, value, displayText, settingsType)(builder); + } + else + { + RenderCellContent(column, value, displayText, settingsType)(builder); + } + + builder.CloseElement(); + builder.CloseElement(); + builder.CloseElement(); } - - // In edit mode with ShowReadOnlyFieldsInEditMode=false, hide readonly columns - return _allDataColumns.Where(c => !c.ReadOnly); - } + + builder.CloseElement(); + }; private static string GetColumnCaption(DxGridDataColumn column) { diff --git a/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.cs b/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.cs index e8e7a4f..97d4ae0 100644 --- a/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.cs +++ b/AyCode.Blazor.Components/Components/Grids/MgGridInfoPanel.razor.cs @@ -27,32 +27,23 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan [Parameter] public bool ShowReadOnlyFieldsInEditMode { get; set; } = false; /// - /// Custom header template. If not set, default header is used. + /// Reference to the wrapper component - automatically registers this InfoPanel /// - [Parameter] public RenderFragment? HeaderTemplate { get; set; } - - /// - /// Custom columns template. If not set, columns are auto-generated. - /// - [Parameter] public RenderFragment? ColumnsTemplate { get; set; } - - /// - /// Custom footer template. If not set, no footer is rendered. - /// - [Parameter] public RenderFragment? FooterTemplate { get; set; } + [CascadingParameter] + public MgGridWithInfoPanel? GridWrapper { get; set; } private ElementReference _panelElement; private bool _isJsInitialized; private const int DefaultTopOffset = 300; // Increased from 180 to account for header + tabs + toolbar - private IMgGridBase? _currentGrid; - private object? _currentDataItem; - private int _focusedRowVisibleIndex = -1; - private List _allDataColumns = []; + protected IMgGridBase? _currentGrid; + protected object? _currentDataItem; + protected int _focusedRowVisibleIndex = -1; + protected List _allDataColumns = []; // Edit mode state - private bool _isEditMode; - private object? _editModel; + protected bool _isEditMode; + protected object? _editModel; // Cache for edit settings to avoid repeated lookups private readonly Dictionary _editSettingsCache = []; @@ -61,6 +52,9 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan { if (firstRender) { + // Register this InfoPanel with the wrapper + GridWrapper?.RegisterInfoPanel(this); + await InitializeStickyAsync(); } } @@ -95,21 +89,21 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan _focusedRowVisibleIndex = visibleIndex; _editSettingsCache.Clear(); + // Clear edit mode when refreshing with new data + _isEditMode = false; + _editModel = null; + System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - _currentDataItem is null: {_currentDataItem == null}, cast success: {dataItem != null && _currentDataItem != null}"); - // Ha nem vagyunk edit módban, frissítjük az oszlopokat - if (!_isEditMode) + if (_currentGrid != null && _currentDataItem != null) { - if (_currentGrid != null && _currentDataItem != null) - { - _allDataColumns = GetAllDataColumns(_currentGrid); - System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - loaded {_allDataColumns.Count} columns"); - } - else - { - _allDataColumns = []; - System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - cleared columns (grid or dataItem is null)"); - } + _allDataColumns = GetAllDataColumns(_currentGrid); + System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - loaded {_allDataColumns.Count} columns"); + } + else + { + _allDataColumns = []; + System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - cleared columns (grid or dataItem is null)"); } StateHasChanged(); @@ -121,6 +115,8 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan /// public void SetEditMode(object editModel) { + System.Diagnostics.Debug.WriteLine($"[InfoPanel] SetEditMode called - editModel type: {editModel?.GetType().Name ?? "null"}, grid.GridEditState: {_currentGrid?.GridEditState}"); + _editModel = editModel; _isEditMode = true; _currentDataItem = _editModel; @@ -130,7 +126,8 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan _allDataColumns = GetAllDataColumns(_currentGrid); } - StateHasChanged(); + InvokeAsync(StateHasChanged); + System.Diagnostics.Debug.WriteLine($"[InfoPanel] SetEditMode - InvokeAsync(StateHasChanged) called, _isEditMode: {_isEditMode}"); } /// @@ -138,10 +135,13 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan /// public void ClearEditMode() { + System.Diagnostics.Debug.WriteLine($"[InfoPanel] ClearEditMode called, grid.GridEditState: {_currentGrid?.GridEditState}"); + _isEditMode = false; _editModel = null; _editSettingsCache.Clear(); - StateHasChanged(); + InvokeAsync(StateHasChanged); + System.Diagnostics.Debug.WriteLine($"[InfoPanel] ClearEditMode - InvokeAsync(StateHasChanged) called"); } /// @@ -177,13 +177,13 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan /// /// Gets the data item to display/edit (EditModel in edit mode, otherwise CurrentDataItem) /// - private object? GetActiveDataItem() => _isEditMode && _editModel != null ? _editModel : _currentDataItem; + protected object? GetActiveDataItem() => _isEditMode && _editModel != null ? _editModel : _currentDataItem; /// /// Gets the display text for a field using the grid's internal formatting. /// For ComboBox columns, tries to get the text from the lookup data source. /// - private string GetDisplayTextFromGrid(DxGridDataColumn column) + protected string GetDisplayTextFromGrid(DxGridDataColumn column) { var dataItem = GetActiveDataItem(); if (_currentGrid == null || dataItem == null || string.IsNullOrWhiteSpace(column.FieldName)) @@ -310,7 +310,37 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan }; } - private static List GetAllDataColumns(IMgGridBase grid) + /// + /// Gets the columns to display based on edit mode and ShowReadOnlyFieldsInEditMode setting + /// + protected IEnumerable GetVisibleColumns() + { + if (!_isEditMode || ShowReadOnlyFieldsInEditMode) + { + return _allDataColumns; + } + + // In edit mode with ShowReadOnlyFieldsInEditMode=false, hide readonly columns + return _allDataColumns.Where(c => !c.ReadOnly); + } + + protected object? GetCellValue(DxGridDataColumn column) + { + var dataItem = GetActiveDataItem(); + if (_currentGrid == null || dataItem == null || string.IsNullOrWhiteSpace(column.FieldName)) + return null; + + try + { + return _currentGrid.GetDataItemValue(dataItem, column.FieldName); + } + catch + { + return null; + } + } + + protected static List GetAllDataColumns(IMgGridBase grid) { var columns = new List(); @@ -342,22 +372,6 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan return columns; } - private object? GetCellValue(DxGridDataColumn column) - { - var dataItem = GetActiveDataItem(); - if (_currentGrid == null || dataItem == null || string.IsNullOrWhiteSpace(column.FieldName)) - return null; - - try - { - return _currentGrid.GetDataItemValue(dataItem, column.FieldName); - } - catch - { - return null; - } - } - /// /// Gets the EditSettings type for rendering logic /// diff --git a/AyCode.Blazor.Components/Components/Grids/MgGridWithInfoPanel.razor b/AyCode.Blazor.Components/Components/Grids/MgGridWithInfoPanel.razor new file mode 100644 index 0000000..80155bc --- /dev/null +++ b/AyCode.Blazor.Components/Components/Grids/MgGridWithInfoPanel.razor @@ -0,0 +1,74 @@ +@using DevExpress.Blazor + + + @if (ShowInfoPanel) + { + + + + @GridContent + + + @if (ChildContent != null) + { + @ChildContent + } + else + { + + } + + + + } + else + { + @GridContent + } + + +@code { + private IInfoPanelBase? _infoPanelInstance; + + /// + /// The grid content to display in the left pane + /// + [Parameter] + public RenderFragment? GridContent { get; set; } + + /// + /// InfoPanel content (e.g., GridShippingDocumentInfoPanel) to display in the right pane. + /// If not set, the default MgGridInfoPanel is used. + /// + [Parameter] + public RenderFragment? ChildContent { get; set; } + + /// + /// Initial size of the InfoPanel pane. Default is "400px". + /// + [Parameter] + public string InfoPanelSize { get; set; } = "400px"; + + /// + /// Whether to show the InfoPanel. Default is true. + /// + [Parameter] + public bool ShowInfoPanel { get; set; } = true; + + /// + /// Gets or sets the InfoPanel instance for grid-InfoPanel communication + /// + public IInfoPanelBase? InfoPanelInstance + { + get => _infoPanelInstance; + set => _infoPanelInstance = value; + } + + /// + /// Registers an InfoPanel instance (called by child InfoPanel components) + /// + public void RegisterInfoPanel(IInfoPanelBase infoPanel) + { + _infoPanelInstance = infoPanel; + } +} diff --git a/AyCode.Blazor.Components/Components/Grids/MgInfoPanelTemplateBase.cs b/AyCode.Blazor.Components/Components/Grids/MgInfoPanelTemplateBase.cs deleted file mode 100644 index d314f46..0000000 --- a/AyCode.Blazor.Components/Components/Grids/MgInfoPanelTemplateBase.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Microsoft.AspNetCore.Components; - -namespace AyCode.Blazor.Components.Components.Grids; - -/// -/// Base class for custom InfoPanel templates. -/// Inherit from this class to create a custom InfoPanel for a specific grid. -/// -public abstract class MgInfoPanelTemplateBase : MgGridInfoPanel -{ - /// - /// Override this to provide custom header content. - /// Return null to use default header. - /// - protected virtual RenderFragment? GetHeaderTemplate() => null; - - /// - /// Override this to provide custom columns/content. - /// Return null to use auto-generated columns. - /// - protected virtual RenderFragment? GetColumnsTemplate() => null; - - /// - /// Override this to provide custom footer content. - /// Return null to hide footer. - /// - protected virtual RenderFragment? GetFooterTemplate() => null; - - protected override void OnInitialized() - { - SetTemplates(); - base.OnInitialized(); - } - - protected override void OnParametersSet() - { - SetTemplates(); - base.OnParametersSet(); - } - - private void SetTemplates() - { - HeaderTemplate = GetHeaderTemplate(); - ColumnsTemplate = GetColumnsTemplate(); - FooterTemplate = GetFooterTemplate(); - } -}