Files
chapter-organizer/WebApp/Components/Features/MeetingSchedule/SaveMeetingHistoryDialog.razor
T
poprhythm a503655f97 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.
2026-01-25 18:58:02 -05:00

419 lines
15 KiB
Plaintext

@namespace WebApp.Components.Features.MeetingSchedule
@using Core.Entities
@using Core.Calculation
@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
@inject INoteNamingService NoteNamingService
@inject ISnackbar Snackbar
@inject IDialogService DialogService
@implements IAsyncDisposable
<MudDialog>
<DialogContent>
@if (_isLoading)
{
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="my-4" />
}
else
{
<MudStack Spacing="3">
<MudText Typo="Typo.h6">@(_isEditMode ? "Edit Meeting History" : "Save Meeting History")</MudText>
<MudDatePicker Label="Meeting Date"
Date="_meetingDate"
DateChanged="OnMeetingDateChanged"
Variant="Variant.Outlined"
Disabled="@_isEditMode" />
<MudDivider />
<MudPaper Elevation="1" Class="pa-2" Style="max-height: 300px; overflow-y: auto;">
<TeamToggleSelector Teams="@AllTeams"
SelectedTeams="_selectedTeams"
SelectedTeamsChanged="OnTeamsChanged"
Title="Teams That Met"
ShowEventAttributes="false" />
</MudPaper>
<MudDivider />
<MudPaper Elevation="1" Class="pa-2" Style="max-height: 300px; overflow-y: auto;">
<StudentToggleSelector Students="@AllStudents"
SelectedStudents="_selectedStudents"
SelectedStudentsChanged="OnStudentsChanged"
Title="Students Present"
ShowFullName="true" />
</MudPaper>
<MudDivider />
<MudExpansionPanels MultiExpansion="false">
<MudExpansionPanel Text="Meeting Notes (Optional)">
<MudStack Spacing="2">
<MudTextField T="string"
Label="Note Title"
@bind-Value="_noteTitle"
Variant="Variant.Outlined"
ReadOnly="true"
HelperText="Title is automatically generated based on meeting date" />
<MudTextField T="string"
Label="Note Content"
@bind-Value="_noteContent"
Variant="Variant.Outlined"
Lines="5"
Placeholder="Enter meeting notes..."
HelperText="Optional markdown content for meeting notes" />
</MudStack>
</MudExpansionPanel>
</MudExpansionPanels>
</MudStack>
}
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel" Disabled="@_isSaving">Cancel</MudButton>
<MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="Save" Disabled="@IsSaveDisabled">
@if (_isSaving)
{
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" />
<span>Saving...</span>
}
else
{
<span>Save</span>
}
</MudButton>
</DialogActions>
</MudDialog>
@code {
[CascadingParameter]
IMudDialogInstance MudDialog { get; set; } = null!;
[Parameter]
public IEnumerable<Team> ScheduledTeams { get; set; } = [];
[Parameter]
public IEnumerable<Student> AbsentStudents { get; set; } = [];
[Parameter]
public IEnumerable<Team> AllTeams { get; set; } = [];
[Parameter]
public IEnumerable<Student> AllStudents { get; set; } = [];
[Parameter]
public int? MeetingHistoryId { get; set; }
private DateTime? _meetingDate = DateTime.Today;
private IEnumerable<Team> _selectedTeams = [];
private IEnumerable<Student> _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;
protected override void OnInitialized()
{
_cancellationTokenSource = new CancellationTokenSource();
}
protected override async Task OnInitializedAsync()
{
await LoadInitialData();
}
private async Task LoadInitialData()
{
if (_isDisposed) return;
try
{
// 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));
}
// Generate default note title if meeting date is set
if (_meetingDate.HasValue)
{
_noteTitle = NoteNamingService.GetMeetingNoteTitle(_meetingDate.Value);
}
}
catch (TaskCanceledException)
{
// Component was disposed, ignore
}
catch (JSDisconnectedException)
{
// JS connection lost, ignore
}
catch (Exception ex)
{
if (!_isDisposed)
{
Snackbar.Add($"Error loading data: {ex.Message}", Severity.Error);
}
}
finally
{
if (!_isDisposed)
{
_isLoading = false;
StateHasChanged();
}
}
}
private bool IsSaveDisabled => _isSaving || _isLoading;
protected override async Task OnParametersSetAsync()
{
if (_meetingDate.HasValue && string.IsNullOrEmpty(_noteTitle))
{
_noteTitle = NoteNamingService.GetMeetingNoteTitle(_meetingDate.Value);
}
await base.OnParametersSetAsync();
}
private async Task OnMeetingDateChanged(DateTime? date)
{
_meetingDate = date;
if (date.HasValue)
{
_noteTitle = NoteNamingService.GetMeetingNoteTitle(date.Value);
}
await InvokeAsync(StateHasChanged);
}
private void OnTeamsChanged(IEnumerable<Team> teams)
{
_selectedTeams = teams;
StateHasChanged();
}
private void OnStudentsChanged(IEnumerable<Student> students)
{
_selectedStudents = students;
StateHasChanged();
}
private async Task Save()
{
if (_isDisposed || _isSaving) return;
if (!_meetingDate.HasValue)
{
Snackbar.Add("Please select a meeting date", Severity.Warning);
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())
{
// 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 ?? "";
}
}
}
}
try
{
_isSaving = true;
StateHasChanged();
// Create or update note if note content is provided
Note? note = null;
if (!string.IsNullOrWhiteSpace(_noteContent))
{
// Use the naming service to get the meeting note title
var noteTitle = NoteNamingService.GetMeetingNoteTitle(_meetingDate.Value);
// Check if note already exists
var existingNote = await NotesService.GetNotesAsync(includeDeleted: false);
note = existingNote.FirstOrDefault(n => n.Title == noteTitle);
if (note != null)
{
// Update existing note
note.Content = _noteContent;
note = await NotesService.UpdateNoteAsync(note);
}
else
{
// Create new note
note = new Note
{
Title = noteTitle,
Content = _noteContent
};
note = await NotesService.CreateNoteAsync(note);
}
}
// Create or update meeting history
TeamMeetingHistory meetingHistory;
if (_isEditMode && MeetingHistoryId.HasValue)
{
// 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);
if (!_isDisposed)
{
Snackbar.Add($"Meeting history saved for {_meetingDate.Value:MM/dd/yyyy}", Severity.Success);
}
}
if (!_isDisposed)
{
MudDialog.Close(DialogResult.Ok(true));
}
}
catch (TaskCanceledException)
{
// Component was disposed, ignore
}
catch (JSDisconnectedException)
{
// JS connection lost, ignore
}
catch (Exception ex)
{
if (!_isDisposed)
{
Snackbar.Add($"Error saving meeting history: {ex.Message}", Severity.Error);
_isSaving = false;
StateHasChanged();
}
}
}
private void Cancel()
{
if (_isDisposed) return;
MudDialog.Cancel();
}
public async ValueTask DisposeAsync()
{
if (!_isDisposed)
{
_isDisposed = true;
_cancellationTokenSource?.Cancel();
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
}
await ValueTask.CompletedTask;
}
}