Files
chapter-organizer/WebApp/Components/Features/Calendar/LocationParsingSettings.razor
T
poprhythm 2d3b29176f Enhance event occurrence parsing with detailed issue reporting and location configuration
This commit introduces a new structure for handling parsing issues in the EventOccurrenceParser, allowing for detailed reporting of parsing problems such as unmatched lines, missing event definitions, and parsing failures for time, date, and location. A new ParsingIssue class has been added to encapsulate these details. Additionally, a LocationParsingConfiguration class has been implemented to support customizable location patterns, enhancing the flexibility of the parser. The EventOccurrenceParserService has been updated to utilize this configuration, and new tests have been added to ensure robust issue detection and reporting. Furthermore, the UI has been updated to display parsing issues, improving user feedback during the import process.
2026-01-06 23:08:42 -05:00

234 lines
8.3 KiB
Plaintext

@page "/calendar/event-occurrences/import/settings"
@attribute [Authorize(Roles = AuthRoles.Administrator)]
@using Core.Models
@using WebApp.Authentication
@using WebApp.Components.Shared.Components
@using System.Text.Json
@using MudBlazor
@inject IWebHostEnvironment Environment
@inject IConfiguration Configuration
@inject NavigationManager NavigationManager
@inject ISnackbar Snackbar
@rendermode InteractiveServer
<PageHeader
Title="Location Parsing Settings"
Description="Configure location prefix patterns for parsing event occurrence locations. Patterns use '*' as a wildcard (e.g., 'Room *' matches 'Room 101', 'Room 202', etc.)."
ShowBackButton="true"
BackButtonUrl="/calendar/event-occurrences/import" />
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-4">
@if (_config != null)
{
<MudPaper Class="pa-6 mb-4">
<MudText Typo="Typo.h5" Class="mb-4">Location Patterns</MudText>
<MudText Typo="Typo.body2" Class="mb-4" Color="Color.Secondary">
Add prefix patterns to match location names. Use '*' as a wildcard to match any text after the prefix.
Examples: "Room *" matches "Room 101", "Room 202"; "Hall *" matches "Hall A", "Main Hall".
</MudText>
<MudStack Spacing="2">
@for (int i = 0; i < _config.LocationPatterns.Count; i++)
{
var index = i;
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
<MudTextField @bind-Value="_config.LocationPatterns[index]"
Label="Pattern"
Variant="Variant.Outlined"
Style="flex-grow: 1;"
Placeholder="Room *"
HelperText="Use * as wildcard" />
<MudButton Variant="Variant.Text"
Color="Color.Error"
StartIcon="@Icons.Material.Filled.Delete"
OnClick="@(() => RemovePattern(index))"
Disabled="_isSaving">
Remove
</MudButton>
</MudStack>
}
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="AddPattern"
Disabled="_isSaving"
Class="mt-2">
Add Pattern
</MudButton>
</MudStack>
</MudPaper>
<MudPaper Class="pa-6 mb-4">
<MudText Typo="Typo.h6" Class="mb-3">Pattern Examples</MudText>
<MudSimpleTable Dense="true">
<thead>
<tr>
<th>Pattern</th>
<th>Matches</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Room *</code></td>
<td>Room 101, Room 202, Room A</td>
</tr>
<tr>
<td><code>Hall *</code></td>
<td>Hall A, Hall B, Main Hall</td>
</tr>
<tr>
<td><code>Conference Room *</code></td>
<td>Conference Room A, Conference Room 1</td>
</tr>
<tr>
<td><code>Building *</code></td>
<td>Building 1, Building A, Building Main</td>
</tr>
</tbody>
</MudSimpleTable>
</MudPaper>
<MudPaper Class="pa-6">
<MudGrid>
<MudItem xs="12">
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Save"
OnClick="SaveConfiguration"
Disabled="_isSaving">
@if (_isSaving)
{
<MudProgressCircular Class="mr-2" Size="Size.Small" Indeterminate="true" />
<span>Saving...</span>
}
else
{
<span>Save Configuration</span>
}
</MudButton>
<MudButton Variant="Variant.Text"
Class="ml-2"
OnClick="Cancel"
Disabled="_isSaving">
Cancel
</MudButton>
</MudItem>
</MudGrid>
</MudPaper>
@if (!string.IsNullOrEmpty(_statusMessage))
{
<MudAlert Severity="@_statusSeverity" Class="mt-4">@_statusMessage</MudAlert>
}
}
else
{
<MudProgressCircular Indeterminate="true" />
}
</MudContainer>
@code {
private LocationParsingConfiguration? _config;
private bool _isSaving;
private string? _statusMessage;
private Severity _statusSeverity = Severity.Success;
protected override void OnInitialized()
{
// Load from IConfiguration
_config = Configuration.GetSection("LocationParsingSettings").Get<LocationParsingConfiguration>()
?? LocationParsingConfiguration.Default;
// Create a copy to avoid modifying the original
_config = new LocationParsingConfiguration
{
LocationPatterns = new List<string>(_config.LocationPatterns)
};
}
private string GetAppSettingsPath()
{
return Path.Combine(
Environment.ContentRootPath,
"Data",
"appsettings.json");
}
private void AddPattern()
{
if (_config == null) return;
_config.LocationPatterns.Add("New Pattern *");
}
private void RemovePattern(int index)
{
if (_config == null || index < 0 || index >= _config.LocationPatterns.Count) return;
_config.LocationPatterns.RemoveAt(index);
}
private async Task SaveConfiguration()
{
if (_config == null) return;
_isSaving = true;
_statusMessage = null;
try
{
var appSettingsPath = GetAppSettingsPath();
// Ensure Data directory exists
var dataDir = Path.GetDirectoryName(appSettingsPath);
if (dataDir != null && !Directory.Exists(dataDir))
{
Directory.CreateDirectory(dataDir);
}
// Read existing appsettings or create new
Dictionary<string, object?> settings;
if (File.Exists(appSettingsPath))
{
var existingJson = await File.ReadAllTextAsync(appSettingsPath);
settings = JsonSerializer.Deserialize<Dictionary<string, object?>>(existingJson)
?? new Dictionary<string, object?>();
}
else
{
settings = new Dictionary<string, object?>();
}
// Update LocationParsingSettings section
settings["LocationParsingSettings"] = _config;
// Write back to file
var options = new JsonSerializerOptions { WriteIndented = true };
var json = JsonSerializer.Serialize(settings, options);
await File.WriteAllTextAsync(appSettingsPath, json);
_statusMessage = "Configuration saved successfully! Changes will take effect on next parse operation.";
_statusSeverity = Severity.Success;
Snackbar.Add("Location parsing settings saved successfully", Severity.Success);
}
catch (Exception ex)
{
_statusMessage = $"Error saving configuration: {ex.Message}";
_statusSeverity = Severity.Error;
Snackbar.Add($"Error saving settings: {ex.Message}", Severity.Error);
}
finally
{
_isSaving = false;
}
}
private void Cancel()
{
NavigationManager.NavigateTo("/calendar/event-occurrences/import");
}
}