Refactor MgGridInfoPanel for theme, UX, and PDF perf

- Refactored MgGridInfoPanel for DevExpress theme compatibility and improved usability; streamlined HTML/CSS, added OnDataItemChanged event, and enhanced empty state handling.
- Updated CSS to use theme variables, improved responsive grid and table styling, and enhanced integration with DevExpress components.
- GridShippingDocumentInfoPanel now uses OnDataItemChanged to load a random PDF per row selection; table layout and totals improved.
- Optimized pdfViewer.js to cache rendered PDFs, skip redundant renders, and improve logging and error handling.
- Added empty helper classes for future extensibility.
- Minor: MainLayout now uses RefreshMainLayout for UI refresh after auto-login.
This commit is contained in:
Loretta 2025-12-19 13:59:00 +01:00
parent 4c86914884
commit 017eb16c4b
7 changed files with 226 additions and 354 deletions

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace AyCode.Blazor.Components.Components.Grids
{
internal class MgGridHelper
{
}
}

View File

@ -2,24 +2,21 @@
@using Microsoft.AspNetCore.Components.Rendering
@using System.Reflection
<div @ref="_panelElement" class="mg-grid-info-panel @(_isEditMode ? "edit-mode" : "view-mode") @GetColumnCountClass()"
style="@GetBreakpointStyles()">
<div @ref="_panelElement" class="mg-grid-info-panel @(_isEditMode ? "edit-mode" : "") @GetColumnCountClass()">
@* Header *@
@if (HeaderTemplate != null)
{
@HeaderTemplate(GetActiveDataItem())
}
else
else if (_currentGrid != null)
{
<div class="dxbl-grid-header-panel px-3 py-2 border-bottom">
<span class="fw-semibold">@(_currentGrid?.Caption ?? "")</span>
</div>
<div class="mg-info-panel-header">@_currentGrid.Caption</div>
}
@* Toolbar *@
@if (_currentGrid != null)
{
<div class="mg-info-panel-toolbar border-bottom">
<div class="mg-info-panel-toolbar">
<MgGridToolbarTemplate Grid="_currentGrid" OnlyGridEditTools="true" ShowOnlyIcon="true" />
</div>
}
@ -28,13 +25,11 @@
<div class="mg-info-panel-content">
@if (GetActiveDataItem() != null && _currentGrid != null)
{
@* Before Columns *@
@if (BeforeColumnsTemplate != null)
{
@BeforeColumnsTemplate(GetActiveDataItem())
}
@* Columns *@
@if (ColumnsTemplate != null)
{
@ColumnsTemplate(GetActiveDataItem())
@ -44,7 +39,6 @@
@RenderDefaultColumns()
}
@* After Columns *@
@if (AfterColumnsTemplate != null)
{
@AfterColumnsTemplate(GetActiveDataItem())
@ -52,7 +46,7 @@
}
else
{
<div class="text-center text-muted py-5">
<div class="mg-info-panel-empty">
<p>Válasszon ki egy sort az adatok megtekintéséhez</p>
</div>
}
@ -66,55 +60,31 @@
</div>
@code {
/// <summary>
/// Custom header template. Receives the current data item.
/// </summary>
[Parameter] public RenderFragment<object?>? HeaderTemplate { get; set; }
/// <summary>
/// Content to render before the columns.
/// </summary>
[Parameter] public RenderFragment<object?>? BeforeColumnsTemplate { get; set; }
/// <summary>
/// Custom columns template. If not set, columns are auto-generated.
/// </summary>
[Parameter] public RenderFragment<object?>? ColumnsTemplate { get; set; }
/// <summary>
/// Content to render after the columns.
/// </summary>
[Parameter] public RenderFragment<object?>? AfterColumnsTemplate { get; set; }
/// <summary>
/// Custom footer template.
/// </summary>
[Parameter] public RenderFragment<object?>? FooterTemplate { get; set; }
/// <summary>
/// Called when the data item changes (row selection changed)
/// </summary>
[Parameter] public EventCallback<object?> OnDataItemChanged { get; set; }
private string GetColumnCountClass()
private string GetColumnCountClass() => FixedColumnCount switch
{
if (FixedColumnCount.HasValue)
{
return FixedColumnCount.Value switch
{
1 => "mg-columns-1",
2 => "mg-columns-2",
3 => "mg-columns-3",
4 => "mg-columns-4",
_ => ""
};
}
return "";
}
private string GetBreakpointStyles()
{
return $"--mg-bp-2col: {TwoColumnBreakpoint}px; --mg-bp-3col: {ThreeColumnBreakpoint}px; --mg-bp-4col: {FourColumnBreakpoint}px;";
}
1 => "mg-columns-1",
2 => "mg-columns-2",
3 => "mg-columns-3",
4 => "mg-columns-4",
_ => ""
};
private RenderFragment RenderDefaultColumns() => builder =>
{
var dataItem = GetActiveDataItem()!;
var dataItem = GetActiveDataItem();
if (dataItem == null) return;
var dataItemType = dataItem.GetType();
var seq = 0;
@ -126,134 +96,92 @@
var displayText = GetDisplayTextFromGrid(column);
var value = GetCellValue(column);
var settingsType = GetEditSettingsType(column);
var isReadOnly = !_isEditMode || column.ReadOnly;
var isEditable = _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.AddAttribute(seq++, "class", isEditable ? "mg-info-panel-label editable" : "mg-info-panel-label");
builder.AddContent(seq++, GetColumnCaption(column));
builder.CloseElement();
builder.OpenElement(seq++, "div");
builder.AddAttribute(seq++, "class", "dxbl-fl-ec");
if (_isEditMode && !column.ReadOnly)
if (isEditable)
{
RenderEditableCell(column, dataItem, dataItemType, value, displayText, settingsType)(builder);
}
else
{
RenderCellContent(column, value, displayText, settingsType)(builder);
RenderCellContent(value, displayText)(builder);
}
builder.CloseElement();
builder.CloseElement();
builder.CloseElement();
builder.CloseElement();
}
builder.CloseElement();
};
private static string GetColumnCaption(DxGridDataColumn column)
{
return !string.IsNullOrWhiteSpace(column.Caption) ? column.Caption : column.FieldName;
}
private static string GetCaptionCssClass(bool isReadOnly)
{
return isReadOnly ? "fw-semibold" : "fw-semibold text-primary";
}
private static string GetColumnCaption(DxGridDataColumn column) =>
!string.IsNullOrWhiteSpace(column.Caption) ? column.Caption : column.FieldName;
private RenderFragment RenderEditableCell(DxGridDataColumn column, object dataItem, Type dataItemType, object? value, string displayText, EditSettingsType settingsType)
{
return builder =>
{
var seq = 0;
var fieldName = column.FieldName;
var propertyInfo = dataItemType.GetProperty(fieldName);
var propertyInfo = dataItemType.GetProperty(column.FieldName);
if (propertyInfo == null)
{
RenderCellContent(column, value, displayText, settingsType)(builder);
RenderCellContent(value, displayText)(builder);
return;
}
var propertyType = propertyInfo.PropertyType;
var underlyingType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;
var underlyingType = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
// ComboBox
if (settingsType == EditSettingsType.ComboBox)
if (settingsType == EditSettingsType.ComboBox && GetEditSettings(column.FieldName) is DxComboBoxSettings comboSettings)
{
var comboSettings = GetEditSettings(column.FieldName) as DxComboBoxSettings;
if (comboSettings != null)
{
RenderComboBoxEditor(builder, ref seq, dataItem, propertyInfo, comboSettings);
return;
}
RenderComboBoxEditor(builder, ref seq, dataItem, propertyInfo, comboSettings);
return;
}
// Render based on type
if (underlyingType == typeof(bool))
RenderCheckBoxEditor(builder, ref seq, dataItem, propertyInfo);
else if (underlyingType == typeof(DateTime))
RenderDateTimeEditor(builder, ref seq, dataItem, propertyInfo, column.DisplayFormat);
else if (underlyingType == typeof(DateOnly))
RenderDateOnlyEditor(builder, ref seq, dataItem, propertyInfo, column.DisplayFormat);
else if (underlyingType == typeof(int))
RenderSpinIntEditor(builder, ref seq, dataItem, propertyInfo);
else if (underlyingType == typeof(decimal))
RenderSpinDecimalEditor(builder, ref seq, dataItem, propertyInfo);
else if (underlyingType == typeof(double))
RenderSpinDoubleEditor(builder, ref seq, dataItem, propertyInfo);
else if (settingsType == EditSettingsType.Memo)
RenderMemoEditor(builder, ref seq, dataItem, propertyInfo);
else
RenderTextBoxEditor(builder, ref seq, dataItem, propertyInfo);
if (underlyingType == typeof(bool)) RenderCheckBoxEditor(builder, ref seq, dataItem, propertyInfo);
else if (underlyingType == typeof(DateTime)) RenderDateTimeEditor(builder, ref seq, dataItem, propertyInfo, column.DisplayFormat);
else if (underlyingType == typeof(DateOnly)) RenderDateOnlyEditor(builder, ref seq, dataItem, propertyInfo, column.DisplayFormat);
else if (underlyingType == typeof(int)) RenderSpinIntEditor(builder, ref seq, dataItem, propertyInfo);
else if (underlyingType == typeof(decimal)) RenderSpinDecimalEditor(builder, ref seq, dataItem, propertyInfo);
else if (underlyingType == typeof(double)) RenderSpinDoubleEditor(builder, ref seq, dataItem, propertyInfo);
else if (settingsType == EditSettingsType.Memo) RenderMemoEditor(builder, ref seq, dataItem, propertyInfo);
else RenderTextBoxEditor(builder, ref seq, dataItem, propertyInfo);
};
}
private void RenderCheckBoxEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo)
{
var currentValue = (bool)(propertyInfo.GetValue(dataItem) ?? false);
builder.OpenComponent<DxCheckBox<bool>>(seq++);
builder.AddAttribute(seq++, "Checked", currentValue);
builder.AddAttribute(seq++, "CheckedChanged", EventCallback.Factory.Create<bool>(this, newValue =>
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
builder.AddAttribute(seq++, "Checked", (bool)(propertyInfo.GetValue(dataItem) ?? false));
builder.AddAttribute(seq++, "CheckedChanged", EventCallback.Factory.Create<bool>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
builder.CloseComponent();
}
private void RenderDateTimeEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, string? displayFormat)
{
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
var currentValue = propertyInfo.GetValue(dataItem);
var value = propertyInfo.GetValue(dataItem);
if (isNullable)
{
builder.OpenComponent<DxDateEdit<DateTime?>>(seq++);
builder.AddAttribute(seq++, "Date", (DateTime?)currentValue);
builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateTime?>(this, newValue =>
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
builder.AddAttribute(seq++, "Date", (DateTime?)value);
builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateTime?>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
}
else
{
builder.OpenComponent<DxDateEdit<DateTime>>(seq++);
builder.AddAttribute(seq++, "Date", (DateTime)(currentValue ?? DateTime.MinValue));
builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateTime>(this, newValue =>
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
builder.AddAttribute(seq++, "Date", (DateTime)(value ?? DateTime.MinValue));
builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateTime>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
}
builder.AddAttribute(seq++, "DisplayFormat", displayFormat ?? "yyyy-MM-dd HH:mm");
builder.CloseComponent();
@ -262,27 +190,19 @@
private void RenderDateOnlyEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, string? displayFormat)
{
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
var currentValue = propertyInfo.GetValue(dataItem);
var value = propertyInfo.GetValue(dataItem);
if (isNullable)
{
builder.OpenComponent<DxDateEdit<DateOnly?>>(seq++);
builder.AddAttribute(seq++, "Date", (DateOnly?)currentValue);
builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateOnly?>(this, newValue =>
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
builder.AddAttribute(seq++, "Date", (DateOnly?)value);
builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateOnly?>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
}
else
{
builder.OpenComponent<DxDateEdit<DateOnly>>(seq++);
builder.AddAttribute(seq++, "Date", (DateOnly)(currentValue ?? DateOnly.MinValue));
builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateOnly>(this, newValue =>
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
builder.AddAttribute(seq++, "Date", (DateOnly)(value ?? DateOnly.MinValue));
builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateOnly>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
}
builder.AddAttribute(seq++, "DisplayFormat", displayFormat ?? "yyyy-MM-dd");
builder.CloseComponent();
@ -291,27 +211,19 @@
private void RenderSpinIntEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo)
{
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
var currentValue = propertyInfo.GetValue(dataItem);
var value = propertyInfo.GetValue(dataItem);
if (isNullable)
{
builder.OpenComponent<DxSpinEdit<int?>>(seq++);
builder.AddAttribute(seq++, "Value", (int?)currentValue);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<int?>(this, newValue =>
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
builder.AddAttribute(seq++, "Value", (int?)value);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<int?>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
}
else
{
builder.OpenComponent<DxSpinEdit<int>>(seq++);
builder.AddAttribute(seq++, "Value", (int)(currentValue ?? 0));
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<int>(this, newValue =>
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
builder.AddAttribute(seq++, "Value", (int)(value ?? 0));
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<int>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
}
builder.CloseComponent();
}
@ -319,27 +231,19 @@
private void RenderSpinDecimalEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo)
{
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
var currentValue = propertyInfo.GetValue(dataItem);
var value = propertyInfo.GetValue(dataItem);
if (isNullable)
{
builder.OpenComponent<DxSpinEdit<decimal?>>(seq++);
builder.AddAttribute(seq++, "Value", (decimal?)currentValue);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<decimal?>(this, newValue =>
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
builder.AddAttribute(seq++, "Value", (decimal?)value);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<decimal?>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
}
else
{
builder.OpenComponent<DxSpinEdit<decimal>>(seq++);
builder.AddAttribute(seq++, "Value", (decimal)(currentValue ?? 0m));
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<decimal>(this, newValue =>
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
builder.AddAttribute(seq++, "Value", (decimal)(value ?? 0m));
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<decimal>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
}
builder.CloseComponent();
}
@ -347,84 +251,56 @@
private void RenderSpinDoubleEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo)
{
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
var currentValue = propertyInfo.GetValue(dataItem);
var value = propertyInfo.GetValue(dataItem);
if (isNullable)
{
builder.OpenComponent<DxSpinEdit<double?>>(seq++);
builder.AddAttribute(seq++, "Value", (double?)currentValue);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<double?>(this, newValue =>
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
builder.AddAttribute(seq++, "Value", (double?)value);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<double?>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
}
else
{
builder.OpenComponent<DxSpinEdit<double>>(seq++);
builder.AddAttribute(seq++, "Value", (double)(currentValue ?? 0d));
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<double>(this, newValue =>
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
builder.AddAttribute(seq++, "Value", (double)(value ?? 0d));
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<double>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
}
builder.CloseComponent();
}
private void RenderTextBoxEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo)
{
var currentValue = propertyInfo.GetValue(dataItem)?.ToString() ?? string.Empty;
builder.OpenComponent<DxTextBox>(seq++);
builder.AddAttribute(seq++, "Text", currentValue);
builder.AddAttribute(seq++, "TextChanged", EventCallback.Factory.Create<string>(this, newValue =>
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
builder.AddAttribute(seq++, "Text", propertyInfo.GetValue(dataItem)?.ToString() ?? "");
builder.AddAttribute(seq++, "TextChanged", EventCallback.Factory.Create<string>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
builder.CloseComponent();
}
private void RenderMemoEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo)
{
var currentValue = propertyInfo.GetValue(dataItem)?.ToString() ?? string.Empty;
builder.OpenComponent<DxMemo>(seq++);
builder.AddAttribute(seq++, "Text", currentValue);
builder.AddAttribute(seq++, "TextChanged", EventCallback.Factory.Create<string>(this, newValue =>
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
builder.AddAttribute(seq++, "Text", propertyInfo.GetValue(dataItem)?.ToString() ?? "");
builder.AddAttribute(seq++, "TextChanged", EventCallback.Factory.Create<string>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
builder.AddAttribute(seq++, "Rows", 3);
builder.CloseComponent();
}
private void RenderComboBoxEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings)
{
var currentValue = propertyInfo.GetValue(dataItem);
var propertyType = propertyInfo.PropertyType;
var underlyingType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;
var dataType = settings.Data?.GetType();
var itemType = typeof(object);
if (dataType?.IsGenericType == true)
{
var genericArgs = dataType.GetGenericArguments();
if (genericArgs.Length > 0)
itemType = genericArgs[0];
}
var value = propertyInfo.GetValue(dataItem);
var underlyingType = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
var itemType = settings.Data?.GetType().GetGenericArguments().FirstOrDefault() ?? typeof(object);
if (underlyingType == typeof(int))
RenderComboBoxInt(builder, ref seq, dataItem, propertyInfo, settings, itemType, currentValue);
RenderComboBoxInt(builder, ref seq, dataItem, propertyInfo, settings, itemType, value);
else if (underlyingType == typeof(long))
RenderComboBoxLong(builder, ref seq, dataItem, propertyInfo, settings, itemType, currentValue);
RenderComboBoxLong(builder, ref seq, dataItem, propertyInfo, settings, itemType, value);
else if (underlyingType == typeof(Guid))
RenderComboBoxGuid(builder, ref seq, dataItem, propertyInfo, settings, itemType, currentValue);
RenderComboBoxGuid(builder, ref seq, dataItem, propertyInfo, settings, itemType, value);
else
{
var displayText = ResolveComboBoxDisplayText(settings, currentValue ?? new object()) ?? currentValue?.ToString() ?? string.Empty;
builder.OpenComponent<DxTextBox>(seq++);
builder.AddAttribute(seq++, "Text", displayText);
builder.AddAttribute(seq++, "Text", ResolveComboBoxDisplayText(settings, value ?? new object()) ?? value?.ToString() ?? "");
builder.AddAttribute(seq++, "ReadOnly", true);
builder.CloseComponent();
}
@ -433,33 +309,16 @@
private void RenderComboBoxInt(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings, Type itemType, object? currentValue)
{
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
var comboType = isNullable
? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(int?))
: typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(int));
var comboType = isNullable ? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(int?)) : typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(int));
builder.OpenComponent(seq++, comboType);
builder.AddAttribute(seq++, "Data", settings.Data);
builder.AddAttribute(seq++, "ValueFieldName", settings.ValueFieldName);
builder.AddAttribute(seq++, "TextFieldName", settings.TextFieldName);
if (isNullable)
{
builder.AddAttribute(seq++, "Value", currentValue as int?);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<int?>(this, newValue =>
{
propertyInfo.SetValue(dataItem, newValue);
StateHasChanged();
}));
}
else
{
builder.AddAttribute(seq++, "Value", currentValue is int intVal ? intVal : 0);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<int>(this, newValue =>
{
propertyInfo.SetValue(dataItem, newValue);
StateHasChanged();
}));
}
builder.AddAttribute(seq++, "Value", isNullable ? currentValue as int? : (currentValue is int intVal ? intVal : 0));
builder.AddAttribute(seq++, "ValueChanged", isNullable
? EventCallback.Factory.Create<int?>(this, v => { propertyInfo.SetValue(dataItem, v); StateHasChanged(); })
: EventCallback.Factory.Create<int>(this, v => { propertyInfo.SetValue(dataItem, v); StateHasChanged(); }));
builder.AddAttribute(seq++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto);
builder.CloseComponent();
}
@ -467,33 +326,16 @@
private void RenderComboBoxLong(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings, Type itemType, object? currentValue)
{
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
var comboType = isNullable
? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(long?))
: typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(long));
var comboType = isNullable ? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(long?)) : typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(long));
builder.OpenComponent(seq++, comboType);
builder.AddAttribute(seq++, "Data", settings.Data);
builder.AddAttribute(seq++, "ValueFieldName", settings.ValueFieldName);
builder.AddAttribute(seq++, "TextFieldName", settings.TextFieldName);
if (isNullable)
{
builder.AddAttribute(seq++, "Value", currentValue as long?);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<long?>(this, newValue =>
{
propertyInfo.SetValue(dataItem, newValue);
StateHasChanged();
}));
}
else
{
builder.AddAttribute(seq++, "Value", currentValue is long longVal ? longVal : 0L);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<long>(this, newValue =>
{
propertyInfo.SetValue(dataItem, newValue);
StateHasChanged();
}));
}
builder.AddAttribute(seq++, "Value", isNullable ? currentValue as long? : (currentValue is long longVal ? longVal : 0L));
builder.AddAttribute(seq++, "ValueChanged", isNullable
? EventCallback.Factory.Create<long?>(this, v => { propertyInfo.SetValue(dataItem, v); StateHasChanged(); })
: EventCallback.Factory.Create<long>(this, v => { propertyInfo.SetValue(dataItem, v); StateHasChanged(); }));
builder.AddAttribute(seq++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto);
builder.CloseComponent();
}
@ -501,53 +343,33 @@
private void RenderComboBoxGuid(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings, Type itemType, object? currentValue)
{
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
var comboType = isNullable
? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(Guid?))
: typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(Guid));
var comboType = isNullable ? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(Guid?)) : typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(Guid));
builder.OpenComponent(seq++, comboType);
builder.AddAttribute(seq++, "Data", settings.Data);
builder.AddAttribute(seq++, "ValueFieldName", settings.ValueFieldName);
builder.AddAttribute(seq++, "TextFieldName", settings.TextFieldName);
if (isNullable)
{
builder.AddAttribute(seq++, "Value", currentValue as Guid?);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<Guid?>(this, newValue =>
{
propertyInfo.SetValue(dataItem, newValue);
StateHasChanged();
}));
}
else
{
builder.AddAttribute(seq++, "Value", currentValue is Guid guidVal ? guidVal : Guid.Empty);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<Guid>(this, newValue =>
{
propertyInfo.SetValue(dataItem, newValue);
StateHasChanged();
}));
}
builder.AddAttribute(seq++, "Value", isNullable ? currentValue as Guid? : (currentValue is Guid guidVal ? guidVal : Guid.Empty));
builder.AddAttribute(seq++, "ValueChanged", isNullable
? EventCallback.Factory.Create<Guid?>(this, v => { propertyInfo.SetValue(dataItem, v); StateHasChanged(); })
: EventCallback.Factory.Create<Guid>(this, v => { propertyInfo.SetValue(dataItem, v); StateHasChanged(); }));
builder.AddAttribute(seq++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto);
builder.CloseComponent();
}
private RenderFragment RenderCellContent(DxGridDataColumn column, object? value, string displayText, EditSettingsType settingsType)
private RenderFragment RenderCellContent(object? value, string displayText)
{
return builder =>
{
var seq = 0;
// View mode: simple span display with DevExpress theme styling
builder.OpenElement(seq++, "span");
builder.AddAttribute(seq++, "class", GetViewModeCssClass(value, settingsType));
builder.AddAttribute(seq++, "class", "mg-info-panel-value");
builder.AddAttribute(seq++, "title", displayText);
// Special handling for boolean - show checkbox icon
if (value is bool boolValue)
{
builder.OpenElement(seq++, "span");
builder.AddAttribute(seq++, "class", boolValue ? "dx-icon dx-icon-check text-success" : "dx-icon dx-icon-close text-muted");
builder.AddAttribute(seq++, "class", boolValue ? "dx-icon dx-icon-check" : "dx-icon dx-icon-close");
builder.CloseElement();
}
else
@ -558,17 +380,4 @@
builder.CloseElement();
};
}
private static string GetViewModeCssClass(object? value, EditSettingsType settingsType)
{
var baseCss = "mg-info-panel-value text-body";
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
};
}
}

