Add CalendarService and integrate calendar functionality into components
This commit introduces the CalendarService, which provides methods for retrieving all calendar items and upcoming events. The service is integrated into various components, including the Calendar and Home pages, enhancing the calendar functionality by allowing users to view upcoming events and meeting histories. Additionally, the Index.razor component is updated to parse query parameters for date selection, improving user experience. The layout of the MeetingHistoryDetailDialog and SaveMeetingHistoryDialog components is also refined for better organization and responsiveness. These changes collectively enhance the calendar feature and improve the overall user interface in managing events and meetings.
This commit is contained in:
@@ -5,9 +5,7 @@
|
||||
@using Heron.MudCalendar
|
||||
@using Microsoft.Extensions.Logging
|
||||
@using WebApp.Authentication
|
||||
@using Core.Utility
|
||||
@inject IEventOccurrenceService EventOccurrenceService
|
||||
@inject ITeamMeetingHistoryService TeamMeetingHistoryService
|
||||
@inject ICalendarService CalendarService
|
||||
@inject ILogger<Index> Logger
|
||||
@inject IDialogService DialogService
|
||||
|
||||
@@ -72,8 +70,17 @@
|
||||
private DateTime _calendarDate = DateTime.Today;
|
||||
private CalendarView _currentView = CalendarView.Month;
|
||||
|
||||
[SupplyParameterFromQuery]
|
||||
private string? Date { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Parse date from query parameter if provided
|
||||
if (!string.IsNullOrEmpty(Date) && DateTime.TryParse(Date, out var parsedDate))
|
||||
{
|
||||
_calendarDate = parsedDate.Date;
|
||||
}
|
||||
|
||||
await LoadCalendarEvents();
|
||||
}
|
||||
|
||||
@@ -82,91 +89,8 @@
|
||||
try
|
||||
{
|
||||
Logger.LogInformation("Loading calendar events");
|
||||
var occurrences = await EventOccurrenceService.GetEventOccurrencesAsync();
|
||||
|
||||
var eventOccurrences = occurrences as EventOccurrence[] ?? occurrences.ToArray();
|
||||
Logger.LogDebug("Received {Count} occurrences from service", eventOccurrences.Count());
|
||||
|
||||
// Get all unique event definition IDs that have occurrences
|
||||
var eventDefinitionIds = eventOccurrences
|
||||
.Where(occ => occ?.EventDefinition?.Id != null)
|
||||
.Select(occ => occ!.EventDefinition!.Id)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
// Load teams for all event definitions
|
||||
var teamsByEventId = await EventOccurrenceService.GetTeamsByEventDefinitionIdsAsync(eventDefinitionIds);
|
||||
|
||||
List<CalendarItemWrapper> items = [];
|
||||
|
||||
// Add event occurrences
|
||||
foreach (var occ in eventOccurrences)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(occ.Name))
|
||||
{
|
||||
Logger.LogWarning("Occurrence with Id={Id} has null or empty Name", occ.Id);
|
||||
}
|
||||
|
||||
// Get student first names for this event definition
|
||||
var studentFirstNames = occ.EventDefinition != null && teamsByEventId.TryGetValue(occ.EventDefinition.Id, out var teams)
|
||||
? TeamStudentNameFormatter.FormatStudentListForEvent(
|
||||
occ.EventDefinition,
|
||||
teams,
|
||||
new TeamStudentNameFormatter.FormatOptions
|
||||
{
|
||||
CaptainIndicator = TeamStudentNameFormatter.CaptainIndicatorStyle.Star,
|
||||
Ordering = TeamStudentNameFormatter.OrderingStyle.Alphabetical
|
||||
})
|
||||
: [];
|
||||
|
||||
var calendarEventItem = new CalendarEventItem(occ, studentFirstNames);
|
||||
items.Add(new CalendarItemWrapper(calendarEventItem));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error creating CalendarEventItem for occurrence Id={Id}, Name={Name}",
|
||||
occ?.Id, occ?.Name);
|
||||
// Continue processing other items
|
||||
}
|
||||
}
|
||||
|
||||
// Load and add meeting histories
|
||||
try
|
||||
{
|
||||
Logger.LogInformation("Loading meeting histories");
|
||||
var meetingHistories = await TeamMeetingHistoryService.GetMeetingHistoriesAsync();
|
||||
|
||||
foreach (var meetingHistory in meetingHistories)
|
||||
{
|
||||
try
|
||||
{
|
||||
var calendarMeetingItem = new CalendarMeetingItem(meetingHistory);
|
||||
items.Add(new CalendarItemWrapper(calendarMeetingItem));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error creating CalendarMeetingItem for meeting history Id={Id}, Date={Date}",
|
||||
meetingHistory?.Id, meetingHistory?.MeetingDate);
|
||||
// Continue processing other items
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogInformation("Added {Count} meeting histories to calendar", meetingHistories.Count());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error loading meeting histories");
|
||||
// Continue - don't fail the entire calendar load if meetings fail
|
||||
}
|
||||
|
||||
_calendarItems = items;
|
||||
Logger.LogInformation("Created {Count} calendar items from {OccurrenceCount} occurrences and meetings",
|
||||
_calendarItems.Count, eventOccurrences.Count());
|
||||
|
||||
// Find the next date with events
|
||||
_calendarDate = GetNextDateWithEvents();
|
||||
_calendarItems = await CalendarService.GetAllCalendarItemsAsync();
|
||||
Logger.LogInformation("Loaded {Count} calendar items", _calendarItems.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -180,51 +104,6 @@
|
||||
}
|
||||
|
||||
|
||||
private DateTime GetNextDateWithEvents()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_calendarItems == null || !_calendarItems.Any())
|
||||
{
|
||||
Logger.LogDebug("No calendar items available, returning today's date");
|
||||
return DateTime.Today;
|
||||
}
|
||||
|
||||
var today = DateTime.Today;
|
||||
var nextItem = _calendarItems
|
||||
.Where(item =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return item.Start.Date >= today;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning(ex, "Error checking item date, skipping item");
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.OrderBy(item => item.Start)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (nextItem != null)
|
||||
{
|
||||
return nextItem.Start.Date;
|
||||
}
|
||||
|
||||
// Fallback to first item if no future items
|
||||
var firstItem = _calendarItems
|
||||
.OrderBy(item => item.Start)
|
||||
.FirstOrDefault();
|
||||
|
||||
return firstItem != null ? firstItem.Start.Date : DateTime.Today;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error in GetNextDateWithEvents");
|
||||
return DateTime.Today;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetEventTooltip(CalendarItemWrapper wrapper)
|
||||
{
|
||||
|
||||
@@ -47,39 +47,42 @@
|
||||
</MudTooltip>
|
||||
</MudStack>
|
||||
|
||||
<MudText Typo="Typo.subtitle1">Teams That Met (@_meetingHistory.Teams.Count)</MudText>
|
||||
<MudPaper Elevation="1" Class="pa-2">
|
||||
<MudStack Row="true" Spacing="1" Wrap="Wrap.Wrap">
|
||||
@foreach (var team in _meetingHistory.Teams.OrderByEventFormatFirst().ThenBy(e => e.ToString()))
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Color="Color.Default" Variant="@AppIcons.TeamChipVariant()" Class="mx-1 my-1">@team.ToString()</MudChip>
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<MudDivider />
|
||||
|
||||
<MudText Typo="Typo.subtitle1">Students (@GetAllStudentsFromTeams().Count)</MudText>
|
||||
<MudPaper Elevation="1" Class="pa-2">
|
||||
<MudStack Row="true" Spacing="1" Wrap="Wrap.Wrap">
|
||||
@{
|
||||
var presentStudentIds = _meetingHistory.Students.Select(s => s.Id).ToHashSet();
|
||||
var allStudents = GetAllStudentsFromTeams().OrderBy(s => s.FirstName);
|
||||
}
|
||||
@foreach (var student in allStudents)
|
||||
{
|
||||
var isPresent = presentStudentIds.Contains(student.Id);
|
||||
<MudChip T="string"
|
||||
Size="Size.Small"
|
||||
Color="Color.Default"
|
||||
Variant="@AppIcons.StudentChipVariant()"
|
||||
Class="mx-1 my-1"
|
||||
Style="@(!isPresent ? "opacity: 0.5;" : "")">
|
||||
@student.FirstNameLastName
|
||||
</MudChip>
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudText Typo="Typo.subtitle1">Teams That Met (@_meetingHistory.Teams.Count)</MudText>
|
||||
<MudPaper Elevation="1" Class="pa-2">
|
||||
<MudStack Row="true" Spacing="1" Wrap="Wrap.Wrap">
|
||||
@foreach (var team in _meetingHistory.Teams.OrderByEventFormatFirst().ThenBy(e => e.ToString()))
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Color="Color.Default" Variant="@AppIcons.TeamChipVariant()" Class="mx-1 my-1">@team.ToString()</MudChip>
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudText Typo="Typo.subtitle1">Students (@GetAllStudentsFromTeams().Count)</MudText>
|
||||
<MudPaper Elevation="1" Class="pa-2">
|
||||
<MudStack Row="true" Spacing="1" Wrap="Wrap.Wrap">
|
||||
@{
|
||||
var presentStudentIds = _meetingHistory.Students.Select(s => s.Id).ToHashSet();
|
||||
var allStudents = GetAllStudentsFromTeams().OrderBy(s => s.FirstName);
|
||||
}
|
||||
@foreach (var student in allStudents)
|
||||
{
|
||||
var isPresent = presentStudentIds.Contains(student.Id);
|
||||
<MudChip T="string"
|
||||
Size="Size.Small"
|
||||
Color="Color.Default"
|
||||
Variant="@AppIcons.StudentChipVariant()"
|
||||
Class="mx-1 my-1"
|
||||
Style="@(!isPresent ? "opacity: 0.5;" : "")">
|
||||
@student.FirstNameLastName
|
||||
</MudChip>
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
@if (_meetingNote != null)
|
||||
{
|
||||
|
||||
@@ -33,23 +33,26 @@
|
||||
|
||||
<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>
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="6">
|
||||
<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>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<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>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudDivider />
|
||||
|
||||
@@ -149,11 +152,28 @@
|
||||
|
||||
// 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));
|
||||
var matchedTeams = AllTeams.Where(t => selectedTeamIds.Contains(t.Id)).ToList();
|
||||
|
||||
// If we couldn't match all teams from AllTeams, use the teams from existing history
|
||||
// This can happen if AllTeams is empty or doesn't contain all the teams
|
||||
if (matchedTeams.Count != existingHistory.Teams.Count && AllTeams.Any())
|
||||
{
|
||||
// Try to match what we can, but log a warning
|
||||
System.Diagnostics.Debug.WriteLine($"Warning: Could not match all teams. Expected {existingHistory.Teams.Count}, matched {matchedTeams.Count}");
|
||||
}
|
||||
_selectedTeams = matchedTeams.Any() ? matchedTeams : existingHistory.Teams;
|
||||
|
||||
// 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));
|
||||
var matchedStudents = AllStudents.Where(s => selectedStudentIds.Contains(s.Id)).ToList();
|
||||
|
||||
// If we couldn't match all students from AllStudents, use the students from existing history
|
||||
if (matchedStudents.Count != existingHistory.Students.Count && AllStudents.Any())
|
||||
{
|
||||
// Try to match what we can, but log a warning
|
||||
System.Diagnostics.Debug.WriteLine($"Warning: Could not match all students. Expected {existingHistory.Students.Count}, matched {matchedStudents.Count}");
|
||||
}
|
||||
_selectedStudents = matchedStudents.Any() ? matchedStudents : existingHistory.Students;
|
||||
|
||||
// Load existing note if available
|
||||
var existingNote = await TeamMeetingHistoryService.GetMeetingNoteAsync(existingHistory.MeetingDate);
|
||||
@@ -247,6 +267,13 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate that we have at least one team selected
|
||||
if (!_selectedTeams.Any())
|
||||
{
|
||||
Snackbar.Add("Please select at least one team", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if a meeting history already exists for this date (only when creating new, not editing)
|
||||
if (!_isEditMode)
|
||||
{
|
||||
@@ -344,10 +371,19 @@
|
||||
return;
|
||||
}
|
||||
|
||||
meetingHistory = existingHistory;
|
||||
meetingHistory.MeetingDate = _meetingDate.Value;
|
||||
meetingHistory.Teams = _selectedTeams.ToList();
|
||||
meetingHistory.Students = _selectedStudents.ToList();
|
||||
// Ensure we have teams and students - use existing if selected lists are empty (fallback)
|
||||
var teamsToSave = _selectedTeams.Any() ? _selectedTeams.ToList() : existingHistory.Teams.ToList();
|
||||
var studentsToSave = _selectedStudents.Any() ? _selectedStudents.ToList() : existingHistory.Students.ToList();
|
||||
|
||||
// Create a new meeting history object with the updated data
|
||||
// Use IDs to ensure we're working with the correct entities
|
||||
meetingHistory = new TeamMeetingHistory
|
||||
{
|
||||
Id = existingHistory.Id,
|
||||
MeetingDate = _meetingDate.Value,
|
||||
Teams = teamsToSave,
|
||||
Students = studentsToSave
|
||||
};
|
||||
|
||||
await TeamMeetingHistoryService.UpdateMeetingHistoryAsync(meetingHistory);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user