From 455be30821fd60e2af0fa61568f92c715952d090 Mon Sep 17 00:00:00 2001 From: James Kolpack Date: Tue, 20 Jan 2026 21:55:38 -0500 Subject: [PATCH] Enhance MeetingHistoryDetailDialog and SaveMeetingHistoryDialog with navigation and editing features This commit introduces navigation buttons for previous and next meeting histories in the MeetingHistoryDetailDialog, improving user experience by allowing easy access to related meetings. Additionally, the SaveMeetingHistoryDialog is updated to support editing existing meeting histories, with logic to load existing data and prevent duplicate entries for the same date. The integration of TeamToggleSelector and StudentToggleSelector components streamlines team and student selection, enhancing the overall functionality of the meeting history management feature. --- .../MeetingHistoryDetailDialog.razor | 156 ++++++++++++++++- .../SaveMeetingHistoryDialog.razor | 160 +++++++++++++----- 2 files changed, 268 insertions(+), 48 deletions(-) diff --git a/WebApp/Components/Features/MeetingSchedule/MeetingHistoryDetailDialog.razor b/WebApp/Components/Features/MeetingSchedule/MeetingHistoryDetailDialog.razor index d127d3d..863cc64 100644 --- a/WebApp/Components/Features/MeetingSchedule/MeetingHistoryDetailDialog.razor +++ b/WebApp/Components/Features/MeetingSchedule/MeetingHistoryDetailDialog.razor @@ -10,6 +10,7 @@ @inject INoteNamingService NoteNamingService @inject ISnackbar Snackbar @inject IDialogService DialogService +@inject IMeetingScheduleDataService DataService @implements IAsyncDisposable @@ -25,7 +26,28 @@ else { - Meeting Details + @* Navigation Header *@ + + + + + + + @_meetingHistory.MeetingDate.ToString("MM/dd/yyyy") + + + + + + @@ -106,6 +128,13 @@ Delete + + Edit + Close } else @@ -127,9 +156,13 @@ private Note? _meetingNote; private bool _isLoading = true; private bool _isDeleting = false; + private Team[] _allTeams = []; + private Student[] _allStudents = []; private CancellationTokenSource? _cancellationTokenSource; private bool _isDisposed = false; private bool IsActionDisabled => _isLoading || _isDeleting; + private TeamMeetingHistory? _previousMeetingHistory; + private TeamMeetingHistory? _nextMeetingHistory; protected override void OnInitialized() { @@ -138,6 +171,9 @@ protected override async Task OnInitializedAsync() { + // Load all teams and students for the edit dialog + _allTeams = await DataService.LoadTeamsAsync(); + _allStudents = await DataService.LoadStudentsAsync(); await LoadMeetingHistory(); } @@ -153,6 +189,9 @@ if (_meetingHistory != null) { _meetingNote = await TeamMeetingHistoryService.GetMeetingNoteAsync(_meetingHistory.MeetingDate); + + // Load all meeting histories to find previous/next + await LoadNavigationMeetings(); } } catch (TaskCanceledException) @@ -180,6 +219,91 @@ } } + private async Task LoadNavigationMeetings() + { + if (_isDisposed || _meetingHistory == null) return; + + try + { + // Get all meeting histories ordered by date + var allMeetings = (await TeamMeetingHistoryService.GetMeetingHistoriesAsync()) + .OrderBy(m => m.MeetingDate) + .ThenBy(m => m.Id) + .ToList(); + + var currentIndex = allMeetings.FindIndex(m => m.Id == _meetingHistory.Id); + + if (currentIndex >= 0) + { + _previousMeetingHistory = currentIndex > 0 ? allMeetings[currentIndex - 1] : null; + _nextMeetingHistory = currentIndex < allMeetings.Count - 1 ? allMeetings[currentIndex + 1] : null; + } + else + { + _previousMeetingHistory = null; + _nextMeetingHistory = null; + } + } + catch (TaskCanceledException) + { + // Component was disposed, ignore + } + catch (JSDisconnectedException) + { + // JS connection lost, ignore + } + catch (Exception ex) + { + if (!_isDisposed) + { + // Log error but don't show snackbar - navigation is not critical + System.Diagnostics.Debug.WriteLine($"Error loading navigation meetings: {ex.Message}"); + } + } + } + + private async Task NavigateToPrevious() + { + if (_previousMeetingHistory == null || _isDisposed) return; + + // Close current dialog and open new one with previous meeting ID + var parameters = new DialogParameters + { + ["MeetingHistoryId"] = _previousMeetingHistory.Id + }; + + var options = new DialogOptions + { + MaxWidth = MaxWidth.Medium, + FullWidth = true, + CloseButton = true + }; + + MudDialog.Close(); + await DialogService.ShowAsync("Meeting History", parameters, options); + } + + private async Task NavigateToNext() + { + if (_nextMeetingHistory == null || _isDisposed) return; + + // Close current dialog and open new one with next meeting ID + var parameters = new DialogParameters + { + ["MeetingHistoryId"] = _nextMeetingHistory.Id + }; + + var options = new DialogOptions + { + MaxWidth = MaxWidth.Medium, + FullWidth = true, + CloseButton = true + }; + + MudDialog.Close(); + await DialogService.ShowAsync("Meeting History", parameters, options); + } + private async Task ConfirmDelete() { if (_isDisposed || _isDeleting || _meetingHistory == null) return; @@ -255,6 +379,36 @@ await LoadMeetingHistory(); } + private async Task OpenEditDialog() + { + if (_meetingHistory == null || _isDisposed) return; + + var parameters = new DialogParameters + { + ["MeetingHistoryId"] = _meetingHistory.Id, + ["AllTeams"] = _allTeams, + ["AllStudents"] = _allStudents, + ["ScheduledTeams"] = new List(), // Not used when editing + ["AbsentStudents"] = new List() // Not used when editing + }; + + var options = new DialogOptions + { + MaxWidth = MaxWidth.Medium, + FullWidth = true, + CloseButton = true + }; + + var dialog = await DialogService.ShowAsync("Edit Meeting History", parameters, options); + var result = await dialog.Result; + + // Refresh meeting history if dialog was saved + if (!result.Canceled && !_isDisposed) + { + await LoadMeetingHistory(); + } + } + private List GetAllStudentsFromTeams() { if (_meetingHistory == null) diff --git a/WebApp/Components/Features/MeetingSchedule/SaveMeetingHistoryDialog.razor b/WebApp/Components/Features/MeetingSchedule/SaveMeetingHistoryDialog.razor index 8a1fb3d..0c15ef1 100644 --- a/WebApp/Components/Features/MeetingSchedule/SaveMeetingHistoryDialog.razor +++ b/WebApp/Components/Features/MeetingSchedule/SaveMeetingHistoryDialog.razor @@ -4,6 +4,8 @@ @using Core.Services @using WebApp.Services @using WebApp.Components.Shared.Components +@using WebApp.Components.Features.Teams.Components +@using WebApp.Components.Features.Students.Components @using WebApp.Models @inject ITeamMeetingHistoryService TeamMeetingHistoryService @inject INotesService NotesService @@ -20,49 +22,32 @@ else { - Save Meeting History + @(_isEditMode ? "Edit Meeting History" : "Save Meeting History") + Variant="Variant.Outlined" + Disabled="@_isEditMode" /> - Teams That Met - - @foreach (var team in AllTeams.OrderByEventFormatFirst().ThenBy(e => e.ToString())) - { - - @team.ToString() - - } - + - Students Present - - @foreach (var student in AllStudents.OrderBy(s => s.FirstName)) - { - - @student.FirstNameLastName - - } - + @@ -121,13 +106,17 @@ [Parameter] public IEnumerable AllStudents { get; set; } = []; + [Parameter] + public int? MeetingHistoryId { get; set; } + private DateTime? _meetingDate = DateTime.Today; - private List _selectedTeams = []; - private List _selectedStudents = []; + private IEnumerable _selectedTeams = []; + private IEnumerable _selectedStudents = []; private string _noteTitle = ""; private string _noteContent = ""; private bool _isLoading = true; private bool _isSaving = false; + private bool _isEditMode = false; private CancellationTokenSource? _cancellationTokenSource; private bool _isDisposed = false; @@ -147,12 +136,41 @@ try { - // Initialize selected teams from scheduled teams - _selectedTeams = ScheduledTeams.ToList(); + // If editing, load existing meeting history + if (MeetingHistoryId.HasValue) + { + _isEditMode = true; + var existingHistory = await TeamMeetingHistoryService.GetMeetingHistoryAsync(MeetingHistoryId.Value); + + if (existingHistory != null) + { + _meetingDate = existingHistory.MeetingDate; + + // 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 ?? ""; + } + } + } + else + { + // Initialize selected teams from scheduled teams + _selectedTeams = ScheduledTeams; - // Initialize selected students (all students except absent ones) - var absentStudentIds = AbsentStudents.Select(s => s.Id).ToHashSet(); - _selectedStudents = AllStudents.Where(s => !absentStudentIds.Contains(s.Id)).ToList(); + // Initialize selected students (all students except absent ones) + var absentStudentIds = AbsentStudents.Select(s => s.Id).ToHashSet(); + _selectedStudents = AllStudents.Where(s => !absentStudentIds.Contains(s.Id)); + } // Generate default note title if meeting date is set if (_meetingDate.HasValue) @@ -208,13 +226,13 @@ private void OnTeamsChanged(IEnumerable teams) { - _selectedTeams = teams.ToList(); + _selectedTeams = teams; StateHasChanged(); } private void OnStudentsChanged(IEnumerable students) { - _selectedStudents = students.ToList(); + _selectedStudents = students; StateHasChanged(); } @@ -228,6 +246,21 @@ return; } + // Check if a meeting history already exists for this date (only when creating new, not editing) + if (!_isEditMode) + { + var dateOnly = _meetingDate.Value.Date; + // GetMeetingHistoriesAsync: startDate >= date, endDate < (endDate.Date + 1 day) + // To get meetings for a single day, pass dateOnly as startDate and dateOnly as endDate + // This becomes: MeetingDate >= dateOnly AND MeetingDate < (dateOnly + 1 day) = just that day + 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; + } + } + try { _isSaving = true; @@ -262,19 +295,52 @@ } } - // Create meeting history - var meetingHistory = new TeamMeetingHistory + // Create or update meeting history + TeamMeetingHistory meetingHistory; + if (_isEditMode && MeetingHistoryId.HasValue) { - MeetingDate = _meetingDate.Value, - Teams = _selectedTeams, - Students = _selectedStudents - }; + // Update existing meeting history + var existingHistory = await TeamMeetingHistoryService.GetMeetingHistoryAsync(MeetingHistoryId.Value); + if (existingHistory == null) + { + Snackbar.Add("Meeting history not found", Severity.Error); + _isSaving = false; + StateHasChanged(); + return; + } + + meetingHistory = existingHistory; + meetingHistory.MeetingDate = _meetingDate.Value; + meetingHistory.Teams = _selectedTeams.ToList(); + meetingHistory.Students = _selectedStudents.ToList(); + + await TeamMeetingHistoryService.UpdateMeetingHistoryAsync(meetingHistory); + + if (!_isDisposed) + { + Snackbar.Add($"Meeting history updated for {_meetingDate.Value:MM/dd/yyyy}", Severity.Success); + } + } + else + { + // Create new meeting history + meetingHistory = new TeamMeetingHistory + { + MeetingDate = _meetingDate.Value, + Teams = _selectedTeams.ToList(), + Students = _selectedStudents.ToList() + }; - await TeamMeetingHistoryService.CreateMeetingHistoryAsync(meetingHistory); + await TeamMeetingHistoryService.CreateMeetingHistoryAsync(meetingHistory); + + if (!_isDisposed) + { + Snackbar.Add($"Meeting history saved for {_meetingDate.Value:MM/dd/yyyy}", Severity.Success); + } + } if (!_isDisposed) { - Snackbar.Add($"Meeting history saved for {_meetingDate.Value:MM/dd/yyyy}", Severity.Success); MudDialog.Close(DialogResult.Ok(true)); } }