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 Microsoft.AspNetCore.Components.Rendering
@using System.Reflection @using System.Reflection
<div @ref="_panelElement" class="mg-grid-info-panel @(_isEditMode ? "edit-mode" : "view-mode") @GetColumnCountClass()" <div @ref="_panelElement" class="mg-grid-info-panel @(_isEditMode ? "edit-mode" : "") @GetColumnCountClass()">
style="@GetBreakpointStyles()">
@* Header *@ @* Header *@
@if (HeaderTemplate != null) @if (HeaderTemplate != null)
{ {
@HeaderTemplate(GetActiveDataItem()) @HeaderTemplate(GetActiveDataItem())
} }
else else if (_currentGrid != null)
{ {
<div class="dxbl-grid-header-panel px-3 py-2 border-bottom"> <div class="mg-info-panel-header">@_currentGrid.Caption</div>
<span class="fw-semibold">@(_currentGrid?.Caption ?? "")</span>
</div>
} }
@* Toolbar *@ @* Toolbar *@
@if (_currentGrid != null) @if (_currentGrid != null)
{ {
<div class="mg-info-panel-toolbar border-bottom"> <div class="mg-info-panel-toolbar">
<MgGridToolbarTemplate Grid="_currentGrid" OnlyGridEditTools="true" ShowOnlyIcon="true" /> <MgGridToolbarTemplate Grid="_currentGrid" OnlyGridEditTools="true" ShowOnlyIcon="true" />
</div> </div>
} }
@ -28,13 +25,11 @@
<div class="mg-info-panel-content"> <div class="mg-info-panel-content">
@if (GetActiveDataItem() != null && _currentGrid != null) @if (GetActiveDataItem() != null && _currentGrid != null)
{ {
@* Before Columns *@
@if (BeforeColumnsTemplate != null) @if (BeforeColumnsTemplate != null)
{ {
@BeforeColumnsTemplate(GetActiveDataItem()) @BeforeColumnsTemplate(GetActiveDataItem())
} }
@* Columns *@
@if (ColumnsTemplate != null) @if (ColumnsTemplate != null)
{ {
@ColumnsTemplate(GetActiveDataItem()) @ColumnsTemplate(GetActiveDataItem())
@ -44,7 +39,6 @@
@RenderDefaultColumns() @RenderDefaultColumns()
} }
@* After Columns *@
@if (AfterColumnsTemplate != null) @if (AfterColumnsTemplate != null)
{ {
@AfterColumnsTemplate(GetActiveDataItem()) @AfterColumnsTemplate(GetActiveDataItem())
@ -52,7 +46,7 @@
} }
else 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> <p>Válasszon ki egy sort az adatok megtekintéséhez</p>
</div> </div>
} }
@ -66,55 +60,31 @@
</div> </div>
@code { @code {
/// <summary>
/// Custom header template. Receives the current data item.
/// </summary>
[Parameter] public RenderFragment<object?>? HeaderTemplate { get; set; } [Parameter] public RenderFragment<object?>? HeaderTemplate { get; set; }
/// <summary>
/// Content to render before the columns.
/// </summary>
[Parameter] public RenderFragment<object?>? BeforeColumnsTemplate { get; set; } [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; } [Parameter] public RenderFragment<object?>? ColumnsTemplate { get; set; }
/// <summary>
/// Content to render after the columns.
/// </summary>
[Parameter] public RenderFragment<object?>? AfterColumnsTemplate { get; set; } [Parameter] public RenderFragment<object?>? AfterColumnsTemplate { get; set; }
/// <summary>
/// Custom footer template.
/// </summary>
[Parameter] public RenderFragment<object?>? FooterTemplate { get; set; } [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) 1 => "mg-columns-1",
{ 2 => "mg-columns-2",
return FixedColumnCount.Value switch 3 => "mg-columns-3",
{ 4 => "mg-columns-4",
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;";
}
private RenderFragment RenderDefaultColumns() => builder => private RenderFragment RenderDefaultColumns() => builder =>
{ {
var dataItem = GetActiveDataItem()!; var dataItem = GetActiveDataItem();
if (dataItem == null) return;
var dataItemType = dataItem.GetType(); var dataItemType = dataItem.GetType();
var seq = 0; var seq = 0;
@ -126,134 +96,92 @@
var displayText = GetDisplayTextFromGrid(column); var displayText = GetDisplayTextFromGrid(column);
var value = GetCellValue(column); var value = GetCellValue(column);
var settingsType = GetEditSettingsType(column); var settingsType = GetEditSettingsType(column);
var isReadOnly = !_isEditMode || column.ReadOnly; var isEditable = _isEditMode && !column.ReadOnly;
builder.OpenElement(seq++, "div"); builder.OpenElement(seq++, "div");
builder.AddAttribute(seq++, "class", "mg-info-panel-item"); 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.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.AddContent(seq++, GetColumnCaption(column));
builder.CloseElement(); builder.CloseElement();
builder.OpenElement(seq++, "div"); builder.OpenElement(seq++, "div");
builder.AddAttribute(seq++, "class", "dxbl-fl-ec"); if (isEditable)
if (_isEditMode && !column.ReadOnly)
{ {
RenderEditableCell(column, dataItem, dataItemType, value, displayText, settingsType)(builder); RenderEditableCell(column, dataItem, dataItemType, value, displayText, settingsType)(builder);
} }
else else
{ {
RenderCellContent(column, value, displayText, settingsType)(builder); RenderCellContent(value, displayText)(builder);
} }
builder.CloseElement();
builder.CloseElement(); builder.CloseElement();
builder.CloseElement();
builder.CloseElement();
} }
builder.CloseElement(); builder.CloseElement();
}; };
private static string GetColumnCaption(DxGridDataColumn column) private static string GetColumnCaption(DxGridDataColumn column) =>
{ !string.IsNullOrWhiteSpace(column.Caption) ? column.Caption : column.FieldName;
return !string.IsNullOrWhiteSpace(column.Caption) ? column.Caption : column.FieldName;
}
private static string GetCaptionCssClass(bool isReadOnly)
{
return isReadOnly ? "fw-semibold" : "fw-semibold text-primary";
}
private RenderFragment RenderEditableCell(DxGridDataColumn column, object dataItem, Type dataItemType, object? value, string displayText, EditSettingsType settingsType) private RenderFragment RenderEditableCell(DxGridDataColumn column, object dataItem, Type dataItemType, object? value, string displayText, EditSettingsType settingsType)
{ {
return builder => return builder =>
{ {
var seq = 0; var seq = 0;
var fieldName = column.FieldName; var propertyInfo = dataItemType.GetProperty(column.FieldName);
var propertyInfo = dataItemType.GetProperty(fieldName);
if (propertyInfo == null) if (propertyInfo == null)
{ {
RenderCellContent(column, value, displayText, settingsType)(builder); RenderCellContent(value, displayText)(builder);
return; return;
} }
var propertyType = propertyInfo.PropertyType; var underlyingType = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
var underlyingType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;
// ComboBox if (settingsType == EditSettingsType.ComboBox && GetEditSettings(column.FieldName) is DxComboBoxSettings comboSettings)
if (settingsType == EditSettingsType.ComboBox)
{ {
var comboSettings = GetEditSettings(column.FieldName) as DxComboBoxSettings; RenderComboBoxEditor(builder, ref seq, dataItem, propertyInfo, comboSettings);
if (comboSettings != null) return;
{
RenderComboBoxEditor(builder, ref seq, dataItem, propertyInfo, comboSettings);
return;
}
} }
// Render based on type if (underlyingType == typeof(bool)) RenderCheckBoxEditor(builder, ref seq, dataItem, propertyInfo);
if (underlyingType == typeof(bool)) else if (underlyingType == typeof(DateTime)) RenderDateTimeEditor(builder, ref seq, dataItem, propertyInfo, column.DisplayFormat);
RenderCheckBoxEditor(builder, ref seq, dataItem, propertyInfo); else if (underlyingType == typeof(DateOnly)) RenderDateOnlyEditor(builder, ref seq, dataItem, propertyInfo, column.DisplayFormat);
else if (underlyingType == typeof(DateTime)) else if (underlyingType == typeof(int)) RenderSpinIntEditor(builder, ref seq, dataItem, propertyInfo);
RenderDateTimeEditor(builder, ref seq, dataItem, propertyInfo, column.DisplayFormat); else if (underlyingType == typeof(decimal)) RenderSpinDecimalEditor(builder, ref seq, dataItem, propertyInfo);
else if (underlyingType == typeof(DateOnly)) else if (underlyingType == typeof(double)) RenderSpinDoubleEditor(builder, ref seq, dataItem, propertyInfo);
RenderDateOnlyEditor(builder, ref seq, dataItem, propertyInfo, column.DisplayFormat); else if (settingsType == EditSettingsType.Memo) RenderMemoEditor(builder, ref seq, dataItem, propertyInfo);
else if (underlyingType == typeof(int)) else RenderTextBoxEditor(builder, ref seq, dataItem, propertyInfo);
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) 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.OpenComponent<DxCheckBox<bool>>(seq++);
builder.AddAttribute(seq++, "Checked", currentValue); builder.AddAttribute(seq++, "Checked", (bool)(propertyInfo.GetValue(dataItem) ?? false));
builder.AddAttribute(seq++, "CheckedChanged", EventCallback.Factory.Create<bool>(this, newValue => builder.AddAttribute(seq++, "CheckedChanged", EventCallback.Factory.Create<bool>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
builder.CloseComponent(); builder.CloseComponent();
} }
private void RenderDateTimeEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, string? displayFormat) private void RenderDateTimeEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, string? displayFormat)
{ {
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null; var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
var currentValue = propertyInfo.GetValue(dataItem); var value = propertyInfo.GetValue(dataItem);
if (isNullable) if (isNullable)
{ {
builder.OpenComponent<DxDateEdit<DateTime?>>(seq++); builder.OpenComponent<DxDateEdit<DateTime?>>(seq++);
builder.AddAttribute(seq++, "Date", (DateTime?)currentValue); builder.AddAttribute(seq++, "Date", (DateTime?)value);
builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateTime?>(this, newValue => builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateTime?>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
} }
else else
{ {
builder.OpenComponent<DxDateEdit<DateTime>>(seq++); builder.OpenComponent<DxDateEdit<DateTime>>(seq++);
builder.AddAttribute(seq++, "Date", (DateTime)(currentValue ?? DateTime.MinValue)); builder.AddAttribute(seq++, "Date", (DateTime)(value ?? DateTime.MinValue));
builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateTime>(this, newValue => builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateTime>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
} }
builder.AddAttribute(seq++, "DisplayFormat", displayFormat ?? "yyyy-MM-dd HH:mm"); builder.AddAttribute(seq++, "DisplayFormat", displayFormat ?? "yyyy-MM-dd HH:mm");
builder.CloseComponent(); builder.CloseComponent();
@ -262,27 +190,19 @@
private void RenderDateOnlyEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, string? displayFormat) private void RenderDateOnlyEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, string? displayFormat)
{ {
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null; var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
var currentValue = propertyInfo.GetValue(dataItem); var value = propertyInfo.GetValue(dataItem);
if (isNullable) if (isNullable)
{ {
builder.OpenComponent<DxDateEdit<DateOnly?>>(seq++); builder.OpenComponent<DxDateEdit<DateOnly?>>(seq++);
builder.AddAttribute(seq++, "Date", (DateOnly?)currentValue); builder.AddAttribute(seq++, "Date", (DateOnly?)value);
builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateOnly?>(this, newValue => builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateOnly?>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
} }
else else
{ {
builder.OpenComponent<DxDateEdit<DateOnly>>(seq++); builder.OpenComponent<DxDateEdit<DateOnly>>(seq++);
builder.AddAttribute(seq++, "Date", (DateOnly)(currentValue ?? DateOnly.MinValue)); builder.AddAttribute(seq++, "Date", (DateOnly)(value ?? DateOnly.MinValue));
builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateOnly>(this, newValue => builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateOnly>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
} }
builder.AddAttribute(seq++, "DisplayFormat", displayFormat ?? "yyyy-MM-dd"); builder.AddAttribute(seq++, "DisplayFormat", displayFormat ?? "yyyy-MM-dd");
builder.CloseComponent(); builder.CloseComponent();
@ -291,27 +211,19 @@
private void RenderSpinIntEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo) private void RenderSpinIntEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo)
{ {
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null; var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
var currentValue = propertyInfo.GetValue(dataItem); var value = propertyInfo.GetValue(dataItem);
if (isNullable) if (isNullable)
{ {
builder.OpenComponent<DxSpinEdit<int?>>(seq++); builder.OpenComponent<DxSpinEdit<int?>>(seq++);
builder.AddAttribute(seq++, "Value", (int?)currentValue); builder.AddAttribute(seq++, "Value", (int?)value);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<int?>(this, newValue => builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<int?>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
} }
else else
{ {
builder.OpenComponent<DxSpinEdit<int>>(seq++); builder.OpenComponent<DxSpinEdit<int>>(seq++);
builder.AddAttribute(seq++, "Value", (int)(currentValue ?? 0)); builder.AddAttribute(seq++, "Value", (int)(value ?? 0));
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<int>(this, newValue => builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<int>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
} }
builder.CloseComponent(); builder.CloseComponent();
} }
@ -319,27 +231,19 @@
private void RenderSpinDecimalEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo) private void RenderSpinDecimalEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo)
{ {
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null; var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
var currentValue = propertyInfo.GetValue(dataItem); var value = propertyInfo.GetValue(dataItem);
if (isNullable) if (isNullable)
{ {
builder.OpenComponent<DxSpinEdit<decimal?>>(seq++); builder.OpenComponent<DxSpinEdit<decimal?>>(seq++);
builder.AddAttribute(seq++, "Value", (decimal?)currentValue); builder.AddAttribute(seq++, "Value", (decimal?)value);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<decimal?>(this, newValue => builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<decimal?>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
} }
else else
{ {
builder.OpenComponent<DxSpinEdit<decimal>>(seq++); builder.OpenComponent<DxSpinEdit<decimal>>(seq++);
builder.AddAttribute(seq++, "Value", (decimal)(currentValue ?? 0m)); builder.AddAttribute(seq++, "Value", (decimal)(value ?? 0m));
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<decimal>(this, newValue => builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<decimal>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
} }
builder.CloseComponent(); builder.CloseComponent();
} }
@ -347,84 +251,56 @@
private void RenderSpinDoubleEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo) private void RenderSpinDoubleEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo)
{ {
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null; var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
var currentValue = propertyInfo.GetValue(dataItem); var value = propertyInfo.GetValue(dataItem);
if (isNullable) if (isNullable)
{ {
builder.OpenComponent<DxSpinEdit<double?>>(seq++); builder.OpenComponent<DxSpinEdit<double?>>(seq++);
builder.AddAttribute(seq++, "Value", (double?)currentValue); builder.AddAttribute(seq++, "Value", (double?)value);
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<double?>(this, newValue => builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<double?>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
} }
else else
{ {
builder.OpenComponent<DxSpinEdit<double>>(seq++); builder.OpenComponent<DxSpinEdit<double>>(seq++);
builder.AddAttribute(seq++, "Value", (double)(currentValue ?? 0d)); builder.AddAttribute(seq++, "Value", (double)(value ?? 0d));
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<double>(this, newValue => builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<double>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
} }
builder.CloseComponent(); builder.CloseComponent();
} }
private void RenderTextBoxEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo) 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.OpenComponent<DxTextBox>(seq++);
builder.AddAttribute(seq++, "Text", currentValue); builder.AddAttribute(seq++, "Text", propertyInfo.GetValue(dataItem)?.ToString() ?? "");
builder.AddAttribute(seq++, "TextChanged", EventCallback.Factory.Create<string>(this, newValue => builder.AddAttribute(seq++, "TextChanged", EventCallback.Factory.Create<string>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
builder.CloseComponent(); builder.CloseComponent();
} }
private void RenderMemoEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo) 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.OpenComponent<DxMemo>(seq++);
builder.AddAttribute(seq++, "Text", currentValue); builder.AddAttribute(seq++, "Text", propertyInfo.GetValue(dataItem)?.ToString() ?? "");
builder.AddAttribute(seq++, "TextChanged", EventCallback.Factory.Create<string>(this, newValue => builder.AddAttribute(seq++, "TextChanged", EventCallback.Factory.Create<string>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
{
propertyInfo.SetValue(dataItem, newValue);
InvokeAsync(StateHasChanged);
}));
builder.AddAttribute(seq++, "Rows", 3); builder.AddAttribute(seq++, "Rows", 3);
builder.CloseComponent(); builder.CloseComponent();
} }
private void RenderComboBoxEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings) private void RenderComboBoxEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings)
{ {
var currentValue = propertyInfo.GetValue(dataItem); var value = propertyInfo.GetValue(dataItem);
var propertyType = propertyInfo.PropertyType; var underlyingType = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
var underlyingType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; var itemType = settings.Data?.GetType().GetGenericArguments().FirstOrDefault() ?? typeof(object);
var dataType = settings.Data?.GetType();
var itemType = typeof(object);
if (dataType?.IsGenericType == true)
{
var genericArgs = dataType.GetGenericArguments();
if (genericArgs.Length > 0)
itemType = genericArgs[0];
}
if (underlyingType == typeof(int)) 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)) 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)) else if (underlyingType == typeof(Guid))
RenderComboBoxGuid(builder, ref seq, dataItem, propertyInfo, settings, itemType, currentValue); RenderComboBoxGuid(builder, ref seq, dataItem, propertyInfo, settings, itemType, value);
else else
{ {
var displayText = ResolveComboBoxDisplayText(settings, currentValue ?? new object()) ?? currentValue?.ToString() ?? string.Empty;
builder.OpenComponent<DxTextBox>(seq++); 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.AddAttribute(seq++, "ReadOnly", true);
builder.CloseComponent(); builder.CloseComponent();
} }
@ -433,33 +309,16 @@
private void RenderComboBoxInt(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings, Type itemType, object? currentValue) 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 isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
var comboType = isNullable var comboType = isNullable ? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(int?)) : typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(int));
? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(int?))
: typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(int));
builder.OpenComponent(seq++, comboType); builder.OpenComponent(seq++, comboType);
builder.AddAttribute(seq++, "Data", settings.Data); builder.AddAttribute(seq++, "Data", settings.Data);
builder.AddAttribute(seq++, "ValueFieldName", settings.ValueFieldName); builder.AddAttribute(seq++, "ValueFieldName", settings.ValueFieldName);
builder.AddAttribute(seq++, "TextFieldName", settings.TextFieldName); builder.AddAttribute(seq++, "TextFieldName", settings.TextFieldName);
builder.AddAttribute(seq++, "Value", isNullable ? currentValue as int? : (currentValue is int intVal ? intVal : 0));
if (isNullable) builder.AddAttribute(seq++, "ValueChanged", isNullable
{ ? EventCallback.Factory.Create<int?>(this, v => { propertyInfo.SetValue(dataItem, v); StateHasChanged(); })
builder.AddAttribute(seq++, "Value", currentValue as int?); : EventCallback.Factory.Create<int>(this, v => { propertyInfo.SetValue(dataItem, v); StateHasChanged(); }));
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++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto); builder.AddAttribute(seq++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto);
builder.CloseComponent(); builder.CloseComponent();
} }
@ -467,33 +326,16 @@
private void RenderComboBoxLong(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings, Type itemType, object? currentValue) 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 isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
var comboType = isNullable var comboType = isNullable ? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(long?)) : typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(long));
? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(long?))
: typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(long));
builder.OpenComponent(seq++, comboType); builder.OpenComponent(seq++, comboType);
builder.AddAttribute(seq++, "Data", settings.Data); builder.AddAttribute(seq++, "Data", settings.Data);
builder.AddAttribute(seq++, "ValueFieldName", settings.ValueFieldName); builder.AddAttribute(seq++, "ValueFieldName", settings.ValueFieldName);
builder.AddAttribute(seq++, "TextFieldName", settings.TextFieldName); builder.AddAttribute(seq++, "TextFieldName", settings.TextFieldName);
builder.AddAttribute(seq++, "Value", isNullable ? currentValue as long? : (currentValue is long longVal ? longVal : 0L));
if (isNullable) builder.AddAttribute(seq++, "ValueChanged", isNullable
{ ? EventCallback.Factory.Create<long?>(this, v => { propertyInfo.SetValue(dataItem, v); StateHasChanged(); })
builder.AddAttribute(seq++, "Value", currentValue as long?); : EventCallback.Factory.Create<long>(this, v => { propertyInfo.SetValue(dataItem, v); StateHasChanged(); }));
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++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto); builder.AddAttribute(seq++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto);
builder.CloseComponent(); builder.CloseComponent();
} }
@ -501,53 +343,33 @@
private void RenderComboBoxGuid(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings, Type itemType, object? currentValue) 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 isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
var comboType = isNullable var comboType = isNullable ? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(Guid?)) : typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(Guid));
? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(Guid?))
: typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(Guid));
builder.OpenComponent(seq++, comboType); builder.OpenComponent(seq++, comboType);
builder.AddAttribute(seq++, "Data", settings.Data); builder.AddAttribute(seq++, "Data", settings.Data);
builder.AddAttribute(seq++, "ValueFieldName", settings.ValueFieldName); builder.AddAttribute(seq++, "ValueFieldName", settings.ValueFieldName);
builder.AddAttribute(seq++, "TextFieldName", settings.TextFieldName); builder.AddAttribute(seq++, "TextFieldName", settings.TextFieldName);
builder.AddAttribute(seq++, "Value", isNullable ? currentValue as Guid? : (currentValue is Guid guidVal ? guidVal : Guid.Empty));
if (isNullable) builder.AddAttribute(seq++, "ValueChanged", isNullable
{ ? EventCallback.Factory.Create<Guid?>(this, v => { propertyInfo.SetValue(dataItem, v); StateHasChanged(); })
builder.AddAttribute(seq++, "Value", currentValue as Guid?); : EventCallback.Factory.Create<Guid>(this, v => { propertyInfo.SetValue(dataItem, v); StateHasChanged(); }));
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++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto); builder.AddAttribute(seq++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto);
builder.CloseComponent(); builder.CloseComponent();
} }
private RenderFragment RenderCellContent(DxGridDataColumn column, object? value, string displayText, EditSettingsType settingsType) private RenderFragment RenderCellContent(object? value, string displayText)
{ {
return builder => return builder =>
{ {
var seq = 0; var seq = 0;
// View mode: simple span display with DevExpress theme styling
builder.OpenElement(seq++, "span"); builder.OpenElement(seq++, "span");
builder.AddAttribute(seq++, "class", GetViewModeCssClass(value, settingsType)); builder.AddAttribute(seq++, "class", "mg-info-panel-value");
builder.AddAttribute(seq++, "title", displayText); builder.AddAttribute(seq++, "title", displayText);
// Special handling for boolean - show checkbox icon
if (value is bool boolValue) if (value is bool boolValue)
{ {
builder.OpenElement(seq++, "span"); 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(); builder.CloseElement();
} }
else else
@ -558,17 +380,4 @@
builder.CloseElement(); 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> /// <summary>
/// Non-generic version of the InfoPanel component /// InfoPanel component for displaying and editing grid row details
/// </summary> /// </summary>
public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPanelBase public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPanelBase
{ {
@ -102,8 +102,6 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
{ {
ArgumentNullException.ThrowIfNull(grid); ArgumentNullException.ThrowIfNull(grid);
System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData called - dataItem type: {dataItem?.GetType().Name ?? "null"}, visibleIndex: {visibleIndex}");
_currentGrid = grid; _currentGrid = grid;
_currentDataItem = dataItem; _currentDataItem = dataItem;
_focusedRowVisibleIndex = visibleIndex; _focusedRowVisibleIndex = visibleIndex;
@ -113,21 +111,19 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
_isEditMode = false; _isEditMode = false;
_editModel = null; _editModel = null;
System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - _currentDataItem is null: {_currentDataItem == null}, cast success: {dataItem != null && _currentDataItem != null}");
if (_currentGrid != null && _currentDataItem != null) if (_currentGrid != null && _currentDataItem != null)
{ {
_allDataColumns = GetAllDataColumns(_currentGrid); _allDataColumns = GetAllDataColumns(_currentGrid);
System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - loaded {_allDataColumns.Count} columns");
} }
else else
{ {
_allDataColumns = []; _allDataColumns = [];
System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - cleared columns (grid or dataItem is null)");
} }
StateHasChanged(); StateHasChanged();
System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - StateHasChanged called");
// Notify subscribers that data item changed
_ = OnDataItemChanged.InvokeAsync(dataItem);
} }
/// <summary> /// <summary>
@ -135,8 +131,6 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
/// </summary> /// </summary>
public void SetEditMode(object editModel) public void SetEditMode(object editModel)
{ {
System.Diagnostics.Debug.WriteLine($"[InfoPanel] SetEditMode called - editModel type: {editModel?.GetType().Name ?? "null"}, grid.GridEditState: {_currentGrid?.GridEditState}");
_editModel = editModel; _editModel = editModel;
_isEditMode = true; _isEditMode = true;
_currentDataItem = _editModel; _currentDataItem = _editModel;
@ -147,7 +141,6 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
} }
InvokeAsync(StateHasChanged); InvokeAsync(StateHasChanged);
System.Diagnostics.Debug.WriteLine($"[InfoPanel] SetEditMode - InvokeAsync(StateHasChanged) called, _isEditMode: {_isEditMode}");
} }
/// <summary> /// <summary>
@ -155,13 +148,10 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
/// </summary> /// </summary>
public void ClearEditMode() public void ClearEditMode()
{ {
System.Diagnostics.Debug.WriteLine($"[InfoPanel] ClearEditMode called, grid.GridEditState: {_currentGrid?.GridEditState}");
_isEditMode = false; _isEditMode = false;
_editModel = null; _editModel = null;
_editSettingsCache.Clear(); _editSettingsCache.Clear();
InvokeAsync(StateHasChanged); InvokeAsync(StateHasChanged);
System.Diagnostics.Debug.WriteLine($"[InfoPanel] ClearEditMode - InvokeAsync(StateHasChanged) called");
} }
/// <summary> /// <summary>
@ -368,8 +358,6 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
{ {
var allColumns = grid.GetDataColumns(); var allColumns = grid.GetDataColumns();
System.Diagnostics.Debug.WriteLine($"[InfoPanel] GetAllDataColumns - grid type: {grid.GetType().Name}, columns count: {allColumns?.Count() ?? 0}");
if (allColumns != null) if (allColumns != null)
{ {
foreach (var column in allColumns) foreach (var column in allColumns)
@ -378,17 +366,15 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
!string.IsNullOrWhiteSpace(dataColumn.FieldName)) !string.IsNullOrWhiteSpace(dataColumn.FieldName))
{ {
columns.Add(dataColumn); 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; 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 { .mg-grid-info-panel {
container-type: inline-size; container-type: inline-size;
container-name: infopanel; container-name: infopanel;
background-color: var(--dxbl-bg-secondary, #f8f9fa); background-color: var(--dxbl-bg-secondary);
transition: background-color 0.3s ease, border-color 0.3s ease; color: var(--dxbl-text);
font-family: var(--dxbl-font-family);
font-size: var(--dxbl-font-size);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
min-height: 0; min-height: 0;
max-height: 100%; max-height: 100%;
border-left: 1px solid var(--dxbl-border-color);
} }
.mg-grid-info-panel.edit-mode { .mg-grid-info-panel.edit-mode {
background-color: #fffbeb !important; background-color: var(--dxbl-warning-bg, #fffbeb);
border-left: 3px solid #f59e0b !important; border-left: 3px solid var(--dxbl-warning, #f59e0b);
} }
.mg-grid-info-panel.view-mode { /* Header styling */
background-color: #f8f9fa !important; .mg-grid-info-panel .mg-info-panel-header {
border-left: 3px solid transparent !important; 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 { .mg-info-panel-content {
flex: 1 1 0; flex: 1 1 0;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
padding: 1rem; padding: var(--dxbl-spacer);
min-height: 0; min-height: 0;
} }
/* Grid layout with responsive column wrapping based on panel width */ /* Grid layout for columns */
.mg-info-panel-grid { .mg-info-panel-grid {
display: grid; display: grid;
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 0.75rem; gap: var(--dxbl-spacer-sm);
} }
/* Fixed column count classes - override responsive behavior */ /* Fixed column count classes */
.mg-columns-1 .mg-info-panel-grid { .mg-columns-1 .mg-info-panel-grid { grid-template-columns: 1fr !important; }
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; }
.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 */ /* Responsive layouts using container queries */
/* 2 columns for medium width (>= 400px) */
@container infopanel (min-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 { .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); grid-template-columns: repeat(2, 1fr);
} }
} }
/* 3 columns for wider panels (>= 800px) */
@container infopanel (min-width: 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 { .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); grid-template-columns: repeat(3, 1fr);
} }
} }
/* 4 columns for very wide panels (>= 1300px) */
@container infopanel (min-width: 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 { .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-template-columns: repeat(4, 1fr);
} }
} }
/* Grid item */
.mg-info-panel-item { .mg-info-panel-item {
min-width: 0; min-width: 0;
} }
/* Toolbar styling */ /* Label styling */
.mg-info-panel-toolbar { .mg-info-panel-label {
padding: 0.25rem 0.5rem; display: block;
background-color: var(--dxbl-bg, #fff); 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 { .mg-info-panel-value {
display: block; display: block;
padding: 0.25rem 0; padding: var(--dxbl-spacer-xs) 0;
font-size: var(--dxbl-font-size, 0.875rem); color: var(--dxbl-text);
color: var(--dxbl-text, #212529);
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
@ -107,13 +113,59 @@
font-variant-numeric: tabular-nums; 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 */ /* Splitter pane styling */
.mg-grid-with-info-panel { .mg-grid-with-info-panel {
height: 100%; height: 100%;
} }
.mg-info-panel-pane { .mg-info-panel-pane {
background-color: var(--dxbl-bg-secondary, #f8f9fa); background-color: var(--dxbl-bg-secondary);
} }
/* Fullscreen window styling */ /* Fullscreen window styling */
@ -140,15 +192,12 @@
flex-direction: column; 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; flex: 1;
height: 100%; height: 100%;
} }
.mg-fullscreen-content .dxbl-grid {
height: 100% !important;
}
/* Fullscreen icon classes */ /* Fullscreen icon classes */
.grid-fullscreen::before { .grid-fullscreen::before {
content: "\e90c"; content: "\e90c";