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:
2026-01-27 22:38:52 -05:00
parent 84eaf338a9
commit 675f04afec
8 changed files with 452 additions and 213 deletions
+170 -19
View File
@@ -5,10 +5,14 @@
@using Core.Entities
@using WebApp.Services
@using WebApp.Components.Shared.Components
@using Heron.MudCalendar
@inject IConfiguration Configuration
@inject AppDbContext Context
@inject INotesService NotesService
@inject IDialogService DialogService
@inject ITeamMeetingHistoryService TeamMeetingHistoryService
@inject ICalendarService CalendarService
@inject NavigationManager Navigation
@implements IAsyncDisposable
<PageTitle>@Configuration["ChapterSettings:Name"] - TSA Chapter Organizer</PageTitle>
@@ -124,13 +128,76 @@ else
<MudGrid>
<DashboardCard Icon="@AppIcons.Scheduler"
Title="Meeting Schedule Planner"
Caption="Optimize meeting times"
NavigateUrl="/meeting-schedule"/>
NavigateUrl="/meeting-schedule">
@if (_recentMeetings.Any())
{
<MudGrid Spacing="2">
<MudItem xs="12" sm="12" md="7">
<MudText Typo="Typo.subtitle2" Class="mb-2" Style="font-weight: 600;">Recent Meetings</MudText>
<MudStack Spacing="1">
@foreach (var meeting in _recentMeetings)
{
<div @onclick="@(() => ShowMeetingDetails(meeting))"
@onclick:stopPropagation="true"
style="cursor: pointer; color: var(--mud-palette-primary);">
<MudText Typo="Typo.body2" Style="text-decoration: underline;">
@meeting.MeetingDate.ToString("MMM d, yyyy")
</MudText>
</div>
}
</MudStack>
</MudItem>
<MudItem xs="12" sm="12" md="5">
<MudStack Spacing="1" AlignItems="AlignItems.Start">
<div @onclick:stopPropagation="true">
<MudTooltip Text="View meeting history">
<MudButton StartIcon="@Icons.Material.Filled.History"
Variant="Variant.Outlined"
Color="Color.Default"
Href="/meeting-schedule/history">
View History
</MudButton>
</MudTooltip>
</div>
</MudStack>
</MudItem>
</MudGrid>
}
else
{
<MudText Typo="Typo.caption" Class="mt-2">Optimize meeting times</MudText>
}
</DashboardCard>
<DashboardCard Icon="@AppIcons.EventCalendar"
Title="Event Calendar"
Caption="Conference schedules"
NavigateUrl="/calendar"/>
NavigateUrl="/calendar">
@if (_nextCalendarItems.Any())
{
<MudText Typo="Typo.subtitle2" Class="mb-2" Style="font-weight: 600;">Next Events</MudText>
<MudStack Spacing="1">
@foreach (var item in _nextCalendarItems)
{
var itemDate = item.Start.Date;
var itemName = item.ItemType == CalendarItemType.Event && item.EventItem != null
? (item.EventItem.EventDefinition?.ShortName ?? item.EventItem.EventOccurrenceData?.Name ?? "Event")
: "Team Meeting";
var dateStr = itemDate.ToString("yyyy-MM-dd");
<div @onclick="@(() => NavigateToCalendar(dateStr))"
@onclick:stopPropagation="true"
style="cursor: pointer; color: var(--mud-palette-primary);">
<MudText Typo="Typo.body2" Style="text-decoration: underline;">
@itemName - @itemDate.ToString("MMM d, yyyy")
</MudText>
</div>
}
</MudStack>
}
else
{
<MudText Typo="Typo.caption" Class="mt-2">Conference schedules</MudText>
}
</DashboardCard>
</MudGrid>
<MudPaper Elevation="0" Class="my-4">
@@ -150,21 +217,6 @@ else
NavigateUrl="/students/teams"/>
</MudGrid>
<MudPaper Elevation="0" Class="my-4">
<MudText Typo="Typo.h4">Team Building</MudText>
</MudPaper>
<MudGrid>
<DashboardCard Icon="@AppIcons.EventRank"
Title="Event Ranking"
Caption="Student event preferences"
NavigateUrl="/students/event-ranking"/>
<DashboardCard Icon="@AppIcons.TeamAssignment"
Title="Team Assignment"
Caption="Build optimal teams"
NavigateUrl="/teams/assignment"/>
</MudGrid>
<MudPaper Elevation="0" Class="my-4">
<MudText Typo="Typo.h4">Chapter Data</MudText>
</MudPaper>
@@ -189,6 +241,21 @@ else
Caption="@($"{_teamEventsCount} Team | {_individualEventsCount} Individual")"
NavigateUrl="/events" />
</MudGrid>
<MudPaper Elevation="0" Class="my-4">
<MudText Typo="Typo.h4">Team Building</MudText>
</MudPaper>
<MudGrid>
<DashboardCard Icon="@AppIcons.EventRank"
Title="Event Ranking"
Caption="Student event preferences"
NavigateUrl="/students/event-ranking"/>
<DashboardCard Icon="@AppIcons.TeamAssignment"
Title="Team Assignment"
Caption="Build optimal teams"
NavigateUrl="/teams/assignment"/>
</MudGrid>
}
@code {
@@ -200,6 +267,9 @@ else
private int _teamCount;
private int _individualTeamsCount;
private int _groupTeamsCount;
private int _meetingHistoryCount;
private List<TeamMeetingHistory> _recentMeetings = [];
private List<CalendarItemWrapper> _nextCalendarItems = [];
private List<Note> _pinnedNotes = [];
private CancellationTokenSource? _cancellationTokenSource;
private bool _isDisposed = false;
@@ -216,6 +286,8 @@ else
{
await LoadStatistics();
await LoadPinnedNotes();
await LoadMeetingHistoryCount();
await LoadNextCalendarItem();
}
private async Task HandleNoteChanged()
@@ -285,6 +357,85 @@ else
_groupTeamsCount = contextTeams.Count(e => e.Event.EventFormat == EventFormat.Team);
}
private async Task LoadMeetingHistoryCount()
{
if (_isDisposed) return;
try
{
var meetingHistories = await TeamMeetingHistoryService.GetMeetingHistoriesAsync();
var historiesList = meetingHistories.ToList();
_meetingHistoryCount = historiesList.Count;
// Get the 3 most recent meetings in descending order
_recentMeetings = historiesList
.OrderByDescending(m => m.MeetingDate)
.ThenByDescending(m => m.Id)
.Take(3)
.ToList();
}
catch (TaskCanceledException)
{
// Component was disposed, ignore
}
catch (JSDisconnectedException)
{
// JS connection lost, ignore
}
catch (Exception)
{
// Error loading meeting history - ignore
_meetingHistoryCount = 0;
_recentMeetings = [];
}
}
private async Task ShowMeetingDetails(TeamMeetingHistory meeting)
{
var parameters = new DialogParameters
{
["MeetingHistoryId"] = meeting.Id
};
var options = new DialogOptions
{
CloseOnEscapeKey = true,
CloseButton = true,
MaxWidth = MaxWidth.Large,
FullWidth = true
};
await DialogService.ShowAsync<MeetingHistoryDetailDialog>("Meeting Details", parameters, options);
}
private async Task LoadNextCalendarItem()
{
if (_isDisposed) return;
try
{
_nextCalendarItems = await CalendarService.GetUpcomingCalendarItemsAsync(3);
}
catch (TaskCanceledException)
{
// Component was disposed, ignore
}
catch (JSDisconnectedException)
{
// JS connection lost, ignore
}
catch (Exception)
{
// Error loading calendar items - ignore
_nextCalendarItems = [];
}
}
private void NavigateToCalendar(string date)
{
Navigation.NavigateTo($"/calendar?date={date}");
}
public async ValueTask DisposeAsync()
{
if (!_isDisposed)