From 99880e78c7cf287df372cb739204d7532b43a632 Mon Sep 17 00:00:00 2001 From: James Kolpack Date: Fri, 9 Jan 2026 11:22:13 -0500 Subject: [PATCH] Add admin calendar management page for event occurrences This commit introduces a new Admin.razor page for managing event occurrences within the calendar feature. The page includes functionality to display statistics about event occurrences, such as total counts and potential duplicates, and allows administrators to delete occurrences by specifying a date range. This enhancement improves the administrative capabilities of the calendar feature, providing a dedicated interface for data management. --- .../Components/Features/Calendar/Admin.razor | 233 ++++++++++++++++++ .../Components/Features/Calendar/Index.razor | 3 + 2 files changed, 236 insertions(+) create mode 100644 WebApp/Components/Features/Calendar/Admin.razor diff --git a/WebApp/Components/Features/Calendar/Admin.razor b/WebApp/Components/Features/Calendar/Admin.razor new file mode 100644 index 0000000..500be5e --- /dev/null +++ b/WebApp/Components/Features/Calendar/Admin.razor @@ -0,0 +1,233 @@ +@page "/calendar/admin" +@attribute [Authorize(Roles = AuthRoles.Administrator)] +@using Core.Entities +@using Microsoft.EntityFrameworkCore +@using WebApp.Components.Shared.Components +@using WebApp.Authentication +@using MudBlazor +@inject AppDbContext Context +@inject ISnackbar Snackbar +@inject IDialogService DialogService + + + + + + + Statistics + + @if (_statistics == null) + { + + Loading statistics... + } + else + { + + Total Occurrences: @_statistics.TotalCount + + @if (_statistics.TotalCount > 0) + { + + Date Range: @_statistics.EarliestDate.ToString("MMM dd, yyyy") - @_statistics.LatestDate.ToString("MMM dd, yyyy") + + + + Unique Dates: @_statistics.UniqueDateCount + + + + Events with Occurrences: @_statistics.EventDefinitionCount + + + + Special Events: @_statistics.SpecialEventCount + + + @if (_statistics.DuplicateCount > 0) + { + + Found @_statistics.DuplicateCount potential duplicate occurrence(s) + + } + } + else + { + No event occurrences in database + } + + } + + + + + + Delete by Date Range + + + + + + + + Delete Occurrences in Range + + + @if (_isDeleting) + { + + Deleting occurrences... + } + + + + + +@code { + private CalendarStatistics? _statistics; + private DateTime? _deleteStartDate; + private DateTime? _deleteEndDate; + private bool _isDeleting = false; + + protected override async Task OnInitializedAsync() + { + await LoadStatistics(); + } + + private async Task LoadStatistics() + { + try + { + var occurrences = await Context.EventOccurrences + .Include(eo => eo.EventDefinition) + .ToListAsync(); + + _statistics = new CalendarStatistics + { + TotalCount = occurrences.Count, + EarliestDate = occurrences.Any() ? occurrences.Min(eo => eo.StartTime.Date) : DateTime.Today, + LatestDate = occurrences.Any() ? occurrences.Max(eo => eo.StartTime.Date) : DateTime.Today, + UniqueDateCount = occurrences.Select(eo => eo.StartTime.Date).Distinct().Count(), + EventDefinitionCount = occurrences.Where(eo => eo.EventDefinitionId.HasValue).Select(eo => eo.EventDefinitionId).Distinct().Count(), + SpecialEventCount = occurrences.Where(eo => !string.IsNullOrEmpty(eo.SpecialEventType)).Count(), + DuplicateCount = CountDuplicates(occurrences) + }; + } + catch (Exception ex) + { + Snackbar.Add($"Error loading statistics: {ex.Message}", Severity.Error); + _statistics = new CalendarStatistics(); + } + } + + private int CountDuplicates(List occurrences) + { + // Count occurrences that have the same name, date, time, and location + return occurrences + .GroupBy(eo => new + { + eo.Name, + Date = eo.StartTime.Date, + Time = eo.Time, + eo.Location, + eo.EventDefinitionId, + eo.SpecialEventType + }) + .Where(g => g.Count() > 1) + .Sum(g => g.Count() - 1); // Count duplicates (total - 1 per group) + } + + private async Task HandleDeleteByDateRange() + { + if (_deleteStartDate == null || _deleteEndDate == null) + { + Snackbar.Add("Please select both start and end dates", Severity.Warning); + return; + } + + if (_deleteStartDate > _deleteEndDate) + { + Snackbar.Add("Start date must be before end date", Severity.Warning); + return; + } + + // Count occurrences that will be deleted + var countToDelete = await Context.EventOccurrences + .Where(eo => eo.StartTime.Date >= _deleteStartDate.Value.Date && + eo.StartTime.Date <= _deleteEndDate.Value.Date) + .CountAsync(); + + if (countToDelete == 0) + { + Snackbar.Add("No occurrences found in the specified date range", Severity.Info); + return; + } + + // Confirm deletion + var result = await DialogService.ShowMessageBox( + "Confirm Deletion", + $"Are you sure you want to delete {countToDelete} occurrence(s) from {_deleteStartDate.Value:MMM dd, yyyy} to {_deleteEndDate.Value:MMM dd, yyyy}? This action cannot be undone.", + yesText: "Delete", + noText: "Cancel"); + + if (result == true) + { + _isDeleting = true; + try + { + var occurrencesToDelete = await Context.EventOccurrences + .Where(eo => eo.StartTime.Date >= _deleteStartDate.Value.Date && + eo.StartTime.Date <= _deleteEndDate.Value.Date) + .ToListAsync(); + + Context.EventOccurrences.RemoveRange(occurrencesToDelete); + await Context.SaveChangesAsync(); + + Snackbar.Add($"Successfully deleted {occurrencesToDelete.Count} occurrence(s)", Severity.Success); + + // Reload statistics + await LoadStatistics(); + + // Clear date pickers + _deleteStartDate = null; + _deleteEndDate = null; + } + catch (Exception ex) + { + Snackbar.Add($"Error deleting occurrences: {ex.Message}", Severity.Error); + } + finally + { + _isDeleting = false; + } + } + } + + private class CalendarStatistics + { + public int TotalCount { get; set; } + public DateTime EarliestDate { get; set; } + public DateTime LatestDate { get; set; } + public int UniqueDateCount { get; set; } + public int EventDefinitionCount { get; set; } + public int SpecialEventCount { get; set; } + public int DuplicateCount { get; set; } + } +} + diff --git a/WebApp/Components/Features/Calendar/Index.razor b/WebApp/Components/Features/Calendar/Index.razor index 5998030..fc7b4fe 100644 --- a/WebApp/Components/Features/Calendar/Index.razor +++ b/WebApp/Components/Features/Calendar/Index.razor @@ -11,6 +11,9 @@ Import + + Admin +