From a503655f979212792364176dd40ea95c27336e39 Mon Sep 17 00:00:00 2001 From: James Kolpack Date: Sun, 25 Jan 2026 18:58:02 -0500 Subject: [PATCH] Enhance MeetingSchedule and MeetingHistoryDetailDialog with load into planner functionality This commit adds a new feature to the MeetingSchedule and MeetingHistoryDetailDialog components, allowing users to load meeting details into the planner. The LoadMeetingIntoPlanner method is implemented in both components, which retrieves team and student data, calculates absent students, and saves the state to localStorage before navigating to the planner. Additionally, a confirmation dialog is introduced in the SaveMeetingHistoryDialog to handle overwriting existing meeting histories, improving user experience and data management in the meeting scheduling feature. --- .../Features/MeetingSchedule/History.razor | 67 +++++++++++++++++++ .../MeetingHistoryDetailDialog.razor | 60 +++++++++++++++++ .../SaveMeetingHistoryDialog.razor | 39 ++++++++++- 3 files changed, 164 insertions(+), 2 deletions(-) diff --git a/WebApp/Components/Features/MeetingSchedule/History.razor b/WebApp/Components/Features/MeetingSchedule/History.razor index bafde1c..6d34354 100644 --- a/WebApp/Components/Features/MeetingSchedule/History.razor +++ b/WebApp/Components/Features/MeetingSchedule/History.razor @@ -9,6 +9,9 @@ @inject IDialogService DialogService @inject ISnackbar Snackbar @inject IConfiguration Configuration +@inject IMeetingScheduleDataService DataService +@inject IMeetingScheduleStateService StateService +@inject NavigationManager NavigationManager @implements IAsyncDisposable @@ -40,6 +43,7 @@ Breakpoint="Breakpoint.Sm"> + @foreach (var team in _allTeams) { @@ -51,6 +55,9 @@ Date + + Actions + @{ var teamIndex = 0; } @@ -77,6 +84,15 @@ @context.MeetingDate.ToString("MM/dd/yy") + + + + + @{ var rowTeamIndex = 0; } @@ -99,6 +115,7 @@ Times Met + @{ var footerTeamIndex = 0; } @@ -268,6 +285,56 @@ } } + private async Task LoadMeetingIntoPlanner(TeamMeetingHistory history) + { + if (_isDisposed) return; + + try + { + // Get all teams and students from database + var allTeams = await DataService.LoadTeamsAsync(); + var allStudents = await DataService.LoadStudentsAsync(); + + // Match teams from history to all teams by ID for reference equality + var historyTeamIds = history.Teams.Select(t => t.Id).ToHashSet(); + var scheduledTeams = allTeams.Where(t => historyTeamIds.Contains(t.Id)); + + // Calculate absent students (all students not in the meeting history's student list) + var presentStudentIds = history.Students.Select(s => s.Id).ToHashSet(); + var absentStudents = allStudents.Where(s => !presentStudentIds.Contains(s.Id)); + + // Save state to localStorage + await StateService.SaveScheduledTeamsAsync(scheduledTeams); + await StateService.SaveAbsentStudentsAsync(absentStudents); + // Clear extended teams and excluded students when loading from history + await StateService.SaveExtendedTeamsAsync([]); + await StateService.SaveExcludedStudentsAsync(new Dictionary<(int teamId, int timeSlotIndex, int studentId), bool>()); + + // Navigate to planner + NavigationManager.NavigateTo("/meeting-schedule"); + + if (!_isDisposed) + { + Snackbar.Add($"Loaded meeting from {history.MeetingDate:MM/dd/yyyy} into planner", Severity.Success); + } + } + catch (TaskCanceledException) + { + // Component was disposed, ignore + } + catch (JSDisconnectedException) + { + // JS connection lost, ignore + } + catch (Exception ex) + { + if (!_isDisposed) + { + Snackbar.Add($"Error loading meeting into planner: {ex.Message}", Severity.Error); + } + } + } + public async ValueTask DisposeAsync() { if (!_isDisposed) diff --git a/WebApp/Components/Features/MeetingSchedule/MeetingHistoryDetailDialog.razor b/WebApp/Components/Features/MeetingSchedule/MeetingHistoryDetailDialog.razor index 863cc64..8aec18f 100644 --- a/WebApp/Components/Features/MeetingSchedule/MeetingHistoryDetailDialog.razor +++ b/WebApp/Components/Features/MeetingSchedule/MeetingHistoryDetailDialog.razor @@ -11,6 +11,8 @@ @inject ISnackbar Snackbar @inject IDialogService DialogService @inject IMeetingScheduleDataService DataService +@inject IMeetingScheduleStateService StateService +@inject NavigationManager NavigationManager @implements IAsyncDisposable @@ -128,6 +130,13 @@ Delete + + Load into Planner + t.Id).ToHashSet(); + var scheduledTeams = allTeams.Where(t => historyTeamIds.Contains(t.Id)); + + // Calculate absent students (all students not in the meeting history's student list) + var presentStudentIds = _meetingHistory.Students.Select(s => s.Id).ToHashSet(); + var absentStudents = allStudents.Where(s => !presentStudentIds.Contains(s.Id)); + + // Save state to localStorage + await StateService.SaveScheduledTeamsAsync(scheduledTeams); + await StateService.SaveAbsentStudentsAsync(absentStudents); + // Clear extended teams and excluded students when loading from history + await StateService.SaveExtendedTeamsAsync([]); + await StateService.SaveExcludedStudentsAsync(new Dictionary<(int teamId, int timeSlotIndex, int studentId), bool>()); + + // Close dialog and navigate to planner + MudDialog.Close(); + NavigationManager.NavigateTo("/meeting-schedule"); + + if (!_isDisposed) + { + Snackbar.Add($"Loaded meeting from {_meetingHistory.MeetingDate:MM/dd/yyyy} into planner", Severity.Success); + } + } + catch (TaskCanceledException) + { + // Component was disposed, ignore + } + catch (JSDisconnectedException) + { + // JS connection lost, ignore + } + catch (Exception ex) + { + if (!_isDisposed) + { + Snackbar.Add($"Error loading meeting into planner: {ex.Message}", Severity.Error); + } + } + } + private void Close() { if (_isDisposed) return; diff --git a/WebApp/Components/Features/MeetingSchedule/SaveMeetingHistoryDialog.razor b/WebApp/Components/Features/MeetingSchedule/SaveMeetingHistoryDialog.razor index 0c15ef1..2a4bde2 100644 --- a/WebApp/Components/Features/MeetingSchedule/SaveMeetingHistoryDialog.razor +++ b/WebApp/Components/Features/MeetingSchedule/SaveMeetingHistoryDialog.razor @@ -11,6 +11,7 @@ @inject INotesService NotesService @inject INoteNamingService NoteNamingService @inject ISnackbar Snackbar +@inject IDialogService DialogService @implements IAsyncDisposable @@ -256,8 +257,42 @@ var existingMeetings = await TeamMeetingHistoryService.GetMeetingHistoriesAsync(dateOnly, dateOnly); if (existingMeetings.Any()) { - Snackbar.Add($"A meeting history already exists for {dateOnly:MM/dd/yyyy}. Only one meeting per day is allowed.", Severity.Warning); - return; + // Show confirmation dialog + var confirmResult = await DialogService.ShowMessageBox( + "Overwrite Meeting?", + "A meeting already exists for this date. Overwrite it?", + yesText: "Overwrite", + cancelText: "Cancel"); + + if (confirmResult != true) + { + return; // User cancelled + } + + // User confirmed, switch to edit mode + var existingMeeting = existingMeetings.First(); + _isEditMode = true; + MeetingHistoryId = existingMeeting.Id; + + // Load existing meeting data + var existingHistory = await TeamMeetingHistoryService.GetMeetingHistoryAsync(existingMeeting.Id); + if (existingHistory != null) + { + // Match teams by ID to ensure reference equality with MudToggleGroup + var selectedTeamIds = existingHistory.Teams.Select(t => t.Id).ToHashSet(); + _selectedTeams = AllTeams.Where(t => selectedTeamIds.Contains(t.Id)); + + // Match students by ID to ensure reference equality with MudToggleGroup + var selectedStudentIds = existingHistory.Students.Select(s => s.Id).ToHashSet(); + _selectedStudents = AllStudents.Where(s => selectedStudentIds.Contains(s.Id)); + + // Load existing note if available + var existingNote = await TeamMeetingHistoryService.GetMeetingNoteAsync(existingHistory.MeetingDate); + if (existingNote != null) + { + _noteContent = existingNote.Content ?? ""; + } + } } }