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.
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
<PageHeader
|
||||||
|
Title="Calendar Data Management"
|
||||||
|
Description="Manage and clean event occurrence data"
|
||||||
|
Icon="@Icons.Material.Filled.AdminPanelSettings"
|
||||||
|
ShowBackButton="true"
|
||||||
|
BackButtonUrl="/calendar" />
|
||||||
|
|
||||||
|
<MudGrid>
|
||||||
|
<MudItem xs="12" md="6">
|
||||||
|
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
|
||||||
|
<MudText Typo="Typo.h5" Class="mb-4">Statistics</MudText>
|
||||||
|
|
||||||
|
@if (_statistics == null)
|
||||||
|
{
|
||||||
|
<MudProgressLinear Indeterminate="true" />
|
||||||
|
<MudText>Loading statistics...</MudText>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudStack Spacing="3">
|
||||||
|
<MudText Typo="Typo.h6">Total Occurrences: @_statistics.TotalCount</MudText>
|
||||||
|
|
||||||
|
@if (_statistics.TotalCount > 0)
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body1">
|
||||||
|
Date Range: @_statistics.EarliestDate.ToString("MMM dd, yyyy") - @_statistics.LatestDate.ToString("MMM dd, yyyy")
|
||||||
|
</MudText>
|
||||||
|
|
||||||
|
<MudText Typo="Typo.body1">
|
||||||
|
Unique Dates: @_statistics.UniqueDateCount
|
||||||
|
</MudText>
|
||||||
|
|
||||||
|
<MudText Typo="Typo.body1">
|
||||||
|
Events with Occurrences: @_statistics.EventDefinitionCount
|
||||||
|
</MudText>
|
||||||
|
|
||||||
|
<MudText Typo="Typo.body1">
|
||||||
|
Special Events: @_statistics.SpecialEventCount
|
||||||
|
</MudText>
|
||||||
|
|
||||||
|
@if (_statistics.DuplicateCount > 0)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Warning" Dense="true">
|
||||||
|
Found @_statistics.DuplicateCount potential duplicate occurrence(s)
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body1" Color="Color.Secondary">No event occurrences in database</MudText>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
<MudItem xs="12" md="6">
|
||||||
|
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
|
||||||
|
<MudText Typo="Typo.h5" Class="mb-4">Delete by Date Range</MudText>
|
||||||
|
|
||||||
|
<MudStack Spacing="3">
|
||||||
|
<MudDatePicker
|
||||||
|
Label="Start Date"
|
||||||
|
@bind-Date="_deleteStartDate"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
|
||||||
|
<MudDatePicker
|
||||||
|
Label="End Date"
|
||||||
|
@bind-Date="_deleteEndDate"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
|
||||||
|
<MudButton
|
||||||
|
Variant="Variant.Filled"
|
||||||
|
Color="Color.Error"
|
||||||
|
StartIcon="@Icons.Material.Filled.Delete"
|
||||||
|
OnClick="HandleDeleteByDateRange"
|
||||||
|
Disabled="@(_deleteStartDate == null || _deleteEndDate == null || _isDeleting)">
|
||||||
|
Delete Occurrences in Range
|
||||||
|
</MudButton>
|
||||||
|
|
||||||
|
@if (_isDeleting)
|
||||||
|
{
|
||||||
|
<MudProgressLinear Indeterminate="true" />
|
||||||
|
<MudText>Deleting occurrences...</MudText>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
|
||||||
|
@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<EventOccurrence> 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -11,6 +11,9 @@
|
|||||||
<PageHeader Title="Event Calendar" Description="View competition schedules and event occurrences" Icon="@AppIcons.EventCalendar">
|
<PageHeader Title="Event Calendar" Description="View competition schedules and event occurrences" Icon="@AppIcons.EventCalendar">
|
||||||
<ActionButtons>
|
<ActionButtons>
|
||||||
<MudButton StartIcon="@Icons.Material.Filled.ImportExport" Href="calendar/event-occurrences/import" Variant="Variant.Filled" Color="Color.Primary">Import</MudButton>
|
<MudButton StartIcon="@Icons.Material.Filled.ImportExport" Href="calendar/event-occurrences/import" Variant="Variant.Filled" Color="Color.Primary">Import</MudButton>
|
||||||
|
<AuthorizeView Roles="@AuthRoles.Administrator">
|
||||||
|
<MudButton StartIcon="@Icons.Material.Filled.AdminPanelSettings" Href="calendar/admin" Variant="Variant.Outlined" Color="Color.Default">Admin</MudButton>
|
||||||
|
</AuthorizeView>
|
||||||
</ActionButtons>
|
</ActionButtons>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user