@page "/calendar/event-occurrences/import" @attribute [Authorize] @using Core.Models @using Core.Services @using Microsoft.EntityFrameworkCore @inject IEventOccurrenceParserService ParserService @inject AppDbContext Context @inject NavigationManager NavigationManager @inject ISnackbar Snackbar @inject IDialogService DialogService Paste Event Occurrence Data Parse Clear Parsed Results @if (_isParsing) { Parsing... } else if (_parseResult == null) { Parse text to see results here } else { @* Errors *@ @if (_parseResult.Errors.Any()) { @foreach (var error in _parseResult.Errors) { @error } } @* Warnings *@ @if (_parseResult.Warnings.Any()) { @foreach (var warning in _parseResult.Warnings) { @warning } } @* Detailed Parsing Issues *@ @if (_parseResult.Issues.Any()) { i.LineNumber).Distinct().Count()} line(s))")" Icon="@Icons.Material.Filled.Warning" iconcolor="Color.Warning"> Line Type Content Message @context.LineNumber @context.IssueType @context.LineContent @context.Message } @* Summary *@ @if (_parseResult.IsSuccess && _parseResult.TotalParsed > 0) { Successfully parsed @_parseResult.TotalParsed occurrence(s) from @_parseResult.Occurrences.Count event definition(s) @if (_parseResult.SkippedEventCount > 0) { (Skipped @_parseResult.SkippedEventCount event occurrence(s) from other school level) } } @* Skipped Section Headers *@ @if (_parseResult.SkippedSectionHeaders.Any() && _parseResult.IsSuccess) { @foreach (var header in _parseResult.SkippedSectionHeaders) { @header } } @* 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)) .Cast() // Cast to non-nullable string after null check .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; Parsed Locations (@allLocations.Count unique@(warningCount > 0 ? $", {warningCount} with warnings" : "")) @foreach (var location in allLocations) { var isLong = longLocations.Contains(location); var hasDateTime = locationsWithDateTime.Contains(location); var hasWarning = isLong || hasDateTime; @if (hasWarning) { Warning } @location } } } @* Parsed Occurrences List *@ @if (_parseResult.IsSuccess && _parseResult.Occurrences.Any()) { Occurrences by Event: @foreach (var kvp in _parseResult.Occurrences.OrderBy(x => GetEventName(x.Key))) { Name Date Time Location @context.Name @context.Date @context.Time @(context.Location ?? "-") } Save to Database Clear Results } else if (_parseResult.IsSuccess && _parseResult.TotalParsed == 0) { No occurrences were parsed from the text. Please check the format. } } @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; var skippedCount = 0; foreach (var kvp in _parseResult.Occurrences) { foreach (var occurrence in kvp.Value) { // Check for duplicates before adding var isDuplicate = await IsDuplicate(occurrence); if (isDuplicate) { skippedCount++; continue; } // Add each occurrence to the database await Context.EventOccurrences.AddAsync(occurrence); savedCount++; } } await Context.SaveChangesAsync(); var message = $"Successfully saved {savedCount} occurrence(s) to database"; if (skippedCount > 0) { message += $" ({skippedCount} duplicate(s) skipped)"; } Snackbar.Add(message, Severity.Success); // Clear input and output instead of navigating _inputText = string.Empty; _parseResult = null; } catch (Exception ex) { Snackbar.Add($"Error saving to database: {ex.Message}", Severity.Error); } finally { _isSaving = false; } } private async Task IsDuplicate(EventOccurrence occurrence) { // Check if an occurrence with the same name, date, time, location, and event definition already exists var query = Context.EventOccurrences .Where(eo => eo.Name == occurrence.Name && eo.StartTime.Date == occurrence.StartTime.Date && eo.Time == occurrence.Time && eo.Location == occurrence.Location); // Match by EventDefinitionId if it exists, otherwise match by SpecialEventType if (occurrence.EventDefinitionId.HasValue) { query = query.Where(eo => eo.EventDefinitionId == occurrence.EventDefinitionId); } else if (!string.IsNullOrEmpty(occurrence.SpecialEventType)) { query = query.Where(eo => eo.SpecialEventType == occurrence.SpecialEventType); } else { // If neither EventDefinitionId nor SpecialEventType is set, match by both being null/empty query = query.Where(eo => eo.EventDefinitionId == null && string.IsNullOrEmpty(eo.SpecialEventType)); } return await query.AnyAsync(); } 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 }; } }