Add grid sync state tracking and robust login redirection

Introduce IsSyncing and OnSyncingStateChanged to IMgGridBase and MgGridBase for real-time sync state tracking and event notification. Update FruitBankToolbarTemplate to enable/disable the reload button based on grid sync and reload state, subscribing to sync events and cleaning up on disposal. Implement IAsyncDisposable in MgGridBase to prevent memory leaks. Update login navigation to use forceLoad for reliability. These changes improve UI responsiveness and resource management.
This commit is contained in:
Loretta 2025-12-09 11:27:21 +01:00
parent 06f397e285
commit 920bc299aa
1 changed files with 63 additions and 5 deletions

View File

@ -15,10 +15,18 @@ namespace AyCode.Blazor.Components.Components.Grids;
public interface IMgGridBase : IGrid public interface IMgGridBase : IGrid
{ {
/// <summary>
/// Indicates whether any synchronization operation is in progress
/// </summary>
bool IsSyncing { get; }
/// <summary>
/// Event fired when synchronization state changes (true = syncing started, false = syncing ended)
/// </summary>
event Action<bool>? OnSyncingStateChanged;
} }
public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient> : DxGrid, IMgGridBase public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient> : DxGrid, IMgGridBase, IAsyncDisposable
where TSignalRDataSource : AcSignalRDataSource<TDataItem, TId, AcObservableCollection<TDataItem>> where TSignalRDataSource : AcSignalRDataSource<TDataItem, TId, AcObservableCollection<TDataItem>>
where TDataItem : class, IId<TId> where TDataItem : class, IId<TId>
where TId : struct where TId : struct
@ -29,11 +37,18 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
protected bool IsFirstInitializeParameters; protected bool IsFirstInitializeParameters;
protected bool IsFirstInitializeParameterCore; protected bool IsFirstInitializeParameterCore;
private bool _isDisposed;
private TSignalRDataSource? _dataSource = null!; private TSignalRDataSource? _dataSource = null!;
private AcObservableCollection<TDataItem>? _dataSourceParam = []; private AcObservableCollection<TDataItem>? _dataSourceParam = [];
private string _gridLogName; private string _gridLogName;
/// <inheritdoc />
public bool IsSyncing => _dataSource?.IsSyncing ?? false;
/// <inheritdoc />
public event Action<bool>? OnSyncingStateChanged;
public MgGridBase() : base() public MgGridBase() : base()
{ {
} }
@ -133,6 +148,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
public void SetGridData(object? data) public void SetGridData(object? data)
{ {
if (_isDisposed) return;
if (ReferenceEquals(Data, data)) return; if (ReferenceEquals(Data, data)) return;
BeginUpdate(); BeginUpdate();
@ -161,12 +177,25 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
_dataSource.OnDataSourceLoaded += OnDataSourceLoaded; _dataSource.OnDataSourceLoaded += OnDataSourceLoaded;
_dataSource.OnDataSourceItemChanged += OnDataSourceItemChanged; _dataSource.OnDataSourceItemChanged += OnDataSourceItemChanged;
_dataSource.OnSyncingStateChanged += OnDataSourceSyncingStateChanged;
await base.OnInitializedAsync(); await base.OnInitializedAsync();
} }
private void OnDataSourceSyncingStateChanged(bool isSyncing)
{
if (_isDisposed) return;
// Forward the event to external subscribers
OnSyncingStateChanged?.Invoke(isSyncing);
// Trigger UI update
InvokeAsync(StateHasChanged);
}
private async Task OnDataSourceItemChanged(ItemChangedEventArgs<TDataItem> args) private async Task OnDataSourceItemChanged(ItemChangedEventArgs<TDataItem> args)
{ {
if (_isDisposed) return;
if (args.TrackingState is TrackingState.GetAll or TrackingState.None) return; if (args.TrackingState is TrackingState.GetAll or TrackingState.None) return;
Logger.Debug($"{_gridLogName} OnDataSourceItemChanged; trackingState: {args.TrackingState}"); Logger.Debug($"{_gridLogName} OnDataSourceItemChanged; trackingState: {args.TrackingState}");
@ -174,7 +203,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
var changedEventArgs = new GridDataItemChangedEventArgs<TDataItem>(this, args.Item, args.TrackingState); var changedEventArgs = new GridDataItemChangedEventArgs<TDataItem>(this, args.Item, args.TrackingState);
await OnGridItemChanged.InvokeAsync(changedEventArgs); await OnGridItemChanged.InvokeAsync(changedEventArgs);
if (!changedEventArgs.CancelStateChangeInvoke) if (!changedEventArgs.CancelStateChangeInvoke && !_isDisposed)
{ {
//BeginUpdate(); //BeginUpdate();
await InvokeAsync(StateHasChanged); //TODO: bezárja a DetailRow-t! pl: az email-nél IsReaded=true update... - J. await InvokeAsync(StateHasChanged); //TODO: bezárja a DetailRow-t! pl: az email-nél IsReaded=true update... - J.
@ -184,6 +213,8 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
private async Task OnDataSourceLoaded() private async Task OnDataSourceLoaded()
{ {
if (_isDisposed) return;
Logger.Debug($"{_gridLogName} OnDataSourceLoaded; Count: {_dataSource?.Count}"); Logger.Debug($"{_gridLogName} OnDataSourceLoaded; Count: {_dataSource?.Count}");
//if(_dataSourceParam.GetType() == typeof()AcObservableCollection<TDataItem>) //if(_dataSourceParam.GetType() == typeof()AcObservableCollection<TDataItem>)
@ -192,8 +223,11 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
//else Reload(); //else Reload();
//_dataSource.LoadItem(_dataSource.First().Id).Forget(); //_dataSource.LoadItem(_dataSource.First().Id).Forget();
await OnDataSourceChanged.InvokeAsync(_dataSource); if (!_isDisposed)
await InvokeAsync(StateHasChanged); {
await OnDataSourceChanged.InvokeAsync(_dataSource);
await InvokeAsync(StateHasChanged);
}
} }
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
@ -482,6 +516,30 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
{ {
return _dataSource.LoadDataSourceAsync(false); return _dataSource.LoadDataSourceAsync(false);
} }
public async ValueTask DisposeAsync()
{
if (_isDisposed) return;
_isDisposed = true;
// Unsubscribe from events to prevent callbacks to disposed component
if (_dataSource != null)
{
_dataSource.OnDataSourceLoaded -= OnDataSourceLoaded;
_dataSource.OnDataSourceItemChanged -= OnDataSourceItemChanged;
_dataSource.OnSyncingStateChanged -= OnDataSourceSyncingStateChanged;
}
CustomizeElement -= OnCustomizeElement;
// Dispose base if it implements IAsyncDisposable
if (this is IAsyncDisposable asyncDisposable && asyncDisposable != this)
{
await asyncDisposable.DisposeAsync();
}
GC.SuppressFinalize(this);
}
} }
public class GridDataItemChangingEventArgs<TDataItem> : GridDataItemChangedEventArgs<TDataItem> where TDataItem : class public class GridDataItemChangingEventArgs<TDataItem> : GridDataItemChangedEventArgs<TDataItem> where TDataItem : class