Refactor grid toolbar and InfoPanel for reusability
Introduce reusable MgGridToolbarBase and MgGridToolbarTemplate components, replacing old toolbar implementations. InfoPanel now displays grid captions and includes a toolbar for quick actions. Refactor InfoPanel value rendering for improved DevExpress-like appearance. Update IMgGridBase and related classes to support captions and use new abstractions. Update usings and project structure accordingly.
This commit is contained in:
parent
90f12a160e
commit
fe1a59a0bd
|
|
@ -22,6 +22,7 @@ public interface IMgGridBase : IGrid
|
|||
/// </summary>
|
||||
bool IsSyncing { get; }
|
||||
|
||||
string Caption { get; set; }
|
||||
/// <summary>
|
||||
/// Current edit state of the grid (None, New, Edit)
|
||||
/// </summary>
|
||||
|
|
@ -254,6 +255,8 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
|||
[Parameter] public string? KeyFieldNameToParentId { get; set; }
|
||||
[Parameter] public object[]? ContextIds { get; set; }
|
||||
|
||||
[Parameter] public string Caption { get; set; } = typeof(TDataItem).Name;
|
||||
|
||||
public bool IsMasterGrid => ParentDataItem == null;
|
||||
protected PropertyInfo? KeyFieldPropertyInfoToParent;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,17 @@
|
|||
<div @ref="_panelElement" class="mg-grid-info-panel @(_isEditMode ? "edit-mode" : "view-mode")">
|
||||
@* Header *@
|
||||
<div class="dxbl-grid-header-panel px-3 py-2 border-bottom">
|
||||
<span class="fw-semibold">Sor részletei</span>
|
||||
<span class="fw-semibold">@((_currentGrid as IMgGridBase)?.Caption ?? "")</span>
|
||||
</div>
|
||||
|
||||
@* Toolbar *@
|
||||
@if (_currentGrid != null)
|
||||
{
|
||||
<div class="mg-info-panel-toolbar border-bottom">
|
||||
<MgGridToolbarTemplate Grid="_currentGrid as IMgGridBase" OnlyGridEditTools="true" ShowOnlyIcon="true" />
|
||||
</div>
|
||||
}
|
||||
|
||||
@* Content *@
|
||||
<div class="mg-info-panel-content">
|
||||
@if (GetActiveDataItem() != null && _currentGrid != null)
|
||||
|
|
@ -446,87 +454,37 @@
|
|||
{
|
||||
var seq = 0;
|
||||
|
||||
switch (settingsType)
|
||||
// View mode: simple text display with DevExpress-like styling (no editors)
|
||||
builder.OpenElement(seq++, "div");
|
||||
builder.AddAttribute(seq++, "class", GetViewModeCssClass(value, settingsType));
|
||||
builder.AddAttribute(seq++, "title", displayText);
|
||||
|
||||
// Special handling for boolean - show checkbox icon
|
||||
if (value is bool boolValue)
|
||||
{
|
||||
case EditSettingsType.ComboBox:
|
||||
builder.OpenElement(seq++, "div");
|
||||
builder.AddAttribute(seq++, "title", displayText);
|
||||
builder.AddAttribute(seq++, "class", "info-panel-text-wrapper");
|
||||
builder.OpenComponent<DxTextBox>(seq++);
|
||||
builder.AddAttribute(seq++, "Text", displayText);
|
||||
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||
builder.CloseComponent();
|
||||
builder.CloseElement();
|
||||
return;
|
||||
|
||||
case EditSettingsType.CheckBox when value is bool boolVal:
|
||||
builder.OpenComponent<DxCheckBox<bool>>(seq++);
|
||||
builder.AddAttribute(seq++, "Checked", boolVal);
|
||||
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||
builder.CloseComponent();
|
||||
return;
|
||||
|
||||
case EditSettingsType.Memo:
|
||||
builder.OpenElement(seq++, "div");
|
||||
builder.AddAttribute(seq++, "title", displayText);
|
||||
builder.OpenComponent<DxMemo>(seq++);
|
||||
builder.AddAttribute(seq++, "Text", displayText);
|
||||
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||
builder.AddAttribute(seq++, "Rows", 3);
|
||||
builder.CloseComponent();
|
||||
builder.CloseElement();
|
||||
return;
|
||||
builder.OpenElement(seq++, "span");
|
||||
builder.AddAttribute(seq++, "class", boolValue ? "dx-icon dx-icon-check text-success" : "dx-icon dx-icon-close text-muted");
|
||||
builder.CloseElement();
|
||||
}
|
||||
|
||||
// Default by value type
|
||||
switch (value)
|
||||
else
|
||||
{
|
||||
case bool boolValue:
|
||||
builder.OpenComponent<DxCheckBox<bool>>(seq++);
|
||||
builder.AddAttribute(seq++, "Checked", boolValue);
|
||||
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||
builder.CloseComponent();
|
||||
break;
|
||||
|
||||
case DateTime dateValue:
|
||||
builder.OpenComponent<DxDateEdit<DateTime>>(seq++);
|
||||
builder.AddAttribute(seq++, "Date", dateValue);
|
||||
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||
builder.AddAttribute(seq++, "DisplayFormat", column.DisplayFormat ?? "yyyy-MM-dd HH:mm");
|
||||
builder.CloseComponent();
|
||||
break;
|
||||
|
||||
case DateOnly dateOnlyValue:
|
||||
builder.OpenComponent<DxDateEdit<DateOnly>>(seq++);
|
||||
builder.AddAttribute(seq++, "Date", dateOnlyValue);
|
||||
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||
builder.AddAttribute(seq++, "DisplayFormat", column.DisplayFormat ?? "yyyy-MM-dd");
|
||||
builder.CloseComponent();
|
||||
break;
|
||||
|
||||
case decimal or double or float or int or long or short:
|
||||
builder.OpenElement(seq++, "div");
|
||||
builder.AddAttribute(seq++, "title", displayText);
|
||||
builder.AddAttribute(seq++, "class", "info-panel-text-wrapper");
|
||||
builder.OpenComponent<DxTextBox>(seq++);
|
||||
builder.AddAttribute(seq++, "Text", displayText);
|
||||
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||
builder.AddAttribute(seq++, "CssClass", "text-end");
|
||||
builder.CloseComponent();
|
||||
builder.CloseElement();
|
||||
break;
|
||||
|
||||
default:
|
||||
builder.OpenElement(seq++, "div");
|
||||
builder.AddAttribute(seq++, "title", displayText);
|
||||
builder.AddAttribute(seq++, "class", "info-panel-text-wrapper");
|
||||
builder.OpenComponent<DxTextBox>(seq++);
|
||||
builder.AddAttribute(seq++, "Text", displayText);
|
||||
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||
builder.CloseComponent();
|
||||
builder.CloseElement();
|
||||
break;
|
||||
builder.AddContent(seq++, displayText);
|
||||
}
|
||||
|
||||
builder.CloseElement();
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetViewModeCssClass(object? value, EditSettingsType settingsType)
|
||||
{
|
||||
var baseCss = "mg-info-panel-value";
|
||||
|
||||
return value switch
|
||||
{
|
||||
bool => $"{baseCss} mg-info-panel-value-bool",
|
||||
decimal or double or float or int or long or short => $"{baseCss} mg-info-panel-value-numeric",
|
||||
DateTime or DateOnly or TimeOnly => $"{baseCss} mg-info-panel-value-date",
|
||||
_ => baseCss
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ public interface IInfoPanelBase
|
|||
{
|
||||
void ClearEditMode();
|
||||
void SetEditMode(object editModel);
|
||||
void RefreshData(DxGrid grid, object? dataItem, int visibleIndex = -1);
|
||||
void RefreshData(IMgGridBase grid, object? dataItem, int visibleIndex = -1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -30,7 +30,7 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
|
|||
private bool _isJsInitialized;
|
||||
private const int DefaultTopOffset = 300; // Increased from 180 to account for header + tabs + toolbar
|
||||
|
||||
private DxGrid? _currentGrid;
|
||||
private IMgGridBase? _currentGrid;
|
||||
private object? _currentDataItem;
|
||||
private int _focusedRowVisibleIndex = -1;
|
||||
private List<DxGridDataColumn> _allDataColumns = [];
|
||||
|
|
@ -69,7 +69,7 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
|
|||
/// <summary>
|
||||
/// Refreshes the InfoPanel with data from the specified grid row (view mode)
|
||||
/// </summary>
|
||||
public void RefreshData(DxGrid grid, object? dataItem, int visibleIndex = -1)
|
||||
public void RefreshData(IMgGridBase grid, object? dataItem, int visibleIndex = -1)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(grid);
|
||||
|
||||
|
|
@ -290,12 +290,12 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
|
|||
decimal decValue => decValue.ToString("N2"),
|
||||
double dblValue => dblValue.ToString("N2"),
|
||||
float fltValue => fltValue.ToString("N2"),
|
||||
int or long or short or byte => string.Format("{0:N0}", value),
|
||||
int or long or short or byte => $"{value:N0}",
|
||||
_ => value.ToString() ?? string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
private static List<DxGridDataColumn> GetAllDataColumns(DxGrid grid)
|
||||
private static List<DxGridDataColumn> GetAllDataColumns(IMgGridBase grid)
|
||||
{
|
||||
var columns = new List<DxGridDataColumn>();
|
||||
|
||||
|
|
|
|||
|
|
@ -105,3 +105,31 @@
|
|||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
/* View mode value styling - matches DevExpress theme */
|
||||
.mg-info-panel-value {
|
||||
padding: 0.375rem 0.75rem;
|
||||
min-height: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start; /* Always align left */
|
||||
background-color: var(--dxbl-bg, #fff);
|
||||
border: 1px solid var(--dxbl-border-color, #dee2e6);
|
||||
border-radius: var(--dxbl-border-radius, 0.25rem);
|
||||
font-size: var(--dxbl-font-size, 0.875rem);
|
||||
color: var(--dxbl-text, #212529);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mg-info-panel-value-numeric {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.mg-info-panel-value-bool {
|
||||
/* Keep left aligned */
|
||||
}
|
||||
|
||||
.mg-info-panel-value-date {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
using DevExpress.Blazor;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AyCode.Blazor.Components.Components.Grids
|
||||
{
|
||||
public class MgGridToolbarBase : DxToolbar
|
||||
{
|
||||
[Parameter] public IMgGridBase Grid { get; set; }
|
||||
[Parameter] public Func<ToolbarItemClickEventArgs, Task> RefreshClick { get; set; }
|
||||
[Parameter] public bool ShowOnlyIcon { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
@using AyCode.Blazor.Components.Components.Grids
|
||||
|
||||
<MgGridToolbarBase @ref="GridToolbar" Grid="Grid" ItemRenderStyleMode="ToolbarRenderStyleMode.Plain" ShowOnlyIcon="ShowOnlyIcon">
|
||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "New")" Click="NewItem_Click" IconCssClass="grid-new-row" Visible="@(!IsEditing)" Enabled="@(!IsSyncing)" />
|
||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Edit")" Click="EditItem_Click" IconCssClass="grid-edit-row" Visible="@(!IsEditing)" Enabled="@(HasFocusedRow && !IsSyncing)" />
|
||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Delete")" Click="DeleteItem_Click" IconCssClass="grid-delete-row" Visible="@(!IsEditing)" Enabled="@(false && HasFocusedRow && !IsSyncing)" />
|
||||
|
||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Save")" Click="SaveItem_Click" IconCssClass="grid-save" Visible="@IsEditing" RenderStyle="ButtonRenderStyle.Primary" />
|
||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Cancel")" Click="CancelEdit_Click" IconCssClass="grid-cancel" Visible="@IsEditing" RenderStyle="ButtonRenderStyle.Secondary" />
|
||||
|
||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Prev Row")" BeginGroup="true" Click="PrevRow_Click" IconCssClass="grid-chevron-up" Enabled="@(HasFocusedRow && !IsSyncing && !IsEditing)" />
|
||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Next Row")" Click="NextRow_Click" IconCssClass="grid-chevron-down" Enabled="@(HasFocusedRow && !IsSyncing && !IsEditing)" />
|
||||
|
||||
@if (!OnlyGridEditTools)
|
||||
{
|
||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Column Chooser")" BeginGroup="true" Click="ColumnChooserItem_Click" IconCssClass="grid-column-chooser" />
|
||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Export")" IconCssClass="grid-export" Enabled="@(false && HasFocusedRow && !IsEditing)">
|
||||
<Items>
|
||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "To CSV")" Click="ExportCsvItem_Click" IconCssClass="grid-export-xlsx" />
|
||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "To XLSX")" Click="ExportXlsxItem_Click" IconCssClass="grid-export-xlsx" />
|
||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "To XLS")" Click="ExportXlsItem_Click" IconCssClass="grid-export-xlsx" />
|
||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "To PDF")" Click="ExportPdfItem_Click" IconCssClass="grid-export-pdf" />
|
||||
</Items>
|
||||
</DxToolbarItem>
|
||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Reload data")" BeginGroup="true" Click="ReloadData_Click" IconCssClass="grid-refresh" Enabled="@(!IsSyncing && !_isReloadInProgress && !IsEditing)" />
|
||||
<DxToolbarItem BeginGroup="true"></DxToolbarItem>
|
||||
@ToolbarItemsExtended
|
||||
}
|
||||
</MgGridToolbarBase>
|
||||
|
||||
@code {
|
||||
[Parameter] public bool OnlyGridEditTools { get; set; } = false;
|
||||
[Parameter] public IMgGridBase Grid { get; set; } = null!;
|
||||
[Parameter] public RenderFragment? ToolbarItemsExtended { get; set; }
|
||||
[Parameter] public EventCallback<ToolbarItemClickEventArgs> OnReloadDataClick { get; set; }
|
||||
[Parameter] public bool ShowOnlyIcon { get; set; } = false;
|
||||
|
||||
public MgGridToolbarBase GridToolbar { get; set; } = null!;
|
||||
const string ExportFileName = "ExportResult";
|
||||
|
||||
private bool _isReloadInProgress;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the grid is currently in edit mode (New or Edit)
|
||||
/// </summary>
|
||||
private bool IsEditing => Grid?.GridEditState != MgGridEditState.None;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the grid is currently syncing data
|
||||
/// </summary>
|
||||
private bool IsSyncing => Grid?.IsSyncing ?? false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether there is a focused row in the grid
|
||||
/// </summary>
|
||||
private bool HasFocusedRow => Grid?.GetFocusedRowIndex() >= 0;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
}
|
||||
|
||||
async Task ReloadData_Click(ToolbarItemClickEventArgs e)
|
||||
{
|
||||
_isReloadInProgress = true;
|
||||
try
|
||||
{
|
||||
await OnReloadDataClick.InvokeAsync(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isReloadInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
async Task NewItem_Click()
|
||||
{
|
||||
await Grid.StartEditNewRowAsync();
|
||||
}
|
||||
|
||||
async Task EditItem_Click()
|
||||
{
|
||||
await Grid.StartEditRowAsync(Grid.GetFocusedRowIndex());
|
||||
}
|
||||
|
||||
void DeleteItem_Click()
|
||||
{
|
||||
Grid.ShowRowDeleteConfirmation(Grid.GetFocusedRowIndex());
|
||||
}
|
||||
|
||||
async Task SaveItem_Click()
|
||||
{
|
||||
await Grid.SaveChangesAsync();
|
||||
}
|
||||
|
||||
async Task CancelEdit_Click()
|
||||
{
|
||||
await Grid.CancelEditAsync();
|
||||
}
|
||||
|
||||
void PrevRow_Click()
|
||||
{
|
||||
Grid.StepPrevRow();
|
||||
}
|
||||
|
||||
void NextRow_Click()
|
||||
{
|
||||
Grid.StepNextRow();
|
||||
}
|
||||
|
||||
void ColumnChooserItem_Click(ToolbarItemClickEventArgs e)
|
||||
{
|
||||
Grid.ShowColumnChooser();
|
||||
}
|
||||
|
||||
async Task ExportXlsxItem_Click()
|
||||
{
|
||||
await Grid.ExportToXlsxAsync(ExportFileName);
|
||||
}
|
||||
|
||||
async Task ExportXlsItem_Click()
|
||||
{
|
||||
await Grid.ExportToXlsAsync(ExportFileName);
|
||||
}
|
||||
|
||||
async Task ExportCsvItem_Click()
|
||||
{
|
||||
await Grid.ExportToCsvAsync(ExportFileName);
|
||||
}
|
||||
|
||||
async Task ExportPdfItem_Click()
|
||||
{
|
||||
await Grid.ExportToPdfAsync(ExportFileName);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue