@page "/students" @attribute [Authorize] @implements IAsyncDisposable @using Microsoft.EntityFrameworkCore @using WebApp.Models @using WebApp.Components.Shared.Components @inject AppDbContext Context @inject IDialogService DialogService @inject ISnackbar Snackbar Create New Event Rankings Registration @context.Item.LastNameFirstName @if (context.Item.OfficerRole != null) { @context.Item.OfficerRole } @((MarkupString)AppIcons.GetOrdinalSuperscript(context.Item.Grade)) (@context.Item.TsaYear) @code { MudDataGrid _dataGrid = null!; private bool _isLoading = true; private CancellationTokenSource? _cancellationTokenSource; private bool _isDisposed = false; protected override void OnInitialized() { _cancellationTokenSource = new CancellationTokenSource(); } private async Task> ServerReload(GridState state) { if (_isDisposed) { return new GridData { TotalItems = 0, Items = [] }; } _isLoading = true; try { var cancellationToken = _cancellationTokenSource?.Token ?? CancellationToken.None; var query = Context.Students .AsNoTracking() .OrderBy(e => e.LastName) .Where(state.FilterDefinitions).OrderBy(state.SortDefinitions); var totalItems = await query.CountAsync(cancellationToken); var pagedData = await query.Skip(state.Page * state.PageSize).Take(state.PageSize).ToArrayAsync(cancellationToken); return new GridData { TotalItems = totalItems, Items = pagedData }; } catch (TaskCanceledException) { return new GridData { TotalItems = 0, Items = [] }; } catch (JSDisconnectedException) { return new GridData { TotalItems = 0, Items = [] }; } finally { if (!_isDisposed) { _isLoading = false; } } } private async Task DeleteStudent(Student student) { if (_isDisposed) return; try { var cancellationToken = _cancellationTokenSource?.Token ?? CancellationToken.None; var result = await DialogService .ShowMessageBox("Delete student", (MarkupString)$"Are you sure want to delete {student.Name}? This cannot be undone.", yesText:"Yes", noText:"Cancel"); if (_isDisposed) return; if (result == true) { // Load the student fresh from database with tracking to avoid tracking conflicts var studentToDelete = await Context.Students .FirstOrDefaultAsync(s => s.Id == student.Id, cancellationToken); if (_isDisposed) return; if (studentToDelete == null) { if (!_isDisposed) { Snackbar.Add("Student not found or already deleted", Severity.Warning); } return; } Context.Students.Remove(studentToDelete); await Context.SaveChangesAsync(cancellationToken); if (!_isDisposed) { Snackbar.Add($"Student {studentToDelete.Name} deleted", Severity.Info); } } if (!_isDisposed) { StateHasChanged(); await _dataGrid.ReloadServerData(); } } catch (TaskCanceledException) { // Component was disposed, ignore } catch (JSDisconnectedException) { // JS connection lost, ignore } catch (Exception ex) { if (!_isDisposed) { Snackbar.Add($"Error deleting student: {ex.Message}", Severity.Error); } } } public async ValueTask DisposeAsync() { if (!_isDisposed) { _isDisposed = true; _cancellationTokenSource?.Cancel(); _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; } await ValueTask.CompletedTask; } }