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.
This commit is contained in:
2026-01-25 18:58:02 -05:00
parent 6e2834f2be
commit a503655f97
3 changed files with 164 additions and 2 deletions
@@ -9,6 +9,9 @@
@inject IDialogService DialogService
@inject ISnackbar Snackbar
@inject IConfiguration Configuration
@inject IMeetingScheduleDataService DataService
@inject IMeetingScheduleStateService StateService
@inject NavigationManager NavigationManager
@implements IAsyncDisposable
<PageHeader Title="Team Meeting Schedule History">
@@ -40,6 +43,7 @@
Breakpoint="Breakpoint.Sm">
<ColGroup>
<col style="width: 100px;" />
<col style="width: 50px;" />
@foreach (var team in _allTeams)
{
<col style="width: 40px;" />
@@ -51,6 +55,9 @@
Date
</MudText>
</MudTh>
<MudTh Style="padding: 4px 8px; border-right: 2px solid var(--mud-palette-divider);">
<MudText Typo="Typo.caption">Actions</MudText>
</MudTh>
@{
var teamIndex = 0;
}
@@ -77,6 +84,15 @@
@context.MeetingDate.ToString("MM/dd/yy")
</MudButton>
</MudTd>
<MudTd Style="padding: 4px 8px; border-right: 2px solid var(--mud-palette-divider);">
<MudTooltip Text="Load into Planner">
<MudIconButton Icon="@Icons.Material.Filled.Upload"
Size="Size.Small"
Color="Color.Secondary"
OnClick="@(() => LoadMeetingIntoPlanner(context))"
Variant="Variant.Text" />
</MudTooltip>
</MudTd>
@{
var rowTeamIndex = 0;
}
@@ -99,6 +115,7 @@
<MudTd Style="padding: 4px 8px; border-right: 2px solid var(--mud-palette-divider); font-weight: bold;">
Times Met
</MudTd>
<MudTd Style="padding: 4px 8px; border-right: 2px solid var(--mud-palette-divider);"></MudTd>
@{
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)
@@ -11,6 +11,8 @@
@inject ISnackbar Snackbar
@inject IDialogService DialogService
@inject IMeetingScheduleDataService DataService
@inject IMeetingScheduleStateService StateService
@inject NavigationManager NavigationManager
@implements IAsyncDisposable
<MudDialog>
@@ -128,6 +130,13 @@
Delete
</MudButton>
<MudSpacer />
<MudButton Variant="Variant.Text"
Color="Color.Secondary"
OnClick="LoadIntoPlanner"
Disabled="@IsActionDisabled"
StartIcon="@Icons.Material.Filled.Upload">
Load into Planner
</MudButton>
<MudButton Variant="Variant.Text"
Color="Color.Primary"
OnClick="OpenEditDialog"
@@ -432,6 +441,57 @@
return allStudents;
}
private async Task LoadIntoPlanner()
{
if (_isDisposed || _meetingHistory == null) 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 = _meetingHistory.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 = _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;
@@ -11,6 +11,7 @@
@inject INotesService NotesService
@inject INoteNamingService NoteNamingService
@inject ISnackbar Snackbar
@inject IDialogService DialogService
@implements IAsyncDisposable
<MudDialog>
@@ -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 ?? "";
}
}
}
}