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); + } +}