diff --git a/WebApp/Components/Features/Events/CareerMapping.razor b/WebApp/Components/Features/Events/CareerMapping.razor
index 3f7ebdc..4bd4e7b 100644
--- a/WebApp/Components/Features/Events/CareerMapping.razor
+++ b/WebApp/Components/Features/Events/CareerMapping.razor
@@ -37,7 +37,11 @@ else
Events are shown in blue, career fields in green. Career fields are clusters of related careers.
Click on a node to see details. Use mouse to zoom and pan the graph.
-
+
+
+
+
+
@if (_selectedNodeInfo != null)
{
@@ -61,9 +65,6 @@ else
}
-
-
-
}
diff --git a/WebApp/Components/Features/Students/Index.razor b/WebApp/Components/Features/Students/Index.razor
index 47fbb65..b840e37 100644
--- a/WebApp/Components/Features/Students/Index.razor
+++ b/WebApp/Components/Features/Students/Index.razor
@@ -30,7 +30,7 @@
-
+
@@ -40,7 +40,7 @@
{
@context.Item.OfficerRole
}
-
+
t?.Event is { RegionalEvent: true }).OrderBy(t => t.Event.Name)
: context.Item.Teams.Where(t => t?.Event != null).OrderBy(t => t.Event.Name);
}
-
+
@foreach (var team in teamsToDisplay)
{
var isCaptain = team.Captain != null && team.Captain.Equals(context.Item.Student);
@@ -98,7 +98,7 @@
}
-
+
diff --git a/WebApp/Components/Features/Teams/Components/TeamStudents.razor b/WebApp/Components/Features/Teams/Components/TeamStudents.razor
index 58cba4a..51c9f0f 100644
--- a/WebApp/Components/Features/Teams/Components/TeamStudents.razor
+++ b/WebApp/Components/Features/Teams/Components/TeamStudents.razor
@@ -1,5 +1,5 @@
@using WebApp.Models
-
+
@foreach (var student in
Team.Students
.OrderBy(e =>
@@ -31,7 +31,7 @@
}
-
+
@code {
[Parameter]
diff --git a/WebApp/Components/Features/Teams/Components/TeamToggleSelector.razor b/WebApp/Components/Features/Teams/Components/TeamToggleSelector.razor
index 32f3709..443ff2b 100644
--- a/WebApp/Components/Features/Teams/Components/TeamToggleSelector.razor
+++ b/WebApp/Components/Features/Teams/Components/TeamToggleSelector.razor
@@ -15,13 +15,13 @@
{
-
+
@team.ToString()
@if (ShowEventAttributes)
{
}
-
+
}
diff --git a/WebApp/Components/Features/Teams/Index.razor b/WebApp/Components/Features/Teams/Index.razor
index 04a3d59..a13fb84 100644
--- a/WebApp/Components/Features/Teams/Index.razor
+++ b/WebApp/Components/Features/Teams/Index.razor
@@ -3,6 +3,7 @@
@using WebApp.Models
@page "/teams"
@attribute [Authorize]
+@implements IAsyncDisposable
@inject AppDbContext Context
@inject IDialogService DialogService
@inject ISnackbar Snackbar
@@ -74,30 +75,58 @@
MudDataGrid _dataGrid = null!;
private bool _isLoading = true;
private bool _showRegionalOnly = false;
+ private CancellationTokenSource? _cancellationTokenSource;
+ private bool _isDisposed = false;
+
+ protected override void OnInitialized()
+ {
+ _cancellationTokenSource = new CancellationTokenSource();
+ }
private async Task ToggleRegionalFilter()
{
+ if (_isDisposed) return;
+
try
{
_showRegionalOnly = !_showRegionalOnly;
- if (_dataGrid != null)
+ if (_dataGrid != null && !_isDisposed)
{
await _dataGrid.ReloadServerData();
}
}
+ catch (TaskCanceledException)
+ {
+ // Component was disposed, ignore
+ }
+ catch (JSDisconnectedException)
+ {
+ // JS connection lost, ignore
+ }
catch (Exception ex)
{
- Snackbar.Add($"Error applying filter: {ex.Message}", Severity.Error);
+ if (!_isDisposed)
+ {
+ Snackbar.Add($"Error applying filter: {ex.Message}", Severity.Error);
+ }
}
}
private async Task> ServerReload(GridState state)
{
+ if (_isDisposed)
+ {
+ return new GridData { TotalItems = 0, Items = [] };
+ }
+
_isLoading = true;
try
{
+ var cancellationToken = _cancellationTokenSource?.Token ?? CancellationToken.None;
+
IQueryable query
= Context.Teams
+ .AsNoTracking()
.Include(e => e.Event)
.Include(e => e.Students)
.ThenInclude(e => e.EventRankings);
@@ -113,7 +142,7 @@
query = query.Where(state.FilterDefinitions);
// Load all data first
- var allTeams = await query.ToArrayAsync();
+ var allTeams = await query.ToArrayAsync(cancellationToken);
// Always sort by EventFormat FIRST to separate group/individual teams
// Sort in memory to ensure this ordering is maintained regardless of user sorts
@@ -173,45 +202,100 @@
Items = pagedData
};
}
+ catch (TaskCanceledException)
+ {
+ // Component was disposed, return empty result
+ return new GridData { TotalItems = 0, Items = [] };
+ }
+ catch (JSDisconnectedException)
+ {
+ // JS connection lost, return empty result
+ return new GridData { TotalItems = 0, Items = [] };
+ }
finally
{
- _isLoading = false;
+ if (!_isDisposed)
+ {
+ _isLoading = false;
+ }
}
}
private async Task DeleteTeam(Team team)
{
- //_isRowBlocked = true;
+ if (_isDisposed) return;
- var result = await DialogService
- .ShowMessageBox("Delete team",
- (MarkupString)$"Are you sure want to delete {team}? This cannot be undone.",
- yesText: "Yes",
- noText: "Cancel");
-
- if (result == true)
+ try
{
- // If deleting a numbered team (1 or 2), clear the identifier of the remaining team
- if (team.Identifier == "1" || team.Identifier == "2")
- {
- var remainingTeam = await Context.Teams
- .Include(t => t.Event)
- .FirstOrDefaultAsync(t => t.Event.Id == team.Event.Id && t.Id != team.Id);
+ var cancellationToken = _cancellationTokenSource?.Token ?? CancellationToken.None;
- if (remainingTeam != null)
+ var result = await DialogService
+ .ShowMessageBox("Delete team",
+ (MarkupString)$"Are you sure want to delete {team}? This cannot be undone.",
+ yesText: "Yes",
+ noText: "Cancel");
+
+ if (_isDisposed) return;
+
+ if (result == true)
+ {
+ // If deleting a numbered team (1 or 2), clear the identifier of the remaining team
+ if (team.Identifier == "1" || team.Identifier == "2")
{
- remainingTeam.Identifier = null;
- Context.Teams.Update(remainingTeam);
+ var remainingTeam = await Context.Teams
+ .Include(t => t.Event)
+ .FirstOrDefaultAsync(t => t.Event.Id == team.Event.Id && t.Id != team.Id, cancellationToken);
+
+ if (_isDisposed) return;
+
+ if (remainingTeam != null)
+ {
+ remainingTeam.Identifier = null;
+ Context.Teams.Update(remainingTeam);
+ }
+ }
+
+ Context.Teams.Remove(team!);
+ await Context.SaveChangesAsync(cancellationToken);
+
+ if (!_isDisposed)
+ {
+ Snackbar.Add($"Team {team} deleted", Severity.Info);
}
}
- Context.Teams.Remove(team!);
- await Context.SaveChangesAsync();
- Snackbar.Add($"Delete event: Delete of Team {team}", 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 team: {ex.Message}", Severity.Error);
+ }
+ }
+ }
- //_isRowBlocked = false;
- StateHasChanged();
- await _dataGrid.ReloadServerData();
+ public async ValueTask DisposeAsync()
+ {
+ if (!_isDisposed)
+ {
+ _isDisposed = true;
+ _cancellationTokenSource?.Cancel();
+ _cancellationTokenSource?.Dispose();
+ _cancellationTokenSource = null;
+ }
+ await ValueTask.CompletedTask;
}
}