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) {