using DevExpress.Blazor; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; namespace AyCode.Blazor.Components.Components.Grids; /// /// Interface for InfoPanel to support grid access /// public interface IInfoPanelBase { void ClearEditMode(); void SetEditMode(object editModel); void RefreshData(IMgGridBase grid, object? dataItem, int visibleIndex = -1); } /// /// Context for InfoPanel templates containing data item and edit mode state /// public record InfoPanelContext(object? DataItem, bool IsEditMode); /// /// InfoPanel component for displaying and editing grid row details /// public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPanelBase { [Inject] private IJSRuntime JSRuntime { get; set; } = null!; /// /// Whether to show readonly fields when in edit mode. Default is false. /// [Parameter] public bool ShowReadOnlyFieldsInEditMode { get; set; } = false; /// /// Minimum width for 2 columns layout. Default is 500px. /// [Parameter] public int TwoColumnBreakpoint { get; set; } = 400; /// /// Minimum width for 3 columns layout. Default is 800px. /// [Parameter] public int ThreeColumnBreakpoint { get; set; } = 800; /// /// Minimum width for 4 columns layout. Default is 1200px. /// [Parameter] public int FourColumnBreakpoint { get; set; } = 1300; /// /// Fixed column count. If set (1-4), overrides responsive breakpoints. Default is null (responsive). /// [Parameter] public int? FixedColumnCount { get; set; } /// /// Reference to the wrapper component - automatically registers this InfoPanel /// [CascadingParameter] public MgGridWithInfoPanel? GridWrapper { get; set; } private ElementReference _panelElement; private bool _isJsInitialized; private const int DefaultTopOffset = 300; // Increased from 180 to account for header + tabs + toolbar protected IMgGridBase? _currentGrid; protected object? _currentDataItem; protected int _focusedRowVisibleIndex = -1; protected List _allDataColumns = []; // Edit mode state protected bool _isEditMode; protected object? _editModel; // Cache for edit settings to avoid repeated lookups private readonly Dictionary _editSettingsCache = []; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { // Register this InfoPanel with the wrapper GridWrapper?.RegisterInfoPanel(this); await InitializeStickyAsync(); } } private async Task InitializeStickyAsync() { try { await JSRuntime.InvokeVoidAsync( "MgGridInfoPanel.initSticky", _panelElement, DefaultTopOffset); _isJsInitialized = true; } catch (JSException) { // JS might not be loaded yet, ignore } } /// /// Refreshes the InfoPanel with data from the specified grid row (view mode) /// public void RefreshData(IMgGridBase grid, object? dataItem, int visibleIndex = -1) { ArgumentNullException.ThrowIfNull(grid); _currentGrid = grid; _currentDataItem = dataItem; _focusedRowVisibleIndex = visibleIndex; _editSettingsCache.Clear(); // Clear edit mode when refreshing with new data _isEditMode = false; _editModel = null; if (_currentGrid != null && _currentDataItem != null) { _allDataColumns = GetAllDataColumns(_currentGrid); } else { _allDataColumns = []; } StateHasChanged(); // Notify subscribers that data item changed _ = OnDataItemChanged.InvokeAsync(dataItem); } /// /// Sets the InfoPanel to edit mode with the given edit model /// public void SetEditMode(object editModel) { _editModel = editModel; _isEditMode = true; _currentDataItem = _editModel; if (_currentGrid != null) { _allDataColumns = GetAllDataColumns(_currentGrid); } InvokeAsync(StateHasChanged); } /// /// Clears edit mode and returns to view mode /// public void ClearEditMode() { _isEditMode = false; _editModel = null; _editSettingsCache.Clear(); InvokeAsync(StateHasChanged); } /// /// Clears the InfoPanel completely /// public void Clear() { _currentGrid = null; _currentDataItem = null; _focusedRowVisibleIndex = -1; _allDataColumns = []; _editSettingsCache.Clear(); _isEditMode = false; _editModel = null; StateHasChanged(); } public async ValueTask DisposeAsync() { if (_isJsInitialized) { try { await JSRuntime.InvokeVoidAsync("MgGridInfoPanel.disposeSticky", _panelElement); } catch { // Ignore disposal errors } } } /// /// Gets the data item to display/edit (EditModel in edit mode, otherwise CurrentDataItem) /// protected object? GetActiveDataItem() => _isEditMode && _editModel != null ? _editModel : _currentDataItem; /// /// Gets the display text for a field using the grid's internal formatting. /// For ComboBox columns, tries to get the text from the lookup data source. /// protected 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 is DxComboBoxSettings comboSettings) { var displayText = ResolveComboBoxDisplayText(comboSettings, 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; } } /// /// Gets edit settings for the specified field (with caching) /// 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 = _currentGrid.GetColumnEditSettings(fieldName) ?? _currentGrid.GetColumnEditSettings(fieldName) ?? _currentGrid.GetColumnEditSettings(fieldName) ?? _currentGrid.GetColumnEditSettings(fieldName) ?? _currentGrid.GetColumnEditSettings(fieldName) ?? _currentGrid.GetColumnEditSettings(fieldName) ?? (IEditSettings?)_currentGrid.GetColumnEditSettings(fieldName); } catch { // Ignore errors } _editSettingsCache[fieldName] = settings; return settings; } 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 static 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 => $"{value:N0}", _ => value.ToString() ?? string.Empty }; } /// /// Gets the columns to display based on edit mode and ShowReadOnlyFieldsInEditMode setting /// protected IEnumerable GetVisibleColumns() { if (!_isEditMode || ShowReadOnlyFieldsInEditMode) { return _allDataColumns; } // In edit mode with ShowReadOnlyFieldsInEditMode=false, hide readonly columns return _allDataColumns.Where(c => !c.ReadOnly); } protected object? GetCellValue(DxGridDataColumn column) { var dataItem = GetActiveDataItem(); if (_currentGrid == null || dataItem == null || string.IsNullOrWhiteSpace(column.FieldName)) return null; try { return _currentGrid.GetDataItemValue(dataItem, column.FieldName); } catch { return null; } } protected static List GetAllDataColumns(IMgGridBase grid) { var columns = new List(); try { var allColumns = grid.GetDataColumns(); if (allColumns != null) { foreach (var column in allColumns) { if (column is DxGridDataColumn dataColumn && !string.IsNullOrWhiteSpace(dataColumn.FieldName)) { columns.Add(dataColumn); } } } } catch (Exception) { // Ignore errors } return columns; } /// /// Gets the EditSettings type for rendering logic /// private EditSettingsType GetEditSettingsType(DxGridDataColumn column) { var settings = GetEditSettings(column.FieldName); return settings switch { DxComboBoxSettings => EditSettingsType.ComboBox, DxDateEditSettings => EditSettingsType.DateEdit, DxTimeEditSettings => EditSettingsType.TimeEdit, DxSpinEditSettings => EditSettingsType.SpinEdit, DxCheckBoxSettings => EditSettingsType.CheckBox, DxMemoSettings => EditSettingsType.Memo, _ => EditSettingsType.None }; } private enum EditSettingsType { None, ComboBox, DateEdit, TimeEdit, SpinEdit, CheckBox, Memo } }