From 160161022687e5809d5356274d2be1a30b7bbdfd Mon Sep 17 00:00:00 2001 From: James Kolpack Date: Tue, 13 Jan 2026 09:32:45 -0500 Subject: [PATCH] Implement TeamMeetingToggleSelector component and extend team management functionality This commit introduces the TeamMeetingToggleSelector component, which allows for the selection and management of teams within the meeting schedule. The Index.razor component has been updated to utilize this new selector, enhancing the user interface for managing scheduled and extended teams. Additionally, new methods for saving and loading extended teams have been added, improving the overall functionality and user experience in team scheduling. These changes contribute to better organization and management of team events in the application. --- .../Features/MeetingSchedule/Index.razor | 98 +++++++++++++++- .../TeamMeetingToggleSelector.razor | 108 ++++++++++++++++++ 2 files changed, 201 insertions(+), 5 deletions(-) create mode 100644 WebApp/Components/Features/Teams/Components/TeamMeetingToggleSelector.razor diff --git a/WebApp/Components/Features/MeetingSchedule/Index.razor b/WebApp/Components/Features/MeetingSchedule/Index.razor index cbc8755..57daf69 100644 --- a/WebApp/Components/Features/MeetingSchedule/Index.razor +++ b/WebApp/Components/Features/MeetingSchedule/Index.razor @@ -91,11 +91,13 @@ Label="Search for absent students" ShowFullName="true"/> - + @@ -112,6 +114,7 @@ private IEnumerable _scheduledTeams = []; private IEnumerable _absentStudents = []; private IEnumerable _possibleAdditions = []; + private IEnumerable _extendedTeams = []; private async Task OnScheduledTeamsChanged(IEnumerable teams) { @@ -131,6 +134,12 @@ await SaveTimeSlotCount(); } + private async Task OnExtendedTeamsChanged(IEnumerable teams) + { + _extendedTeams = teams; + await SaveExtendedTeams(); + } + private async void AddRegionals() { _scheduledTeams @@ -240,6 +249,7 @@ await LoadScheduledTeams(); await LoadAbsentStudents(); await LoadTimeSlotCount(); + await LoadExtendedTeams(); } private async Task SaveScheduledTeams() @@ -286,6 +296,21 @@ } } + private async Task SaveExtendedTeams() + { + var teamIds = _extendedTeams.Select(t => t.Id).ToArray(); + await LocalStorage.SetIntArrayAsync("MeetingSchedule_ExtendedTeams", teamIds); + } + + private async Task LoadExtendedTeams() + { + var teamIds = await LocalStorage.GetIntArrayAsync("MeetingSchedule_ExtendedTeams"); + if (teamIds.Length > 0) + { + _extendedTeams = _teams.Where(t => teamIds.Contains(t.Id)).ToArray(); + } + } + private async Task> SolveSchedule(TableState arg1, CancellationToken arg2) { _isSolving = true; @@ -311,10 +336,22 @@ // Update parameters with absent student names _parameters.AbsentStudents = _absentStudents.Select(s => s.FirstNameLastName).ToArray(); + + // Update parameters with extended team event names + _parameters.ExtendedTeams = _extendedTeams + .Where(t => t.Event != null) + .Select(t => t.Event!.Name) + .ToArray(); var teamScheduler = new TeamScheduler(_scheduledTeams, _parameters.TimeSlots, availableStudents); _solution = teamScheduler.Solve(); + // Post-process: extend teams to next consecutive time slot + if (_extendedTeams.Any()) + { + ExtendTeamsInSolution(_solution, _extendedTeams, availableStudents); + } + // Try recommendation strategies in priority order var scheduler = new UnassignedStudentScheduler(_teams, _solution.TimeSlots); UnassignedScheduleStrategy[] strategies = @@ -340,6 +377,57 @@ _solutionData.ReloadServerData(); } + private void ExtendTeamsInSolution(TeamSchedulerSolution solution, IEnumerable extendedTeams, Student[] allStudents) + { + if (solution.TimeSlots == null || !solution.TimeSlots.Any()) + return; + + var extendedTeamsList = extendedTeams.ToList(); + if (!extendedTeamsList.Any()) + return; + + var extendedTeamIds = extendedTeamsList.Select(t => t.Id).ToHashSet(); + + // Find which time slot each extended team is in + for (int slotIndex = 0; slotIndex < solution.TimeSlots.Length; slotIndex++) + { + var currentSlot = solution.TimeSlots[slotIndex]; + var teamsToExtend = currentSlot.Teams.Where(t => extendedTeamIds.Contains(t.Id)).ToList(); + + if (!teamsToExtend.Any()) + continue; + + // Check if there's a next time slot + if (slotIndex + 1 >= solution.TimeSlots.Length) + { + // Cannot extend - this is the last time slot + continue; + } + + var nextSlot = solution.TimeSlots[slotIndex + 1]; + + // Add extended teams to the next time slot + var nextSlotTeamsList = nextSlot.Teams.ToList(); + var nextSlotTeamIds = nextSlotTeamsList.Select(t => t.Id).ToHashSet(); + + foreach (var team in teamsToExtend) + { + if (!nextSlotTeamIds.Contains(team.Id)) + { + nextSlotTeamsList.Add(team); + nextSlotTeamIds.Add(team.Id); + } + } + + // Update the next slot with the extended teams + nextSlot.Teams = nextSlotTeamsList.ToArray(); + + // Recalculate overlaps and unscheduled students for the next slot + nextSlot.StudentOverlaps = TeamSchedulerSolution.GetStudentTeamOverlaps(nextSlot.Teams); + nextSlot.UnscheduledStudents = TeamSchedulerSolution.GetStudentsNotInTimSlot(nextSlot.Teams, allStudents); + } + } + async Task CopyToClipboard() { var sb = new StringBuilder(); diff --git a/WebApp/Components/Features/Teams/Components/TeamMeetingToggleSelector.razor b/WebApp/Components/Features/Teams/Components/TeamMeetingToggleSelector.razor new file mode 100644 index 0000000..6393b28 --- /dev/null +++ b/WebApp/Components/Features/Teams/Components/TeamMeetingToggleSelector.razor @@ -0,0 +1,108 @@ +@using WebApp.Models +@using Core.Utility + +@if (Title != null) +{ + @Title +} + + + @foreach (var team in Teams.OrderByEventFormatFirst().ThenBy(e => e.Event.Name)) + { + +
+ + @team.ToString() + + @if (IsSelected(team)) + { + var isExtended = IsExtended(team); + + + + } + @if (ShowEventAttributes) + { + + } +
+
+ } +
+ +@code { + [Parameter] + public IEnumerable Teams { get; set; } = []; + + [Parameter] + public IEnumerable SelectedTeams { get; set; } = []; + + [Parameter] + public EventCallback> SelectedTeamsChanged { get; set; } + + [Parameter] + public IEnumerable ExtendedTeams { get; set; } = []; + + [Parameter] + public EventCallback> ExtendedTeamsChanged { get; set; } + + [Parameter] + public string? Title { get; set; } + + [Parameter] + public bool ShowEventAttributes { get; set; } = true; + + private bool IsSelected(Team team) + { + var selectedTeamIds = SelectedTeams.Select(t => t.Id).ToHashSet(); + return selectedTeamIds.Contains(team.Id); + } + + private bool IsExtended(Team team) + { + var extendedTeamIds = ExtendedTeams.Select(t => t.Id).ToHashSet(); + return extendedTeamIds.Contains(team.Id); + } + + private async Task OnSelectedTeamsChanged(IEnumerable value) + { + SelectedTeams = value; + await SelectedTeamsChanged.InvokeAsync(value); + } + + private async Task ToggleExtended(Team team) + { + var extendedTeamIds = ExtendedTeams.Select(t => t.Id).ToHashSet(); + IEnumerable newExtendedTeams; + + if (extendedTeamIds.Contains(team.Id)) + { + // Remove from extended teams + newExtendedTeams = ExtendedTeams.Where(t => t.Id != team.Id); + } + else + { + // Add to extended teams + newExtendedTeams = ExtendedTeams.Concat([team]); + } + + ExtendedTeams = newExtendedTeams; + await ExtendedTeamsChanged.InvokeAsync(newExtendedTeams); + } +}