From f401416fbd4a0365a0f2c15facdf984105c9bccc Mon Sep 17 00:00:00 2001 From: Nick Beckley Date: Sun, 14 Jun 2026 21:14:22 +0100 Subject: [PATCH] Phase 12H: Move Continuity Warnings into the Main Warnings Page. --- .../ContinuityExplorerController.cs | 55 ---- PlotLine/Controllers/WarningsController.cs | 85 +++++- PlotLine/Services/CoreServices.cs | 231 +++++++++++++++- PlotLine/ViewModels/CoreViewModels.cs | 56 ++++ .../Views/ContinuityExplorer/Index.cshtml | 189 ------------- PlotLine/Views/Warnings/Index.cshtml | 256 +++++++++++++----- 6 files changed, 540 insertions(+), 332 deletions(-) diff --git a/PlotLine/Controllers/ContinuityExplorerController.cs b/PlotLine/Controllers/ContinuityExplorerController.cs index 338b3e2..141a0a2 100644 --- a/PlotLine/Controllers/ContinuityExplorerController.cs +++ b/PlotLine/Controllers/ContinuityExplorerController.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using PlotLine.Models; using PlotLine.Services; using PlotLine.ViewModels; @@ -26,58 +25,4 @@ public sealed class ContinuityExplorerController(IStoryStateService storyState) { return RedirectToAction(nameof(Index), filter); } - - [HttpPost] - [ValidateAntiForgeryToken] - public async Task AcknowledgeWarning( - string warningType, - int? sceneId, - int? characterId, - int? assetId, - string? notes, - [Bind(Prefix = "ReturnFilter")] ContinuityFilter returnFilter) - { - if (returnFilter.ProjectID == 0 || string.IsNullOrWhiteSpace(warningType)) - { - return BadRequest("Warning identity is required."); - } - - await storyState.AcknowledgeWarningAsync(new ContinuityWarningAcknowledgement - { - ProjectID = returnFilter.ProjectID, - WarningType = warningType, - SceneID = sceneId, - CharacterID = characterId, - AssetID = assetId, - Notes = notes - }); - - return RedirectToAction(nameof(Index), returnFilter); - } - - [HttpPost] - [ValidateAntiForgeryToken] - public async Task RestoreWarning( - string warningType, - int? sceneId, - int? characterId, - int? assetId, - [Bind(Prefix = "ReturnFilter")] ContinuityFilter returnFilter) - { - if (returnFilter.ProjectID == 0 || string.IsNullOrWhiteSpace(warningType)) - { - return BadRequest("Warning identity is required."); - } - - await storyState.RestoreWarningAsync(new ContinuityWarningAcknowledgement - { - ProjectID = returnFilter.ProjectID, - WarningType = warningType, - SceneID = sceneId, - CharacterID = characterId, - AssetID = assetId - }); - - return RedirectToAction(nameof(Index), returnFilter); - } } diff --git a/PlotLine/Controllers/WarningsController.cs b/PlotLine/Controllers/WarningsController.cs index 2ffcd35..ad42b12 100644 --- a/PlotLine/Controllers/WarningsController.cs +++ b/PlotLine/Controllers/WarningsController.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; +using PlotLine.Models; using PlotLine.Services; +using PlotLine.ViewModels; namespace PlotLine.Controllers; @@ -10,14 +12,17 @@ public sealed class WarningsController(IContinuityValidationService validation) public async Task Index( int projectId, int? bookId, + int? chapterId, int? sceneId, int? warningTypeId, int? warningSeverityId, string? entityType, + string? category, bool includeDismissed = false, - bool includeIntentional = false) + bool includeIntentional = false, + bool showAcknowledgedWarnings = false) { - var model = await validation.GetDashboardAsync(projectId, bookId, sceneId, warningTypeId, warningSeverityId, entityType, includeDismissed, includeIntentional); + var model = await validation.GetDashboardAsync(projectId, bookId, chapterId, sceneId, warningTypeId, warningSeverityId, entityType, category, includeDismissed, includeIntentional, showAcknowledgedWarnings); return model is null ? NotFound() : View(model); } @@ -26,7 +31,7 @@ public sealed class WarningsController(IContinuityValidationService validation) public async Task ValidateProject(int projectId, int? bookId) { var summary = await validation.ValidateProjectAsync(projectId); - var model = await validation.GetDashboardAsync(projectId, bookId, null, null, null, null, false, false, summary); + var model = await validation.GetDashboardAsync(projectId, bookId, null, null, null, null, null, null, false, false, false, summary); return model is null ? NotFound() : View("Index", model); } @@ -35,7 +40,7 @@ public sealed class WarningsController(IContinuityValidationService validation) public async Task ValidateBook(int projectId, int bookId) { var summary = await validation.ValidateBookAsync(bookId); - var model = await validation.GetDashboardAsync(projectId, bookId, null, null, null, null, false, false, summary); + var model = await validation.GetDashboardAsync(projectId, bookId, null, null, null, null, null, null, false, false, false, summary); return model is null ? NotFound() : View("Index", model); } @@ -76,6 +81,60 @@ public sealed class WarningsController(IContinuityValidationService validation) return RedirectToAction(nameof(Index), new { projectId, bookId, sceneId, includeDismissed = true, includeIntentional = true }); } + [HttpPost] + [ValidateAntiForgeryToken] + public async Task AcknowledgeGeneratedWarning( + string warningType, + int? sceneId, + int? characterId, + int? assetId, + string? notes, + [Bind(Prefix = "ReturnFilter")] WarningDashboardReturnFilter returnFilter) + { + if (returnFilter.ProjectID == 0 || string.IsNullOrWhiteSpace(warningType)) + { + return BadRequest("Warning identity is required."); + } + + await validation.AcknowledgeGeneratedWarningAsync(new ContinuityWarningAcknowledgement + { + ProjectID = returnFilter.ProjectID, + WarningType = warningType, + SceneID = sceneId, + CharacterID = characterId, + AssetID = assetId, + Notes = notes + }); + + return RedirectToDashboard(returnFilter); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task RestoreGeneratedWarning( + string warningType, + int? sceneId, + int? characterId, + int? assetId, + [Bind(Prefix = "ReturnFilter")] WarningDashboardReturnFilter returnFilter) + { + if (returnFilter.ProjectID == 0 || string.IsNullOrWhiteSpace(warningType)) + { + return BadRequest("Warning identity is required."); + } + + await validation.RestoreGeneratedWarningAsync(new ContinuityWarningAcknowledgement + { + ProjectID = returnFilter.ProjectID, + WarningType = warningType, + SceneID = sceneId, + CharacterID = characterId, + AssetID = assetId + }); + + return RedirectToDashboard(returnFilter); + } + private IActionResult RedirectAfterAction(int projectId, int? bookId, int? sceneId, bool returnToTimeline) { if (returnToTimeline && sceneId.HasValue) @@ -85,4 +144,22 @@ public sealed class WarningsController(IContinuityValidationService validation) return RedirectToAction(nameof(Index), new { projectId, bookId, sceneId }); } + + private IActionResult RedirectToDashboard(WarningDashboardReturnFilter filter) + { + return RedirectToAction(nameof(Index), new + { + projectId = filter.ProjectID, + bookId = filter.BookID, + chapterId = filter.ChapterID, + sceneId = filter.SceneID, + warningTypeId = filter.WarningTypeID, + warningSeverityId = filter.WarningSeverityID, + entityType = filter.EntityType, + category = filter.Category, + includeDismissed = filter.IncludeDismissed, + includeIntentional = filter.IncludeIntentional, + showAcknowledgedWarnings = filter.ShowAcknowledgedWarnings + }); + } } diff --git a/PlotLine/Services/CoreServices.cs b/PlotLine/Services/CoreServices.cs index ac89d50..c6dab5b 100644 --- a/PlotLine/Services/CoreServices.cs +++ b/PlotLine/Services/CoreServices.cs @@ -176,7 +176,7 @@ public interface ILocationService public interface IContinuityValidationService { - Task GetDashboardAsync(int projectId, int? bookId, int? sceneId, int? warningTypeId, int? warningSeverityId, string? entityType, bool includeDismissed, bool includeIntentional, ContinuityValidationSummary? summary = null); + Task GetDashboardAsync(int projectId, int? bookId, int? chapterId, int? sceneId, int? warningTypeId, int? warningSeverityId, string? entityType, string? category, bool includeDismissed, bool includeIntentional, bool showAcknowledgedWarnings, ContinuityValidationSummary? summary = null); Task ValidateProjectAsync(int projectId); Task ValidateBookAsync(int bookId); Task ValidateSceneAsync(int sceneId); @@ -188,6 +188,8 @@ public interface IContinuityValidationService Task DismissAsync(int warningId); Task MarkIntentionalAsync(int warningId); Task RestoreAsync(int warningId); + Task AcknowledgeGeneratedWarningAsync(ContinuityWarningAcknowledgement acknowledgement); + Task RestoreGeneratedWarningAsync(ContinuityWarningAcknowledgement acknowledgement); } public interface ICharacterService @@ -2343,10 +2345,6 @@ public sealed class StoryStateService( .ThenBy(x => x.LocationName) .ToList(); } - else if (string.Equals(filter.DisplayMode, "ContinuityWarnings", StringComparison.OrdinalIgnoreCase)) - { - model.ContinuityWarnings = await GetContinuityWarningsAsync(filter.ProjectID, filter); - } return model; } @@ -3403,6 +3401,12 @@ public sealed class StoryStateService( { filter.DisplayMode = "StoryState"; } + else if (!string.Equals(filter.DisplayMode, "StoryState", StringComparison.OrdinalIgnoreCase) + && !string.Equals(filter.DisplayMode, "Journey", StringComparison.OrdinalIgnoreCase) + && !string.Equals(filter.DisplayMode, "LocationActivity", StringComparison.OrdinalIgnoreCase)) + { + filter.DisplayMode = "StoryState"; + } } private static string SceneLabel(decimal sceneNumber, string sceneTitle) @@ -3911,17 +3915,21 @@ public sealed class ContinuityValidationService( IAssetRepository assets, ICharacterRepository characters, IWarningRepository warnings, - IDynamicsRepository dynamics) : IContinuityValidationService + IDynamicsRepository dynamics, + IStoryStateService storyState) : IContinuityValidationService { public async Task GetDashboardAsync( int projectId, int? bookId, + int? chapterId, int? sceneId, int? warningTypeId, int? warningSeverityId, string? entityType, + string? category, bool includeDismissed, bool includeIntentional, + bool showAcknowledgedWarnings, ContinuityValidationSummary? summary = null) { var project = await projects.GetAsync(projectId); @@ -3931,28 +3939,217 @@ public sealed class ContinuityValidationService( } var lookupData = await warnings.GetLookupsAsync(); - var rows = await warnings.ListAsync(projectId, bookId, sceneId, warningTypeId, warningSeverityId, entityType, includeDismissed, includeIntentional); + var projectBooks = (await books.ListByProjectAsync(projectId)).ToList(); + var projectChapters = new List(); + foreach (var book in projectBooks) + { + projectChapters.AddRange(await chapters.ListByBookAsync(book.BookID)); + } + + var severityName = warningSeverityId.HasValue + ? lookupData.Severities.FirstOrDefault(x => x.WarningSeverityID == warningSeverityId.Value)?.SeverityName + : null; + var rows = (await warnings.ListAsync(projectId, bookId, sceneId, warningTypeId, null, entityType, includeDismissed, includeIntentional)) + .Where(x => !chapterId.HasValue || x.ChapterID == chapterId.Value) + .ToList(); + var continuityWarnings = await storyState.GetContinuityWarningsAsync(projectId, BuildWarningContinuityFilter(projectId, bookId, chapterId, sceneId)); + var dashboardWarnings = rows.Select(ToProjectWarning).Concat(continuityWarnings.Select(ToProjectWarning)).ToList(); + var availableWarnings = dashboardWarnings.ToList(); + var hasAnyWarnings = availableWarnings.Any(); + + dashboardWarnings = dashboardWarnings + .Where(x => string.IsNullOrWhiteSpace(severityName) || string.Equals(x.Severity, severityName, StringComparison.OrdinalIgnoreCase)) + .Where(x => string.IsNullOrWhiteSpace(entityType) || string.Equals(x.EntityType, entityType, StringComparison.OrdinalIgnoreCase)) + .Where(x => string.IsNullOrWhiteSpace(category) || string.Equals(x.Category, category, StringComparison.OrdinalIgnoreCase)) + .Where(x => showAcknowledgedWarnings || !x.IsAcknowledged) + .OrderBy(x => WarningSeveritySort(x.Severity)) + .ThenBy(x => x.BookDisplayName) + .ThenBy(x => x.ChapterDisplayName) + .ThenBy(x => x.SceneID ?? int.MaxValue) + .ThenBy(x => x.Category) + .ThenBy(x => x.WarningTypeDisplayName) + .ThenBy(x => x.Message) + .ToList(); + return new WarningDashboardViewModel { Project = project, BookID = bookId, + ChapterID = chapterId, SceneID = sceneId, WarningTypeID = warningTypeId, WarningSeverityID = warningSeverityId, EntityType = entityType, + Category = category, IncludeDismissed = includeDismissed, IncludeIntentional = includeIntentional, + ShowAcknowledgedWarnings = showAcknowledgedWarnings, Warnings = rows, - BookOptions = (await books.ListByProjectAsync(projectId)).Select(x => new SelectListItem($"Book {x.BookNumber}: {x.BookTitle}", x.BookID.ToString(), x.BookID == bookId)).ToList(), - WarningTypeOptions = ChapterService.ToSelectList(lookupData.WarningTypes, x => x.WarningTypeID, x => x.TypeName), - SeverityOptions = ChapterService.ToSelectList(lookupData.Severities, x => x.WarningSeverityID, x => x.SeverityName), - EntityTypeOptions = rows.Select(x => x.EntityType).Distinct().OrderBy(x => x).Select(x => new SelectListItem(x, x, x == entityType)).ToList(), - CountsBySeverity = rows.GroupBy(x => x.SeverityName).ToDictionary(x => x.Key, x => x.Count()), - CountsByType = rows.GroupBy(x => x.WarningTypeName).OrderByDescending(x => x.Count()).ToDictionary(x => x.Key, x => x.Count()), - LastValidationSummary = summary + ProjectWarnings = dashboardWarnings, + BookOptions = projectBooks.Select(x => new SelectListItem($"Book {x.BookNumber}: {x.BookTitle}", x.BookID.ToString(), x.BookID == bookId)).ToList(), + ChapterOptions = projectChapters.Select(x => new SelectListItem($"Ch {x.ChapterNumber:g}: {x.ChapterTitle}", x.ChapterID.ToString(), x.ChapterID == chapterId)).ToList(), + WarningTypeOptions = lookupData.WarningTypes.Select(x => new SelectListItem(x.TypeName, x.WarningTypeID.ToString(), x.WarningTypeID == warningTypeId)).ToList(), + SeverityOptions = lookupData.Severities.Select(x => new SelectListItem(x.SeverityName, x.WarningSeverityID.ToString(), x.WarningSeverityID == warningSeverityId)).ToList(), + EntityTypeOptions = availableWarnings.Select(x => x.EntityType).Where(x => !string.IsNullOrWhiteSpace(x)).Distinct(StringComparer.OrdinalIgnoreCase).OrderBy(x => x).Select(x => new SelectListItem(x, x, string.Equals(x, entityType, StringComparison.OrdinalIgnoreCase))).ToList(), + CategoryOptions = availableWarnings.Select(x => x.Category).Distinct(StringComparer.OrdinalIgnoreCase).OrderBy(x => x).Select(x => new SelectListItem(x, x, string.Equals(x, category, StringComparison.OrdinalIgnoreCase))).ToList(), + CountsBySeverity = dashboardWarnings.GroupBy(x => x.Severity).ToDictionary(x => x.Key, x => x.Count()), + CountsByType = dashboardWarnings.GroupBy(x => x.WarningTypeDisplayName).OrderByDescending(x => x.Count()).ToDictionary(x => x.Key, x => x.Count()), + LastValidationSummary = summary, + HasAnyWarnings = hasAnyWarnings }; } + private static ContinuityFilter BuildWarningContinuityFilter(int projectId, int? bookId, int? chapterId, int? sceneId) + { + var filter = new ContinuityFilter + { + ProjectID = projectId, + DisplayMode = "ContinuityWarnings", + ShowAcknowledgedWarnings = true + }; + + if (sceneId.HasValue) + { + filter.SceneRangeMode = "Custom"; + filter.StartSceneID = sceneId; + filter.EndSceneID = sceneId; + } + else if (chapterId.HasValue) + { + filter.SceneRangeMode = "Chapter"; + filter.ChapterID = chapterId; + } + else if (bookId.HasValue) + { + filter.SceneRangeMode = "Book"; + filter.BookID = bookId; + } + + return filter; + } + + private static ProjectWarningViewModel ToProjectWarning(ContinuityWarning warning) + { + return new ProjectWarningViewModel + { + Source = "Persisted", + ContinuityWarningID = warning.ContinuityWarningID, + WarningType = warning.WarningTypeName, + WarningTypeDisplayName = warning.WarningTypeName, + Category = WarningCategoryForPersisted(warning), + Severity = warning.SeverityName, + Message = warning.Message, + Details = warning.Details, + BookID = warning.BookID, + BookDisplayName = string.IsNullOrWhiteSpace(warning.BookTitle) ? string.Empty : warning.BookTitle, + ChapterID = warning.ChapterID, + ChapterDisplayName = warning.ChapterNumber.HasValue + ? $"Ch {warning.ChapterNumber:g}: {warning.ChapterTitle}" + : string.Empty, + SceneID = warning.SceneID, + SceneDisplayName = warning.SceneID.HasValue + ? SceneLabel(warning.SceneNumber ?? 0, warning.SceneTitle ?? string.Empty) + : string.Empty, + EntityType = warning.EntityType, + IsDismissed = warning.IsDismissed, + IsIntentional = warning.IsIntentional, + LastDetectedDate = warning.LastDetectedDate + }; + } + + private static ProjectWarningViewModel ToProjectWarning(ContinuityWarningViewModel warning) + { + return new ProjectWarningViewModel + { + Source = "Generated", + WarningType = warning.WarningType, + WarningTypeDisplayName = warning.WarningTypeDisplayName, + Category = WarningCategoryForGenerated(warning), + Severity = warning.Severity, + Message = warning.Message, + BookDisplayName = warning.BookDisplayName, + ChapterDisplayName = warning.ChapterDisplayName, + SceneID = warning.SceneID, + SceneDisplayName = warning.SceneDisplayName, + CharacterID = warning.CharacterID, + CharacterName = warning.CharacterName, + AssetID = warning.AssetID, + EntityType = GeneratedEntityType(warning), + IsAcknowledged = warning.IsAcknowledged, + AcknowledgementNotes = warning.AcknowledgementNotes, + WarningKey = warning.WarningKey, + KnowledgeId = warning.KnowledgeId, + KnowledgeTitle = warning.KnowledgeTitle, + EstablishedSceneId = warning.EstablishedSceneId, + EstablishedSceneDisplayName = warning.EstablishedSceneDisplayName, + ReferencedSceneId = warning.ReferencedSceneId, + ReferencedSceneDisplayName = warning.ReferencedSceneDisplayName, + LastDetectedDate = DateTime.UtcNow + }; + } + + private static string WarningCategoryForPersisted(ContinuityWarning warning) + { + return warning.EntityType switch + { + "Character" => "Character", + "Asset" or "StoryAsset" => "Asset", + "Scene" => "Timeline", + "Relationship" => "Character", + "PlotThread" => "Plot Thread", + _ => "General" + }; + } + + private static string WarningCategoryForGenerated(ContinuityWarningViewModel warning) + { + if (string.Equals(warning.WarningTypeDisplayName, "Knowledge Chronology Violation", StringComparison.OrdinalIgnoreCase)) + { + return "Knowledge"; + } + + return warning.WarningTypeDisplayName switch + { + "Multiple Character Locations" => "Character", + "Characters With Unknown Locations" => "Character", + "Asset Location Conflict" => "Asset", + "Assets With Unknown Locations" => "Asset", + _ => "Continuity" + }; + } + + private static string GeneratedEntityType(ContinuityWarningViewModel warning) + { + if (warning.CharacterID.HasValue) + { + return "Character"; + } + + if (warning.AssetID.HasValue) + { + return "Asset"; + } + + return "Continuity"; + } + + private static int WarningSeveritySort(string severity) + { + return severity switch + { + "Critical" => 0, + "Warning" => 1, + _ => 2 + }; + } + + private static string SceneLabel(decimal sceneNumber, string sceneTitle) + { + return string.IsNullOrWhiteSpace(sceneTitle) + ? $"Scene {sceneNumber:g}" + : $"Scene {sceneNumber:g}: {sceneTitle}"; + } + public async Task ValidateProjectAsync(int projectId) { var summary = await warnings.ValidateProjectAsync(projectId); @@ -4021,6 +4218,12 @@ public sealed class ContinuityValidationService( public Task MarkIntentionalAsync(int warningId) => warnings.MarkIntentionalAsync(warningId); public Task RestoreAsync(int warningId) => warnings.RestoreAsync(warningId); + + public Task AcknowledgeGeneratedWarningAsync(ContinuityWarningAcknowledgement acknowledgement) => + storyState.AcknowledgeWarningAsync(acknowledgement); + + public Task RestoreGeneratedWarningAsync(ContinuityWarningAcknowledgement acknowledgement) => + storyState.RestoreWarningAsync(acknowledgement); } public sealed class SceneMetricTypeService(IProjectRepository projects, ISceneMetricTypeRepository metricTypes) : ISceneMetricTypeService diff --git a/PlotLine/ViewModels/CoreViewModels.cs b/PlotLine/ViewModels/CoreViewModels.cs index 1921d2c..6a11a76 100644 --- a/PlotLine/ViewModels/CoreViewModels.cs +++ b/PlotLine/ViewModels/CoreViewModels.cs @@ -1639,20 +1639,76 @@ public sealed class WarningDashboardViewModel { public Project Project { get; set; } = new(); public int? BookID { get; set; } + public int? ChapterID { get; set; } public int? SceneID { get; set; } public int? WarningTypeID { get; set; } public int? WarningSeverityID { get; set; } public string? EntityType { get; set; } + public string? Category { get; set; } public bool IncludeDismissed { get; set; } public bool IncludeIntentional { get; set; } + public bool ShowAcknowledgedWarnings { get; set; } public IReadOnlyList Warnings { get; set; } = []; + public IReadOnlyList ProjectWarnings { get; set; } = []; public IReadOnlyList BookOptions { get; set; } = []; + public IReadOnlyList ChapterOptions { get; set; } = []; public IReadOnlyList WarningTypeOptions { get; set; } = []; public IReadOnlyList SeverityOptions { get; set; } = []; public IReadOnlyList EntityTypeOptions { get; set; } = []; + public IReadOnlyList CategoryOptions { get; set; } = []; public IReadOnlyDictionary CountsBySeverity { get; set; } = new Dictionary(); public IReadOnlyDictionary CountsByType { get; set; } = new Dictionary(); public ContinuityValidationSummary? LastValidationSummary { get; set; } + public bool HasAnyWarnings { get; set; } +} + +public sealed class ProjectWarningViewModel +{ + public string Source { get; set; } = "Persisted"; + public int? ContinuityWarningID { get; set; } + public string WarningType { get; set; } = string.Empty; + public string WarningTypeDisplayName { get; set; } = string.Empty; + public string Category { get; set; } = "General"; + public string Severity { get; set; } = "Information"; + public string Message { get; set; } = string.Empty; + public string? Details { get; set; } + public int? BookID { get; set; } + public string BookDisplayName { get; set; } = string.Empty; + public int? ChapterID { get; set; } + public string ChapterDisplayName { get; set; } = string.Empty; + public int? SceneID { get; set; } + public string SceneDisplayName { get; set; } = string.Empty; + public int? CharacterID { get; set; } + public string CharacterName { get; set; } = string.Empty; + public int? AssetID { get; set; } + public string EntityType { get; set; } = string.Empty; + public bool IsDismissed { get; set; } + public bool IsIntentional { get; set; } + public bool IsAcknowledged { get; set; } + public string? AcknowledgementNotes { get; set; } + public string WarningKey { get; set; } = string.Empty; + public int? KnowledgeId { get; set; } + public string KnowledgeTitle { get; set; } = string.Empty; + public int? EstablishedSceneId { get; set; } + public string EstablishedSceneDisplayName { get; set; } = string.Empty; + public int? ReferencedSceneId { get; set; } + public string ReferencedSceneDisplayName { get; set; } = string.Empty; + public DateTime LastDetectedDate { get; set; } +} + +public sealed class WarningDashboardReturnFilter +{ + public int ProjectID { get; set; } + public int? BookID { get; set; } + public int? ChapterID { get; set; } + public int? SceneID { get; set; } + public int? WarningTypeID { get; set; } + public int? WarningSeverityID { get; set; } + public string? EntityType { get; set; } + public string? Category { get; set; } + public bool IncludeDismissed { get; set; } + public bool IncludeIntentional { get; set; } + public bool ShowAcknowledgedWarnings { get; set; } } public sealed class SceneDependencyCreateViewModel diff --git a/PlotLine/Views/ContinuityExplorer/Index.cshtml b/PlotLine/Views/ContinuityExplorer/Index.cshtml index 364d7b3..2228ded 100644 --- a/PlotLine/Views/ContinuityExplorer/Index.cshtml +++ b/PlotLine/Views/ContinuityExplorer/Index.cshtml @@ -151,20 +151,12 @@ Location Activity -
-
@@ -365,187 +357,6 @@ else if (Model.Filter.DisplayMode == "Journey") } } -else if (Model.Filter.DisplayMode == "ContinuityWarnings") -{ -
-

Continuity Warnings

- @if (!Model.ContinuityWarnings.Any()) - { -

No continuity concerns detected.

-

Continuity warnings highlight potential issues but cannot replace author judgement.

- } - else - { -
- - - - - - - - - - - - - - - - - @foreach (var warning in Model.ContinuityWarnings) - { - - - - - - - - - - - - - } - -
SeverityStatusTypeCharacterKnowledgeEstablishedEarlier ReferenceMessageSceneActions
@warning.Severity - @if (warning.IsAcknowledged) - { - Acknowledged - } - else - { - Active - } - @warning.WarningTypeDisplayName - @if (!string.IsNullOrWhiteSpace(warning.CharacterName)) - { - @warning.CharacterName - } - else - { - - - } - - @if (!string.IsNullOrWhiteSpace(warning.KnowledgeTitle)) - { - @warning.KnowledgeTitle - } - else - { - - - } - - @if (warning.EstablishedSceneId.HasValue) - { - @warning.EstablishedSceneDisplayName - } - else - { - - - } - - @if (warning.ReferencedSceneId.HasValue) - { - @warning.ReferencedSceneDisplayName - } - else - { - - - } - -
@warning.Message
- @if (warning.IsAcknowledged && !string.IsNullOrWhiteSpace(warning.AcknowledgementNotes)) - { - Note: @warning.AcknowledgementNotes - } -
- @if (warning.SceneID.HasValue) - { - @warning.SceneDisplayName - } - else - { - Project-wide - } - - @if (warning.IsAcknowledged) - { -
- - - - - - @foreach (var entityType in Model.Filter.EntityTypes) - { - - } - @foreach (var characterId in Model.Filter.CharacterIDs) - { - - } - @foreach (var assetId in Model.Filter.AssetIDs) - { - - } - @foreach (var locationId in Model.Filter.LocationIDs) - { - - } - - - - - - - - - -
- } - else - { -
- - - - - - @foreach (var entityType in Model.Filter.EntityTypes) - { - - } - @foreach (var characterId in Model.Filter.CharacterIDs) - { - - } - @foreach (var assetId in Model.Filter.AssetIDs) - { - - } - @foreach (var locationId in Model.Filter.LocationIDs) - { - - } - - - - - - - - - - -
- } -
-
- } -
-} else {
diff --git a/PlotLine/Views/Warnings/Index.cshtml b/PlotLine/Views/Warnings/Index.cshtml index 76e8c5e..a9211e7 100644 --- a/PlotLine/Views/Warnings/Index.cshtml +++ b/PlotLine/Views/Warnings/Index.cshtml @@ -4,6 +4,15 @@ ViewData["ProjectSection"] = "Warnings"; } +@functions { + private static string SeverityBadgeClass(string severity) => severity switch + { + "Critical" => "bg-danger", + "Warning" => "bg-warning text-dark", + _ => "bg-info text-dark" + }; +} +