349 lines
15 KiB
Plaintext
349 lines
15 KiB
Plaintext
@page "/calendar/event-occurrences/import"
|
|
@attribute [Authorize]
|
|
@using Core.Entities
|
|
@using Core.Models
|
|
@using Core.Services
|
|
@using Microsoft.EntityFrameworkCore
|
|
@using WebApp.Components.Shared.Components
|
|
@using MudBlazor
|
|
@inject IEventOccurrenceParserService ParserService
|
|
@inject AppDbContext Context
|
|
@inject NavigationManager NavigationManager
|
|
@inject ISnackbar Snackbar
|
|
@inject IDialogService DialogService
|
|
|
|
<PageHeader
|
|
Title="Import Event Occurrences"
|
|
Description="Parse and import event occurrence data from text"
|
|
ShowBackButton="true"
|
|
BackButtonUrl="/calendar/event-occurrences" />
|
|
|
|
<MudGrid>
|
|
<MudItem xs="12" md="6">
|
|
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
|
|
<MudText Typo="Typo.h5" Class="mb-4">Paste Event Occurrence Data</MudText>
|
|
<MudStack Spacing="3">
|
|
|
|
<MudStack Row="true" Spacing="2">
|
|
<MudButton
|
|
Variant="Variant.Filled"
|
|
Color="Color.Primary"
|
|
StartIcon="@Icons.Material.Filled.Article"
|
|
OnClick="HandleParse"
|
|
Disabled="@_isParsing">
|
|
Parse
|
|
</MudButton>
|
|
<MudButton
|
|
Variant="Variant.Text"
|
|
OnClick="HandleClear"
|
|
Disabled="@_isParsing">
|
|
Clear
|
|
</MudButton>
|
|
</MudStack>
|
|
<MudTextField
|
|
T="string"
|
|
Label="Event Occurrence Text"
|
|
@bind-Value="_inputText"
|
|
|
|
Variant="Variant.Outlined"
|
|
Lines="15"
|
|
AutoGrow="true"
|
|
Placeholder="Paste event occurrence text here..."
|
|
HelperText="Paste the event schedule text in the format expected by the parser" />
|
|
<!-- @bind-Value:event="oninput" -->
|
|
</MudStack>
|
|
</MudPaper>
|
|
</MudItem>
|
|
|
|
<MudItem xs="12" md="6">
|
|
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
|
|
<MudText Typo="Typo.h5" Class="mb-4">Parsed Results</MudText>
|
|
|
|
@if (_isParsing)
|
|
{
|
|
<MudProgressLinear Indeterminate="true" Class="mb-4" />
|
|
<MudText>Parsing...</MudText>
|
|
}
|
|
else if (_parseResult == null)
|
|
{
|
|
<MudText Class="mud-text-secondary">Parse text to see results here</MudText>
|
|
}
|
|
else
|
|
{
|
|
<MudStack Spacing="3">
|
|
@* Errors *@
|
|
@if (_parseResult.Errors.Any())
|
|
{
|
|
@foreach (var error in _parseResult.Errors)
|
|
{
|
|
<MudAlert Severity="Severity.Error" Dense="true">@error</MudAlert>
|
|
}
|
|
}
|
|
|
|
@* Warnings *@
|
|
@if (_parseResult.Warnings.Any())
|
|
{
|
|
@foreach (var warning in _parseResult.Warnings)
|
|
{
|
|
<MudAlert Severity="Severity.Warning" Dense="true">@warning</MudAlert>
|
|
}
|
|
}
|
|
|
|
@* Detailed Parsing Issues *@
|
|
@if (_parseResult.Issues.Any())
|
|
{
|
|
<MudExpansionPanels Elevation="0" Class="mt-2">
|
|
<MudExpansionPanel Text="@($"Parsing Issues ({_parseResult.Issues.Count} found on {_parseResult.Issues.Select(i => i.LineNumber).Distinct().Count()} line(s))")"
|
|
Icon="@Icons.Material.Filled.Warning"
|
|
iconcolor="Color.Warning">
|
|
<MudTable Items="@_parseResult.Issues" Dense="true" Hover="true" Striped="true" sortmode="SortMode.Multiple">
|
|
<HeaderContent>
|
|
<MudTh>Line</MudTh>
|
|
<MudTh>Type</MudTh>
|
|
<MudTh>Content</MudTh>
|
|
<MudTh>Message</MudTh>
|
|
</HeaderContent>
|
|
<RowTemplate>
|
|
<MudTd DataLabel="Line">@context.LineNumber</MudTd>
|
|
<MudTd DataLabel="Type">
|
|
<MudChip T="string" Size="Size.Small" Color="@GetIssueTypeColor(context.IssueType)">
|
|
@context.IssueType
|
|
</MudChip>
|
|
</MudTd>
|
|
<MudTd DataLabel="Content">
|
|
<code style="font-size: 0.85em; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block;" title="@context.LineContent">@context.LineContent</code>
|
|
</MudTd>
|
|
<MudTd DataLabel="Message">@context.Message</MudTd>
|
|
</RowTemplate>
|
|
</MudTable>
|
|
</MudExpansionPanel>
|
|
</MudExpansionPanels>
|
|
}
|
|
|
|
@* Summary *@
|
|
@if (_parseResult.IsSuccess && _parseResult.TotalParsed > 0)
|
|
{
|
|
<MudAlert Severity="Severity.Success" Dense="true">
|
|
Successfully parsed @_parseResult.TotalParsed occurrence(s) from @_parseResult.Occurrences.Count event definition(s)
|
|
</MudAlert>
|
|
}
|
|
|
|
@* Locations Summary *@
|
|
@if (_parseResult.IsSuccess && _parseResult.Occurrences.Any())
|
|
{
|
|
var allLocations = _parseResult.Occurrences.Values
|
|
.SelectMany(list => list)
|
|
.Select(eo => eo.Location)
|
|
.Where(loc => !string.IsNullOrWhiteSpace(loc))
|
|
.Distinct()
|
|
.OrderBy(loc => loc)
|
|
.ToList();
|
|
|
|
// Check which locations have warnings (long or contain date/time)
|
|
var dateTimePattern = new System.Text.RegularExpressions.Regex(
|
|
@"\b(January|February|March|April|May|June|July|August|September|October|November|December)\s+\d{1,2}\b|\b\d{1,2}:\d{2}\s*(a|p)\.?m\.?\b|\bNOON\b",
|
|
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
|
|
|
var longLocations = allLocations.Where(loc => loc.Length > 50).ToList();
|
|
var locationsWithDateTime = allLocations.Where(loc => dateTimePattern.IsMatch(loc)).ToList();
|
|
|
|
@if (allLocations.Any())
|
|
{
|
|
var warningCount = longLocations.Count + locationsWithDateTime.Count;
|
|
<MudText Typo="Typo.h6" Class="mt-4 mb-2">Parsed Locations (@allLocations.Count unique@(warningCount > 0 ? $", {warningCount} with warnings" : ""))</MudText>
|
|
<MudExpansionPanels Elevation="0">
|
|
<MudExpansionPanel Text="All Locations">
|
|
<MudList T="string">
|
|
@foreach (var location in allLocations)
|
|
{
|
|
var isLong = longLocations.Contains(location);
|
|
var hasDateTime = locationsWithDateTime.Contains(location);
|
|
var hasWarning = isLong || hasDateTime;
|
|
<MudListItem T="string">
|
|
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
|
@if (hasWarning)
|
|
{
|
|
<MudChip T="string" Color="Color.Warning" Size="Size.Small">Warning</MudChip>
|
|
}
|
|
<MudText Style="@(hasWarning ? "color: var(--mud-palette-warning);" : "")">@location</MudText>
|
|
</MudStack>
|
|
</MudListItem>
|
|
}
|
|
</MudList>
|
|
</MudExpansionPanel>
|
|
</MudExpansionPanels>
|
|
}
|
|
}
|
|
|
|
@* Parsed Occurrences List *@
|
|
@if (_parseResult.IsSuccess && _parseResult.Occurrences.Any())
|
|
{
|
|
<MudText Typo="Typo.h6" Class="mt-4 mb-2">Occurrences by Event:</MudText>
|
|
<MudExpansionPanels Elevation="0">
|
|
@foreach (var kvp in _parseResult.Occurrences.OrderBy(x => GetEventName(x.Key)))
|
|
{
|
|
<MudExpansionPanel Text="@GetEventName(kvp.Key)">
|
|
<MudTable Items="@kvp.Value" Dense="true" Hover="true" Striped="true">
|
|
<HeaderContent>
|
|
<MudTh>Name</MudTh>
|
|
<MudTh>Date</MudTh>
|
|
<MudTh>Time</MudTh>
|
|
<MudTh>Location</MudTh>
|
|
</HeaderContent>
|
|
<RowTemplate>
|
|
<MudTd DataLabel="Name">@context.Name</MudTd>
|
|
<MudTd DataLabel="Date">@context.Date</MudTd>
|
|
<MudTd DataLabel="Time">@context.Time</MudTd>
|
|
<MudTd DataLabel="Location">@(context.Location ?? "-")</MudTd>
|
|
</RowTemplate>
|
|
</MudTable>
|
|
</MudExpansionPanel>
|
|
}
|
|
</MudExpansionPanels>
|
|
|
|
<MudStack Row="true" Spacing="2" Class="mt-4">
|
|
<MudButton
|
|
Variant="Variant.Filled"
|
|
Color="Color.Success"
|
|
StartIcon="@Icons.Material.Filled.Save"
|
|
OnClick="HandleSaveToDatabase"
|
|
Disabled="@_isSaving">
|
|
Save to Database
|
|
</MudButton>
|
|
<MudButton
|
|
Variant="Variant.Text"
|
|
OnClick="HandleClearResults">
|
|
Clear Results
|
|
</MudButton>
|
|
</MudStack>
|
|
}
|
|
else if (_parseResult.IsSuccess && _parseResult.TotalParsed == 0)
|
|
{
|
|
<MudAlert Severity="Severity.Info" Dense="true">
|
|
No occurrences were parsed from the text. Please check the format.
|
|
</MudAlert>
|
|
}
|
|
</MudStack>
|
|
}
|
|
</MudPaper>
|
|
</MudItem>
|
|
</MudGrid>
|
|
|
|
@code {
|
|
private string _inputText = string.Empty;
|
|
private EventOccurrenceParseResult? _parseResult;
|
|
private bool _isParsing = false;
|
|
private bool _isSaving = false;
|
|
|
|
private async Task HandleParse()
|
|
{
|
|
if (string.IsNullOrWhiteSpace(_inputText))
|
|
{
|
|
Snackbar.Add("Please enter text to parse", Severity.Warning);
|
|
return;
|
|
}
|
|
|
|
_isParsing = true;
|
|
try
|
|
{
|
|
// Get EventDefinitions from database
|
|
var events = await Context.Events.ToListAsync();
|
|
|
|
// Parse the text
|
|
_parseResult = ParserService.ParseFromText(_inputText, events);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"Error parsing text: {ex.Message}", Severity.Error);
|
|
_parseResult = new EventOccurrenceParseResult
|
|
{
|
|
Errors = { $"Error: {ex.Message}" }
|
|
};
|
|
}
|
|
finally
|
|
{
|
|
_isParsing = false;
|
|
}
|
|
}
|
|
|
|
private void HandleClear()
|
|
{
|
|
_inputText = string.Empty;
|
|
_parseResult = null;
|
|
}
|
|
|
|
private void HandleClearResults()
|
|
{
|
|
_parseResult = null;
|
|
}
|
|
|
|
private async Task HandleSaveToDatabase()
|
|
{
|
|
if (_parseResult == null || !_parseResult.IsSuccess || _parseResult.TotalParsed == 0)
|
|
{
|
|
Snackbar.Add("No valid parsed occurrences to save", Severity.Warning);
|
|
return;
|
|
}
|
|
|
|
_isSaving = true;
|
|
try
|
|
{
|
|
var savedCount = 0;
|
|
foreach (var kvp in _parseResult.Occurrences)
|
|
{
|
|
foreach (var occurrence in kvp.Value)
|
|
{
|
|
// Add each occurrence to the database
|
|
await Context.EventOccurrences.AddAsync(occurrence);
|
|
savedCount++;
|
|
}
|
|
}
|
|
|
|
await Context.SaveChangesAsync();
|
|
Snackbar.Add($"Successfully saved {savedCount} occurrence(s) to database", Severity.Success);
|
|
|
|
// Navigate back to the calendar index after a short delay
|
|
await Task.Delay(1000);
|
|
NavigationManager.NavigateTo("/calendar/event-occurrences");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"Error saving to database: {ex.Message}", Severity.Error);
|
|
}
|
|
finally
|
|
{
|
|
_isSaving = false;
|
|
}
|
|
}
|
|
|
|
private string GetEventName(EventDefinition eventDefinition)
|
|
{
|
|
if (eventDefinition == EventDefinition.GeneralSchedule)
|
|
return "General Schedule";
|
|
if (eventDefinition == EventDefinition.MeetTheCandidates)
|
|
return "Meet the Candidates";
|
|
if (eventDefinition == EventDefinition.ChapterOfficerMeeting)
|
|
return "Chapter Officer Meeting";
|
|
if (eventDefinition == EventDefinition.VotingDelegateMeeting)
|
|
return "Voting Delegate Meeting";
|
|
if (eventDefinition == EventDefinition.SocialGathering)
|
|
return "Social Gathering";
|
|
return eventDefinition.Name;
|
|
}
|
|
|
|
private Color GetIssueTypeColor(ParsingIssueType issueType)
|
|
{
|
|
return issueType switch
|
|
{
|
|
ParsingIssueType.UnmatchedLine => Color.Info,
|
|
ParsingIssueType.MissingEventDefinition => Color.Warning,
|
|
ParsingIssueType.TimeParseFailure => Color.Error,
|
|
ParsingIssueType.DateParseFailure => Color.Error,
|
|
ParsingIssueType.InvalidFormat => Color.Error,
|
|
_ => Color.Default
|
|
};
|
|
}
|
|
|
|
}
|
|
|