Improve formatting for Event Create

This commit is contained in:
2025-09-25 09:58:56 -04:00
parent 7b1a52d5b3
commit 227dbcf336
9 changed files with 135 additions and 190 deletions
+4 -3
View File
@@ -84,9 +84,10 @@ public class TeamScheduler
foreach (var s in _timeSlots) foreach (var s in _timeSlots)
model.Add(x[ts.Item1, s] != x[ts.Item2, s]); model.Add(x[ts.Item1, s] != x[ts.Item2, s]);
var solver = new CpSolver(); var solver = new CpSolver();
solver.StringParameters = "max_time_in_seconds:2.0";
var cpSolverStatus = solver.Solve(model);
var cpSolverStatus = solver.Solve(model);
Debug.WriteLine($"Solver status: {cpSolverStatus}"); Debug.WriteLine($"Solver status: {cpSolverStatus}");
var timeSlotTeams = new Team[_timeSlots.Length][]; var timeSlotTeams = new Team[_timeSlots.Length][];
+12
View File
@@ -29,4 +29,16 @@ public class TeamSchedulerSolution(
where gs.Key.Count() > 1 where gs.Key.Count() > 1
select Tuple.Create(gs.First(), gs.Key); select Tuple.Create(gs.First(), gs.Key);
} }
public static Student[] GetStudentsNotInTimSlot(Team[] timeSlot, Student[] students)
{
var studentsInTimeSlot = timeSlot.SelectMany(ts => ts.Students).Distinct();
return
(from allStudent in students
where studentsInTimeSlot.FirstOrDefault(e => e.Equals(allStudent)) == null
select allStudent
).ToArray();
}
} }
+14 -2
View File
@@ -5,9 +5,20 @@ namespace Core.Entities;
public class EventDefinition public class EventDefinition
{ {
public int Id { get; set; } public int Id { get; set; }
[Required]
[StringLength(100, MinimumLength = 2)]
[Display(Name = "Event Name")]
public string Name { get; set; } public string Name { get; set; }
public string? ShortName { get; set; }
public EventFormat EventFormat { get; set; } [Required]
[StringLength(40, MinimumLength = 2)]
[Display(Name = "Event Short Name")]
public string? ShortName { get; set; }
[Required]
public EventFormat EventFormat { get; set; }
[Range(1, 6)] [Range(1, 6)]
public int MinTeamSize { get; set; } public int MinTeamSize { get; set; }
@@ -55,6 +66,7 @@ public class EventDefinition
public string? Documentation { get; set; } public string? Documentation { get; set; }
[Required]
public string Eligibility { get; set; } public string Eligibility { get; set; }
public string? Theme { get; set; } public string? Theme { get; set; }
public string? Description { get; set; } public string? Description { get; set; }
@@ -1,134 +1,63 @@
@page "/events/create" @page "/events/create"
@using Microsoft.EntityFrameworkCore
@using Core.Entities
@using Data
@inject AppDbContext context @inject AppDbContext context
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
<PageTitle>Create</PageTitle> <PageTitle>Create Event - TSA Chapter Organizer</PageTitle>
<h1>Create</h1> <MudText Typo="Typo.h3">Create</MudText>
<MudText Typo="Typo.h4">Event</MudText>
<MudDivider />
<h2>EventDefinition</h2>
<hr /> <EditForm Model="EventDefinition" OnValidSubmit="OnValidSubmit" Enhance>
<div class="row"> <DataAnnotationsValidator />
<div class="col-md-4"> <MudGrid>
<EditForm method="post" Model="EventDefinition" OnValidSubmit="AddEventDefinition" FormName="create" Enhance> <MudItem xs="12" sm="7">
<DataAnnotationsValidator /> <MudPaper Class="pa-4">
<ValidationSummary class="text-danger" role="alert"/> <MudTextField T="string" Label="Event Name" @bind-Value="EventDefinition.Name" For="@(() => EventDefinition.Name)"></MudTextField>
<div class="mb-3"> <MudTextField T="string" Label="Short Name" @bind-Value="EventDefinition.ShortName" For="@(() => EventDefinition.ShortName)"></MudTextField>
<label for="name" class="form-label">Name:</label>
<InputText id="name" @bind-Value="EventDefinition.Name" class="form-control" /> <label for="@EventDefinition.EventFormat" class="form-label">Format:</label>
<ValidationMessage For="() => EventDefinition.Name" class="text-danger" /> <MudRadioGroup T="EventFormat" @bind-Value="@EventDefinition.EventFormat">
</div> @* <MudRadio T="EventFormat" Value="EventFormat.Team">Team</MudRadio>
<div class="mb-3"> <MudRadio T="EventFormat" Value="EventFormat.Individual">Individual</MudRadio> *@
<label for="shortname" class="form-label">ShortName:</label> @foreach (EventFormat format in Enum.GetValues(typeof(EventFormat)))
<InputText id="shortname" @bind-Value="EventDefinition.ShortName" class="form-control" />
<ValidationMessage For="() => EventDefinition.ShortName" class="text-danger" />
</div>
<div class="mb-3">
<label for="eventformat" class="form-label">EventFormat:</label>
<InputSelect @bind-Value="@EventDefinition.EventFormat">
@foreach (var format in Enum.GetValues(typeof(EventFormat)))
{ {
<option value="@format">@(@format.ToString())</option> <MudRadio T="EventFormat" value="@format">@(format.ToString())</MudRadio>
} }
</InputSelect> </MudRadioGroup>
<ValidationMessage For="() => EventDefinition.EventFormat" class="text-danger" /> <ValidationMessage For="() => EventDefinition.EventFormat" class="text-danger" />
</div>
<div class="mb-3"> <MudTextField T="string" Label="Theme" AutoGrow="true" @bind-Value="EventDefinition.Theme" For="@(() => EventDefinition.Theme)"></MudTextField>
<label for="minteamsize" class="form-label">MinTeamSize:</label> <MudTextField T="string" Label="Documentation" @bind-Value="EventDefinition.Documentation" For="@(() => EventDefinition.Documentation)"></MudTextField>
<InputNumber id="minteamsize" @bind-Value="EventDefinition.MinTeamSize" class="form-control" /> <MudNumericField T="int?" Label="Level of Effort" @bind-Value="EventDefinition.LevelOfEffort" For="@(() => EventDefinition.LevelOfEffort)"></MudNumericField>
<ValidationMessage For="() => EventDefinition.MinTeamSize" class="text-danger" />
</div> <MudNumericField T="int" Label="Minimum Team Size" @bind-Value="EventDefinition.MinTeamSize" For="@(() => EventDefinition.MinTeamSize)"></MudNumericField>
<div class="mb-3"> <MudNumericField T="int" Label="Maxiumum Team Size" @bind-Value="EventDefinition.MaxTeamSize" For="@(() => EventDefinition.MaxTeamSize)"></MudNumericField>
<label for="maxteamsize" class="form-label">MaxTeamSize:</label> <MudNumericField T="int" Label="Maxiumum Team Count at State" @bind-Value="EventDefinition.MaxTeamCountState" For="@(() => EventDefinition.MaxTeamCountState)"></MudNumericField>
<InputNumber id="maxteamsize" @bind-Value="EventDefinition.MaxTeamSize" class="form-control" />
<ValidationMessage For="() => EventDefinition.MaxTeamSize" class="text-danger" /> <MudTextField T="string" Label="Semifinalist Activity" @bind-Value="EventDefinition.SemifinalistActivity" For="@(() => EventDefinition.SemifinalistActivity)"></MudTextField>
</div> <MudCheckBox T="bool" Label="Regional Event" @bind-Value="EventDefinition.RegionalEvent" For="@(() => EventDefinition.RegionalEvent)"></MudCheckBox>
<div class="mb-3"> <MudCheckBox T="bool" Label="Requires Presubmission" @bind-Value="EventDefinition.StatePresubmission" For="@(() => EventDefinition.StatePresubmission)"></MudCheckBox>
<label for="semifinalistactivity" class="form-label">SemifinalistActivity:</label> <MudCheckBox T="bool" Label="Preliminary Round" @bind-Value="EventDefinition.StatePreliminaryRound" For="@(() => EventDefinition.StatePreliminaryRound)"></MudCheckBox>
<InputText id="semifinalistactivity" @bind-Value="EventDefinition.SemifinalistActivity" class="form-control" /> <MudTextField T="string" Label="Eligibility" @bind-Value="EventDefinition.Eligibility" For="@(() => EventDefinition.Eligibility)"></MudTextField>
<ValidationMessage For="() => EventDefinition.SemifinalistActivity" class="text-danger" />
</div> </MudPaper>
<div class="mb-3"> </MudItem>
<label for="notes" class="form-label">Notes:</label> </MudGrid>
<InputText id="notes" @bind-Value="EventDefinition.Notes" class="form-control" /> <MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="events">Back</MudButton>
<ValidationMessage For="() => EventDefinition.Notes" class="text-danger" /> <MudButton ButtonType="ButtonType.Submit" StartIcon="@Icons.Material.Filled.Save">Save</MudButton>
</div> </EditForm>
<div class="mb-3">
<label for="maxteamcountstate" class="form-label">MaxTeamCountState:</label>
<InputNumber id="maxteamcountstate" @bind-Value="EventDefinition.MaxTeamCountState" class="form-control" />
<ValidationMessage For="() => EventDefinition.MaxTeamCountState" class="text-danger" />
</div>
<div class="mb-3">
<label for="regionalevent" class="form-label">RegionalEvent:</label>
<InputCheckbox id="regionalevent" @bind-Value="EventDefinition.RegionalEvent" class="form-check-input" />
<ValidationMessage For="() => EventDefinition.RegionalEvent" class="text-danger" />
</div>
<div class="mb-3">
<label for="regionalpresubmit" class="form-label">RegionalPresubmit:</label>
<InputCheckbox id="regionalpresubmit" @bind-Value="EventDefinition.RegionalPresubmit" class="form-check-input" />
<ValidationMessage For="() => EventDefinition.RegionalPresubmit" class="text-danger" />
</div>
<div class="mb-3">
<label for="statepresubmission" class="form-label">StatePresubmission:</label>
<InputCheckbox id="statepresubmission" @bind-Value="EventDefinition.StatePresubmission" class="form-check-input" />
<ValidationMessage For="() => EventDefinition.StatePresubmission" class="text-danger" />
</div>
<div class="mb-3">
<label for="statepretesting" class="form-label">StatePretesting:</label>
<InputCheckbox id="statepretesting" @bind-Value="EventDefinition.StatePretesting" class="form-check-input" />
<ValidationMessage For="() => EventDefinition.StatePretesting" class="text-danger" />
</div>
<div class="mb-3">
<label for="statepreliminaryround" class="form-label">StatePreliminaryRound:</label>
<InputCheckbox id="statepreliminaryround" @bind-Value="EventDefinition.StatePreliminaryRound" class="form-check-input" />
<ValidationMessage For="() => EventDefinition.StatePreliminaryRound" class="text-danger" />
</div>
<div class="mb-3">
<label for="documentation" class="form-label">Documentation:</label>
<InputText id="documentation" @bind-Value="EventDefinition.Documentation" class="form-control" />
<ValidationMessage For="() => EventDefinition.Documentation" class="text-danger" />
</div>
<div class="mb-3">
<label for="eligibility" class="form-label">Eligibility:</label>
<InputText id="eligibility" @bind-Value="EventDefinition.Eligibility" class="form-control" />
<ValidationMessage For="() => EventDefinition.Eligibility" class="text-danger" />
</div>
<div class="mb-3">
<label for="theme" class="form-label">Theme:</label>
<InputText id="theme" @bind-Value="EventDefinition.Theme" class="form-control" />
<ValidationMessage For="() => EventDefinition.Theme" class="text-danger" />
</div>
<div class="mb-3">
<label for="description" class="form-label">Description:</label>
<InputText id="description" @bind-Value="EventDefinition.Description" class="form-control" />
<ValidationMessage For="() => EventDefinition.Description" class="text-danger" />
</div>
<div class="mb-3">
<label for="levelofeffort" class="form-label">LevelOfEffort:</label>
<InputNumber id="levelofeffort" @bind-Value="EventDefinition.LevelOfEffort" class="form-control" />
<ValidationMessage For="() => EventDefinition.LevelOfEffort" class="text-danger" />
</div>
<button type="submit" class="btn btn-primary">Create</button>
</EditForm>
</div>
</div>
<div>
<a href="/events">Back to List</a>
</div>
@code { @code {
[SupplyParameterFromForm] [SupplyParameterFromForm]
private EventDefinition EventDefinition { get; set; } = new(); private EventDefinition EventDefinition { get; set; } = new();
// To protect from overposting attacks, see https://learn.microsoft.com/aspnet/core/blazor/forms/#mitigate-overposting-attacks. private void OnValidSubmit()
private async Task AddEventDefinition()
{ {
context.Events.Add(EventDefinition); context.Events.Add(EventDefinition);
await context.SaveChangesAsync(); context.SaveChanges();
NavigationManager.NavigateTo("/events"); NavigationManager.NavigateTo("/events");
} }
} }
@@ -1,7 +1,5 @@
@page "/events/delete" @page "/events/delete"
@using Microsoft.EntityFrameworkCore @using Microsoft.EntityFrameworkCore
@using Core.Entities
@using Data
@inject AppDbContext context @inject AppDbContext context
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@@ -1,7 +1,5 @@
@page "/events/details" @page "/events/details"
@using Microsoft.EntityFrameworkCore @using Microsoft.EntityFrameworkCore
@using Core.Entities
@using Data
@inject AppDbContext context @inject AppDbContext context
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@@ -8,50 +8,34 @@
<MudText Typo="Typo.h4">Student</MudText> <MudText Typo="Typo.h4">Student</MudText>
<MudDivider /> <MudDivider />
<div class="row">
<div class="col-md-4">
<EditForm method="post" Model="Student" OnValidSubmit="AddStudent" FormName="create" Enhance>
<DataAnnotationsValidator />
<ValidationSummary class="text-danger" role="alert"/>
<div class="mb-3">
<label for="firstname" class="form-label">First Name:</label>
<InputText id="firstname" @bind-Value="Student.FirstName" class="form-control" />
<ValidationMessage For="() => Student.FirstName" class="text-danger" />
</div>
<div class="mb-3">
<label for="lastname" class="form-label">Last Name:</label>
<InputText id="lastname" @bind-Value="Student.LastName" class="form-control" />
<ValidationMessage For="() => Student.LastName" class="text-danger" />
</div>
<div class="mb-3">
<label for="grade" class="form-label">Grade:</label>
<InputNumber id="grade" @bind-Value="Student.Grade" class="form-control" />
<ValidationMessage For="() => Student.Grade" class="text-danger" />
</div>
<div class="mb-3">
<label for="tsayear" class="form-label">TSA Year:</label>
<InputNumber id="tsayear" @bind-Value="Student.TsaYear" class="form-control" />
<ValidationMessage For="() => Student.TsaYear" class="text-danger" />
</div>
<button type="submit" class="btn btn-primary">Create</button>
</EditForm>
</div>
</div>
<div> <EditForm Model="Student" OnValidSubmit="OnValidSubmit" Enhance>
<a href="/students">Back to List</a> <DataAnnotationsValidator />
</div> <MudGrid>
<MudItem xs="12" sm="7">
<MudPaper Class="pa-4">
<MudTextField T="string" Label="First Name" @bind-Value="Student.FirstName" For="@(() => Student.FirstName)"></MudTextField>
<MudTextField T="string" Label="Last Name" @bind-Value="Student.LastName" For="@(() => Student.LastName)"></MudTextField>
<MudTextField T="string" Label="Email Adress" @bind-Value="Student.Email" For="@(() => Student.Email)"></MudTextField>
<MudTextField T="string" Label="Phone Number" @bind-Value="Student.PhoneNumber" For="@(() => Student.PhoneNumber)"></MudTextField>
<MudTextField T="int" Label="Grade" @bind-Value="Student.Grade" For="@(() => Student.Grade)"></MudTextField>
<MudTextField T="int" Label="TSA Year" @bind-Value="Student.TsaYear" For="@(() => Student.TsaYear)"></MudTextField>
</MudPaper>
</MudItem>
</MudGrid>
<MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="students">Back</MudButton>
<MudButton ButtonType="ButtonType.Submit" StartIcon="@Icons.Material.Filled.Save">Save</MudButton>
</EditForm>
@code { @code {
[SupplyParameterFromForm] [SupplyParameterFromForm]
private Student Student { get; set; } = new() { TsaYear = 1 }; private Student Student { get; set; } = new() { TsaYear = 1 };
private async Task AddStudent() private void OnValidSubmit(EditContext context)
{ {
Context.Students.Add(Student); Context.Students.Add(Student);
await Context.SaveChangesAsync(); Context.SaveChanges();
NavigationManager.NavigateTo("/students"); NavigationManager.NavigateTo("/students");
} }
} }
@@ -21,39 +21,19 @@ else
<MudTh>Name</MudTh> <MudTh>Name</MudTh>
<MudTh>Grade</MudTh> <MudTh>Grade</MudTh>
<MudTh>TSA Year</MudTh> <MudTh>TSA Year</MudTh>
<MudTh>1st</MudTh>
<MudTh>2nd</MudTh>
<MudTh>3rd</MudTh>
<MudTh>4th</MudTh>
<MudTh>5th</MudTh>
<MudTh>6th</MudTh>
<MudTh>7th</MudTh>
<MudTh>8th</MudTh>
<MudTh>9th</MudTh>
<MudTh>10th</MudTh>
<MudTh></MudTh> <MudTh></MudTh>
<MudTh>Warnings</MudTh> <MudTh>Warnings</MudTh>
@for (var i = 1; i <= 10; i++)
{
var ii = i;
<MudTh>@AppIcons.GetOrdinal(ii)</MudTh>
}
</HeaderContent> </HeaderContent>
<RowTemplate> <RowTemplate>
<MudTd>@context.FirstName</MudTd> <MudTd>@context.FirstName</MudTd>
<MudTh>@context.Grade</MudTh> <MudTh>@context.Grade</MudTh>
<MudTh>@context.TsaYear</MudTh> <MudTh>@context.TsaYear</MudTh>
@for (var i = 1; i <= 10; i++)
{
var st = context.EventRankings.FirstOrDefault(e => e.Rank == i);
<MudTd Class="@($"event-rank-{i})")">
@if (st != null)
{
<span>@st.EventDefinition.ShortName&nbsp;
@if(st.EventDefinition.EventFormat == EventFormat.Individual) { <span>ⓘ</span>}
@if(st.EventDefinition.RegionalEvent) {<span>ⓡ</span>}
@if(st.EventDefinition.OnSiteActivity) {<span>ⓐ</span>}
</span>
}
</MudTd>
}
<MudTd><MudButton StartIcon="@Icons.Material.Filled.TableChart" Href="@($"students/event-ranking-edit/{context.Id}")">Edit</MudButton></MudTd> <MudTd><MudButton StartIcon="@Icons.Material.Filled.TableChart" Href="@($"students/event-ranking-edit/{context.Id}")">Edit</MudButton></MudTd>
<MudTd> <MudTd>
@if (!context.RankedEvents.Any(re => re.OnSiteActivity)) @if (!context.RankedEvents.Any(re => re.OnSiteActivity))
@@ -76,6 +56,22 @@ else
</MudTooltip> </MudTooltip>
} }
</MudTd> </MudTd>
@for (var i = 1; i <= 10; i++)
{
var ii = i;
var st = context.EventRankings.FirstOrDefault(e => e.Rank == i);
<MudTd Class="@($"event-rank-{ii})")">
@if (st != null)
{
<span>@st.EventDefinition.ShortName&nbsp;
@if(st.EventDefinition.EventFormat == EventFormat.Individual) { <span>ⓘ</span>}
@if(st.EventDefinition.RegionalEvent) {<span>ⓡ</span>}
@if(st.EventDefinition.OnSiteActivity) {<span>ⓐ</span>}
</span>
}
</MudTd>
}
</RowTemplate> </RowTemplate>
</MudTable> </MudTable>
<MudTable Items="_eventStudentRankings" Hover="true" Breakpoint="Breakpoint.Sm" LoadingProgressColor="Color.Info"> <MudTable Items="_eventStudentRankings" Hover="true" Breakpoint="Breakpoint.Sm" LoadingProgressColor="Color.Info">
@@ -16,19 +16,31 @@
</HeaderContent> </HeaderContent>
<RowTemplate> <RowTemplate>
<MudTd> <MudTd>
@{
var ol = TeamSchedulerSolution.GetStudentTeamOverlaps(context);}
@foreach (var t in context) @foreach (var t in context)
{ {
<MudItem> <MudItem>
@t.ToString() - @t.ToString() -
@string.Join(", ", t.Students) @string.Join(", ", t.Students.Select(s => s.FirstName + " " + (ol.Any(o => o.Item1.Equals(s)) ? "*" : "" )) )
</MudItem> </MudItem>
} }
@foreach (var overlap in TeamSchedulerSolution.GetStudentTeamOverlaps(context)) @* @foreach (var overlap in ol)
{ {
<MudItem> <MudItem>
@string.Join(", ", overlap.Item1) @string.Join(", ", overlap.Item1)
</MudItem> </MudItem>
} *@
@{ var notInTimeSLot = TeamSchedulerSolution.GetStudentsNotInTimSlot(context, _students); }
@if (notInTimeSLot.Any()) {
<MudItem>
<i>
Not scheduled: @string.Join(", ", notInTimeSLot.Select(s => s.FirstName))
</i>
</MudItem>
} }
</MudTd> </MudTd>
</RowTemplate> </RowTemplate>
</MudTable> </MudTable>
@@ -93,9 +105,12 @@
); );
var mustIncludeTeams = _teams; var mustIncludeTeams = _teams;
mustIncludeTeams = mustIncludeTeams.Where(t => t.Event.EventFormat == EventFormat.Team).ToArray(); 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, scheduleOptions.TimeSlots); var teamScheduler = new TeamScheduler(mustIncludeTeams, 3);
// teamScheduler // teamScheduler
// .ScheduleSeparate( // .ScheduleSeparate(