Refactor grid editing, info panel, and toolbar system
- Introduce MgEditState enum and expose EditState on IMgGridBase - Replace event-based syncing state with property-based state - Redesign MgGridInfoPanel to support both view and edit modes with dynamic DevExpress editors and two-way binding - Add visual distinction for edit/view modes in info panel - Replace FruitBankToolbarTemplate with generic MgGridToolbarTemplate; toolbar adapts to grid edit/sync state - Update all grid usages to use new toolbar - Improve robustness, error handling, and maintainability throughout grid, info panel, and toolbar code
This commit is contained in:
parent
c1cf30b8f0
commit
45294199cf
|
|
@ -0,0 +1,22 @@
|
||||||
|
namespace AyCode.Blazor.Components.Components.Grids;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the current edit state of the MgGrid
|
||||||
|
/// </summary>
|
||||||
|
public enum MgEditState
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No edit operation in progress
|
||||||
|
/// </summary>
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adding a new row
|
||||||
|
/// </summary>
|
||||||
|
New,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Editing an existing row
|
||||||
|
/// </summary>
|
||||||
|
Edit
|
||||||
|
}
|
||||||
|
|
@ -23,9 +23,9 @@ public interface IMgGridBase : IGrid
|
||||||
bool IsSyncing { get; }
|
bool IsSyncing { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event fired when synchronization state changes (true = syncing started, false = syncing ended)
|
/// Current edit state of the grid (None, New, Edit)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event Action<bool>? OnSyncingStateChanged;
|
MgEditState EditState { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient> : DxGrid, IMgGridBase, IAsyncDisposable
|
public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient> : DxGrid, IMgGridBase, IAsyncDisposable
|
||||||
|
|
@ -49,7 +49,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
public bool IsSyncing => _dataSource?.IsSyncing ?? false;
|
public bool IsSyncing => _dataSource?.IsSyncing ?? false;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public event Action<bool>? OnSyncingStateChanged;
|
public MgEditState EditState { get; private set; } = MgEditState.None;
|
||||||
|
|
||||||
[Parameter] public bool ShowInfoPanel { get; set; } = true;
|
[Parameter] public bool ShowInfoPanel { get; set; } = true;
|
||||||
|
|
||||||
|
|
@ -319,7 +319,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
if (_isDisposed) return;
|
if (_isDisposed) return;
|
||||||
|
|
||||||
// Forward the event to external subscribers
|
// Forward the event to external subscribers
|
||||||
OnSyncingStateChanged?.Invoke(isSyncing);
|
//OnSyncingStateChanged?.Invoke(isSyncing);
|
||||||
|
|
||||||
// Trigger UI update
|
// Trigger UI update
|
||||||
InvokeAsync(StateHasChanged);
|
InvokeAsync(StateHasChanged);
|
||||||
|
|
@ -349,12 +349,8 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
|
|
||||||
Logger.Debug($"{_gridLogName} OnDataSourceLoaded; Count: {_dataSource?.Count}");
|
Logger.Debug($"{_gridLogName} OnDataSourceLoaded; Count: {_dataSource?.Count}");
|
||||||
|
|
||||||
//if(_dataSourceParam.GetType() == typeof()AcObservableCollection<TDataItem>)
|
|
||||||
|
|
||||||
SetGridData(_dataSource!.GetReferenceInnerList());
|
SetGridData(_dataSource!.GetReferenceInnerList());
|
||||||
//else Reload();
|
|
||||||
|
|
||||||
//_dataSource.LoadItem(_dataSource.First().Id).Forget();
|
|
||||||
if (!_isDisposed)
|
if (!_isDisposed)
|
||||||
{
|
{
|
||||||
await OnDataSourceChanged.InvokeAsync(_dataSource);
|
await OnDataSourceChanged.InvokeAsync(_dataSource);
|
||||||
|
|
@ -437,37 +433,31 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
e.EditModel = editModel;
|
e.EditModel = editModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set edit state
|
||||||
|
EditState = e.IsNew ? MgEditState.New : MgEditState.Edit;
|
||||||
|
|
||||||
await OnGridCustomizeEditModel.InvokeAsync(e);
|
await OnGridCustomizeEditModel.InvokeAsync(e);
|
||||||
|
|
||||||
|
// Frissítjük az InfoPanel-t edit módba - itt az EditModel már elérhető
|
||||||
|
if (ShowInfoPanel && _infoPanelInstance != null)
|
||||||
|
{
|
||||||
|
_infoPanelInstance.SetEditMode(editModel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnEditStart(GridEditStartEventArgs e)
|
private async Task OnEditStart(GridEditStartEventArgs e)
|
||||||
{
|
{
|
||||||
var dataItem = (e.DataItem as TDataItem)!;
|
|
||||||
|
|
||||||
await OnGridEditStart.InvokeAsync(e);
|
await OnGridEditStart.InvokeAsync(e);
|
||||||
|
// Az InfoPanel-t és az EditMode-ot a CustomizeEditModel-ben frissítjük, mert ott az EditModel már elérhető
|
||||||
}
|
}
|
||||||
|
|
||||||
//void Grid_CustomizeEditModel(GridCustomizeEditModelEventArgs e)
|
|
||||||
//{
|
|
||||||
// var model = e.EditModel as EditableWorkOrder;
|
|
||||||
// if (model == null)
|
|
||||||
// {
|
|
||||||
// model = new EditableWorkOrder();
|
|
||||||
|
|
||||||
// model.WorkOrderNum = "123";
|
|
||||||
// model.Description = "hey";
|
|
||||||
|
|
||||||
// e.EditModel = model;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
protected virtual async Task OnFocusedRowChanged(GridFocusedRowChangedEventArgs e)
|
protected virtual async Task OnFocusedRowChanged(GridFocusedRowChangedEventArgs e)
|
||||||
{
|
{
|
||||||
_focusedDataItem = e.DataItem;
|
_focusedDataItem = e.DataItem;
|
||||||
|
|
||||||
if (ShowInfoPanel && _infoPanelInstance != null && e.DataItem is TDataItem dataItem)
|
if (ShowInfoPanel && _infoPanelInstance != null && e.DataItem is TDataItem dataItem)
|
||||||
{
|
{
|
||||||
_infoPanelInstance.RefreshData(this, dataItem);
|
_infoPanelInstance.RefreshData(this, dataItem, e.VisibleIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
await OnGridFocusedRowChanged.InvokeAsync(e);
|
await OnGridFocusedRowChanged.InvokeAsync(e);
|
||||||
|
|
@ -480,15 +470,6 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
if (e.IsNew)
|
if (e.IsNew)
|
||||||
{
|
{
|
||||||
if (!HasIdValue(dataItem)) SetNewId(dataItem);
|
if (!HasIdValue(dataItem)) SetNewId(dataItem);
|
||||||
|
|
||||||
//if (ParentDataItem != null && !KeyFieldNameToParentId.IsNullOrWhiteSpace())
|
|
||||||
//{
|
|
||||||
// Type examType = typeof(TDataItem);
|
|
||||||
|
|
||||||
// // Change the static property value.
|
|
||||||
// PropertyInfo piShared = examType.GetProperty(KeyFieldNameToParentId);
|
|
||||||
// piShared.SetValue(dataItem, ParentDataItem.Id);
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var logText = e.IsNew ? "add" : "update";
|
var logText = e.IsNew ? "add" : "update";
|
||||||
|
|
@ -509,11 +490,24 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
}
|
}
|
||||||
else await UpdateDataItemAsync(dataItem);
|
else await UpdateDataItemAsync(dataItem);
|
||||||
|
|
||||||
//var equalityComparer = EqualityComparer<TId>.Default;
|
// Kilépés edit módból
|
||||||
//var index = CollectionExtensions.FindIndex(_dataSource, x => equalityComparer.Equals(x.Id, dataItem.Id));
|
EditState = MgEditState.None;
|
||||||
//_dataSource.UpdateCollectionByIndex(index, dataItem, false);
|
|
||||||
|
if (ShowInfoPanel && _infoPanelInstance != null)
|
||||||
|
{
|
||||||
|
_infoPanelInstance.ClearEditMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//_dataSource.UpdateCollectionById<TId>(dataItem.Id, false);
|
private async Task OnEditCanceling(GridEditCancelingEventArgs e)
|
||||||
|
{
|
||||||
|
// Kilépés edit módból
|
||||||
|
EditState = MgEditState.None;
|
||||||
|
|
||||||
|
if (ShowInfoPanel && _infoPanelInstance != null)
|
||||||
|
{
|
||||||
|
_infoPanelInstance.ClearEditMode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task SaveChangesToServerAsync()
|
private Task SaveChangesToServerAsync()
|
||||||
|
|
@ -590,9 +584,9 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
base.DataItemDeleting = EventCallback.Factory.Create<GridDataItemDeletingEventArgs>(this, OnItemDeleting);
|
base.DataItemDeleting = EventCallback.Factory.Create<GridDataItemDeletingEventArgs>(this, OnItemDeleting);
|
||||||
base.EditModelSaving = EventCallback.Factory.Create<GridEditModelSavingEventArgs>(this, OnItemSaving);
|
base.EditModelSaving = EventCallback.Factory.Create<GridEditModelSavingEventArgs>(this, OnItemSaving);
|
||||||
base.CustomizeEditModel = EventCallback.Factory.Create<GridCustomizeEditModelEventArgs>(this, OnCustomizeEditModel);
|
base.CustomizeEditModel = EventCallback.Factory.Create<GridCustomizeEditModelEventArgs>(this, OnCustomizeEditModel);
|
||||||
//base.customizecel= EventCallback.Factory.Create<GridCustomizeEditModelEventArgs>(this, OnCustomizeEditModel);
|
|
||||||
base.FocusedRowChanged = EventCallback.Factory.Create<GridFocusedRowChangedEventArgs>(this, OnFocusedRowChanged);
|
base.FocusedRowChanged = EventCallback.Factory.Create<GridFocusedRowChangedEventArgs>(this, OnFocusedRowChanged);
|
||||||
base.EditStart = EventCallback.Factory.Create<GridEditStartEventArgs>(this, OnEditStart);
|
base.EditStart = EventCallback.Factory.Create<GridEditStartEventArgs>(this, OnEditStart);
|
||||||
|
base.EditCanceling = EventCallback.Factory.Create<GridEditCancelingEventArgs>(this, OnEditCanceling);
|
||||||
|
|
||||||
CustomizeElement += OnCustomizeElement;
|
CustomizeElement += OnCustomizeElement;
|
||||||
|
|
||||||
|
|
@ -606,16 +600,6 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
HighlightRowOnHover = true;
|
HighlightRowOnHover = true;
|
||||||
AutoCollapseDetailRow = true;
|
AutoCollapseDetailRow = true;
|
||||||
AutoExpandAllGroupRows = false;
|
AutoExpandAllGroupRows = false;
|
||||||
//KeyboardNavigationEnabled = true;
|
|
||||||
|
|
||||||
//var dataColumns = GetDataColumns();
|
|
||||||
|
|
||||||
//var idColumn = dataColumns.FirstOrDefault(x => x.FieldName == nameof(IId<TId>.Id));
|
|
||||||
//if (idColumn != null)
|
|
||||||
//{
|
|
||||||
// idColumn.ShowInColumnChooser = AcDomain.IsDeveloperVersion;
|
|
||||||
// idColumn.Visible = !AcDomain.IsDeveloperVersion;
|
|
||||||
//}
|
|
||||||
|
|
||||||
IsFirstInitializeParameterCore = true;
|
IsFirstInitializeParameterCore = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,43 @@
|
||||||
@using DevExpress.Blazor
|
@using DevExpress.Blazor
|
||||||
@using Microsoft.AspNetCore.Components.Rendering
|
@using Microsoft.AspNetCore.Components.Rendering
|
||||||
|
@using System.Reflection
|
||||||
@typeparam TDataItem where TDataItem : class
|
@typeparam TDataItem where TDataItem : class
|
||||||
|
|
||||||
<div class="mg-grid-info-panel">
|
<div class="mg-grid-info-panel @(_isEditMode ? "edit-mode" : "view-mode")">
|
||||||
@if (_currentDataItem != null && _currentGrid != null)
|
@if (GetActiveDataItem() != null && _currentGrid != null)
|
||||||
{
|
{
|
||||||
var colSpan = _allDataColumns.Count > 10 ? 6 : 12;
|
var colSpan = _allDataColumns.Count > 10 ? 6 : 12;
|
||||||
|
var dataItem = GetActiveDataItem()!;
|
||||||
|
|
||||||
<DxFormLayout Data="_currentDataItem"
|
<DxFormLayout CssClass="info-panel-form"
|
||||||
CssClass="info-panel-form"
|
|
||||||
CaptionPosition="CaptionPosition.Vertical"
|
CaptionPosition="CaptionPosition.Vertical"
|
||||||
SizeMode="SizeMode.Small">
|
SizeMode="SizeMode.Small">
|
||||||
@foreach (var column in _allDataColumns)
|
@foreach (var column in _allDataColumns)
|
||||||
{
|
{
|
||||||
|
var displayText = GetDisplayTextFromGrid(column);
|
||||||
|
var value = GetCellValue(column);
|
||||||
|
var settingsType = GetEditSettingsType(column);
|
||||||
|
var isReadOnly = !_isEditMode || column.ReadOnly;
|
||||||
|
|
||||||
<DxFormLayoutItem Caption="@GetColumnCaption(column)"
|
<DxFormLayoutItem Caption="@GetColumnCaption(column)"
|
||||||
CaptionCssClass="fw-semibold"
|
CaptionCssClass="@GetCaptionCssClass(isReadOnly)"
|
||||||
Field="@column.FieldName"
|
|
||||||
ColSpanXxl="@colSpan"
|
ColSpanXxl="@colSpan"
|
||||||
ColSpanXl="@colSpan"
|
ColSpanXl="@colSpan"
|
||||||
ColSpanLg="@colSpan"
|
ColSpanLg="@colSpan"
|
||||||
ColSpanMd="@colSpan"
|
ColSpanMd="@colSpan"
|
||||||
ColSpanSm="@colSpan"
|
ColSpanSm="@colSpan"
|
||||||
ColSpanXs="@colSpan"
|
ColSpanXs="@colSpan">
|
||||||
ReadOnly="@column.ReadOnly" />
|
<Template>
|
||||||
|
@if (_isEditMode && !column.ReadOnly)
|
||||||
|
{
|
||||||
|
@RenderEditableCell(column, dataItem, value, displayText, settingsType)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@RenderCellContent(column, value, displayText, settingsType)
|
||||||
|
}
|
||||||
|
</Template>
|
||||||
|
</DxFormLayoutItem>
|
||||||
}
|
}
|
||||||
</DxFormLayout>
|
</DxFormLayout>
|
||||||
}
|
}
|
||||||
|
|
@ -39,4 +54,649 @@
|
||||||
{
|
{
|
||||||
return !string.IsNullOrWhiteSpace(column.Caption) ? column.Caption : column.FieldName;
|
return !string.IsNullOrWhiteSpace(column.Caption) ? column.Caption : column.FieldName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetCaptionCssClass(bool isReadOnly)
|
||||||
|
{
|
||||||
|
return isReadOnly ? "fw-semibold" : "fw-semibold text-primary";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders an editable cell with two-way binding to the EditModel
|
||||||
|
/// </summary>
|
||||||
|
private RenderFragment RenderEditableCell(DxGridDataColumn column, TDataItem dataItem, object? value, string displayText, EditSettingsType settingsType)
|
||||||
|
{
|
||||||
|
return builder =>
|
||||||
|
{
|
||||||
|
var seq = 0;
|
||||||
|
var fieldName = column.FieldName;
|
||||||
|
var propertyInfo = typeof(TDataItem).GetProperty(fieldName);
|
||||||
|
|
||||||
|
if (propertyInfo == null)
|
||||||
|
{
|
||||||
|
// Fallback to readonly if property not found
|
||||||
|
RenderCellContent(column, value, displayText, settingsType)(builder);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var propertyType = propertyInfo.PropertyType;
|
||||||
|
var underlyingType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;
|
||||||
|
|
||||||
|
// ComboBox columns
|
||||||
|
if (settingsType == EditSettingsType.ComboBox)
|
||||||
|
{
|
||||||
|
var comboSettings = GetEditSettings(fieldName) as DxComboBoxSettings;
|
||||||
|
if (comboSettings != null)
|
||||||
|
{
|
||||||
|
RenderComboBoxEditor(builder, ref seq, dataItem, propertyInfo, comboSettings);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render based on property 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(TimeOnly))
|
||||||
|
{
|
||||||
|
RenderTimeOnlyEditor(builder, ref seq, dataItem, propertyInfo);
|
||||||
|
}
|
||||||
|
else if (underlyingType == typeof(TimeSpan))
|
||||||
|
{
|
||||||
|
RenderTimeSpanEditor(builder, ref seq, dataItem, propertyInfo);
|
||||||
|
}
|
||||||
|
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 || (propertyType == typeof(string) && fieldName.Contains("Comment", StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
RenderMemoEditor(builder, ref seq, dataItem, propertyInfo);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RenderTextBoxEditor(builder, ref seq, dataItem, propertyInfo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderCheckBoxEditor(RenderTreeBuilder builder, ref int seq, TDataItem 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.CloseComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderDateTimeEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo, string? displayFormat)
|
||||||
|
{
|
||||||
|
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
|
||||||
|
var currentValue = 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);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
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++, "DisplayFormat", displayFormat ?? "yyyy-MM-dd HH:mm");
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderDateOnlyEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo, string? displayFormat)
|
||||||
|
{
|
||||||
|
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
|
||||||
|
var currentValue = 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);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
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++, "DisplayFormat", displayFormat ?? "yyyy-MM-dd");
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderTimeOnlyEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo)
|
||||||
|
{
|
||||||
|
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
|
||||||
|
var currentValue = propertyInfo.GetValue(dataItem);
|
||||||
|
|
||||||
|
if (isNullable)
|
||||||
|
{
|
||||||
|
builder.OpenComponent<DxTimeEdit<TimeOnly?>>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Time", (TimeOnly?)currentValue);
|
||||||
|
builder.AddAttribute(seq++, "TimeChanged", EventCallback.Factory.Create<TimeOnly?>(this, newValue =>
|
||||||
|
{
|
||||||
|
propertyInfo.SetValue(dataItem, newValue);
|
||||||
|
InvokeAsync(StateHasChanged);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.OpenComponent<DxTimeEdit<TimeOnly>>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Time", (TimeOnly)(currentValue ?? TimeOnly.MinValue));
|
||||||
|
builder.AddAttribute(seq++, "TimeChanged", EventCallback.Factory.Create<TimeOnly>(this, newValue =>
|
||||||
|
{
|
||||||
|
propertyInfo.SetValue(dataItem, newValue);
|
||||||
|
InvokeAsync(StateHasChanged);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderTimeSpanEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo)
|
||||||
|
{
|
||||||
|
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
|
||||||
|
var currentValue = propertyInfo.GetValue(dataItem);
|
||||||
|
|
||||||
|
if (isNullable)
|
||||||
|
{
|
||||||
|
builder.OpenComponent<DxTimeEdit<TimeSpan?>>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Time", (TimeSpan?)currentValue);
|
||||||
|
builder.AddAttribute(seq++, "TimeChanged", EventCallback.Factory.Create<TimeSpan?>(this, newValue =>
|
||||||
|
{
|
||||||
|
propertyInfo.SetValue(dataItem, newValue);
|
||||||
|
InvokeAsync(StateHasChanged);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.OpenComponent<DxTimeEdit<TimeSpan>>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Time", (TimeSpan)(currentValue ?? TimeSpan.Zero));
|
||||||
|
builder.AddAttribute(seq++, "TimeChanged", EventCallback.Factory.Create<TimeSpan>(this, newValue =>
|
||||||
|
{
|
||||||
|
propertyInfo.SetValue(dataItem, newValue);
|
||||||
|
InvokeAsync(StateHasChanged);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderSpinIntEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo)
|
||||||
|
{
|
||||||
|
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
|
||||||
|
var currentValue = 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);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
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.CloseComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderSpinDecimalEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo)
|
||||||
|
{
|
||||||
|
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
|
||||||
|
var currentValue = 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);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
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.CloseComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderSpinDoubleEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo)
|
||||||
|
{
|
||||||
|
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
|
||||||
|
var currentValue = 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);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
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.CloseComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderTextBoxEditor(RenderTreeBuilder builder, ref int seq, TDataItem 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.CloseComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderMemoEditor(RenderTreeBuilder builder, ref int seq, TDataItem 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++, "Rows", 3);
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderComboBoxEditor(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings)
|
||||||
|
{
|
||||||
|
var currentValue = propertyInfo.GetValue(dataItem);
|
||||||
|
var propertyType = propertyInfo.PropertyType;
|
||||||
|
var underlyingType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;
|
||||||
|
|
||||||
|
// Determine TData type from settings.Data
|
||||||
|
var dataType = settings.Data?.GetType();
|
||||||
|
var itemType = typeof(object);
|
||||||
|
if (dataType != null && dataType.IsGenericType)
|
||||||
|
{
|
||||||
|
var genericArgs = dataType.GetGenericArguments();
|
||||||
|
if (genericArgs.Length > 0)
|
||||||
|
{
|
||||||
|
itemType = genericArgs[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle common value types for FK fields
|
||||||
|
if (underlyingType == typeof(int))
|
||||||
|
{
|
||||||
|
RenderComboBoxInt(builder, ref seq, dataItem, propertyInfo, settings, itemType, currentValue);
|
||||||
|
}
|
||||||
|
else if (underlyingType == typeof(long))
|
||||||
|
{
|
||||||
|
RenderComboBoxLong(builder, ref seq, dataItem, propertyInfo, settings, itemType, currentValue);
|
||||||
|
}
|
||||||
|
else if (underlyingType == typeof(Guid))
|
||||||
|
{
|
||||||
|
RenderComboBoxGuid(builder, ref seq, dataItem, propertyInfo, settings, itemType, currentValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Fallback: render as TextBox with display text
|
||||||
|
var displayText = ResolveComboBoxDisplayText(settings, currentValue ?? new object()) ?? currentValue?.ToString() ?? string.Empty;
|
||||||
|
builder.OpenComponent<DxTextBox>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Text", displayText);
|
||||||
|
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderComboBoxInt(RenderTreeBuilder builder, ref int seq, TDataItem dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings, Type itemType, object? currentValue)
|
||||||
|
{
|
||||||
|
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
|
||||||
|
|
||||||
|
// Create the generic ComboBox type: DxComboBox<TItem, int> or DxComboBox<TItem, 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++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto);
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderComboBoxLong(RenderTreeBuilder builder, ref int seq, TDataItem 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));
|
||||||
|
|
||||||
|
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++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto);
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderComboBoxGuid(RenderTreeBuilder builder, ref int seq, TDataItem 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));
|
||||||
|
|
||||||
|
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++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto);
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private RenderFragment RenderCellContent(DxGridDataColumn column, object? value, string displayText, EditSettingsType settingsType)
|
||||||
|
{
|
||||||
|
return builder =>
|
||||||
|
{
|
||||||
|
var seq = 0;
|
||||||
|
|
||||||
|
// If column has EditSettings, render based on that
|
||||||
|
switch (settingsType)
|
||||||
|
{
|
||||||
|
case EditSettingsType.ComboBox:
|
||||||
|
// ComboBox columns show resolved display text
|
||||||
|
builder.OpenComponent<DxTextBox>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Text", displayText);
|
||||||
|
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||||
|
builder.CloseComponent();
|
||||||
|
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.DateEdit:
|
||||||
|
RenderDateEditor(builder, ref seq, value, column.DisplayFormat);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case EditSettingsType.TimeEdit:
|
||||||
|
RenderTimeEditor(builder, ref seq, value, column.DisplayFormat);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case EditSettingsType.SpinEdit:
|
||||||
|
builder.OpenComponent<DxTextBox>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Text", displayText);
|
||||||
|
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||||
|
builder.AddAttribute(seq++, "CssClass", "text-end");
|
||||||
|
builder.CloseComponent();
|
||||||
|
return;
|
||||||
|
|
||||||
|
case EditSettingsType.Memo:
|
||||||
|
builder.OpenComponent<DxMemo>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Text", displayText);
|
||||||
|
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||||
|
builder.AddAttribute(seq++, "Rows", 3);
|
||||||
|
builder.CloseComponent();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default: render based on value type
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
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 TimeOnly timeOnlyValue:
|
||||||
|
builder.OpenComponent<DxTimeEdit<TimeOnly>>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Time", timeOnlyValue);
|
||||||
|
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||||
|
builder.CloseComponent();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TimeSpan timeSpanValue:
|
||||||
|
builder.OpenComponent<DxTimeEdit<TimeSpan>>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Time", timeSpanValue);
|
||||||
|
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||||
|
builder.CloseComponent();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case decimal or double or float or int or long or short:
|
||||||
|
builder.OpenComponent<DxTextBox>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Text", displayText);
|
||||||
|
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||||
|
builder.AddAttribute(seq++, "CssClass", "text-end");
|
||||||
|
builder.CloseComponent();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
builder.OpenComponent<DxTextBox>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Text", displayText);
|
||||||
|
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||||
|
builder.CloseComponent();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderDateEditor(RenderTreeBuilder builder, ref int seq, object? value, string? displayFormat)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case DateTime dateTime:
|
||||||
|
builder.OpenComponent<DxDateEdit<DateTime>>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Date", dateTime);
|
||||||
|
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||||
|
builder.AddAttribute(seq++, "DisplayFormat", displayFormat ?? "yyyy-MM-dd HH:mm");
|
||||||
|
builder.CloseComponent();
|
||||||
|
break;
|
||||||
|
case DateOnly dateOnly:
|
||||||
|
builder.OpenComponent<DxDateEdit<DateOnly>>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Date", dateOnly);
|
||||||
|
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||||
|
builder.AddAttribute(seq++, "DisplayFormat", displayFormat ?? "yyyy-MM-dd");
|
||||||
|
builder.CloseComponent();
|
||||||
|
break;
|
||||||
|
case DateTimeOffset dateTimeOffset:
|
||||||
|
builder.OpenComponent<DxDateEdit<DateTimeOffset>>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Date", dateTimeOffset);
|
||||||
|
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||||
|
builder.AddAttribute(seq++, "DisplayFormat", displayFormat ?? "yyyy-MM-dd HH:mm");
|
||||||
|
builder.CloseComponent();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
builder.OpenComponent<DxTextBox>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Text", value?.ToString() ?? string.Empty);
|
||||||
|
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||||
|
builder.CloseComponent();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderTimeEditor(RenderTreeBuilder builder, ref int seq, object? value, string? displayFormat)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case TimeOnly timeOnly:
|
||||||
|
builder.OpenComponent<DxTimeEdit<TimeOnly>>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Time", timeOnly);
|
||||||
|
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||||
|
builder.CloseComponent();
|
||||||
|
break;
|
||||||
|
case TimeSpan timeSpan:
|
||||||
|
builder.OpenComponent<DxTimeEdit<TimeSpan>>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Time", timeSpan);
|
||||||
|
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||||
|
builder.CloseComponent();
|
||||||
|
break;
|
||||||
|
case DateTime dateTime:
|
||||||
|
builder.OpenComponent<DxTimeEdit<DateTime>>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Time", dateTime);
|
||||||
|
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||||
|
builder.CloseComponent();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
builder.OpenComponent<DxTextBox>(seq++);
|
||||||
|
builder.AddAttribute(seq++, "Text", value?.ToString() ?? string.Empty);
|
||||||
|
builder.AddAttribute(seq++, "ReadOnly", true);
|
||||||
|
builder.CloseComponent();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using DevExpress.Blazor;
|
using DevExpress.Blazor;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace AyCode.Blazor.Components.Components.Grids;
|
namespace AyCode.Blazor.Components.Components.Grids;
|
||||||
|
|
||||||
|
|
@ -8,41 +7,249 @@ public partial class MgGridInfoPanel<TDataItem> : ComponentBase where TDataItem
|
||||||
{
|
{
|
||||||
private DxGrid? _currentGrid;
|
private DxGrid? _currentGrid;
|
||||||
private TDataItem? _currentDataItem;
|
private TDataItem? _currentDataItem;
|
||||||
|
private int _focusedRowVisibleIndex = -1;
|
||||||
private List<DxGridDataColumn> _allDataColumns = [];
|
private List<DxGridDataColumn> _allDataColumns = [];
|
||||||
|
|
||||||
|
// Edit mode state
|
||||||
|
private bool _isEditMode;
|
||||||
|
private TDataItem? _editModel;
|
||||||
|
|
||||||
|
// Cache for edit settings to avoid repeated lookups
|
||||||
|
private readonly Dictionary<string, IEditSettings?> _editSettingsCache = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Refreshes the InfoPanel with data from the specified grid row
|
/// Refreshes the InfoPanel with data from the specified grid row (view mode)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="grid">The grid instance</param>
|
public void RefreshData(DxGrid grid, TDataItem? dataItem, int visibleIndex = -1)
|
||||||
/// <param name="dataItem">The data item from the focused row</param>
|
|
||||||
public void RefreshData(DxGrid grid, TDataItem? dataItem)
|
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(grid);
|
ArgumentNullException.ThrowIfNull(grid);
|
||||||
|
|
||||||
_currentGrid = grid;
|
_currentGrid = grid;
|
||||||
_currentDataItem = dataItem;
|
_currentDataItem = dataItem;
|
||||||
|
_focusedRowVisibleIndex = visibleIndex;
|
||||||
if (_currentGrid != null && _currentDataItem != null)
|
_editSettingsCache.Clear();
|
||||||
|
|
||||||
|
// Ha nem vagyunk edit módban, frissítjük az oszlopokat
|
||||||
|
if (!_isEditMode)
|
||||||
{
|
{
|
||||||
_allDataColumns = GetAllDataColumns(_currentGrid);
|
if (_currentGrid != null && _currentDataItem != null)
|
||||||
}
|
{
|
||||||
else
|
_allDataColumns = GetAllDataColumns(_currentGrid);
|
||||||
{
|
}
|
||||||
_allDataColumns = [];
|
else
|
||||||
|
{
|
||||||
|
_allDataColumns = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InvokeAsync(StateHasChanged);
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clears the InfoPanel
|
/// Sets the InfoPanel to edit mode with the given edit model
|
||||||
|
/// </summary>
|
||||||
|
public void SetEditMode(TDataItem editModel)
|
||||||
|
{
|
||||||
|
_editModel = editModel;
|
||||||
|
_isEditMode = true;
|
||||||
|
_currentDataItem = editModel;
|
||||||
|
|
||||||
|
if (_currentGrid != null)
|
||||||
|
{
|
||||||
|
_allDataColumns = GetAllDataColumns(_currentGrid);
|
||||||
|
}
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears edit mode and returns to view mode
|
||||||
|
/// </summary>
|
||||||
|
public void ClearEditMode()
|
||||||
|
{
|
||||||
|
_isEditMode = false;
|
||||||
|
_editModel = null;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the InfoPanel completely
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
_currentGrid = null;
|
_currentGrid = null;
|
||||||
_currentDataItem = null;
|
_currentDataItem = null;
|
||||||
|
_focusedRowVisibleIndex = -1;
|
||||||
_allDataColumns = [];
|
_allDataColumns = [];
|
||||||
InvokeAsync(StateHasChanged);
|
_editSettingsCache.Clear();
|
||||||
|
_isEditMode = false;
|
||||||
|
_editModel = null;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the data item to display/edit (EditModel in edit mode, otherwise CurrentDataItem)
|
||||||
|
/// </summary>
|
||||||
|
private TDataItem? GetActiveDataItem() => _isEditMode && _editModel != null ? _editModel : _currentDataItem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the display text for a field using the grid's internal formatting.
|
||||||
|
/// For ComboBox columns, tries to get the text from the lookup data source.
|
||||||
|
/// </summary>
|
||||||
|
private string GetDisplayTextFromGrid(DxGridDataColumn column)
|
||||||
|
{
|
||||||
|
var dataItem = GetActiveDataItem();
|
||||||
|
if (_currentGrid == null || dataItem == null || string.IsNullOrWhiteSpace(column.FieldName))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var value = _currentGrid.GetDataItemValue(dataItem, column.FieldName);
|
||||||
|
|
||||||
|
if (value == null)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
// Try to resolve display text from EditSettings
|
||||||
|
var editSettings = GetEditSettings(column.FieldName);
|
||||||
|
if (editSettings != null)
|
||||||
|
{
|
||||||
|
var displayText = ResolveEditSettingsDisplayText(editSettings, value);
|
||||||
|
if (!string.IsNullOrEmpty(displayText))
|
||||||
|
return displayText;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply column's DisplayFormat if available
|
||||||
|
if (!string.IsNullOrEmpty(column.DisplayFormat))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return string.Format(column.DisplayFormat, value);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// If format fails, fall through to default formatting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FormatValue(value);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets edit settings for the specified field (with caching)
|
||||||
|
/// </summary>
|
||||||
|
private IEditSettings? GetEditSettings(string fieldName)
|
||||||
|
{
|
||||||
|
if (_currentGrid == null || string.IsNullOrEmpty(fieldName))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (_editSettingsCache.TryGetValue(fieldName, out var cached))
|
||||||
|
return cached;
|
||||||
|
|
||||||
|
IEditSettings? settings = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Try each EditSettings type
|
||||||
|
settings = TryGetEditSettings<DxComboBoxSettings>(fieldName)
|
||||||
|
?? TryGetEditSettings<DxDateEditSettings>(fieldName)
|
||||||
|
?? TryGetEditSettings<DxTimeEditSettings>(fieldName)
|
||||||
|
?? TryGetEditSettings<DxSpinEditSettings>(fieldName)
|
||||||
|
?? TryGetEditSettings<DxCheckBoxSettings>(fieldName)
|
||||||
|
?? TryGetEditSettings<DxMemoSettings>(fieldName)
|
||||||
|
?? TryGetEditSettings<DxMaskedInputSettings>(fieldName)
|
||||||
|
?? TryGetEditSettings<DxTextBoxSettings>(fieldName)
|
||||||
|
?? (IEditSettings?)TryGetEditSettings<DxDropDownEditSettings>(fieldName);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore errors
|
||||||
|
}
|
||||||
|
|
||||||
|
_editSettingsCache[fieldName] = settings;
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private T? TryGetEditSettings<T>(string fieldName) where T : class, IEditSettings
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _currentGrid?.GetColumnEditSettings<T>(fieldName);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves display text based on EditSettings type
|
||||||
|
/// </summary>
|
||||||
|
private string? ResolveEditSettingsDisplayText(IEditSettings settings, object value)
|
||||||
|
{
|
||||||
|
return settings switch
|
||||||
|
{
|
||||||
|
DxComboBoxSettings comboSettings => ResolveComboBoxDisplayText(comboSettings, value),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves the display text from a ComboBox data source
|
||||||
|
/// </summary>
|
||||||
|
private string? ResolveComboBoxDisplayText(DxComboBoxSettings settings, object value)
|
||||||
|
{
|
||||||
|
if (settings.Data == null || string.IsNullOrEmpty(settings.ValueFieldName) || string.IsNullOrEmpty(settings.TextFieldName))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var item in (System.Collections.IEnumerable)settings.Data)
|
||||||
|
{
|
||||||
|
if (item == null) continue;
|
||||||
|
|
||||||
|
var itemType = item.GetType();
|
||||||
|
var valueProperty = itemType.GetProperty(settings.ValueFieldName);
|
||||||
|
var textProperty = itemType.GetProperty(settings.TextFieldName);
|
||||||
|
|
||||||
|
if (valueProperty == null || textProperty == null) continue;
|
||||||
|
|
||||||
|
var itemValue = valueProperty.GetValue(item);
|
||||||
|
if (itemValue != null && itemValue.Equals(value))
|
||||||
|
{
|
||||||
|
return textProperty.GetValue(item)?.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// If lookup fails, return null and fall back to default formatting
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FormatValue(object? value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return value switch
|
||||||
|
{
|
||||||
|
DateTime dateTime => dateTime.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||||
|
DateOnly dateOnly => dateOnly.ToString("yyyy-MM-dd"),
|
||||||
|
TimeOnly timeOnly => timeOnly.ToString("HH:mm:ss"),
|
||||||
|
TimeSpan timeSpan => timeSpan.ToString(@"hh\:mm\:ss"),
|
||||||
|
bool boolValue => boolValue ? "Igen" : "Nem",
|
||||||
|
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),
|
||||||
|
_ => value.ToString() ?? string.Empty
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<DxGridDataColumn> GetAllDataColumns(DxGrid grid)
|
private List<DxGridDataColumn> GetAllDataColumns(DxGrid grid)
|
||||||
|
|
@ -56,8 +263,6 @@ public partial class MgGridInfoPanel<TDataItem> : ComponentBase where TDataItem
|
||||||
{
|
{
|
||||||
foreach (var column in allColumns)
|
foreach (var column in allColumns)
|
||||||
{
|
{
|
||||||
// Minden DxGridDataColumn-t felveszünk, ha van FieldName-je
|
|
||||||
// NEM vizsgáljuk a Visible property-t!
|
|
||||||
if (column is DxGridDataColumn dataColumn &&
|
if (column is DxGridDataColumn dataColumn &&
|
||||||
!string.IsNullOrWhiteSpace(dataColumn.FieldName))
|
!string.IsNullOrWhiteSpace(dataColumn.FieldName))
|
||||||
{
|
{
|
||||||
|
|
@ -76,28 +281,13 @@ public partial class MgGridInfoPanel<TDataItem> : ComponentBase where TDataItem
|
||||||
|
|
||||||
private object? GetCellValue(DxGridDataColumn column)
|
private object? GetCellValue(DxGridDataColumn column)
|
||||||
{
|
{
|
||||||
if (_currentDataItem == null || string.IsNullOrWhiteSpace(column.FieldName))
|
var dataItem = GetActiveDataItem();
|
||||||
|
if (_currentGrid == null || dataItem == null || string.IsNullOrWhiteSpace(column.FieldName))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var fieldName = column.FieldName;
|
return _currentGrid.GetDataItemValue(dataItem, column.FieldName);
|
||||||
var properties = fieldName.Split('.');
|
|
||||||
object? currentValue = _currentDataItem;
|
|
||||||
|
|
||||||
foreach (var propertyName in properties)
|
|
||||||
{
|
|
||||||
if (currentValue == null)
|
|
||||||
break;
|
|
||||||
|
|
||||||
var propertyInfo = currentValue.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
|
|
||||||
if (propertyInfo == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
currentValue = propertyInfo.GetValue(currentValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
return currentValue;
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
@ -105,31 +295,33 @@ public partial class MgGridInfoPanel<TDataItem> : ComponentBase where TDataItem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetDisplayText(DxGridDataColumn column, object? value)
|
/// <summary>
|
||||||
|
/// Gets the EditSettings type for rendering logic
|
||||||
|
/// </summary>
|
||||||
|
private EditSettingsType GetEditSettingsType(DxGridDataColumn column)
|
||||||
{
|
{
|
||||||
if (value == null)
|
var settings = GetEditSettings(column.FieldName);
|
||||||
return string.Empty;
|
|
||||||
|
return settings switch
|
||||||
if (value is DateTime dateTime)
|
|
||||||
{
|
{
|
||||||
return dateTime.ToString("yyyy-MM-dd HH:mm:ss");
|
DxComboBoxSettings => EditSettingsType.ComboBox,
|
||||||
}
|
DxDateEditSettings => EditSettingsType.DateEdit,
|
||||||
|
DxTimeEditSettings => EditSettingsType.TimeEdit,
|
||||||
|
DxSpinEditSettings => EditSettingsType.SpinEdit,
|
||||||
|
DxCheckBoxSettings => EditSettingsType.CheckBox,
|
||||||
|
DxMemoSettings => EditSettingsType.Memo,
|
||||||
|
_ => EditSettingsType.None
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (value is bool boolValue)
|
private enum EditSettingsType
|
||||||
{
|
{
|
||||||
return boolValue ? "Igen" : "Nem";
|
None,
|
||||||
}
|
ComboBox,
|
||||||
|
DateEdit,
|
||||||
if (value is decimal || value is double || value is float)
|
TimeEdit,
|
||||||
{
|
SpinEdit,
|
||||||
return string.Format("{0:N2}", value);
|
CheckBox,
|
||||||
}
|
Memo
|
||||||
|
|
||||||
if (value is int || value is long || value is short || value is byte)
|
|
||||||
{
|
|
||||||
return string.Format("{0:N0}", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.ToString() ?? string.Empty;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,18 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
background-color: var(--dxbl-bg-secondary, #f8f9fa);
|
||||||
|
transition: background-color 0.3s ease, border-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mg-grid-info-panel.edit-mode {
|
||||||
|
background-color: #fffbeb;
|
||||||
|
border-left: 3px solid #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mg-grid-info-panel.view-mode {
|
||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-panel-form {
|
.info-panel-form {
|
||||||
|
|
@ -11,16 +22,20 @@
|
||||||
|
|
||||||
.info-panel-form .fw-semibold {
|
.info-panel-form .fw-semibold {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #495057;
|
color: var(--dxbl-text-secondary, #495057);
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.info-panel-form .fw-semibold.text-primary {
|
||||||
|
color: var(--dxbl-primary, #0d6efd);
|
||||||
|
}
|
||||||
|
|
||||||
.info-panel-empty {
|
.info-panel-empty {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
color: #6c757d;
|
color: var(--dxbl-text-secondary, #6c757d);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue