diff --git a/Tests/Parsers/TestInput/~$2024-25 RMS TSA student & event - Event Definitions.xlsx b/Tests/Parsers/TestInput/~$2024-25 RMS TSA student & event - Event Definitions.xlsx deleted file mode 100644 index b8350cb..0000000 Binary files a/Tests/Parsers/TestInput/~$2024-25 RMS TSA student & event - Event Definitions.xlsx and /dev/null differ diff --git a/WebApp/ChapterSettings.cs b/WebApp/ChapterSettings.cs index 0cd25fc..99f20cc 100644 --- a/WebApp/ChapterSettings.cs +++ b/WebApp/ChapterSettings.cs @@ -2,13 +2,22 @@ namespace WebApp { - public class ChapterSettings + public class StateContainer { - public required string Name { get; set; } - public required string ShortName { get; set; } - public required string RegionalId { get; set; } - public required string StateId { get; set; } - public required string NationalId { get; set; } - public required string CompetitionYear { get; set; } + private int[]? _scheduledTeams; + + public int[] UserId + { + get => _scheduledTeams ?? []; + set + { + _scheduledTeams = value; + NotifyStateChanged(); + } + } + + public event Action? OnChange; + + private void NotifyStateChanged() => OnChange?.Invoke(); } } diff --git a/WebApp/ClipboardService.cs b/WebApp/ClipboardService.cs new file mode 100644 index 0000000..82a78c8 --- /dev/null +++ b/WebApp/ClipboardService.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; +using Microsoft.JSInterop; + +public sealed class ClipboardService +{ + private readonly IJSRuntime _jsRuntime; + + public ClipboardService(IJSRuntime jsRuntime) + { + _jsRuntime = jsRuntime; + } + + public ValueTask ReadTextAsync() + { + return _jsRuntime.InvokeAsync("navigator.clipboard.readText"); + } + + public ValueTask WriteTextAsync(string text) + { + return _jsRuntime.InvokeVoidAsync("navigator.clipboard.writeText", text); + } +} \ No newline at end of file diff --git a/WebApp/Components/Pages/MeetingSchedulePages/Index.razor b/WebApp/Components/Pages/MeetingSchedulePages/Index.razor index 5588d3b..55fc588 100644 --- a/WebApp/Components/Pages/MeetingSchedulePages/Index.razor +++ b/WebApp/Components/Pages/MeetingSchedulePages/Index.razor @@ -1,8 +1,10 @@ -@using Core.Calculation +@using System.Text +@using Core.Calculation @using Microsoft.EntityFrameworkCore @page "/meeting-schedule" @inject IConfiguration Configuration @inject AppDbContext Context +@inject ClipboardService ClipboardService @Configuration["ChapterSettings:Shortname"] TSA Schedule @Configuration["ChapterSettings:CompetitionYear"] @@ -10,88 +12,127 @@ - @* Include: @string.Join(", ", _requiredTeams) *@ + @* Include: @string.Join(", ", _scheduledTeams) *@ + + Time Slots + + Solve + + + + + + + + + + @{ + var overlaps + = TeamSchedulerSolution.GetStudentTeamOverlaps(context).ToArray(); + } + @foreach (var team in context.OrderBy(e => e.ToString())) + { + var removed = !_scheduledTeams.Contains(team); + + + + @team - + @foreach (var student in team.Students) + { + var overlap = overlaps.Any(o => o.Item1.Equals(student)); + var color = overlap ? Color.Warning : Color.Default; + + +  @student.FirstName@(overlap ? "*" : "") + + } + + + + + } + + + + + @{ + var unscheduled = TeamSchedulerSolution.GetStudentsNotInTimSlot(context, _students); + } + @if (unscheduled.Any()) + { + + Unscheduled + foreach (var student in unscheduled) + { + + @student.FirstName   + @{ + var pa = _possibleAdditions.ToArray(); + } + @foreach (var unassignedTeam in _solution.StudentUnassignedTeams(student)) + { + var color = pa.Contains(unassignedTeam) ? Color.Error : Color.Default; + var added = _scheduledTeams.Contains(unassignedTeam); + + + @unassignedTeam + + } + + } + } + + + + - + Add High Effort Add Regionals Remove Individual Remove Low Effort Invert - + @string.Join(", ", (_possibleAdditions ?? []).Select(e => e.ToString())) @foreach (var team in _teams.OrderBy(e => e.Event.Name)) { s.FirstName))"> -
- @team.ToString() - -
+
+ @team.ToString() + +
}
- - Time Slots - - Solve - - - - - - - @{ - var ol = TeamSchedulerSolution.GetStudentTeamOverlaps(context); - } - @foreach (var t in context) - { - - @t.ToString() - - @string.Join(", ", t.Students.Select(s => s.FirstName + (ol.Any(o => o.Item1.Equals(s)) ? "*" : ""))) - - } - @* @foreach (var overlap in ol) - { - - @string.Join(", ", overlap.Item1) - - - } *@ - - @{ var unscheduled = TeamSchedulerSolution.GetStudentsNotInTimSlot(context, _students); } - @if (unscheduled.Any()) - { - Unscheduled - foreach (var student in unscheduled) - { - - @student.FirstName - @string.Join(", ", _solution.StudentUnassignedTeams(student).Select(e => e.ToString())) - - } - - } - - - - -
@@ -101,48 +142,51 @@ MudTable _solutionData; private TeamSchedulerSolution _solution; private TeamSchedulerOptions _parameters; - bool _isSolving = false; - private IEnumerable _requiredTeams = []; - - private Team[]? _possibleAdditions; - //private Team[] _requiredTeams = []; - - private async Task OnSelectedValuesChanged(IEnumerable obj) - { - await _solutionData.ReloadServerData(); - } + bool _isSolving; + private IEnumerable _scheduledTeams = []; + private IEnumerable _possibleAdditions = []; private async Task AddRegionals() { - _requiredTeams - = _teams.Where(e => e.Event.RegionalEvent).Concat(_requiredTeams).Distinct(); + _scheduledTeams + = _teams.Where(e => e.Event.RegionalEvent).Concat(_scheduledTeams).Distinct(); } private async Task AddHighLevelOfEffort() { - _requiredTeams - = _teams.Where(e => e.Event.LevelOfEffort >= 3).Concat(_requiredTeams).Distinct(); + _scheduledTeams + = _teams.Where(e => e.Event.LevelOfEffort >= 3).Concat(_scheduledTeams).Distinct(); } private async Task RemoveIndividual() { - _requiredTeams - = _requiredTeams.Where(t => t.Event.EventFormat != EventFormat.Individual); + _scheduledTeams + = _scheduledTeams.Where(t => t.Event.EventFormat != EventFormat.Individual); } private async Task RemoveLowLevelOfEffort() { - _requiredTeams - = _requiredTeams.Where(t => t.Event.LevelOfEffort > 1); + _scheduledTeams + = _scheduledTeams.Where(t => t.Event.LevelOfEffort > 1); } private async Task Invert() { - var rt = _requiredTeams.ToArray(); - _requiredTeams + var rt = _scheduledTeams.ToArray(); + _scheduledTeams = _teams.Where(t => !rt.Contains(t)); } + private void ToggleRequiredTeam(Team unassignedTeam) + { + if (_scheduledTeams.Contains(unassignedTeam)) + _scheduledTeams = _scheduledTeams.Where(t => t != unassignedTeam); + else + { + _scheduledTeams = _scheduledTeams.Concat(new[] { unassignedTeam }); + } + } + protected override async Task OnInitializedAsync() { _parameters = @@ -194,7 +238,7 @@ private async Task> SolveSchedule(TableState arg1, CancellationToken arg2) { _isSolving = true; - var teamScheduler = new TeamScheduler(_requiredTeams, _parameters.TimeSlots); + var teamScheduler = new TeamScheduler(_scheduledTeams, _parameters.TimeSlots); // teamScheduler // .ScheduleSeparate( @@ -210,12 +254,11 @@ var anyNotMeetingAlready = new UnassignedStudentScheduler(_teams, _solution.TimeSlots).ScheduleStrategy(UnassignedScheduleStrategy.AnyNotMeetingAlready); _possibleAdditions = loe; - if (_possibleAdditions.Length == 0) + if (!_possibleAdditions.Any()) _possibleAdditions = biggest; - if (_possibleAdditions.Length == 0) + if (!_possibleAdditions.Any()) _possibleAdditions = anyNotMeetingAlready; - - if (_possibleAdditions.Length == 0) + if (!_possibleAdditions.Any()) _possibleAdditions = individual; await InvokeAsync(StateHasChanged); // let the UI know that the solution has been found @@ -228,4 +271,57 @@ { _solutionData.ReloadServerData(); } + + async Task CopyToClipboard() + { + var sb = new StringBuilder(); + foreach (var timeslot in _solution.TimeSlots) + { + var overlaps + = TeamSchedulerSolution.GetStudentTeamOverlaps(timeslot).Select(e => e.Item1).ToArray(); + foreach (var scheduledTeam in timeslot.OrderBy(e => e.ToString())) + { + var t = scheduledTeam.ToString(); + var s = + string.Join(", ", + scheduledTeam.Students + .OrderBy(e => e == scheduledTeam.Captain) + .ThenBy(e => e.FirstName) + .Select(e => e.FirstName + (overlaps.Contains(e) ? "*": ""))); + + if (scheduledTeam.Event.EventFormat is EventFormat.Individual) + sb.Append(t); + else + sb.Append($"{t} - {s}"); + sb.Append(Environment.NewLine); + } + var unscheduled = TeamSchedulerSolution.GetStudentsNotInTimSlot(timeslot, _students); + + if (unscheduled.Any()) + { + sb.Append("--Unscheduled"); + sb.Append(Environment.NewLine); + foreach (var student in unscheduled) + { + var s = student.FirstName; + var unassignedTeams = _solution.StudentUnassignedTeams(student); + var t = string.Join(", ", unassignedTeams.Select(e => e.ToString())); + + sb.Append($"{s} - {t}"); + sb.Append(Environment.NewLine); + } + } + + sb.Append(Environment.NewLine); + } + + try + { + await ClipboardService.WriteTextAsync(sb.ToString()); + } + catch + { + Console.WriteLine("Cannot write text to clipboard"); + } + } } diff --git a/WebApp/Program.cs b/WebApp/Program.cs index adfc86f..494492f 100644 --- a/WebApp/Program.cs +++ b/WebApp/Program.cs @@ -1,6 +1,7 @@ using Data; using Microsoft.EntityFrameworkCore; using MudBlazor.Services; +using WebApp; using WebApp.Components; var builder = WebApplication.CreateBuilder(args); @@ -19,6 +20,11 @@ builder.Services.AddQuickGridEntityFrameworkAdapter(); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); +builder.Services.AddScoped(); + +builder.Services.AddScoped(); // Server- side +builder.Services.AddSingleton();//Client-side + var app = builder.Build(); // Configure the HTTP request pipeline.