19e5ef0675
This commit introduces a new property to track skipped high school section headers in the EventOccurrenceParseResult and EventOccurrenceParserResult classes. The EventOccurrenceParser has been updated to gracefully skip HS section headers that do not match any event definitions, improving the parsing logic. Additionally, the LocationParsingConfiguration has been removed from the EventOccurrenceParser, simplifying its constructor. Unit tests have been updated to reflect these changes and ensure correct behavior during parsing.
316 lines
13 KiB
Plaintext
316 lines
13 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">
|
|
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Class="mb-4">
|
|
<MudText Typo="Typo.h5">Paste Event Occurrence Data</MudText>
|
|
<MudButton
|
|
Variant="Variant.Text"
|
|
Color="Color.Secondary"
|
|
Size="Size.Small"
|
|
StartIcon="@Icons.Material.Filled.Settings"
|
|
OnClick="OpenLocationSettings">
|
|
Location Settings
|
|
</MudButton>
|
|
</MudStack>
|
|
<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>
|
|
}
|
|
|
|
@* 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
|
|
};
|
|
}
|
|
|
|
private void OpenLocationSettings()
|
|
{
|
|
NavigationManager.NavigateTo("/calendar/event-occurrences/import/settings");
|
|
}
|
|
}
|
|
|