View File

@ -15,7 +15,7 @@ public interface IInfoPanelBase
}
/// <summary>
/// Non-generic version of the InfoPanel component
/// InfoPanel component for displaying and editing grid row details
/// </summary>
public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPanelBase
{
@ -102,8 +102,6 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
{
ArgumentNullException.ThrowIfNull(grid);
System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData called - dataItem type: {dataItem?.GetType().Name ?? "null"}, visibleIndex: {visibleIndex}");
_currentGrid = grid;
_currentDataItem = dataItem;
_focusedRowVisibleIndex = visibleIndex;
@ -113,21 +111,19 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
_isEditMode = false;
_editModel = null;
System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - _currentDataItem is null: {_currentDataItem == null}, cast success: {dataItem != 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)");
}
StateHasChanged();
System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - StateHasChanged called");
// Notify subscribers that data item changed
_ = OnDataItemChanged.InvokeAsync(dataItem);
}
/// <summary>
@ -135,8 +131,6 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
/// </summary>
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;
@ -147,7 +141,6 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
}
InvokeAsync(StateHasChanged);
System.Diagnostics.Debug.WriteLine($"[InfoPanel] SetEditMode - InvokeAsync(StateHasChanged) called, _isEditMode: {_isEditMode}");
}
/// <summary>
@ -155,13 +148,10 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
/// </summary>
public void ClearEditMode()
{
System.Diagnostics.Debug.WriteLine($"[InfoPanel] ClearEditMode called, grid.GridEditState: {_currentGrid?.GridEditState}");
_isEditMode = false;
_editModel = null;
_editSettingsCache.Clear();
InvokeAsync(StateHasChanged);
System.Diagnostics.Debug.WriteLine($"[InfoPanel] ClearEditMode - InvokeAsync(StateHasChanged) called");
}
/// <summary>
@ -368,8 +358,6 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
{
var allColumns = grid.GetDataColumns();
System.Diagnostics.Debug.WriteLine($"[InfoPanel] GetAllDataColumns - grid type: {grid.GetType().Name}, columns count: {allColumns?.Count() ?? 0}");
if (allColumns != null)
{
foreach (var column in allColumns)
@ -378,17 +366,15 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
!string.IsNullOrWhiteSpace(dataColumn.FieldName))
{
columns.Add(dataColumn);
System.Diagnostics.Debug.WriteLine($"[InfoPanel] - Column: {dataColumn.FieldName}");
}
}
}
}
catch (Exception ex)
catch (Exception)
{
System.Diagnostics.Debug.WriteLine($"[InfoPanel] GetAllDataColumns error: {ex.Message}");
// Ignore errors
}
System.Diagnostics.Debug.WriteLine($"[InfoPanel] GetAllDataColumns result: {columns.Count} columns");
return columns;
}

