Refactor Teams components for improved sorting and filtering functionality
Updated the Teams Index and Printout components to prioritize sorting by EventFormat, enhancing the organization of team data. Introduced a regional filter toggle in the Teams Index to allow users to view only regional teams. Adjusted the ScheduledTeamsList to sort teams by EventFormat first, ensuring consistent ordering across components. Additionally, added necessary using directives for improved code clarity.
This commit is contained in:
@@ -52,19 +52,18 @@
|
|||||||
<EventAttributes EventDefinition="context.Item"></EventAttributes>
|
<EventAttributes EventDefinition="context.Item"></EventAttributes>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
<PropertyColumn Property="@(e => e.EventFormat)" Title="Event Format" />
|
|
||||||
|
|
||||||
<TemplateColumn Title="Team Size" CellStyle="white-space:nowrap">
|
<TemplateColumn Title="Team Size" CellStyle="white-space:nowrap">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudTooltip Text="@context.Item.Eligibility">
|
<MudTooltip Text="@context.Item.Eligibility">
|
||||||
[@context.Item.MinTeamSize - @context.Item.MaxTeamSize]
|
[@context.Item.MinTeamSize - @context.Item.MaxTeamSize]
|
||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
<PropertyColumn Property="@(e => e.ChapterEligibilityCountState)" Title="State#" />
|
|
||||||
|
|
||||||
|
|
||||||
<PropertyColumn Property="@(e => e.LevelOfEffort)" Title="Level of Effort" />
|
<PropertyColumn Property="@(e => e.LevelOfEffort)" Title="Level of Effort" />
|
||||||
|
<PropertyColumn Property="@(e => e.EventFormat)" Title="Event Format" />
|
||||||
|
|
||||||
</Columns>
|
</Columns>
|
||||||
<PagerContent>
|
<PagerContent>
|
||||||
<MudDataGridPager T="EventDefinition"></MudDataGridPager>
|
<MudDataGridPager T="EventDefinition"></MudDataGridPager>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
@using Core.Calculation
|
@using Core.Calculation
|
||||||
|
@using WebApp.Models
|
||||||
|
|
||||||
<MudStack>
|
<MudStack>
|
||||||
<MudText Typo="Typo.h6">@TimeSlotName</MudText>
|
<MudText Typo="Typo.h6">@TimeSlotName</MudText>
|
||||||
@foreach (var team in Teams.OrderBy(e => e.ToString()))
|
@foreach (var team in Teams.OrderByEventFormatFirst().ThenBy(e => e.ToString()))
|
||||||
{
|
{
|
||||||
var removed = !ScheduledTeams.Contains(team);
|
var removed = !ScheduledTeams.Contains(team);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@using WebApp.Models
|
||||||
|
|
||||||
@if (Title != null)
|
@if (Title != null)
|
||||||
{
|
{
|
||||||
<MudText Typo="Typo.h4">@Title</MudText>
|
<MudText Typo="Typo.h4">@Title</MudText>
|
||||||
@@ -9,7 +11,7 @@
|
|||||||
ValuesChanged="@OnSelectedTeamsChanged"
|
ValuesChanged="@OnSelectedTeamsChanged"
|
||||||
Vertical="true"
|
Vertical="true"
|
||||||
CheckMark>
|
CheckMark>
|
||||||
@foreach (var team in Teams.OrderBy(e => e.Event.Name))
|
@foreach (var team in Teams.OrderByEventFormatFirst().ThenBy(e => e.Event.Name))
|
||||||
{
|
{
|
||||||
<MudToggleItem Value="@team" Style="font-size: .75rem;">
|
<MudToggleItem Value="@team" Style="font-size: .75rem;">
|
||||||
<MudTooltip Text="@team.StudentsFirstNames">
|
<MudTooltip Text="@team.StudentsFirstNames">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
@using WebApp.Components.Shared.Components
|
@using WebApp.Components.Shared.Components
|
||||||
|
@using WebApp.Models
|
||||||
@page "/teams"
|
@page "/teams"
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@inject AppDbContext Context
|
@inject AppDbContext Context
|
||||||
@@ -9,9 +10,14 @@
|
|||||||
<PageHeader Title="Teams">
|
<PageHeader Title="Teams">
|
||||||
<ActionButtons>
|
<ActionButtons>
|
||||||
<MudButton StartIcon="@Icons.Material.Filled.Create" Href="teams/create" Variant="Variant.Filled" Color="Color.Primary">Create New</MudButton>
|
<MudButton StartIcon="@Icons.Material.Filled.Create" Href="teams/create" Variant="Variant.Filled" Color="Color.Primary">Create New</MudButton>
|
||||||
<MudButton StartIcon="@Icons.Material.Filled.Assignment" Href="teams/assignment" Variant="Variant.Outlined">Assignment</MudButton>
|
|
||||||
<MudButton StartIcon="@Icons.Material.Filled.Print" Href="teams/printout" Variant="Variant.Outlined">Printout</MudButton>
|
<MudButton StartIcon="@Icons.Material.Filled.Print" Href="teams/printout" Variant="Variant.Outlined">Printout</MudButton>
|
||||||
<MudButton StartIcon="@Icons.Material.Filled.Print" Href="teams/handout" Variant="Variant.Outlined">Handout</MudButton>
|
<MudButton StartIcon="@Icons.Material.Filled.Print" Href="teams/handout" Variant="Variant.Outlined">Handout</MudButton>
|
||||||
|
<MudButton StartIcon="@Icons.Material.Filled.FilterAlt"
|
||||||
|
Variant="@(_showRegionalOnly ? Variant.Filled : Variant.Outlined)"
|
||||||
|
Color="Color.Primary"
|
||||||
|
OnClick="ToggleRegionalFilter">
|
||||||
|
@(_showRegionalOnly ? "Showing Regional Only" : "Show Regional Only")
|
||||||
|
</MudButton>
|
||||||
</ActionButtons>
|
</ActionButtons>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
@@ -20,7 +26,7 @@
|
|||||||
ServerData="ServerReload"
|
ServerData="ServerReload"
|
||||||
@ref="_dataGrid"
|
@ref="_dataGrid"
|
||||||
Filterable="true"
|
Filterable="true"
|
||||||
RowsPerPage="35"
|
RowsPerPage="50"
|
||||||
Dense="true"
|
Dense="true"
|
||||||
Striped="true"
|
Striped="true"
|
||||||
Hover="true"
|
Hover="true"
|
||||||
@@ -67,24 +73,99 @@
|
|||||||
@code {
|
@code {
|
||||||
MudDataGrid<Team> _dataGrid = null!;
|
MudDataGrid<Team> _dataGrid = null!;
|
||||||
private bool _isLoading = true;
|
private bool _isLoading = true;
|
||||||
|
private bool _showRegionalOnly = false;
|
||||||
|
|
||||||
|
private async Task ToggleRegionalFilter()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_showRegionalOnly = !_showRegionalOnly;
|
||||||
|
if (_dataGrid != null)
|
||||||
|
{
|
||||||
|
await _dataGrid.ReloadServerData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"Error applying filter: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<GridData<Team>> ServerReload(GridState<Team> state)
|
private async Task<GridData<Team>> ServerReload(GridState<Team> state)
|
||||||
{
|
{
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var query
|
IQueryable<Team> query
|
||||||
= Context.Teams
|
= Context.Teams
|
||||||
.Include(e => e.Event)
|
.Include(e => e.Event)
|
||||||
.Include(e => e.Students)
|
.Include(e => e.Students)
|
||||||
.ThenInclude(e => e.EventRankings)
|
.ThenInclude(e => e.EventRankings);
|
||||||
.OrderBy(e => e.Event.Name)
|
|
||||||
.ThenBy(e => e.Identifier)
|
|
||||||
.Where(state.FilterDefinitions)
|
|
||||||
.OrderBy(state.SortDefinitions);
|
|
||||||
|
|
||||||
var totalItems = await query.CountAsync();
|
// Apply regional filter if enabled
|
||||||
var pagedData = await query.Skip(state.Page * state.PageSize).Take(state.PageSize).ToArrayAsync();
|
// Note: RegionalEvent is a computed property, so we must use the underlying property
|
||||||
|
if (_showRegionalOnly)
|
||||||
|
{
|
||||||
|
query = query.Where(t => t.Event.ChapterEligibilityCountRegionals > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply grid filter definitions
|
||||||
|
query = query.Where(state.FilterDefinitions);
|
||||||
|
|
||||||
|
// Load all data first
|
||||||
|
var allTeams = await query.ToArrayAsync();
|
||||||
|
|
||||||
|
// Always sort by EventFormat FIRST to separate group/individual teams
|
||||||
|
// Sort in memory to ensure this ordering is maintained regardless of user sorts
|
||||||
|
var sortedTeams = allTeams.OrderByEventFormatFirst();
|
||||||
|
|
||||||
|
// Apply user sort definitions as secondary sorts (within each group)
|
||||||
|
bool appliedUserSort = false;
|
||||||
|
if (state.SortDefinitions != null && state.SortDefinitions.Any())
|
||||||
|
{
|
||||||
|
var sortDef = state.SortDefinitions.First();
|
||||||
|
var sortBy = sortDef.SortBy ?? "";
|
||||||
|
|
||||||
|
// Handle Event.Name sorting
|
||||||
|
if (sortBy == "Event.Name" || (sortBy.Contains("Event") && sortBy.Contains("Name")))
|
||||||
|
{
|
||||||
|
sortedTeams = sortDef.Descending
|
||||||
|
? sortedTeams.ThenByDescending(t => t.Event.Name)
|
||||||
|
: sortedTeams.ThenBy(t => t.Event.Name);
|
||||||
|
appliedUserSort = true;
|
||||||
|
}
|
||||||
|
// Handle Identifier sorting
|
||||||
|
else if (sortBy == "Identifier")
|
||||||
|
{
|
||||||
|
sortedTeams = sortDef.Descending
|
||||||
|
? sortedTeams.ThenByDescending(t => t.Identifier ?? "")
|
||||||
|
: sortedTeams.ThenBy(t => t.Identifier ?? "");
|
||||||
|
appliedUserSort = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply default secondary sorting
|
||||||
|
if (!appliedUserSort)
|
||||||
|
{
|
||||||
|
sortedTeams = sortedTeams
|
||||||
|
.ThenBy(t => t.Event.Name)
|
||||||
|
.ThenBy(t => t.Identifier ?? "");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Add default sorting as tie-breakers
|
||||||
|
if (state.SortDefinitions?.First().SortBy != "Event.Name")
|
||||||
|
{
|
||||||
|
sortedTeams = sortedTeams.ThenBy(t => t.Event.Name);
|
||||||
|
}
|
||||||
|
if (state.SortDefinitions?.First().SortBy != "Identifier")
|
||||||
|
{
|
||||||
|
sortedTeams = sortedTeams.ThenBy(t => t.Identifier ?? "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalItems = sortedTeams.Count();
|
||||||
|
var pagedData = sortedTeams.Skip(state.Page * state.PageSize).Take(state.PageSize).ToArray();
|
||||||
|
|
||||||
return new GridData<Team>
|
return new GridData<Team>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -220,8 +220,9 @@ else
|
|||||||
= await Context.Teams
|
= await Context.Teams
|
||||||
.Include(e => e.Event)
|
.Include(e => e.Event)
|
||||||
.Include(e => e.Students)
|
.Include(e => e.Students)
|
||||||
.OrderBy(e => e.Event.Name)
|
.OrderByEventFormatFirst()
|
||||||
.ThenBy(e => e.Identifier)
|
.ThenBy(e => e.Event.Name)
|
||||||
|
.ThenBy(e => e.Identifier ?? "")
|
||||||
.ToArrayAsync();
|
.ToArrayAsync();
|
||||||
|
|
||||||
_maxTeamSize = _teams.Max(t => t.Students.Count);
|
_maxTeamSize = _teams.Max(t => t.Students.Count);
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using Core.Entities;
|
||||||
|
|
||||||
|
namespace WebApp.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for sorting teams with group teams (EventFormat.Team) appearing before individual teams (EventFormat.Individual).
|
||||||
|
/// </summary>
|
||||||
|
public static class TeamExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Orders teams with group teams (EventFormat.Team) first, then individual teams (EventFormat.Individual).
|
||||||
|
/// For use with EF Core IQueryable queries.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="teams">The queryable collection of teams to sort.</param>
|
||||||
|
/// <returns>An ordered queryable with group teams first, then individual teams.</returns>
|
||||||
|
public static IOrderedQueryable<Team> OrderByEventFormatFirst(this IQueryable<Team> teams)
|
||||||
|
{
|
||||||
|
return teams.OrderByDescending(t => t.Event.EventFormat == EventFormat.Team ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Orders teams with group teams (EventFormat.Team) first, then individual teams (EventFormat.Individual).
|
||||||
|
/// For use with in-memory IEnumerable collections.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="teams">The collection of teams to sort.</param>
|
||||||
|
/// <returns>An ordered enumerable with group teams first, then individual teams.</returns>
|
||||||
|
public static IOrderedEnumerable<Team> OrderByEventFormatFirst(this IEnumerable<Team> teams)
|
||||||
|
{
|
||||||
|
return teams.OrderByDescending(t => t.Event.EventFormat == EventFormat.Team ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Orders teams with group teams first, then applies default secondary sorting:
|
||||||
|
/// Event.Name (ascending), then Identifier (ascending with nulls last).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="teams">The collection of teams to sort.</param>
|
||||||
|
/// <returns>An ordered enumerable with group teams first and default secondary sorting applied.</returns>
|
||||||
|
public static IOrderedEnumerable<Team> OrderByEventFormatFirstWithDefaults(this IEnumerable<Team> teams)
|
||||||
|
{
|
||||||
|
return teams
|
||||||
|
.OrderByEventFormatFirst()
|
||||||
|
.ThenBy(t => t.Event.Name)
|
||||||
|
.ThenBy(t => t.Identifier ?? "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -479,3 +479,4 @@ curl http://localhost:8080
|
|||||||
- See `docker-compose.example.yml` for Docker Compose configuration examples
|
- See `docker-compose.example.yml` for Docker Compose configuration examples
|
||||||
- Authentication implementation: `WebApp/Authentication/`
|
- Authentication implementation: `WebApp/Authentication/`
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user