diff --git a/WebApp/Components/Features/MeetingSchedule/Index.razor b/WebApp/Components/Features/MeetingSchedule/Index.razor
index 17de2ad..0581bd0 100644
--- a/WebApp/Components/Features/MeetingSchedule/Index.razor
+++ b/WebApp/Components/Features/MeetingSchedule/Index.razor
@@ -46,7 +46,13 @@
Reset
- Solve
+
+ Solve
+
@@ -120,75 +126,75 @@
private IEnumerable _extendedTeams = [];
// Key: (teamId, timeSlotIndex, studentId) - Value: true if excluded
private Dictionary<(int teamId, int timeSlotIndex, int studentId), bool> _excludedStudents = new();
+ // Track last saved state for dirty/clean comparison
+ private MeetingScheduleState? _lastSavedState;
private async Task OnScheduledTeamsChanged(IEnumerable teams)
{
_scheduledTeams = teams;
- await SaveScheduledTeams();
+ StateHasChanged();
}
private async Task OnAbsentStudentsChanged(IEnumerable students)
{
_absentStudents = students;
- await SaveAbsentStudents();
+ StateHasChanged();
}
private async Task OnTimeSlotCountChanged(int timeSlots)
{
_parameters.TimeSlots = timeSlots;
- await SaveTimeSlotCount();
+ StateHasChanged();
}
private async Task OnExtendedTeamsChanged(IEnumerable teams)
{
_extendedTeams = teams;
- await SaveExtendedTeams();
+ StateHasChanged();
}
- private async void AddRegionals()
+ private void AddRegionals()
{
_scheduledTeams
= _teams.Where(e => e.Event.RegionalEvent).Concat(_scheduledTeams).Distinct();
- await SaveScheduledTeams();
+ StateHasChanged();
}
- private async void AddHighLevelOfEffort()
+ private void AddHighLevelOfEffort()
{
_scheduledTeams
= _teams.Where(e => e.Event.LevelOfEffort >= 3).Concat(_scheduledTeams).Distinct();
- await SaveScheduledTeams();
+ StateHasChanged();
}
- private async void RemoveIndividual()
+ private void RemoveIndividual()
{
_scheduledTeams
= _scheduledTeams.Where(t => t.Event.EventFormat != EventFormat.Individual);
- await SaveScheduledTeams();
+ StateHasChanged();
}
- private async void RemoveLowLevelOfEffort()
+ private void RemoveLowLevelOfEffort()
{
_scheduledTeams
= _scheduledTeams.Where(t => t.Event.LevelOfEffort > 1);
- await SaveScheduledTeams();
+ StateHasChanged();
}
- private async void Invert()
+ private void Invert()
{
var rt = _scheduledTeams.ToArray();
_scheduledTeams
= _teams.Where(t => !rt.Contains(t));
- await SaveScheduledTeams();
+ StateHasChanged();
}
- private async void Reset()
+ private void Reset()
{
_scheduledTeams = [];
_extendedTeams = [];
_excludedStudents.Clear();
- await SaveScheduledTeams();
- await SaveExtendedTeams();
- await SaveExcludedStudents();
+ StateHasChanged();
}
private async Task ToggleRequiredTeam(Team unassignedTeam)
@@ -264,6 +270,19 @@
await LoadTimeSlotCount();
await LoadExtendedTeams();
await LoadExcludedStudents();
+
+ // Initialize last saved state from loaded values
+ _lastSavedState = await MeetingScheduleState.FromLocalStorage(LocalStorage, _teams, _students);
+ if (_lastSavedState == null)
+ {
+ // If no saved state exists, create initial state from current values
+ _lastSavedState = MeetingScheduleState.FromCurrent(
+ _scheduledTeams,
+ _absentStudents,
+ _parameters.TimeSlots,
+ _extendedTeams,
+ _excludedStudents);
+ }
}
private async Task SaveScheduledTeams()
@@ -368,7 +387,6 @@
{
_excludedStudents[key] = true;
}
- await SaveExcludedStudents();
StateHasChanged();
}
@@ -409,6 +427,21 @@
private record ExcludedStudent(int TeamId, int TimeSlotIndex, int StudentId);
+ private bool IsDirty()
+ {
+ if (_lastSavedState == null)
+ return true;
+
+ var currentState = MeetingScheduleState.FromCurrent(
+ _scheduledTeams,
+ _absentStudents,
+ _parameters.TimeSlots,
+ _extendedTeams,
+ _excludedStudents);
+
+ return !currentState.Equals(_lastSavedState);
+ }
+
private async Task> SolveSchedule(TableState arg1, CancellationToken arg2)
{
_isSolving = true;
@@ -516,6 +549,16 @@
.Select(strategy => scheduler.ScheduleStrategy(strategy))
.FirstOrDefault(result => result.Any()) ?? [];
+ // Save state to localStorage after solving completes successfully
+ var currentState = MeetingScheduleState.FromCurrent(
+ _scheduledTeams,
+ _absentStudents,
+ _parameters.TimeSlots,
+ _extendedTeams,
+ _excludedStudents);
+ await currentState.SaveToLocalStorage(LocalStorage);
+ _lastSavedState = currentState;
+
await InvokeAsync(StateHasChanged); // let the UI know that the solution has been found
_isSolving = false;
@@ -726,4 +769,113 @@
}
}
+ private class MeetingScheduleState : IEquatable
+ {
+ public HashSet ScheduledTeamIds { get; set; } = [];
+ public HashSet AbsentStudentIds { get; set; } = [];
+ public int TimeSlotCount { get; set; }
+ public HashSet ExtendedTeamIds { get; set; } = [];
+ public HashSet<(int teamId, int timeSlotIndex, int studentId)> ExcludedStudents { get; set; } = [];
+
+ public bool Equals(MeetingScheduleState? other)
+ {
+ if (other == null) return false;
+ return ScheduledTeamIds.SetEquals(other.ScheduledTeamIds) &&
+ AbsentStudentIds.SetEquals(other.AbsentStudentIds) &&
+ TimeSlotCount == other.TimeSlotCount &&
+ ExtendedTeamIds.SetEquals(other.ExtendedTeamIds) &&
+ ExcludedStudents.SetEquals(other.ExcludedStudents);
+ }
+
+ public override bool Equals(object? obj) => Equals(obj as MeetingScheduleState);
+
+ public override int GetHashCode()
+ {
+ var hash = new HashCode();
+ hash.Add(ScheduledTeamIds.Count);
+ foreach (var id in ScheduledTeamIds.OrderBy(x => x))
+ hash.Add(id);
+ hash.Add(AbsentStudentIds.Count);
+ foreach (var id in AbsentStudentIds.OrderBy(x => x))
+ hash.Add(id);
+ hash.Add(TimeSlotCount);
+ hash.Add(ExtendedTeamIds.Count);
+ foreach (var id in ExtendedTeamIds.OrderBy(x => x))
+ hash.Add(id);
+ hash.Add(ExcludedStudents.Count);
+ foreach (var key in ExcludedStudents.OrderBy(x => x))
+ hash.Add(key);
+ return hash.ToHashCode();
+ }
+
+ public static MeetingScheduleState FromCurrent(
+ IEnumerable scheduledTeams,
+ IEnumerable absentStudents,
+ int timeSlotCount,
+ IEnumerable extendedTeams,
+ Dictionary<(int teamId, int timeSlotIndex, int studentId), bool> excludedStudents)
+ {
+ return new MeetingScheduleState
+ {
+ ScheduledTeamIds = scheduledTeams.Select(t => t.Id).ToHashSet(),
+ AbsentStudentIds = absentStudents.Select(s => s.Id).ToHashSet(),
+ TimeSlotCount = timeSlotCount,
+ ExtendedTeamIds = extendedTeams.Select(t => t.Id).ToHashSet(),
+ ExcludedStudents = excludedStudents.Keys
+ .Where(k => excludedStudents[k])
+ .ToHashSet()
+ };
+ }
+
+ public static async Task FromLocalStorage(
+ LocalStorageService localStorage,
+ Team[] allTeams,
+ Student[] allStudents)
+ {
+ // Load scheduled teams
+ var scheduledTeamIds = await localStorage.GetIntArrayAsync("MeetingSchedule_ScheduledTeams");
+ var absentStudentIds = await localStorage.GetIntArrayAsync("MeetingSchedule_AbsentStudents");
+ var timeSlotCount = await localStorage.GetIntAsync("MeetingSchedule_TimeSlotCount", defaultValue: 2);
+ var extendedTeamIds = await localStorage.GetIntArrayAsync("MeetingSchedule_ExtendedTeams");
+ var exclusions = await localStorage.GetJsonAsync("MeetingSchedule_ExcludedStudents");
+
+ // If no state exists, return null
+ if (scheduledTeamIds.Length == 0 && absentStudentIds.Length == 0 &&
+ timeSlotCount == 2 && extendedTeamIds.Length == 0 &&
+ (exclusions == null || exclusions.Length == 0))
+ {
+ return null;
+ }
+
+ var excludedStudentsSet = new HashSet<(int teamId, int timeSlotIndex, int studentId)>();
+ if (exclusions != null && exclusions.Length > 0)
+ {
+ foreach (var exclusion in exclusions)
+ {
+ excludedStudentsSet.Add((exclusion.TeamId, exclusion.TimeSlotIndex, exclusion.StudentId));
+ }
+ }
+
+ return new MeetingScheduleState
+ {
+ ScheduledTeamIds = scheduledTeamIds.ToHashSet(),
+ AbsentStudentIds = absentStudentIds.ToHashSet(),
+ TimeSlotCount = timeSlotCount > 0 ? timeSlotCount : 2,
+ ExtendedTeamIds = extendedTeamIds.ToHashSet(),
+ ExcludedStudents = excludedStudentsSet
+ };
+ }
+
+ public async Task SaveToLocalStorage(LocalStorageService localStorage)
+ {
+ await localStorage.SetIntArrayAsync("MeetingSchedule_ScheduledTeams", ScheduledTeamIds.ToArray());
+ await localStorage.SetIntArrayAsync("MeetingSchedule_AbsentStudents", AbsentStudentIds.ToArray());
+ await localStorage.SetIntAsync("MeetingSchedule_TimeSlotCount", TimeSlotCount);
+ await localStorage.SetIntArrayAsync("MeetingSchedule_ExtendedTeams", ExtendedTeamIds.ToArray());
+
+ var exclusions = ExcludedStudents.Select(e => new ExcludedStudent(e.teamId, e.timeSlotIndex, e.studentId)).ToArray();
+ await localStorage.SetJsonAsync("MeetingSchedule_ExcludedStudents", exclusions);
+ }
+ }
+
}
\ No newline at end of file