diff --git a/WebApp/Components/Pages/MeetingSchedulePages/Index.razor b/WebApp/Components/Pages/MeetingSchedulePages/Index.razor index 1ddb944..709cb2a 100644 --- a/WebApp/Components/Pages/MeetingSchedulePages/Index.razor +++ b/WebApp/Components/Pages/MeetingSchedulePages/Index.razor @@ -25,23 +25,23 @@ - Add High Effort + Add High Effort - Add Regionals + Add Regionals - Remove Individual + Remove Individual - Remove Low Effort + Remove Low Effort - Invert + Invert - Reset + Reset Solve @@ -58,74 +58,22 @@ - + - - @context.Name - @foreach (var team in context.Teams.OrderBy(e => e.ToString())) - { - var removed = !_scheduledTeams.Contains(team); - - - - - @team - - @{var first = true;} - @foreach (var student in team.Students) - { - var overlap = context.StudentHasOverlaps(student); - var isAbsent = _absentStudents.Contains(student); - var color = overlap ? Color.Warning : Color.Default; - - @if(!first) {, } - {first = false;} - - @student.FirstName@(overlap ? "*" : "")@(isAbsent ? " (absent)" : "") - - } - - - } - + - @if (context.UnscheduledStudents.Any()) - { - Unscheduled - - @foreach (var student in context.UnscheduledStudents) - { - var isAbsent = _absentStudents.Contains(student); - - @student.FirstName@(isAbsent ? " (absent)" : "") - @{var first = true;} - @foreach (var unassignedTeam in _solution.StudentUnassignedTeams(student)) - { - var color = _possibleAdditions.Contains(unassignedTeam, new TeamIdComparer()) ? Color.Success : Color.Default; - var added = _scheduledTeams.Contains(unassignedTeam); - @if(!first) {, } - {first = false;} - - - - @unassignedTeam - - } - - } - - } + @@ -140,24 +88,10 @@ Label="Search for absent students" ShowFullName="true"/> - Scheduled Teams - - @foreach (var team in _teams.OrderBy(e => e.Event.Name)) - { - - - - @team.ToString() - - - - - } - + @@ -175,38 +109,38 @@ private IEnumerable _absentStudents = []; private IEnumerable _possibleAdditions = []; - private async Task AddRegionals() + private void AddRegionals() { _scheduledTeams = _teams.Where(e => e.Event.RegionalEvent).Concat(_scheduledTeams).Distinct(); } - private async Task AddHighLevelOfEffort() + private void AddHighLevelOfEffort() { _scheduledTeams = _teams.Where(e => e.Event.LevelOfEffort >= 3).Concat(_scheduledTeams).Distinct(); } - private async Task RemoveIndividual() + private void RemoveIndividual() { _scheduledTeams = _scheduledTeams.Where(t => t.Event.EventFormat != EventFormat.Individual); } - private async Task RemoveLowLevelOfEffort() + private void RemoveLowLevelOfEffort() { _scheduledTeams = _scheduledTeams.Where(t => t.Event.LevelOfEffort > 1); } - private async Task Invert() + private void Invert() { var rt = _scheduledTeams.ToArray(); _scheduledTeams = _teams.Where(t => !rt.Contains(t)); } - private async Task Reset() + private void Reset() { _scheduledTeams = []; } @@ -282,18 +216,19 @@ var teamScheduler = new TeamScheduler(_scheduledTeams, _parameters.TimeSlots, availableStudents); _solution = teamScheduler.Solve(); - var loe = new UnassignedStudentScheduler(_teams, _solution.TimeSlots).ScheduleStrategy(UnassignedScheduleStrategy.LevelOfEffort); - var biggest = new UnassignedStudentScheduler(_teams, _solution.TimeSlots).ScheduleStrategy(UnassignedScheduleStrategy.BiggestGroup); - var individual = new UnassignedStudentScheduler(_teams, _solution.TimeSlots).ScheduleStrategy(UnassignedScheduleStrategy.IndividualEvents); - var anyNotMeetingAlready = new UnassignedStudentScheduler(_teams, _solution.TimeSlots).ScheduleStrategy(UnassignedScheduleStrategy.AnyNotMeetingAlready); + // Try recommendation strategies in priority order + var scheduler = new UnassignedStudentScheduler(_teams, _solution.TimeSlots); + var strategies = new[] + { + UnassignedScheduleStrategy.LevelOfEffort, + UnassignedScheduleStrategy.BiggestGroup, + UnassignedScheduleStrategy.AnyNotMeetingAlready, + UnassignedScheduleStrategy.IndividualEvents + }; - _possibleAdditions = loe; - if (!_possibleAdditions.Any()) - _possibleAdditions = biggest; - if (!_possibleAdditions.Any()) - _possibleAdditions = anyNotMeetingAlready; - if (!_possibleAdditions.Any()) - _possibleAdditions = individual; + _possibleAdditions = strategies + .Select(strategy => scheduler.ScheduleStrategy(strategy)) + .FirstOrDefault(result => result.Any()) ?? []; await InvokeAsync(StateHasChanged); // let the UI know that the solution has been found @@ -311,40 +246,8 @@ 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.Teams.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 + (timeslot.StudentHasOverlaps(e) ? "*" : "") + (_absentStudents.Contains(e) ? " (absent)" : ""))); - - if (scheduledTeam.Event.EventFormat is EventFormat.Individual) - sb.Append(t); - else - sb.Append($"{t} - {s}"); - sb.Append(Environment.NewLine); - } - - if (timeslot.UnscheduledStudents.Any()) - { - sb.Append("--Unscheduled"); - sb.Append(Environment.NewLine); - foreach (var student in timeslot.UnscheduledStudents) - { - var s = student.FirstName + (_absentStudents.Contains(student) ? " (absent)" : ""); - var unassignedTeams = _solution.StudentUnassignedTeams(student); - var t = string.Join(", ", unassignedTeams.Select(e => e.ToString())); - - sb.Append($"{s} - {t}"); - sb.Append(Environment.NewLine); - } - } - + AppendScheduledTeams(sb, timeslot); + AppendUnscheduledStudents(sb, timeslot); sb.Append(Environment.NewLine); } @@ -358,4 +261,64 @@ } } + private void AppendScheduledTeams(StringBuilder sb, TeamScheduleTimeSlot timeslot) + { + foreach (var scheduledTeam in timeslot.Teams.OrderBy(e => e.ToString())) + { + var teamName = scheduledTeam.ToString(); + + if (scheduledTeam.Event.EventFormat is EventFormat.Individual) + { + sb.Append(teamName); + } + else + { + var studentsList = FormatStudentList(scheduledTeam, timeslot); + sb.Append($"{teamName} - {studentsList}"); + } + sb.Append(Environment.NewLine); + } + } + + private string FormatStudentList(Team team, TeamScheduleTimeSlot timeslot) + { + return string.Join(", ", + team.Students + .OrderBy(e => e == team.Captain) + .ThenBy(e => e.FirstName) + .Select(e => FormatStudentName(e, timeslot))); + } + + private string FormatStudentName(Student student, TeamScheduleTimeSlot timeslot) + { + var name = student.FirstName; + if (timeslot.StudentHasOverlaps(student)) + name += "*"; + if (_absentStudents.Contains(student)) + name += " (absent)"; + return name; + } + + private void AppendUnscheduledStudents(StringBuilder sb, TeamScheduleTimeSlot timeslot) + { + if (!timeslot.UnscheduledStudents.Any()) + return; + + sb.Append("--Unscheduled"); + sb.Append(Environment.NewLine); + + foreach (var student in timeslot.UnscheduledStudents) + { + var studentName = student.FirstName; + if (_absentStudents.Contains(student)) + studentName += " (absent)"; + + var unassignedTeams = _solution.StudentUnassignedTeams(student); + var teamsList = string.Join(", ", unassignedTeams.Select(e => e.ToString())); + + sb.Append($"{studentName} - {teamsList}"); + sb.Append(Environment.NewLine); + } + } + } \ No newline at end of file diff --git a/WebApp/Components/Pages/MeetingSchedulePages/ScheduledTeamsList.razor b/WebApp/Components/Pages/MeetingSchedulePages/ScheduledTeamsList.razor new file mode 100644 index 0000000..618cf61 --- /dev/null +++ b/WebApp/Components/Pages/MeetingSchedulePages/ScheduledTeamsList.razor @@ -0,0 +1,62 @@ +@using Core.Calculation + + + @TimeSlotName + @foreach (var team in Teams.OrderBy(e => e.ToString())) + { + var removed = !ScheduledTeams.Contains(team); + + + + + @team - + @foreach (var student in team.Students) + { + var overlap = StudentHasOverlaps(student); + var isAbsent = AbsentStudents.Contains(student); + var color = overlap ? Color.Warning : Color.Default; + var suffix = GetStudentSuffix(overlap, isAbsent); + + if (student != team.Students.First()) + { + , + } + + @student.FirstName@suffix + + } + + } + + +@code { + [Parameter] + public string TimeSlotName { get; set; } = string.Empty; + + [Parameter] + public IEnumerable Teams { get; set; } = []; + + [Parameter] + public IEnumerable ScheduledTeams { get; set; } = []; + + [Parameter] + public IEnumerable AbsentStudents { get; set; } = []; + + [Parameter] + public Func StudentHasOverlaps { get; set; } = null!; + + [Parameter] + public EventCallback OnToggleTeam { get; set; } + + private string GetStudentSuffix(bool overlap, bool isAbsent) + { + var suffix = overlap ? "*" : ""; + suffix += isAbsent ? " (absent)" : ""; + return suffix; + } +} diff --git a/WebApp/Components/Pages/MeetingSchedulePages/UnscheduledStudentsList.razor b/WebApp/Components/Pages/MeetingSchedulePages/UnscheduledStudentsList.razor new file mode 100644 index 0000000..8d38ce6 --- /dev/null +++ b/WebApp/Components/Pages/MeetingSchedulePages/UnscheduledStudentsList.razor @@ -0,0 +1,57 @@ +@using Core.Calculation + +@if (UnscheduledStudents.Any()) +{ + Unscheduled + + @foreach (var student in UnscheduledStudents) + { + var isAbsent = AbsentStudents.Contains(student); + + + @student.FirstName@(isAbsent ? " (absent)" : "") + + @foreach (var unassignedTeam in UnassignedTeams(student)) + { + var isPossibleAddition = PossibleAdditions.Contains(unassignedTeam, new TeamIdComparer()); + var isScheduled = ScheduledTeams.Contains(unassignedTeam); + var color = isPossibleAddition ? Color.Success : Color.Default; + + if (unassignedTeam != UnassignedTeams(student).First()) + { + , + } + + + + @unassignedTeam + + } + + } + +} + +@code { + [Parameter] + public IEnumerable UnscheduledStudents { get; set; } = []; + + [Parameter] + public IEnumerable AbsentStudents { get; set; } = []; + + [Parameter] + public IEnumerable ScheduledTeams { get; set; } = []; + + [Parameter] + public IEnumerable PossibleAdditions { get; set; } = []; + + [Parameter] + public Func> UnassignedTeams { get; set; } = null!; + + [Parameter] + public EventCallback OnToggleTeam { get; set; } +} diff --git a/WebApp/Components/TeamToggleSelector.razor b/WebApp/Components/TeamToggleSelector.razor new file mode 100644 index 0000000..3439d2d --- /dev/null +++ b/WebApp/Components/TeamToggleSelector.razor @@ -0,0 +1,49 @@ +@if (Title != null) +{ + @Title +} + + + @foreach (var team in Teams.OrderBy(e => e.Event.Name)) + { + + + + @team.ToString() + @if (ShowEventAttributes) + { + + } + + + + } + + +@code { + [Parameter] + public IEnumerable Teams { get; set; } = []; + + [Parameter] + public IEnumerable SelectedTeams { get; set; } = []; + + [Parameter] + public EventCallback> SelectedTeamsChanged { get; set; } + + [Parameter] + public string? Title { get; set; } + + [Parameter] + public bool ShowEventAttributes { get; set; } = true; + + private async Task OnSelectedTeamsChanged(IEnumerable value) + { + SelectedTeams = value; + await SelectedTeamsChanged.InvokeAsync(value); + } +}