View File

@ -0,0 +1,6 @@
namespace AyCode.Blazor.Components.Components.Grids;
public class MgGridInfoPanelHelper
{
}

View File

@ -0,0 +1,6 @@
namespace AyCode.Blazor.Components.Components.Grids;
public class MgGridToolbarHelper
{
}

View File

@ -0,0 +1,6 @@
namespace AyCode.Blazor.Components.Components;
public class MgComponentsHelper
{
}

View File

@ -1,99 +1,105 @@
/* MgGridInfoPanel styles - Global CSS for proper container query support */
/* MgGridInfoPanel styles - DevExpress theme compatible */
/* Main panel - contained within splitter pane */
/* Main panel - uses DevExpress theme variables */
.mg-grid-info-panel {
container-type: inline-size;
container-name: infopanel;
background-color: var(--dxbl-bg-secondary, #f8f9fa);
transition: background-color 0.3s ease, border-color 0.3s ease;
background-color: var(--dxbl-bg-secondary);
color: var(--dxbl-text);
font-family: var(--dxbl-font-family);
font-size: var(--dxbl-font-size);
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
max-height: 100%;
border-left: 1px solid var(--dxbl-border-color);
}
.mg-grid-info-panel.edit-mode {
background-color: #fffbeb !important;
border-left: 3px solid #f59e0b !important;
background-color: var(--dxbl-warning-bg, #fffbeb);
border-left: 3px solid var(--dxbl-warning, #f59e0b);
}
.mg-grid-info-panel.view-mode {
background-color: #f8f9fa !important;
border-left: 3px solid transparent !important;
/* Header styling */
.mg-grid-info-panel .mg-info-panel-header {
padding: var(--dxbl-spacer-sm) var(--dxbl-spacer);
background-color: var(--dxbl-bg);
border-bottom: 1px solid var(--dxbl-border-color);
font-weight: 600;
}
/* Content area - scrollable, takes remaining space */
/* Toolbar styling */
.mg-info-panel-toolbar {
padding: var(--dxbl-spacer-xs) var(--dxbl-spacer-sm);
background-color: var(--dxbl-bg);
border-bottom: 1px solid var(--dxbl-border-color);
}
/* Content area - scrollable */
.mg-info-panel-content {
flex: 1 1 0;
overflow-y: auto;
overflow-x: hidden;
padding: 1rem;
padding: var(--dxbl-spacer);
min-height: 0;
}
/* Grid layout with responsive column wrapping based on panel width */
/* Grid layout for columns */
.mg-info-panel-grid {
display: grid;
grid-template-columns: 1fr;
gap: 0.75rem;
gap: var(--dxbl-spacer-sm);
}
/* Fixed column count classes - override responsive behavior */
.mg-columns-1 .mg-info-panel-grid {
grid-template-columns: 1fr !important;
}
.mg-columns-2 .mg-info-panel-grid {
grid-template-columns: repeat(2, 1fr) !important;
}
.mg-columns-3 .mg-info-panel-grid {
grid-template-columns: repeat(3, 1fr) !important;
}
.mg-columns-4 .mg-info-panel-grid {
grid-template-columns: repeat(4, 1fr) !important;
}
/* Fixed column count classes */
.mg-columns-1 .mg-info-panel-grid { grid-template-columns: 1fr !important; }
.mg-columns-2 .mg-info-panel-grid { grid-template-columns: repeat(2, 1fr) !important; }
.mg-columns-3 .mg-info-panel-grid { grid-template-columns: repeat(3, 1fr) !important; }
.mg-columns-4 .mg-info-panel-grid { grid-template-columns: repeat(4, 1fr) !important; }
/* Responsive layouts using container queries */
/* 2 columns for medium width (>= 400px) */
@container infopanel (min-width: 400px) {
.mg-grid-info-panel:not(.mg-columns-1):not(.mg-columns-2):not(.mg-columns-3):not(.mg-columns-4) .mg-info-panel-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* 3 columns for wider panels (>= 800px) */
@container infopanel (min-width: 800px) {
.mg-grid-info-panel:not(.mg-columns-1):not(.mg-columns-2):not(.mg-columns-3):not(.mg-columns-4) .mg-info-panel-grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* 4 columns for very wide panels (>= 1300px) */
@container infopanel (min-width: 1300px) {
.mg-grid-info-panel:not(.mg-columns-1):not(.mg-columns-2):not(.mg-columns-3):not(.mg-columns-4) .mg-info-panel-grid {
grid-template-columns: repeat(4, 1fr);
}
}
/* Grid item */
.mg-info-panel-item {
min-width: 0;
}
/* Toolbar styling */
.mg-info-panel-toolbar {
padding: 0.25rem 0.5rem;
background-color: var(--dxbl-bg, #fff);
/* Label styling */
.mg-info-panel-label {
display: block;
margin-bottom: var(--dxbl-spacer-xs);
font-size: calc(var(--dxbl-font-size) * 0.875);
font-weight: 600;
color: var(--dxbl-text-secondary);
}
/* View mode value styling - simple span with DevExpress theme */
.mg-info-panel-label.editable {
color: var(--dxbl-primary);
}
/* View mode value styling */
.mg-info-panel-value {
display: block;
padding: 0.25rem 0;
font-size: var(--dxbl-font-size, 0.875rem);
color: var(--dxbl-text, #212529);
padding: var(--dxbl-spacer-xs) 0;
color: var(--dxbl-text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@ -107,13 +113,59 @@
font-variant-numeric: tabular-nums;
}
/* Empty state */
.mg-info-panel-empty {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: var(--dxbl-text-muted);
padding: var(--dxbl-spacer-lg);
text-align: center;
}
/* Tables inside info panel */
.mg-info-panel-content table {
width: 100%;
border-collapse: collapse;
font-size: var(--dxbl-font-size);
color: var(--dxbl-text);
margin-bottom: var(--dxbl-spacer);
}
.mg-info-panel-content table th,
.mg-info-panel-content table td {
padding: var(--dxbl-spacer-xs) var(--dxbl-spacer-sm);
border: 1px solid var(--dxbl-border-color);
text-align: left;
white-space: nowrap;
}
.mg-info-panel-content table th {
background-color: var(--dxbl-bg-secondary);
font-weight: 600;
color: var(--dxbl-text-secondary);
}
.mg-info-panel-content table tbody tr:nth-child(odd) {
background-color: var(--dxbl-bg);
}
.mg-info-panel-content table tbody tr:nth-child(even) {
background-color: var(--dxbl-bg-secondary);
}
.mg-info-panel-content table tbody tr:hover {
background-color: var(--dxbl-row-hover-bg);
}
/* Splitter pane styling */
.mg-grid-with-info-panel {
height: 100%;
}
.mg-info-panel-pane {
background-color: var(--dxbl-bg-secondary, #f8f9fa);
background-color: var(--dxbl-bg-secondary);
}
/* Fullscreen window styling */
@ -140,15 +192,12 @@
flex-direction: column;
}
.mg-fullscreen-content .mg-grid-with-info-panel {
.mg-fullscreen-content .mg-grid-with-info-panel,
.mg-fullscreen-content .dxbl-grid {
flex: 1;
height: 100%;
}
.mg-fullscreen-content .dxbl-grid {
height: 100% !important;
}
/* Fullscreen icon classes */
.grid-fullscreen::before {
content: "\e90c";