Team schedule improvements

This commit is contained in:
2025-10-17 07:58:14 -04:00
parent 551688f6fe
commit 3964a61794
11 changed files with 99 additions and 172 deletions
+3 -3
View File
@@ -14,9 +14,9 @@ public class TeamScheduler
private readonly List<Tuple<int,int>> _scheduleSeparateTeams = []; private readonly List<Tuple<int,int>> _scheduleSeparateTeams = [];
public TeamScheduler(Team[] teams, int numTimeSlots) public TeamScheduler(IEnumerable<Team> teams, int numTimeSlots)
{ {
_teamObjects = teams; _teamObjects = teams.ToArray();
_studentObjects = teams.SelectMany(t => t.Students).Distinct().ToList(); _studentObjects = teams.SelectMany(t => t.Students).Distinct().ToList();
_students = Enumerable.Range(0, _studentObjects.Count).ToArray(); _students = Enumerable.Range(0, _studentObjects.Count).ToArray();
@@ -31,7 +31,7 @@ public class TeamScheduler
_scheduleSeparateTeams.Add(Tuple.Create(one,two)); _scheduleSeparateTeams.Add(Tuple.Create(one,two));
} }
public static TeamScheduler CreateInstance(Team[] teams, int numTimeSlots) public static TeamScheduler CreateInstance(IEnumerable<Team> teams, int numTimeSlots)
{ {
return new TeamScheduler(teams, numTimeSlots); return new TeamScheduler(teams, numTimeSlots);
} }
Binary file not shown.
+23 -1
View File
@@ -1,5 +1,5 @@
@using WebApp.Models @using WebApp.Models
@*
<MudTooltip Text="Level of Effort">@AppIcons.LevelOfEffortIcon(EventDefinition.LevelOfEffort)</MudTooltip> <MudTooltip Text="Level of Effort">@AppIcons.LevelOfEffortIcon(EventDefinition.LevelOfEffort)</MudTooltip>
@if(EventDefinition.EventFormat == EventFormat.Individual) { @if(EventDefinition.EventFormat == EventFormat.Individual) {
@@ -18,6 +18,28 @@
@if (EventDefinition.Presubmission) @if (EventDefinition.Presubmission)
{ {
<MudTooltip Text="Presubmission Event">@AppIcons.PresubmissionEvent</MudTooltip> <MudTooltip Text="Presubmission Event">@AppIcons.PresubmissionEvent</MudTooltip>
} *@
@AppIcons.LevelOfEffortIcon(EventDefinition.LevelOfEffort)
@if (EventDefinition.EventFormat == EventFormat.Individual)
{
@AppIcons.IndividualEvent
}
@if (EventDefinition.OnSiteActivity)
{
@AppIcons.OnSiteActivity
}
@if (EventDefinition.RegionalEvent)
{
@AppIcons.RegionalEvent
}
@if (EventDefinition.Presubmission)
{
@AppIcons.PresubmissionEvent
} }
@@ -15,129 +15,9 @@
<MudNumericField @bind-Value="_parameters.TimeSlots" <MudNumericField @bind-Value="_parameters.TimeSlots"
Label="Time Slots" Min="1" Max="4"></MudNumericField> Label="Time Slots" Min="1" Max="4"></MudNumericField>
</MudItem> </MudItem>
@foreach (var evt in _options) <TeamSelector Teams="@_teams" SelectedTeams="@_requiredTeams" Label="Required"></TeamSelector>
{ <MudButton OnClick="() => RequireRegionals()">Require Regionals</MudButton>
<MudItem Style="width:200px">
<MudButtonGroup>
<MudTooltip Text="Require">
<MudToggleIconButton @bind-Toggled="@evt.Require"
Icon="@Icons.Material.Filled.CheckBoxOutlineBlank"
ToggledIcon="@Icons.Material.Filled.CheckBox"
ToggledColor="@Color.Success"
Disabled="@evt.Omit"
Size="Size.Small"
title="@(evt.Require ? "Required" : "Optional")" />
</MudTooltip>
<MudTooltip Text="Extend">
<MudToggleIconButton @bind-Toggled="@evt.Extend"
Icon="@Icons.Material.Filled.PlusOne"
ToggledIcon="@Icons.Material.Filled.PlusOne"
ToggledColor="@Color.Success"
Disabled="@evt.Omit"
Size="Size.Small"
title="@(evt.Extend ? "Extend" : "Single")" />
</MudTooltip>
<MudTooltip Text="Extend">
<MudToggleIconButton @bind-Toggled="@evt.Omit"
Icon="@Icons.Material.Filled.EventBusy"
ToggledIcon="@Icons.Material.Filled.EventBusy"
ToggledColor="@Color.Error"
Size="Size.Small"
title="@(evt.Omit ? "Omit" : "Optional")" />
</MudTooltip>
</MudButtonGroup>
<MudTooltip Text="@string.Join(", ", evt.Team.Students.Select(e => e.FirstName))">
<MudText>@evt.Team.ToString()</MudText>
</MudTooltip>
</MudItem>
}
@* <MudItem>
<MudTooltip Text="Require at least one On-Site Event">
<MudSwitch @bind-Value="_parameters" Color="Color.Info"
Label="On-Site" />
</MudTooltip>
</MudItem>
<MudItem>
<MudTooltip Text="Require at least one Regional Event">
<MudSwitch @bind-Value="_parameters.RequireRegional" Color="Color.Info"
Label="Regional" />
</MudTooltip>
</MudItem>
<MudItem>
<MudStack Style="width:100px;">
<MudTooltip Text="Student Event Count Assignment Range">
<MudInputLabel>Event Count</MudInputLabel>
</MudTooltip>
<MudNumericField @bind-Value="_parameters.EventsLowerBound"
Label="At Least" Min="2" Max="4"></MudNumericField>
<MudNumericField @bind-Value="_parameters.EventsUpperBound"
Label="Up to" Min="3" Max="5"></MudNumericField>
</MudStack>
</MudItem>
<MudItem>
<MudStack Style="width:100px;">
<MudTooltip Text="Student Level of Effort Range">
<MudInputLabel>LOE</MudInputLabel>
</MudTooltip>
<MudNumericField @bind-Value="_parameters.EffortLowerBound"
Label="At Least" Min="4" Max="7"></MudNumericField>
<MudNumericField @bind-Value="_parameters.EffortUpperBound"
Label="Up to" Min="7" Max="12"></MudNumericField>
</MudStack>
</MudItem>
<MudItem>
<MudInputLabel>Assignment Requirements</MudInputLabel>
<MudTable T="AssignmentRequirement" ServerData="ReloadAssignmentRequirements" @ref="_assignmentRequirementData">
<RowTemplate Context="item">
<MudTd Class="align-center">
<MudIconButton Icon="@Icons.Material.Filled.RemoveCircle" Size="Size.Small"
OnClick="() => RemoveRequireEvent(item)"></MudIconButton>
</MudTd>
<MudTd Class="align-center">
@item.Student.FirstName
@item.EventDefinition.ShortName
@if (item.Requirement == Requirement.Include)
{
<MudIcon Class="ml-3" Icon="@Icons.Material.Filled.ThumbUp" Size="Size.Small"></MudIcon>
}
@if (item.Requirement == Requirement.Exclude)
{
<MudIcon Class="ml-3" Icon="@Icons.Material.Filled.ThumbDownAlt" Size="Size.Small"></MudIcon>
}
</MudTd>
</RowTemplate>
</MudTable>
</MudItem>
<MudItem>
<MudInputLabel>Two Team Events</MudInputLabel>
<MudTable T="EventDefinition" ServerData="ReloadEventTwoTeam" @ref="_eventTwoTeamData">
<RowTemplate Context="item">
<MudTd Class="align-center">
<MudIconButton Icon="@Icons.Material.Filled.RemoveCircle" Size="Size.Small"
OnClick="() => RemoveTwoTeam(item)"></MudIconButton>
</MudTd>
<MudTd Class="align-center">@item.ShortName</MudTd>
</RowTemplate>
</MudTable>
</MudItem>
<MudItem>
<MudInputLabel>Omitted Events</MudInputLabel>
<MudTable T="EventDefinition" ServerData="ReloadOmittedEvents" @ref="_eventOmittedData">
<RowTemplate Context="item">
<MudTd Class="align-center">
<MudIconButton Icon="@Icons.Material.Filled.RemoveCircle" Size="Size.Small"
OnClick="() => RemoveOmitted(item)"></MudIconButton>
</MudTd>
<MudTd Class="align-center">@item.ShortName</MudTd>
</RowTemplate>
</MudTable>
</MudItem> *@
</MudGrid> </MudGrid>
<MudButton Class="ma-3" OnClick="Solve" Variant="Variant.Filled" Color="Color.Primary" Disabled="@_isSolving">Solve</MudButton> <MudButton Class="ma-3" OnClick="Solve" Variant="Variant.Filled" Color="Color.Primary" Disabled="@_isSolving">Solve</MudButton>
</MudPaper> </MudPaper>
@@ -188,16 +68,15 @@
private TeamSchedulerSolution _solution; private TeamSchedulerSolution _solution;
private TeamSchedulerOptions _parameters; private TeamSchedulerOptions _parameters;
bool _isSolving = false; bool _isSolving = false;
private HashSet<Team> _requiredTeams = [];
public class TeamOptions private void RequireRegionals()
{ {
public Team Team { get; set; } foreach (var team in _teams.Where(e => e.Event.RegionalEvent))
public bool Require { get; set; } = false; {
public bool Omit { get; set; } = false; _requiredTeams.Add(team);
public bool Extend { get; set; } = false; }
} }
private TeamOptions[]? _options;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@@ -246,11 +125,6 @@
.Include(e => e.EventRankings) .Include(e => e.EventRankings)
.ThenInclude(e => e.EventDefinition) .ThenInclude(e => e.EventDefinition)
.OrderBy(e => e.FirstName).ToArrayAsync(); .OrderBy(e => e.FirstName).ToArrayAsync();
_options = _teams
.Select(e => new TeamOptions { Team = e })
.ToArray();
} }
private async Task<TableData<Team[]>> SolveSchedule(TableState arg1, CancellationToken arg2) private async Task<TableData<Team[]>> SolveSchedule(TableState arg1, CancellationToken arg2)
@@ -259,12 +133,9 @@
var mustIncludeTeams = _teams; var requiredTeams = _teams;
mustIncludeTeams = mustIncludeTeams.Where(t => t.Event.EventFormat != EventFormat.Individual).ToArray();
mustIncludeTeams = mustIncludeTeams.Where(t => !t.ToString().Contains("#1")).ToArray();
//mustIncludeTeams = mustIncludeTeams.Where(t => !t.ToString().Contains("Photo")).ToArray();
var teamScheduler = new TeamScheduler(mustIncludeTeams, 3); var teamScheduler = new TeamScheduler(_requiredTeams, _parameters.TimeSlots);
// teamScheduler // teamScheduler
// .ScheduleSeparate( // .ScheduleSeparate(
@@ -64,9 +64,7 @@ else
@if (st != null) @if (st != null)
{ {
<span>@st.EventDefinition.ShortName&nbsp; <span>@st.EventDefinition.ShortName&nbsp;
@if(st.EventDefinition.EventFormat == EventFormat.Individual) { <span>ⓘ</span>} <EventAttributes EventDefinition="@st.EventDefinition"></EventAttributes>
@if(st.EventDefinition.RegionalEvent) {<span>ⓡ</span>}
@if(st.EventDefinition.OnSiteActivity) {<span>ⓐ</span>}
</span> </span>
} }
</MudTd> </MudTd>
@@ -4,6 +4,7 @@
@using WebApp.Models @using WebApp.Models
@using EventAssignment = Core.Calculation.EventAssignment @using EventAssignment = Core.Calculation.EventAssignment
@inject AppDbContext Context @inject AppDbContext Context
@inject IDialogService DialogService
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
<PageTitle>Event Assignment - TSA Chapter Organizer</PageTitle> <PageTitle>Event Assignment - TSA Chapter Organizer</PageTitle>
@@ -238,7 +239,7 @@
</MudTable> </MudTable>
</MudItem> </MudItem>
<MudItem xs="12" lg="4"> <MudItem xs="12" lg="4">
<MudText Typo="Typo.h4">TimeSlots</MudText> <MudText Typo="Typo.h4">Teams</MudText>
<MudTable T="Team" ServerData="SolveAssignments" @ref="_teamData"> <MudTable T="Team" ServerData="SolveAssignments" @ref="_teamData">
<ColGroup> <ColGroup>
<col style="width: 200px;" /> <col style="width: 200px;" />
@@ -254,7 +255,7 @@
} }
<MudTd> <MudTd>
<b>@context.Event.Name</b>&nbsp; <b>@context.Event.Name</b>&nbsp;
<span>@AppIcons.EventEffort(context.Event) @AppIcons.EventAttributes(context.Event)</span> <EventAttributes EventDefinition="context.Event"></EventAttributes>
</MudTd> </MudTd>
<MudTd Style="white-space:nowrap"> <MudTd Style="white-space:nowrap">
@@ -444,11 +445,19 @@
public async Task SaveTeams() public async Task SaveTeams()
{ {
var teams = await Context.Teams.ExecuteDeleteAsync(); var result = await DialogService
.ShowMessageBox("Save Teams",
(MarkupString)$"Are you sure want to save these teams? Current teams will be erased. This cannot be undone.",
yesText: "Yes",
noText: "Cancel");
if (result == true)
{
await Context.Teams.ExecuteDeleteAsync();
await Context.Teams.AddRangeAsync(_teams); await Context.Teams.AddRangeAsync(_teams);
await Context.SaveChangesAsync(); await Context.SaveChangesAsync();
NavigationManager.NavigateTo("/teams"); NavigationManager.NavigateTo("/teams");
} }
} }
}
+9 -1
View File
@@ -31,7 +31,15 @@ else
<MudSelectItem T="Student" Value="@student">@student.Name</MudSelectItem> <MudSelectItem T="Student" Value="@student">@student.Name</MudSelectItem>
} }
</MudSelect> </MudSelect>
<MudTextField T="Student" Label="Captain" @bind-Value="Team.Captain" For="@(() => Team.Captain)" Required="false" Clearable="true"></MudTextField> <MudStack>
<MudText>Captain</MudText>
<MudToggleGroup T="Student" SelectionMode="SelectionMode.ToggleSelection" @bind-Value="Team.Captain" CheckMark>
@foreach (var student in _selectedStudents.OrderBy(e => e.FirstName))
{
<MudToggleItem Value="@(student)" Text="@student.Name" />
}
</MudToggleGroup>
</MudStack>
<MudTextField T="string?" Label="Identifier" @bind-Value="Team.Identifier" For="@(() => Team.Identifier)" Required="false" Clearable="true"></MudTextField> <MudTextField T="string?" Label="Identifier" @bind-Value="Team.Identifier" For="@(() => Team.Identifier)" Required="false" Clearable="true"></MudTextField>
</MudPaper> </MudPaper>
</MudItem> </MudItem>
@@ -1,6 +1,5 @@
@page "/teams" @page "/teams"
@using Microsoft.EntityFrameworkCore @using Microsoft.EntityFrameworkCore
@using WebApp.Models
@inject AppDbContext Context @inject AppDbContext Context
@inject IDialogService DialogService @inject IDialogService DialogService
@inject ISnackbar Snackbar @inject ISnackbar Snackbar
@@ -31,11 +30,6 @@
<TeamStudents Team="@context.Item"></TeamStudents> <TeamStudents Team="@context.Item"></TeamStudents>
</CellTemplate> </CellTemplate>
</TemplateColumn> </TemplateColumn>
@* <TemplateColumn Title="Grade (TSA Year)" SortBy="e => e.Grade" Sortable="true">
<CellTemplate>
@context.Item.Grade (@context.Item.TsaYear)
</CellTemplate>
</TemplateColumn> *@
<TemplateColumn> <TemplateColumn>
<CellTemplate> <CellTemplate>
<CrudActions <CrudActions
@@ -47,7 +41,7 @@
</TemplateColumn> </TemplateColumn>
</Columns> </Columns>
<PagerContent> <PagerContent>
<MudDataGridPager T="Student"></MudDataGridPager> <MudDataGridPager T="Team"></MudDataGridPager>
</PagerContent> </PagerContent>
</MudDataGrid> </MudDataGrid>
@@ -33,14 +33,14 @@ else
@context.ToString() @context.ToString()
</MudTd> </MudTd>
<MudTd> <MudTd>
@AppIcons.EventEffort(context.Event) <EventAttributes EventDefinition="context.Event"></EventAttributes>
@AppIcons.EventAttributes(context.Event)
</MudTd> </MudTd>
@{ @{
var students var students
= context.Students = context.Students
.OrderBy(s => s.EventRankings.Find(e => e.EventDefinition == context.Event)?.Rank ?? int.MaxValue) .OrderByDescending(s => s == context.Captain)
.ThenBy(s => s.EventRankings.Find(e => e.EventDefinition == context.Event)?.Rank ?? int.MaxValue)
.ThenByDescending(e => e.Grade) .ThenByDescending(e => e.Grade)
.ThenBy(e => e.FirstName) .ThenBy(e => e.FirstName)
.ToArray(); .ToArray();
@@ -54,8 +54,8 @@ else
var rank = student.EventRankings var rank = student.EventRankings
.Find(e => e.EventDefinition == context.Event)?.Rank ?? int.MaxValue; .Find(e => e.EventDefinition == context.Event)?.Rank ?? int.MaxValue;
<MudTd Class="@($"event-rank-{rank}")"> <MudTd Class="@(EventRankClass(rank))">
@student.Name @student.Name @if(context?.Captain == student) {<span> (Cpt)</span>}
</MudTd> </MudTd>
} }
else else
@@ -117,10 +117,9 @@ else
{ {
var rank = context.EventRankings var rank = context.EventRankings
.Find(e => e.EventDefinition == team.Event)?.Rank ?? int.MaxValue; .Find(e => e.EventDefinition == team.Event)?.Rank ?? int.MaxValue;
<MudTh Class="@($"event-rank-{rank}")"> <MudTh Class="@(EventRankClass(rank))">
@team.ToString() @team.ToString()
@AppIcons.EventEffort(team.Event) <EventAttributes EventDefinition="team.Event"></EventAttributes>
@AppIcons.EventAttributes(team.Event)
@if (rank == int.MaxValue) @if (rank == int.MaxValue)
{ {
@@ -181,7 +180,7 @@ else
<RowTemplate> <RowTemplate>
@{ var rank = context.EventRankings @{ var rank = context.EventRankings
.Find(e => e.EventDefinition == team.Event)?.Rank ?? int.MaxValue; } .Find(e => e.EventDefinition == team.Event)?.Rank ?? int.MaxValue; }
<MudTd Class="@($"event-rank-{rank}")"> <MudTd Class="@(EventRankClass(rank))">
@team.ToString() @team.ToString()
@AppIcons.EventEffort(team.Event) @AppIcons.EventEffort(team.Event)
@AppIcons.EventAttributes(team.Event) @AppIcons.EventAttributes(team.Event)
@@ -205,6 +204,14 @@ else
private Team[]? _teams; private Team[]? _teams;
private int _maxTeamSize; private int _maxTeamSize;
private Student[]? _students; private Student[]? _students;
private bool _rankColorEnabled;
private string EventRankClass(int rank)
{
if (!_rankColorEnabled)
return "";
return "event-rank-" + rank;
}
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@@ -16,7 +16,7 @@
@student.FirstName @student.FirstName
@if (captain) @if (captain)
{ {
<span> *</span> <span> (Cpt)</span>
} }
</MudPaper> </MudPaper>
} }
@@ -0,0 +1,18 @@
<MudSelect
T="Team"
MultiSelection="true"
@bind-SelectedValues="@SelectedTeams"
ToStringFunc="e => e.ToString()"
Label="@Label">
@foreach (var evt in Teams.OrderBy(e => e.ToString()))
{
<MudSelectItem T="Team" Value="@evt">@evt.ToString()</MudSelectItem>
}
</MudSelect>
@code {
[Parameter] public Team[]? Teams { get; set; }
[Parameter] public required IEnumerable<Team> SelectedTeams { get; set; }
[Parameter] public string Label { get; set; } = "Teams";
}