Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
675f04afec | ||
|
|
84eaf338a9 | ||
|
|
15d7edec8f | ||
|
|
46836fde2e | ||
|
|
d0ce71397b | ||
|
|
840a8edbf1 |
@@ -5,9 +5,7 @@
|
|||||||
@using Heron.MudCalendar
|
@using Heron.MudCalendar
|
||||||
@using Microsoft.Extensions.Logging
|
@using Microsoft.Extensions.Logging
|
||||||
@using WebApp.Authentication
|
@using WebApp.Authentication
|
||||||
@using Core.Utility
|
@inject ICalendarService CalendarService
|
||||||
@inject IEventOccurrenceService EventOccurrenceService
|
|
||||||
@inject ITeamMeetingHistoryService TeamMeetingHistoryService
|
|
||||||
@inject ILogger<Index> Logger
|
@inject ILogger<Index> Logger
|
||||||
@inject IDialogService DialogService
|
@inject IDialogService DialogService
|
||||||
|
|
||||||
@@ -37,29 +35,30 @@
|
|||||||
|
|
||||||
<MudCalendar T="CalendarItemWrapper"
|
<MudCalendar T="CalendarItemWrapper"
|
||||||
Items="_calendarItems"
|
Items="_calendarItems"
|
||||||
View="_currentView"
|
@bind-View="_currentView"
|
||||||
CurrentDay="@_calendarDate"
|
@bind-CurrentDay="_calendarDate"
|
||||||
Class="event-calendar"
|
Class="event-calendar"
|
||||||
ItemClicked="OnItemClicked">
|
ItemClicked="OnItemClicked">
|
||||||
<MonthTemplate>
|
<MonthTemplate>
|
||||||
@* <MudTooltip Text="@GetEventTooltip(context)"> *@
|
<MudTooltip Text="@GetEventTooltip(context)">
|
||||||
<div class="d-flex gap-1">
|
<div class="calendar-event-item">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Circle" Color="Color.Secondary" Size="Size.Small"/>
|
|
||||||
<div>@context.Text</div>
|
|
||||||
</div>
|
|
||||||
@* </MudTooltip> *@
|
|
||||||
</MonthTemplate>
|
|
||||||
<WeekTemplate>
|
|
||||||
@* <MudTooltip Text="@GetEventTooltip(context)"> *@
|
|
||||||
<div style="width: 100%; height: 100%;">
|
|
||||||
@context.Text
|
@context.Text
|
||||||
</div>
|
</div>
|
||||||
@* </MudTooltip> *@
|
</MudTooltip>
|
||||||
|
</MonthTemplate>
|
||||||
|
<WeekTemplate>
|
||||||
|
<MudTooltip Text="@GetEventTooltip(context)">
|
||||||
|
<div class="calendar-event-item">
|
||||||
|
@context.Text
|
||||||
|
</div>
|
||||||
|
</MudTooltip>
|
||||||
</WeekTemplate>
|
</WeekTemplate>
|
||||||
<DayTemplate>
|
<DayTemplate>
|
||||||
@* <MudTooltip Text="@GetEventTooltip(context)"> *@
|
<MudTooltip Text="@GetEventTooltip(context)">
|
||||||
<div>@context.Text</div>
|
<div class="calendar-event-item">
|
||||||
@* </MudTooltip> *@
|
@context.Text
|
||||||
|
</div>
|
||||||
|
</MudTooltip>
|
||||||
</DayTemplate>
|
</DayTemplate>
|
||||||
</MudCalendar>
|
</MudCalendar>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
@@ -71,8 +70,17 @@
|
|||||||
private DateTime _calendarDate = DateTime.Today;
|
private DateTime _calendarDate = DateTime.Today;
|
||||||
private CalendarView _currentView = CalendarView.Month;
|
private CalendarView _currentView = CalendarView.Month;
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery]
|
||||||
|
private string? Date { get; set; }
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
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();
|
await LoadCalendarEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,91 +89,8 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Logger.LogInformation("Loading calendar events");
|
Logger.LogInformation("Loading calendar events");
|
||||||
var occurrences = await EventOccurrenceService.GetEventOccurrencesAsync();
|
_calendarItems = await CalendarService.GetAllCalendarItemsAsync();
|
||||||
|
Logger.LogInformation("Loaded {Count} calendar items", _calendarItems.Count);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -179,50 +104,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private DateTime GetNextDateWithEvents()
|
|
||||||
|
private string GetEventTooltip(CalendarItemWrapper wrapper)
|
||||||
{
|
{
|
||||||
try
|
if (wrapper.ItemType == CalendarItemType.Event && wrapper.EventItem != null)
|
||||||
{
|
{
|
||||||
if (_calendarItems == null || !_calendarItems.Any())
|
return GetEventTooltip(wrapper.EventItem);
|
||||||
{
|
|
||||||
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)
|
else if (wrapper.ItemType == CalendarItemType.Meeting && wrapper.MeetingItem != null)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Error in GetNextDateWithEvents");
|
return GetMeetingTooltip(wrapper.MeetingItem);
|
||||||
return DateTime.Today;
|
|
||||||
}
|
}
|
||||||
|
return wrapper.Text;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetEventTooltip(CalendarEventItem item)
|
private string GetEventTooltip(CalendarEventItem item)
|
||||||
@@ -257,6 +150,14 @@
|
|||||||
return string.Join("\n", parts);
|
return string.Join("\n", parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetMeetingTooltip(CalendarMeetingItem item)
|
||||||
|
{
|
||||||
|
if (item.MeetingHistoryData == null)
|
||||||
|
return "Team Meeting";
|
||||||
|
|
||||||
|
return $"Team Meeting\nDate: {item.MeetingHistoryData.MeetingDate:g}";
|
||||||
|
}
|
||||||
|
|
||||||
private async Task OnItemClicked(CalendarItemWrapper wrapper)
|
private async Task OnItemClicked(CalendarItemWrapper wrapper)
|
||||||
{
|
{
|
||||||
if (_calendarItems == null)
|
if (_calendarItems == null)
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
@namespace WebApp.Components.Features.MeetingSchedule
|
@namespace WebApp.Components.Features.MeetingSchedule
|
||||||
@using Core.Entities
|
|
||||||
@using Core.Services
|
@using Core.Services
|
||||||
@using Core.Utility
|
|
||||||
@using WebApp.Services
|
|
||||||
@using WebApp.Components.Shared.Components
|
|
||||||
@using WebApp.Models
|
@using WebApp.Models
|
||||||
@inject ITeamMeetingHistoryService TeamMeetingHistoryService
|
@inject ITeamMeetingHistoryService TeamMeetingHistoryService
|
||||||
@inject INotesService NotesService
|
@inject INotesService NotesService
|
||||||
@@ -51,39 +47,42 @@
|
|||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
<MudText Typo="Typo.subtitle1">Teams That Met (@_meetingHistory.Teams.Count)</MudText>
|
<MudGrid>
|
||||||
<MudPaper Elevation="1" Class="pa-2">
|
<MudItem xs="12" md="6">
|
||||||
<MudStack Row="true" Spacing="1" Wrap="Wrap.Wrap">
|
<MudText Typo="Typo.subtitle1">Teams That Met (@_meetingHistory.Teams.Count)</MudText>
|
||||||
@foreach (var team in _meetingHistory.Teams.OrderByEventFormatFirst().ThenBy(e => e.ToString()))
|
<MudPaper Elevation="1" Class="pa-2">
|
||||||
{
|
<MudStack Row="true" Spacing="1" Wrap="Wrap.Wrap">
|
||||||
<MudChip T="string" Size="Size.Small" Color="Color.Default" Variant="@AppIcons.TeamChipVariant()" Class="mx-1 my-1">@team.ToString()</MudChip>
|
@foreach (var team in _meetingHistory.Teams.OrderByEventFormatFirst().ThenBy(e => e.ToString()))
|
||||||
}
|
{
|
||||||
</MudStack>
|
<MudChip T="string" Size="Size.Small" Color="Color.Default" Variant="@AppIcons.TeamChipVariant()" Class="mx-1 my-1">@team.ToString()</MudChip>
|
||||||
</MudPaper>
|
}
|
||||||
|
</MudStack>
|
||||||
<MudDivider />
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
<MudText Typo="Typo.subtitle1">Students (@GetAllStudentsFromTeams().Count)</MudText>
|
<MudItem xs="12" md="6">
|
||||||
<MudPaper Elevation="1" Class="pa-2">
|
<MudText Typo="Typo.subtitle1">Students (@GetAllStudentsFromTeams().Count)</MudText>
|
||||||
<MudStack Row="true" Spacing="1" Wrap="Wrap.Wrap">
|
<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);
|
var presentStudentIds = _meetingHistory.Students.Select(s => s.Id).ToHashSet();
|
||||||
}
|
var allStudents = GetAllStudentsFromTeams().OrderBy(s => s.FirstName);
|
||||||
@foreach (var student in allStudents)
|
}
|
||||||
{
|
@foreach (var student in allStudents)
|
||||||
var isPresent = presentStudentIds.Contains(student.Id);
|
{
|
||||||
<MudChip T="string"
|
var isPresent = presentStudentIds.Contains(student.Id);
|
||||||
Size="Size.Small"
|
<MudChip T="string"
|
||||||
Color="Color.Default"
|
Size="Size.Small"
|
||||||
Variant="@AppIcons.StudentChipVariant()"
|
Color="Color.Default"
|
||||||
Class="mx-1 my-1"
|
Variant="@AppIcons.StudentChipVariant()"
|
||||||
Style="@(!isPresent ? "opacity: 0.5;" : "")">
|
Class="mx-1 my-1"
|
||||||
@student.FirstNameLastName
|
Style="@(!isPresent ? "opacity: 0.5;" : "")">
|
||||||
</MudChip>
|
@student.FirstNameLastName
|
||||||
}
|
</MudChip>
|
||||||
</MudStack>
|
}
|
||||||
</MudPaper>
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
|
||||||
@if (_meetingNote != null)
|
@if (_meetingNote != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,23 +33,26 @@
|
|||||||
|
|
||||||
<MudDivider />
|
<MudDivider />
|
||||||
|
|
||||||
<MudPaper Elevation="1" Class="pa-2" Style="max-height: 300px; overflow-y: auto;">
|
<MudGrid>
|
||||||
<TeamToggleSelector Teams="@AllTeams"
|
<MudItem xs="12" md="6">
|
||||||
SelectedTeams="_selectedTeams"
|
<MudPaper Elevation="1" Class="pa-2" Style="max-height: 300px; overflow-y: auto;">
|
||||||
SelectedTeamsChanged="OnTeamsChanged"
|
<TeamToggleSelector Teams="@AllTeams"
|
||||||
Title="Teams That Met"
|
SelectedTeams="_selectedTeams"
|
||||||
ShowEventAttributes="false" />
|
SelectedTeamsChanged="OnTeamsChanged"
|
||||||
</MudPaper>
|
Title="Teams That Met"
|
||||||
|
ShowEventAttributes="false" />
|
||||||
<MudDivider />
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
<MudPaper Elevation="1" Class="pa-2" Style="max-height: 300px; overflow-y: auto;">
|
<MudItem xs="12" md="6">
|
||||||
<StudentToggleSelector Students="@AllStudents"
|
<MudPaper Elevation="1" Class="pa-2" Style="max-height: 300px; overflow-y: auto;">
|
||||||
SelectedStudents="_selectedStudents"
|
<StudentToggleSelector Students="@AllStudents"
|
||||||
SelectedStudentsChanged="OnStudentsChanged"
|
SelectedStudents="_selectedStudents"
|
||||||
Title="Students Present"
|
SelectedStudentsChanged="OnStudentsChanged"
|
||||||
ShowFullName="true" />
|
Title="Students Present"
|
||||||
</MudPaper>
|
ShowFullName="true" />
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
|
||||||
<MudDivider />
|
<MudDivider />
|
||||||
|
|
||||||
@@ -149,11 +152,28 @@
|
|||||||
|
|
||||||
// Match teams by ID to ensure reference equality with MudToggleGroup
|
// Match teams by ID to ensure reference equality with MudToggleGroup
|
||||||
var selectedTeamIds = existingHistory.Teams.Select(t => t.Id).ToHashSet();
|
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
|
// Match students by ID to ensure reference equality with MudToggleGroup
|
||||||
var selectedStudentIds = existingHistory.Students.Select(s => s.Id).ToHashSet();
|
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
|
// Load existing note if available
|
||||||
var existingNote = await TeamMeetingHistoryService.GetMeetingNoteAsync(existingHistory.MeetingDate);
|
var existingNote = await TeamMeetingHistoryService.GetMeetingNoteAsync(existingHistory.MeetingDate);
|
||||||
@@ -247,6 +267,13 @@
|
|||||||
return;
|
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)
|
// Check if a meeting history already exists for this date (only when creating new, not editing)
|
||||||
if (!_isEditMode)
|
if (!_isEditMode)
|
||||||
{
|
{
|
||||||
@@ -344,10 +371,19 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
meetingHistory = existingHistory;
|
// Ensure we have teams and students - use existing if selected lists are empty (fallback)
|
||||||
meetingHistory.MeetingDate = _meetingDate.Value;
|
var teamsToSave = _selectedTeams.Any() ? _selectedTeams.ToList() : existingHistory.Teams.ToList();
|
||||||
meetingHistory.Teams = _selectedTeams.ToList();
|
var studentsToSave = _selectedStudents.Any() ? _selectedStudents.ToList() : existingHistory.Students.ToList();
|
||||||
meetingHistory.Students = _selectedStudents.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);
|
await TeamMeetingHistoryService.UpdateMeetingHistoryAsync(meetingHistory);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
@namespace WebApp.Components.Features.Teams.Components
|
||||||
|
@using Core.Entities
|
||||||
|
@using WebApp.Services
|
||||||
|
@using MudBlazor
|
||||||
|
@inject ITeamMeetingHistoryService TeamMeetingHistoryService
|
||||||
|
@inject IDialogService DialogService
|
||||||
|
@implements IAsyncDisposable
|
||||||
|
|
||||||
|
@if (_meetingCount.HasValue && _meetingCount.Value > 0)
|
||||||
|
{
|
||||||
|
<MudTooltip Text="View Meeting History">
|
||||||
|
<MudBadge Content="@_meetingCount.Value.ToString()"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Overlap="true">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.History"
|
||||||
|
Size="Size.Small"
|
||||||
|
Color="Color.Default"
|
||||||
|
OnClick="OpenMeetingHistoryDialog"
|
||||||
|
Variant="Variant.Text" />
|
||||||
|
</MudBadge>
|
||||||
|
</MudTooltip>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public int TeamId { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string TeamName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
private int? _meetingCount;
|
||||||
|
private CancellationTokenSource? _cancellationTokenSource;
|
||||||
|
private bool _isDisposed = false;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await LoadMeetingCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadMeetingCount()
|
||||||
|
{
|
||||||
|
if (_isDisposed) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cancellationToken = _cancellationTokenSource?.Token ?? CancellationToken.None;
|
||||||
|
_meetingCount = await TeamMeetingHistoryService.GetMeetingHistoryCountForTeamAsync(TeamId);
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
// Component was disposed, ignore
|
||||||
|
}
|
||||||
|
catch (JSDisconnectedException)
|
||||||
|
{
|
||||||
|
// JS connection lost, ignore
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Ignore errors - just don't show the badge
|
||||||
|
_meetingCount = null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OpenMeetingHistoryDialog()
|
||||||
|
{
|
||||||
|
if (_isDisposed) return;
|
||||||
|
|
||||||
|
var parameters = new DialogParameters
|
||||||
|
{
|
||||||
|
["TeamId"] = TeamId,
|
||||||
|
["TeamName"] = TeamName
|
||||||
|
};
|
||||||
|
|
||||||
|
var options = new DialogOptions
|
||||||
|
{
|
||||||
|
CloseOnEscapeKey = true,
|
||||||
|
CloseButton = true,
|
||||||
|
MaxWidth = MaxWidth.Medium,
|
||||||
|
FullWidth = true
|
||||||
|
};
|
||||||
|
|
||||||
|
await DialogService.ShowAsync<TeamMeetingHistoryDialog>($"{TeamName} Meeting History", parameters, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
_isDisposed = true;
|
||||||
|
_cancellationTokenSource?.Cancel();
|
||||||
|
_cancellationTokenSource?.Dispose();
|
||||||
|
_cancellationTokenSource = null;
|
||||||
|
}
|
||||||
|
await ValueTask.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
@using WebApp.Models
|
@using WebApp.Models
|
||||||
@using Core.Utility
|
@using Core.Utility
|
||||||
|
@using WebApp.Components.Features.Teams.Components
|
||||||
|
|
||||||
@if (Title != null)
|
@if (Title != null)
|
||||||
{
|
{
|
||||||
@@ -28,14 +29,17 @@
|
|||||||
@if (IsSelected(team))
|
@if (IsSelected(team))
|
||||||
{
|
{
|
||||||
var isExtended = IsExtended(team);
|
var isExtended = IsExtended(team);
|
||||||
<MudTooltip Text="@(isExtended ? "Remove from extended teams" : "Extend to 2 time slots")">
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||||
<MudIconButton Icon="@(isExtended ? Icons.Material.Filled.AddCircle : Icons.Material.Filled.Add)"
|
<MudTooltip Text="@(isExtended ? "Remove from extended teams" : "Extend to 2 time slots")">
|
||||||
Size="Size.Small"
|
<MudIconButton Icon="@(isExtended ? Icons.Material.Filled.AddCircle : Icons.Material.Filled.Add)"
|
||||||
Color="@(isExtended ? Color.Primary : Color.Default)"
|
Size="Size.Small"
|
||||||
Variant="@(isExtended ? Variant.Filled : Variant.Text)"
|
Color="@(isExtended ? Color.Primary : Color.Default)"
|
||||||
OnClick="@(() => ToggleExtended(team))"
|
Variant="@(isExtended ? Variant.Filled : Variant.Text)"
|
||||||
Style="margin-left: 4px; padding: 2px;" />
|
OnClick="@(() => ToggleExtended(team))"
|
||||||
</MudTooltip>
|
Style="margin-left: 4px; padding: 2px;" />
|
||||||
|
</MudTooltip>
|
||||||
|
<TeamMeetingHistoryBadge TeamId="@team.Id" TeamName="@team.ToString()" />
|
||||||
|
</MudStack>
|
||||||
}
|
}
|
||||||
@if (ShowEventAttributes)
|
@if (ShowEventAttributes)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,9 +3,11 @@
|
|||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
@using WebApp.Components.Shared.Components
|
@using WebApp.Components.Shared.Components
|
||||||
@using WebApp.Components.Features.Teams.Components
|
@using WebApp.Components.Features.Teams.Components
|
||||||
|
@using MudBlazor
|
||||||
@inject AppDbContext Context
|
@inject AppDbContext Context
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IJSRuntime JSRuntime
|
@inject IJSRuntime JSRuntime
|
||||||
|
@inject IDialogService DialogService
|
||||||
|
|
||||||
@if (Team is null)
|
@if (Team is null)
|
||||||
{
|
{
|
||||||
@@ -29,6 +31,11 @@
|
|||||||
Href="@($"/teams/edit?id={Team.Id}&returnUrl={ReturnUrl ?? "/teams"}")"
|
Href="@($"/teams/edit?id={Team.Id}&returnUrl={ReturnUrl ?? "/teams"}")"
|
||||||
Variant="Variant.Outlined">Edit</MudButton>
|
Variant="Variant.Outlined">Edit</MudButton>
|
||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
|
<MudTooltip Text="View Meeting History">
|
||||||
|
<MudButton StartIcon="@Icons.Material.Filled.History"
|
||||||
|
OnClick="ShowMeetingHistory"
|
||||||
|
Variant="Variant.Outlined">Meeting History</MudButton>
|
||||||
|
</MudTooltip>
|
||||||
</div>
|
</div>
|
||||||
</ActionButtons>
|
</ActionButtons>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
@@ -86,4 +93,26 @@
|
|||||||
{
|
{
|
||||||
await JSRuntime.InvokeVoidAsync("window.print");
|
await JSRuntime.InvokeVoidAsync("window.print");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ShowMeetingHistory()
|
||||||
|
{
|
||||||
|
if (Team == null) return;
|
||||||
|
|
||||||
|
var teamName = Team.ToString();
|
||||||
|
var parameters = new DialogParameters
|
||||||
|
{
|
||||||
|
["TeamId"] = Team.Id,
|
||||||
|
["TeamName"] = teamName
|
||||||
|
};
|
||||||
|
|
||||||
|
var options = new DialogOptions
|
||||||
|
{
|
||||||
|
CloseOnEscapeKey = true,
|
||||||
|
CloseButton = true,
|
||||||
|
MaxWidth = MaxWidth.Medium,
|
||||||
|
FullWidth = true
|
||||||
|
};
|
||||||
|
|
||||||
|
await DialogService.ShowAsync<TeamMeetingHistoryDialog>($"{teamName} Meeting History", parameters, options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,11 +47,12 @@
|
|||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Spacing="1">
|
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Spacing="1">
|
||||||
<MudLink Href="@($"/teams/details?id={context.Item.Id}&returnUrl=/teams")"
|
<MudLink Href="@($"/teams/details?id={context.Item.Id}&returnUrl=/teams")"
|
||||||
Underline="Underline.Hover"
|
Underline="Underline.Hover"
|
||||||
Color="Color.Primary">
|
Color="Color.Primary">
|
||||||
@context.Item.ToString()
|
@context.Item.ToString()
|
||||||
</MudLink>
|
</MudLink>
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||||
|
<TeamMeetingHistoryBadge TeamId="@context.Item.Id" TeamName="@context.Item.ToString()" />
|
||||||
<IconButtonWithTooltip Icon="@Icons.Material.Filled.Edit"
|
<IconButtonWithTooltip Icon="@Icons.Material.Filled.Edit"
|
||||||
TooltipText="Edit"
|
TooltipText="Edit"
|
||||||
Href="@($"/teams/edit?id={context.Item.Id}&returnUrl=/teams")" />
|
Href="@($"/teams/edit?id={context.Item.Id}&returnUrl=/teams")" />
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
@namespace WebApp.Components.Features.Teams
|
||||||
|
@using Core.Entities
|
||||||
|
@using WebApp.Services
|
||||||
|
@using Microsoft.AspNetCore.Components
|
||||||
|
@using WebApp.Models
|
||||||
|
@using WebApp.Components.Shared.Components
|
||||||
|
@using Core.Utility
|
||||||
|
@inject ITeamMeetingHistoryService TeamMeetingHistoryService
|
||||||
|
@implements IAsyncDisposable
|
||||||
|
|
||||||
|
<MudDialog>
|
||||||
|
<TitleContent>
|
||||||
|
<MudText Typo="Typo.h6">@TeamName Meeting History</MudText>
|
||||||
|
</TitleContent>
|
||||||
|
<DialogContent>
|
||||||
|
@if (_isLoading)
|
||||||
|
{
|
||||||
|
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="my-4" />
|
||||||
|
}
|
||||||
|
else if (!_meetingHistories.Any())
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info">No meeting history found for this team.</MudAlert>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudTable Items="_meetingHistories" Hover="true" Dense="true">
|
||||||
|
<HeaderContent>
|
||||||
|
<MudTh>Date</MudTh>
|
||||||
|
<MudTh>Team Members</MudTh>
|
||||||
|
</HeaderContent>
|
||||||
|
<RowTemplate>
|
||||||
|
@{
|
||||||
|
var team = context.Teams.FirstOrDefault(t => t.Id == TeamId);
|
||||||
|
var presentStudentIds = context.Students.Select(s => s.Id).ToHashSet();
|
||||||
|
var absentStudents = team?.Students.Where(s => !presentStudentIds.Contains(s.Id)).ToList() ?? [];
|
||||||
|
}
|
||||||
|
<MudTd>@context.MeetingDate.ToString("MM/dd/yyyy")</MudTd>
|
||||||
|
<MudTd>
|
||||||
|
@if (team != null)
|
||||||
|
{
|
||||||
|
<MudStack Row="true" Spacing="1" Wrap="Wrap.Wrap" AlignItems="AlignItems.Center">
|
||||||
|
@foreach (var student in team.Students)
|
||||||
|
{
|
||||||
|
var isAbsent = absentStudents.Contains(student);
|
||||||
|
var formattedName = TeamStudentNameFormatter.FormatStudentName(
|
||||||
|
student,
|
||||||
|
team,
|
||||||
|
new TeamStudentNameFormatter.FormatOptions
|
||||||
|
{
|
||||||
|
MarkAbsent = true,
|
||||||
|
AbsentStudents = absentStudents
|
||||||
|
});
|
||||||
|
<MudChip T="string"
|
||||||
|
Size="Size.Small"
|
||||||
|
Color="Color.Default"
|
||||||
|
Variant="@AppIcons.StudentChipVariant()"
|
||||||
|
Style="@(isAbsent ? "opacity: 0.5;" : "")">
|
||||||
|
<span>@formattedName</span>
|
||||||
|
</MudChip>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
</MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
</MudTable>
|
||||||
|
}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<MudSpacer />
|
||||||
|
<MudButton OnClick="Close">Close</MudButton>
|
||||||
|
</DialogActions>
|
||||||
|
</MudDialog>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[CascadingParameter]
|
||||||
|
IMudDialogInstance MudDialog { get; set; } = null!;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public int TeamId { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string TeamName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
private List<TeamMeetingHistory> _meetingHistories = [];
|
||||||
|
private bool _isLoading = true;
|
||||||
|
private CancellationTokenSource? _cancellationTokenSource;
|
||||||
|
private bool _isDisposed = false;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await LoadMeetingHistories();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadMeetingHistories()
|
||||||
|
{
|
||||||
|
if (_isDisposed) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cancellationToken = _cancellationTokenSource?.Token ?? CancellationToken.None;
|
||||||
|
var histories = await TeamMeetingHistoryService.GetMeetingHistoriesForTeamAsync(TeamId);
|
||||||
|
_meetingHistories = histories.ToList();
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
// Component was disposed, ignore
|
||||||
|
}
|
||||||
|
catch (JSDisconnectedException)
|
||||||
|
{
|
||||||
|
// JS connection lost, ignore
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Error loading meeting histories: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
_isLoading = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Close()
|
||||||
|
{
|
||||||
|
if (_isDisposed) return;
|
||||||
|
MudDialog.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
_isDisposed = true;
|
||||||
|
_cancellationTokenSource?.Cancel();
|
||||||
|
_cancellationTokenSource?.Dispose();
|
||||||
|
_cancellationTokenSource = null;
|
||||||
|
}
|
||||||
|
await ValueTask.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,10 +5,14 @@
|
|||||||
@using Core.Entities
|
@using Core.Entities
|
||||||
@using WebApp.Services
|
@using WebApp.Services
|
||||||
@using WebApp.Components.Shared.Components
|
@using WebApp.Components.Shared.Components
|
||||||
|
@using Heron.MudCalendar
|
||||||
@inject IConfiguration Configuration
|
@inject IConfiguration Configuration
|
||||||
@inject AppDbContext Context
|
@inject AppDbContext Context
|
||||||
@inject INotesService NotesService
|
@inject INotesService NotesService
|
||||||
@inject IDialogService DialogService
|
@inject IDialogService DialogService
|
||||||
|
@inject ITeamMeetingHistoryService TeamMeetingHistoryService
|
||||||
|
@inject ICalendarService CalendarService
|
||||||
|
@inject NavigationManager Navigation
|
||||||
@implements IAsyncDisposable
|
@implements IAsyncDisposable
|
||||||
|
|
||||||
<PageTitle>@Configuration["ChapterSettings:Name"] - TSA Chapter Organizer</PageTitle>
|
<PageTitle>@Configuration["ChapterSettings:Name"] - TSA Chapter Organizer</PageTitle>
|
||||||
@@ -124,13 +128,76 @@ else
|
|||||||
<MudGrid>
|
<MudGrid>
|
||||||
<DashboardCard Icon="@AppIcons.Scheduler"
|
<DashboardCard Icon="@AppIcons.Scheduler"
|
||||||
Title="Meeting Schedule Planner"
|
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"
|
<DashboardCard Icon="@AppIcons.EventCalendar"
|
||||||
Title="Event Calendar"
|
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>
|
</MudGrid>
|
||||||
|
|
||||||
<MudPaper Elevation="0" Class="my-4">
|
<MudPaper Elevation="0" Class="my-4">
|
||||||
@@ -150,21 +217,6 @@ else
|
|||||||
NavigateUrl="/students/teams"/>
|
NavigateUrl="/students/teams"/>
|
||||||
</MudGrid>
|
</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">
|
<MudPaper Elevation="0" Class="my-4">
|
||||||
<MudText Typo="Typo.h4">Chapter Data</MudText>
|
<MudText Typo="Typo.h4">Chapter Data</MudText>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
@@ -189,6 +241,21 @@ else
|
|||||||
Caption="@($"{_teamEventsCount} Team | {_individualEventsCount} Individual")"
|
Caption="@($"{_teamEventsCount} Team | {_individualEventsCount} Individual")"
|
||||||
NavigateUrl="/events" />
|
NavigateUrl="/events" />
|
||||||
</MudGrid>
|
</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 {
|
@code {
|
||||||
@@ -200,6 +267,9 @@ else
|
|||||||
private int _teamCount;
|
private int _teamCount;
|
||||||
private int _individualTeamsCount;
|
private int _individualTeamsCount;
|
||||||
private int _groupTeamsCount;
|
private int _groupTeamsCount;
|
||||||
|
private int _meetingHistoryCount;
|
||||||
|
private List<TeamMeetingHistory> _recentMeetings = [];
|
||||||
|
private List<CalendarItemWrapper> _nextCalendarItems = [];
|
||||||
private List<Note> _pinnedNotes = [];
|
private List<Note> _pinnedNotes = [];
|
||||||
private CancellationTokenSource? _cancellationTokenSource;
|
private CancellationTokenSource? _cancellationTokenSource;
|
||||||
private bool _isDisposed = false;
|
private bool _isDisposed = false;
|
||||||
@@ -216,6 +286,8 @@ else
|
|||||||
{
|
{
|
||||||
await LoadStatistics();
|
await LoadStatistics();
|
||||||
await LoadPinnedNotes();
|
await LoadPinnedNotes();
|
||||||
|
await LoadMeetingHistoryCount();
|
||||||
|
await LoadNextCalendarItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleNoteChanged()
|
private async Task HandleNoteChanged()
|
||||||
@@ -285,6 +357,85 @@ else
|
|||||||
_groupTeamsCount = contextTeams.Count(e => e.Event.EventFormat == EventFormat.Team);
|
_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()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
if (!_isDisposed)
|
if (!_isDisposed)
|
||||||
|
|||||||
@@ -18,16 +18,16 @@
|
|||||||
<MudNavLink Href="/students/teams" Icon="@AppIcons.Registration">Registration</MudNavLink>
|
<MudNavLink Href="/students/teams" Icon="@AppIcons.Registration">Registration</MudNavLink>
|
||||||
</MudNavGroup>
|
</MudNavGroup>
|
||||||
|
|
||||||
<MudNavGroup Title="Team Building" Icon="@Icons.Material.Filled.GroupAdd" Expanded="false">
|
|
||||||
<MudNavLink Href="/students/event-ranking" Icon="@AppIcons.EventRank">Event Ranking</MudNavLink>
|
|
||||||
<MudNavLink Href="/teams/assignment" Icon="@AppIcons.TeamAssignment">Team Assignment</MudNavLink>
|
|
||||||
</MudNavGroup>
|
|
||||||
|
|
||||||
<MudNavGroup Title="Chapter Data" Icon="@Icons.Material.Filled.Storage" Expanded="false">
|
<MudNavGroup Title="Chapter Data" Icon="@Icons.Material.Filled.Storage" Expanded="false">
|
||||||
<MudNavLink Href="/students" Icon="@Icons.Material.Filled.People">Students</MudNavLink>
|
<MudNavLink Href="/students" Icon="@Icons.Material.Filled.People">Students</MudNavLink>
|
||||||
<MudNavLink Href="/events" Icon="@AppIcons.Events">Events</MudNavLink>
|
<MudNavLink Href="/events" Icon="@AppIcons.Events">Events</MudNavLink>
|
||||||
</MudNavGroup>
|
</MudNavGroup>
|
||||||
|
|
||||||
|
<MudNavGroup Title="Team Building" Icon="@Icons.Material.Filled.GroupAdd" Expanded="false">
|
||||||
|
<MudNavLink Href="/students/event-ranking" Icon="@AppIcons.EventRank">Event Ranking</MudNavLink>
|
||||||
|
<MudNavLink Href="/teams/assignment" Icon="@AppIcons.TeamAssignment">Team Assignment</MudNavLink>
|
||||||
|
</MudNavGroup>
|
||||||
|
|
||||||
<MudNavGroup Title="Tools" Icon="@Icons.Material.Filled.Build" Expanded="false">
|
<MudNavGroup Title="Tools" Icon="@Icons.Material.Filled.Build" Expanded="false">
|
||||||
<MudNavLink Href="/notes" Icon="@Icons.Material.Filled.Note">Notes</MudNavLink>
|
<MudNavLink Href="/notes" Icon="@Icons.Material.Filled.Note">Notes</MudNavLink>
|
||||||
</MudNavGroup>
|
</MudNavGroup>
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ public class CalendarMeetingItem : CalendarItem
|
|||||||
MeetingHistoryData = meetingHistory;
|
MeetingHistoryData = meetingHistory;
|
||||||
// Set base class properties that the calendar component uses
|
// Set base class properties that the calendar component uses
|
||||||
Text = "Team Meeting";
|
Text = "Team Meeting";
|
||||||
// Use meeting date at 3:00 PM as default time
|
// Set start to 9:00 AM on the meeting date
|
||||||
Start = meetingHistory.MeetingDate.Date.AddHours(15);
|
Start = meetingHistory.MeetingDate.Date.AddHours(9);
|
||||||
// Default to 1 hour duration
|
// Set end to 5:00 PM on the meeting date (5:01 PM to ensure it displays until 5pm if end is exclusive)
|
||||||
End = Start.AddHours(1);
|
End = meetingHistory.MeetingDate.Date.AddHours(17).AddMinutes(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,6 +195,7 @@ builder.Services.AddScoped<WebApp.Services.FormValidationService>();
|
|||||||
builder.Services.AddScoped<WebApp.Services.EventDefinitionService>();
|
builder.Services.AddScoped<WebApp.Services.EventDefinitionService>();
|
||||||
builder.Services.AddScoped<WebApp.Services.INotesService, WebApp.Services.NotesService>();
|
builder.Services.AddScoped<WebApp.Services.INotesService, WebApp.Services.NotesService>();
|
||||||
builder.Services.AddScoped<WebApp.Services.ITeamMeetingHistoryService, WebApp.Services.TeamMeetingHistoryService>();
|
builder.Services.AddScoped<WebApp.Services.ITeamMeetingHistoryService, WebApp.Services.TeamMeetingHistoryService>();
|
||||||
|
builder.Services.AddScoped<WebApp.Services.ICalendarService, WebApp.Services.CalendarService>();
|
||||||
builder.Services.AddScoped<Core.Services.INoteNamingService, Core.Services.NoteNamingService>();
|
builder.Services.AddScoped<Core.Services.INoteNamingService, Core.Services.NoteNamingService>();
|
||||||
builder.Services.AddScoped<WebApp.Services.MarkdownTablePasteService>();
|
builder.Services.AddScoped<WebApp.Services.MarkdownTablePasteService>();
|
||||||
builder.Services.AddScoped<WebApp.Services.IMeetingScheduleStateService, WebApp.Services.MeetingScheduleStateService>();
|
builder.Services.AddScoped<WebApp.Services.IMeetingScheduleStateService, WebApp.Services.MeetingScheduleStateService>();
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
using Core.Entities;
|
||||||
|
using Core.Utility;
|
||||||
|
using WebApp.Models;
|
||||||
|
|
||||||
|
namespace WebApp.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service for calendar-related operations.
|
||||||
|
/// </summary>
|
||||||
|
public class CalendarService : ICalendarService
|
||||||
|
{
|
||||||
|
private readonly IEventOccurrenceService _eventOccurrenceService;
|
||||||
|
private readonly ITeamMeetingHistoryService _teamMeetingHistoryService;
|
||||||
|
|
||||||
|
public CalendarService(
|
||||||
|
IEventOccurrenceService eventOccurrenceService,
|
||||||
|
ITeamMeetingHistoryService teamMeetingHistoryService)
|
||||||
|
{
|
||||||
|
_eventOccurrenceService = eventOccurrenceService;
|
||||||
|
_teamMeetingHistoryService = teamMeetingHistoryService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<CalendarItemWrapper>> GetAllCalendarItemsAsync()
|
||||||
|
{
|
||||||
|
var items = new List<CalendarItemWrapper>();
|
||||||
|
await AddEventItemsAsync(items, includeStudentNames: true);
|
||||||
|
await AddMeetingItemsAsync(items);
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<CalendarItemWrapper>> GetUpcomingCalendarItemsAsync(int count = 3)
|
||||||
|
{
|
||||||
|
var today = DateTime.Today;
|
||||||
|
var items = new List<CalendarItemWrapper>();
|
||||||
|
await AddEventItemsAsync(items, startDate: today, includeStudentNames: false);
|
||||||
|
await AddMeetingItemsAsync(items, startDate: today);
|
||||||
|
|
||||||
|
// Sort by date and take top N
|
||||||
|
return items
|
||||||
|
.OrderBy(item => item.Start)
|
||||||
|
.Take(count)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddEventItemsAsync(
|
||||||
|
List<CalendarItemWrapper> items,
|
||||||
|
DateTime? startDate = null,
|
||||||
|
bool includeStudentNames = false)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var occurrences = await _eventOccurrenceService.GetEventOccurrencesAsync();
|
||||||
|
var eventOccurrences = occurrences as EventOccurrence[] ?? occurrences.ToArray();
|
||||||
|
|
||||||
|
// Filter by start date if provided
|
||||||
|
if (startDate.HasValue)
|
||||||
|
{
|
||||||
|
eventOccurrences = eventOccurrences
|
||||||
|
.Where(occ => occ.StartTime.Date >= startDate.Value)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<int, List<Team>>? teamsByEventId = null;
|
||||||
|
if (includeStudentNames)
|
||||||
|
{
|
||||||
|
// 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
|
||||||
|
teamsByEventId = await _eventOccurrenceService.GetTeamsByEventDefinitionIdsAsync(eventDefinitionIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add event occurrences
|
||||||
|
foreach (var occ in eventOccurrences)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
List<string>? studentFirstNames = null;
|
||||||
|
if (includeStudentNames && occ.EventDefinition != null && teamsByEventId != null)
|
||||||
|
{
|
||||||
|
studentFirstNames = 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)
|
||||||
|
{
|
||||||
|
// Continue processing other items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Continue - don't fail the entire calendar load if events fail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddMeetingItemsAsync(
|
||||||
|
List<CalendarItemWrapper> items,
|
||||||
|
DateTime? startDate = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var meetingHistories = await _teamMeetingHistoryService.GetMeetingHistoriesAsync();
|
||||||
|
|
||||||
|
IEnumerable<TeamMeetingHistory> meetings = meetingHistories;
|
||||||
|
if (startDate.HasValue)
|
||||||
|
{
|
||||||
|
meetings = meetings.Where(m => m.MeetingDate.Date >= startDate.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var meetingHistory in meetings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var calendarMeetingItem = new CalendarMeetingItem(meetingHistory);
|
||||||
|
items.Add(new CalendarItemWrapper(calendarMeetingItem));
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Continue processing other items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Continue - don't fail the entire calendar load if meetings fail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using Core.Entities;
|
||||||
|
using WebApp.Models;
|
||||||
|
|
||||||
|
namespace WebApp.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service for calendar-related operations.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICalendarService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all calendar items (events and meetings) for display in the calendar.
|
||||||
|
/// Includes student names for events.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A list of CalendarItemWrapper objects ready for display.</returns>
|
||||||
|
Task<List<CalendarItemWrapper>> GetAllCalendarItemsAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the next N upcoming calendar items (events and meetings) starting from today.
|
||||||
|
/// Returns CalendarItemWrapper objects ready for display.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="count">The maximum number of items to return. Default is 3.</param>
|
||||||
|
/// <returns>A list of CalendarItemWrapper objects ready for display.</returns>
|
||||||
|
Task<List<CalendarItemWrapper>> GetUpcomingCalendarItemsAsync(int count = 3);
|
||||||
|
}
|
||||||
@@ -47,4 +47,19 @@ public interface ITeamMeetingHistoryService
|
|||||||
/// <param name="meetingDate">The date of the meeting</param>
|
/// <param name="meetingDate">The date of the meeting</param>
|
||||||
/// <returns>The note if found, null otherwise</returns>
|
/// <returns>The note if found, null otherwise</returns>
|
||||||
Task<Note?> GetMeetingNoteAsync(DateTime meetingDate);
|
Task<Note?> GetMeetingNoteAsync(DateTime meetingDate);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all meeting histories for a specific team, ordered by date descending.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="teamId">The ID of the team</param>
|
||||||
|
/// <returns>Meeting histories for the team, ordered by date descending</returns>
|
||||||
|
Task<IEnumerable<TeamMeetingHistory>> GetMeetingHistoriesForTeamAsync(int teamId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the count of meeting histories for a specific team.
|
||||||
|
/// This is more efficient than loading all histories just to count them.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="teamId">The ID of the team</param>
|
||||||
|
/// <returns>The number of meeting histories for the team</returns>
|
||||||
|
Task<int> GetMeetingHistoryCountForTeamAsync(int teamId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -202,4 +202,27 @@ public class TeamMeetingHistoryService : ITeamMeetingHistoryService
|
|||||||
.Distinct()
|
.Distinct()
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<TeamMeetingHistory>> GetMeetingHistoriesForTeamAsync(int teamId)
|
||||||
|
{
|
||||||
|
return await _context.TeamMeetingHistories
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(tmh => tmh.Teams)
|
||||||
|
.ThenInclude(t => t.Event)
|
||||||
|
.Include(tmh => tmh.Teams)
|
||||||
|
.ThenInclude(t => t.Students)
|
||||||
|
.Include(tmh => tmh.Students)
|
||||||
|
.Where(tmh => tmh.Teams.Any(t => t.Id == teamId))
|
||||||
|
.OrderByDescending(tmh => tmh.MeetingDate)
|
||||||
|
.ThenByDescending(tmh => tmh.Id)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetMeetingHistoryCountForTeamAsync(int teamId)
|
||||||
|
{
|
||||||
|
return await _context.TeamMeetingHistories
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(tmh => tmh.Teams.Any(t => t.Id == teamId))
|
||||||
|
.CountAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -265,4 +265,43 @@
|
|||||||
|
|
||||||
.note-color-2 {
|
.note-color-2 {
|
||||||
background-color: #f3e5f5;
|
background-color: #f3e5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calendar event item styling */
|
||||||
|
.calendar-event-item {
|
||||||
|
background-color: var(--mud-palette-primary);
|
||||||
|
color: var(--mud-palette-primary-text);
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
display: block !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure tooltip wrapper doesn't constrain width or height and adds spacing */
|
||||||
|
.event-calendar .mud-tooltip-root {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
padding: 2px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-event-item:hover {
|
||||||
|
background-color: var(--mud-palette-primary-darken);
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-event-item:active {
|
||||||
|
background-color: var(--mud-palette-primary-darken);
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user