diff --git a/WebApp/Components/Features/Students/Index.razor b/WebApp/Components/Features/Students/Index.razor
index b8498bc..230768a 100644
--- a/WebApp/Components/Features/Students/Index.razor
+++ b/WebApp/Components/Features/Students/Index.razor
@@ -12,24 +12,24 @@
Create New
Event Rankings
+Registration
- @* *@
-
+
- @context.Item.LastNameFirstName
+ @context.Item.LastNameFirstName
@if (context.Item.OfficerRole != null)
{
@context.Item.OfficerRole
}
-
-
+
+
@context.Item.Grade (@context.Item.TsaYear)
-
+
Registration - TSA Chapter Organizer
+
+Registration
+
+
+
+ @(_showRegionalOnly ? "Showing Regional Only" : "Show Regional Only")
+
+
+
+
+ = Team Captain
+
+
+
+ Show Columns:
+
+
+
+
+
+
+
+
+
+
+ @context.Item.Student.LastNameFirstName
+
+
+ @if (_showGrade)
+ {
+
+ }
+ @if (_showRegionalId)
+ {
+
+ }
+ @if (_showStateId)
+ {
+
+ }
+ @if (_showNationalId)
+ {
+
+ }
+
+
+ @if (context.Item.Teams != null)
+ {
+ var teamsToDisplay = _showRegionalOnly
+ ? context.Item.Teams.Where(t => t?.Event != null && t.Event.RegionalEvent).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);
+ var teamMembers = string.Join(", ", team.Students.Select(s => s.FirstName));
+
+
+
+ @team
+ @if (isCaptain)
+ {
+
+ }
+
+
+ }
+ }
+
+
+
+
+
+
+
+
+@code {
+ MudDataGrid _dataGrid = null!;
+ private bool _showRegionalOnly;
+ private bool _showGrade;
+ private bool _showRegionalId;
+ private bool _showStateId;
+ private bool _showNationalId;
+
+ // TODO: Remove this workaround once MudBlazor fixes dynamic column ordering
+ // https://dev.to/the_real_slim_janey/get-in-line-customizing-column-order-in-mudblazor-3ail
+ // To remove:
+ // 1. Delete this _gridKey field
+ // 2. Remove "_gridKey++;" from all checkbox ValueChanged handlers (lines 26-29)
+ // 3. Remove "@key="_gridKey"" attribute from MudDataGrid (line 32)
+ private int _gridKey = 0;
+
+ private async Task ToggleRegionalFilter()
+ {
+ _showRegionalOnly = !_showRegionalOnly;
+ StateHasChanged();
+ await Task.Delay(10);
+ await _dataGrid.ReloadServerData();
+ }
+
+ private async Task> ServerReload(GridState state)
+ {
+ // Load all students with their teams
+ var students = await Context.Students
+ .Include(s => s.Teams)
+ .ThenInclude(t => t.Event)
+ .Include(s => s.Teams)
+ .ThenInclude(t => t.Captain)
+ .ToListAsync();
+
+ // Filter to only students with teams
+ var studentTeams = students
+ .Where(s => s.Teams.Any(t => t?.Event != null && (!_showRegionalOnly || t.Event.RegionalEvent)))
+ .Select(s => new StudentTeamInfo
+ {
+ Student = s,
+ Teams = s.Teams?.Where(t => t?.Event != null).ToList() ?? []
+ })
+ .ToList();
+
+ // Apply sorting
+ var sortedData = ApplySorting(studentTeams, state.SortDefinitions);
+
+ var totalItems = sortedData.Count();
+ var pagedData = sortedData.Skip(state.Page * state.PageSize).Take(state.PageSize).ToArray();
+
+ return new GridData
+ {
+ TotalItems = totalItems,
+ Items = pagedData
+ };
+ }
+
+ ///
+ /// Dictionary mapping column property names to their corresponding sort expressions.
+ /// Used to provide type-safe sorting for data grid columns.
+ ///
+ ///
+ /// Each entry maps a property path (e.g., "Student.LastName") to a function that
+ /// extracts the comparable value from a StudentTeamInfo instance.
+ /// Nullable strings are coalesced to empty strings to ensure consistent sorting behavior.
+ ///
+ private static readonly Dictionary> SortExpressions = new()
+ {
+ { "Student.LastName", s => s.Student.LastName },
+ { "Student.Grade", s => s.Student.Grade },
+ { "Student.RegionalId", s => s.Student.RegionalId ?? string.Empty },
+ { "Student.StateId", s => s.Student.StateId ?? string.Empty },
+ { "Student.NationalId", s => s.Student.NationalId ?? string.Empty }
+ };
+
+ ///
+ /// Applies sorting to the student team data based on the provided sort definitions.
+ ///
+ /// The list of StudentTeamInfo records to sort.
+ /// Collection of sort definitions from the MudDataGrid state.
+ ///
+ /// An IEnumerable of StudentTeamInfo sorted according to the first sort definition,
+ /// or sorted by LastName if no valid sort definition is provided.
+ ///
+ ///
+ /// If no sort definitions are provided, or if the requested property is not found in SortExpressions,
+ /// the data will default to sorting by Student.LastName in ascending order.
+ /// Only the first sort definition is applied (multi-column sorting is not supported).
+ ///
+ private IEnumerable ApplySorting(
+ List data,
+ ICollection> sortDefinitions)
+ {
+ if (!sortDefinitions.Any())
+ {
+ return data.OrderBy(s => s.Student.LastName);
+ }
+
+ var sortDef = sortDefinitions.First();
+ var propertyName = sortDef.SortBy;
+
+ if (!SortExpressions.TryGetValue(propertyName, out var sortExpression))
+ {
+ return data.OrderBy(s => s.Student.LastName);
+ }
+
+ return sortDef.Descending
+ ? data.OrderByDescending(sortExpression)
+ : data.OrderBy(sortExpression);
+ }
+
+ public class StudentTeamInfo
+ {
+ public required Student Student { get; init; }
+ public List Teams { get; init; } = [];
+ }
+}
diff --git a/WebApp/Components/Shared/Layout/NavMenu.razor b/WebApp/Components/Shared/Layout/NavMenu.razor
index 15e20a7..bf1aee1 100644
--- a/WebApp/Components/Shared/Layout/NavMenu.razor
+++ b/WebApp/Components/Shared/Layout/NavMenu.razor
@@ -11,6 +11,7 @@
Teams
+ Registration
Print out
Handout
diff --git a/WebApp/Models/AppIcons.cs b/WebApp/Models/AppIcons.cs
index f0e8917..61c7b74 100644
--- a/WebApp/Models/AppIcons.cs
+++ b/WebApp/Models/AppIcons.cs
@@ -11,6 +11,7 @@ namespace WebApp.Models
public static string TeamAssignment = Icons.Material.Filled.GroupAdd;
public static string Events = Icons.Material.Filled.Dashboard;
public static string Scheduler = Icons.Material.Filled.CalendarMonth;
+ public static string Captain = Icons.Material.Filled.Star;
public static string LevelOfEffortIcon(int? loe)
{