Phase 12H: Move Continuity Warnings into the Main Warnings Page.

This commit is contained in:
Nick Beckley 2026-06-14 21:14:22 +01:00
parent e008da0cf4
commit f401416fbd
6 changed files with 540 additions and 332 deletions

View File

@ -1,6 +1,5 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using PlotLine.Models;
using PlotLine.Services; using PlotLine.Services;
using PlotLine.ViewModels; using PlotLine.ViewModels;
@ -26,58 +25,4 @@ public sealed class ContinuityExplorerController(IStoryStateService storyState)
{ {
return RedirectToAction(nameof(Index), filter); return RedirectToAction(nameof(Index), filter);
} }
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> 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<IActionResult> 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);
}
} }

View File

@ -1,6 +1,8 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using PlotLine.Models;
using PlotLine.Services; using PlotLine.Services;
using PlotLine.ViewModels;
namespace PlotLine.Controllers; namespace PlotLine.Controllers;
@ -10,14 +12,17 @@ public sealed class WarningsController(IContinuityValidationService validation)
public async Task<IActionResult> Index( public async Task<IActionResult> Index(
int projectId, int projectId,
int? bookId, int? bookId,
int? chapterId,
int? sceneId, int? sceneId,
int? warningTypeId, int? warningTypeId,
int? warningSeverityId, int? warningSeverityId,
string? entityType, string? entityType,
string? category,
bool includeDismissed = false, 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); return model is null ? NotFound() : View(model);
} }
@ -26,7 +31,7 @@ public sealed class WarningsController(IContinuityValidationService validation)
public async Task<IActionResult> ValidateProject(int projectId, int? bookId) public async Task<IActionResult> ValidateProject(int projectId, int? bookId)
{ {
var summary = await validation.ValidateProjectAsync(projectId); 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); return model is null ? NotFound() : View("Index", model);
} }
@ -35,7 +40,7 @@ public sealed class WarningsController(IContinuityValidationService validation)
public async Task<IActionResult> ValidateBook(int projectId, int bookId) public async Task<IActionResult> ValidateBook(int projectId, int bookId)
{ {
var summary = await validation.ValidateBookAsync(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); 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 }); return RedirectToAction(nameof(Index), new { projectId, bookId, sceneId, includeDismissed = true, includeIntentional = true });
} }
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> 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<IActionResult> 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) private IActionResult RedirectAfterAction(int projectId, int? bookId, int? sceneId, bool returnToTimeline)
{ {
if (returnToTimeline && sceneId.HasValue) if (returnToTimeline && sceneId.HasValue)
@ -85,4 +144,22 @@ public sealed class WarningsController(IContinuityValidationService validation)
return RedirectToAction(nameof(Index), new { projectId, bookId, sceneId }); 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
});
}
} }

View File

@ -176,7 +176,7 @@ public interface ILocationService
public interface IContinuityValidationService public interface IContinuityValidationService
{ {
Task<WarningDashboardViewModel?> GetDashboardAsync(int projectId, int? bookId, int? sceneId, int? warningTypeId, int? warningSeverityId, string? entityType, bool includeDismissed, bool includeIntentional, ContinuityValidationSummary? summary = null); Task<WarningDashboardViewModel?> 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<ContinuityValidationSummary> ValidateProjectAsync(int projectId); Task<ContinuityValidationSummary> ValidateProjectAsync(int projectId);
Task<ContinuityValidationSummary> ValidateBookAsync(int bookId); Task<ContinuityValidationSummary> ValidateBookAsync(int bookId);
Task<ContinuityValidationSummary> ValidateSceneAsync(int sceneId); Task<ContinuityValidationSummary> ValidateSceneAsync(int sceneId);
@ -188,6 +188,8 @@ public interface IContinuityValidationService
Task DismissAsync(int warningId); Task DismissAsync(int warningId);
Task MarkIntentionalAsync(int warningId); Task MarkIntentionalAsync(int warningId);
Task RestoreAsync(int warningId); Task RestoreAsync(int warningId);
Task AcknowledgeGeneratedWarningAsync(ContinuityWarningAcknowledgement acknowledgement);
Task RestoreGeneratedWarningAsync(ContinuityWarningAcknowledgement acknowledgement);
} }
public interface ICharacterService public interface ICharacterService
@ -2343,10 +2345,6 @@ public sealed class StoryStateService(
.ThenBy(x => x.LocationName) .ThenBy(x => x.LocationName)
.ToList(); .ToList();
} }
else if (string.Equals(filter.DisplayMode, "ContinuityWarnings", StringComparison.OrdinalIgnoreCase))
{
model.ContinuityWarnings = await GetContinuityWarningsAsync(filter.ProjectID, filter);
}
return model; return model;
} }
@ -3403,6 +3401,12 @@ public sealed class StoryStateService(
{ {
filter.DisplayMode = "StoryState"; 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) private static string SceneLabel(decimal sceneNumber, string sceneTitle)
@ -3911,17 +3915,21 @@ public sealed class ContinuityValidationService(
IAssetRepository assets, IAssetRepository assets,
ICharacterRepository characters, ICharacterRepository characters,
IWarningRepository warnings, IWarningRepository warnings,
IDynamicsRepository dynamics) : IContinuityValidationService IDynamicsRepository dynamics,
IStoryStateService storyState) : IContinuityValidationService
{ {
public async Task<WarningDashboardViewModel?> GetDashboardAsync( public async Task<WarningDashboardViewModel?> GetDashboardAsync(
int projectId, int projectId,
int? bookId, int? bookId,
int? chapterId,
int? sceneId, int? sceneId,
int? warningTypeId, int? warningTypeId,
int? warningSeverityId, int? warningSeverityId,
string? entityType, string? entityType,
string? category,
bool includeDismissed, bool includeDismissed,
bool includeIntentional, bool includeIntentional,
bool showAcknowledgedWarnings,
ContinuityValidationSummary? summary = null) ContinuityValidationSummary? summary = null)
{ {
var project = await projects.GetAsync(projectId); var project = await projects.GetAsync(projectId);
@ -3931,28 +3939,217 @@ public sealed class ContinuityValidationService(
} }
var lookupData = await warnings.GetLookupsAsync(); 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<Chapter>();
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 return new WarningDashboardViewModel
{ {
Project = project, Project = project,
BookID = bookId, BookID = bookId,
ChapterID = chapterId,
SceneID = sceneId, SceneID = sceneId,
WarningTypeID = warningTypeId, WarningTypeID = warningTypeId,
WarningSeverityID = warningSeverityId, WarningSeverityID = warningSeverityId,
EntityType = entityType, EntityType = entityType,
Category = category,
IncludeDismissed = includeDismissed, IncludeDismissed = includeDismissed,
IncludeIntentional = includeIntentional, IncludeIntentional = includeIntentional,
ShowAcknowledgedWarnings = showAcknowledgedWarnings,
Warnings = rows, Warnings = rows,
BookOptions = (await books.ListByProjectAsync(projectId)).Select(x => new SelectListItem($"Book {x.BookNumber}: {x.BookTitle}", x.BookID.ToString(), x.BookID == bookId)).ToList(), ProjectWarnings = dashboardWarnings,
WarningTypeOptions = ChapterService.ToSelectList(lookupData.WarningTypes, x => x.WarningTypeID, x => x.TypeName), BookOptions = projectBooks.Select(x => new SelectListItem($"Book {x.BookNumber}: {x.BookTitle}", x.BookID.ToString(), x.BookID == bookId)).ToList(),
SeverityOptions = ChapterService.ToSelectList(lookupData.Severities, x => x.WarningSeverityID, x => x.SeverityName), ChapterOptions = projectChapters.Select(x => new SelectListItem($"Ch {x.ChapterNumber:g}: {x.ChapterTitle}", x.ChapterID.ToString(), x.ChapterID == chapterId)).ToList(),
EntityTypeOptions = rows.Select(x => x.EntityType).Distinct().OrderBy(x => x).Select(x => new SelectListItem(x, x, x == entityType)).ToList(), WarningTypeOptions = lookupData.WarningTypes.Select(x => new SelectListItem(x.TypeName, x.WarningTypeID.ToString(), x.WarningTypeID == warningTypeId)).ToList(),
CountsBySeverity = rows.GroupBy(x => x.SeverityName).ToDictionary(x => x.Key, x => x.Count()), SeverityOptions = lookupData.Severities.Select(x => new SelectListItem(x.SeverityName, x.WarningSeverityID.ToString(), x.WarningSeverityID == warningSeverityId)).ToList(),
CountsByType = rows.GroupBy(x => x.WarningTypeName).OrderByDescending(x => x.Count()).ToDictionary(x => x.Key, x => x.Count()), 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(),
LastValidationSummary = summary 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<ContinuityValidationSummary> ValidateProjectAsync(int projectId) public async Task<ContinuityValidationSummary> ValidateProjectAsync(int projectId)
{ {
var summary = await warnings.ValidateProjectAsync(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 MarkIntentionalAsync(int warningId) => warnings.MarkIntentionalAsync(warningId);
public Task RestoreAsync(int warningId) => warnings.RestoreAsync(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 public sealed class SceneMetricTypeService(IProjectRepository projects, ISceneMetricTypeRepository metricTypes) : ISceneMetricTypeService

View File

@ -1639,20 +1639,76 @@ public sealed class WarningDashboardViewModel
{ {
public Project Project { get; set; } = new(); public Project Project { get; set; } = new();
public int? BookID { get; set; } public int? BookID { get; set; }
public int? ChapterID { get; set; }
public int? SceneID { get; set; } public int? SceneID { get; set; }
public int? WarningTypeID { get; set; } public int? WarningTypeID { get; set; }
public int? WarningSeverityID { get; set; } public int? WarningSeverityID { get; set; }
public string? EntityType { get; set; } public string? EntityType { get; set; }
public string? Category { get; set; }
public bool IncludeDismissed { get; set; } public bool IncludeDismissed { get; set; }
public bool IncludeIntentional { get; set; } public bool IncludeIntentional { get; set; }
public bool ShowAcknowledgedWarnings { get; set; }
public IReadOnlyList<ContinuityWarning> Warnings { get; set; } = []; public IReadOnlyList<ContinuityWarning> Warnings { get; set; } = [];
public IReadOnlyList<ProjectWarningViewModel> ProjectWarnings { get; set; } = [];
public IReadOnlyList<SelectListItem> BookOptions { get; set; } = []; public IReadOnlyList<SelectListItem> BookOptions { get; set; } = [];
public IReadOnlyList<SelectListItem> ChapterOptions { get; set; } = [];
public IReadOnlyList<SelectListItem> WarningTypeOptions { get; set; } = []; public IReadOnlyList<SelectListItem> WarningTypeOptions { get; set; } = [];
public IReadOnlyList<SelectListItem> SeverityOptions { get; set; } = []; public IReadOnlyList<SelectListItem> SeverityOptions { get; set; } = [];
public IReadOnlyList<SelectListItem> EntityTypeOptions { get; set; } = []; public IReadOnlyList<SelectListItem> EntityTypeOptions { get; set; } = [];
public IReadOnlyList<SelectListItem> CategoryOptions { get; set; } = [];
public IReadOnlyDictionary<string, int> CountsBySeverity { get; set; } = new Dictionary<string, int>(); public IReadOnlyDictionary<string, int> CountsBySeverity { get; set; } = new Dictionary<string, int>();
public IReadOnlyDictionary<string, int> CountsByType { get; set; } = new Dictionary<string, int>(); public IReadOnlyDictionary<string, int> CountsByType { get; set; } = new Dictionary<string, int>();
public ContinuityValidationSummary? LastValidationSummary { get; set; } 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 public sealed class SceneDependencyCreateViewModel

View File

@ -151,20 +151,12 @@
<input class="form-check-input" type="radio" name="DisplayMode" value="LocationActivity" checked="@(Model.Filter.DisplayMode == "LocationActivity")" /> <input class="form-check-input" type="radio" name="DisplayMode" value="LocationActivity" checked="@(Model.Filter.DisplayMode == "LocationActivity")" />
<span class="form-check-label">Location Activity</span> <span class="form-check-label">Location Activity</span>
</label> </label>
<label class="form-check">
<input class="form-check-input" type="radio" name="DisplayMode" value="ContinuityWarnings" checked="@(Model.Filter.DisplayMode == "ContinuityWarnings")" />
<span class="form-check-label">Continuity Warnings</span>
</label>
</div> </div>
</div> </div>
<div class="col-lg-8"> <div class="col-lg-8">
<label class="form-label" for="SelectedSceneID">Story State scene</label> <label class="form-label" for="SelectedSceneID">Story State scene</label>
<select class="form-select form-select-sm" id="SelectedSceneID" name="SelectedSceneID" asp-items="Model.SceneOptions"></select> <select class="form-select form-select-sm" id="SelectedSceneID" name="SelectedSceneID" asp-items="Model.SceneOptions"></select>
<label class="form-check mt-2">
<input class="form-check-input" type="checkbox" name="ShowAcknowledgedWarnings" value="true" checked="@Model.Filter.ShowAcknowledgedWarnings" />
<span class="form-check-label">Show acknowledged warnings</span>
</label>
</div> </div>
</div> </div>
@ -365,187 +357,6 @@ else if (Model.Filter.DisplayMode == "Journey")
} }
</section> </section>
} }
else if (Model.Filter.DisplayMode == "ContinuityWarnings")
{
<section class="story-bible-section">
<h2>Continuity Warnings</h2>
@if (!Model.ContinuityWarnings.Any())
{
<p class="muted">No continuity concerns detected.</p>
<p class="muted">Continuity warnings highlight potential issues but cannot replace author judgement.</p>
}
else
{
<div class="table-responsive">
<table class="table table-sm align-middle">
<thead>
<tr>
<th>Severity</th>
<th>Status</th>
<th>Type</th>
<th>Character</th>
<th>Knowledge</th>
<th>Established</th>
<th>Earlier Reference</th>
<th>Message</th>
<th>Scene</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach (var warning in Model.ContinuityWarnings)
{
<tr>
<td><span class="badge @WarningBadgeClass(warning.Severity)">@warning.Severity</span></td>
<td>
@if (warning.IsAcknowledged)
{
<span class="badge bg-secondary">Acknowledged</span>
}
else
{
<span class="badge bg-warning text-dark">Active</span>
}
</td>
<td>@warning.WarningTypeDisplayName</td>
<td>
@if (!string.IsNullOrWhiteSpace(warning.CharacterName))
{
@warning.CharacterName
}
else
{
<span class="muted">-</span>
}
</td>
<td>
@if (!string.IsNullOrWhiteSpace(warning.KnowledgeTitle))
{
@warning.KnowledgeTitle
}
else
{
<span class="muted">-</span>
}
</td>
<td>
@if (warning.EstablishedSceneId.HasValue)
{
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@warning.EstablishedSceneId">@warning.EstablishedSceneDisplayName</a>
}
else
{
<span class="muted">-</span>
}
</td>
<td>
@if (warning.ReferencedSceneId.HasValue)
{
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@warning.ReferencedSceneId">@warning.ReferencedSceneDisplayName</a>
}
else
{
<span class="muted">-</span>
}
</td>
<td>
<div>@warning.Message</div>
@if (warning.IsAcknowledged && !string.IsNullOrWhiteSpace(warning.AcknowledgementNotes))
{
<small class="text-muted">Note: @warning.AcknowledgementNotes</small>
}
</td>
<td>
@if (warning.SceneID.HasValue)
{
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@warning.SceneID">@warning.SceneDisplayName</a>
}
else
{
<span class="muted">Project-wide</span>
}
</td>
<td>
@if (warning.IsAcknowledged)
{
<form asp-action="RestoreWarning" method="post" class="d-inline">
<input type="hidden" name="WarningType" value="@warning.WarningType" />
<input type="hidden" name="SceneID" value="@warning.SceneID" />
<input type="hidden" name="CharacterID" value="@warning.CharacterID" />
<input type="hidden" name="AssetID" value="@warning.AssetID" />
<input type="hidden" name="ReturnFilter.ProjectID" value="@Model.Filter.ProjectID" />
@foreach (var entityType in Model.Filter.EntityTypes)
{
<input type="hidden" name="ReturnFilter.EntityTypes" value="@entityType" />
}
@foreach (var characterId in Model.Filter.CharacterIDs)
{
<input type="hidden" name="ReturnFilter.CharacterIDs" value="@characterId" />
}
@foreach (var assetId in Model.Filter.AssetIDs)
{
<input type="hidden" name="ReturnFilter.AssetIDs" value="@assetId" />
}
@foreach (var locationId in Model.Filter.LocationIDs)
{
<input type="hidden" name="ReturnFilter.LocationIDs" value="@locationId" />
}
<input type="hidden" name="ReturnFilter.SceneRangeMode" value="@Model.Filter.SceneRangeMode" />
<input type="hidden" name="ReturnFilter.BookID" value="@Model.Filter.BookID" />
<input type="hidden" name="ReturnFilter.ChapterID" value="@Model.Filter.ChapterID" />
<input type="hidden" name="ReturnFilter.StartSceneID" value="@Model.Filter.StartSceneID" />
<input type="hidden" name="ReturnFilter.EndSceneID" value="@Model.Filter.EndSceneID" />
<input type="hidden" name="ReturnFilter.DisplayMode" value="@Model.Filter.DisplayMode" />
<input type="hidden" name="ReturnFilter.SelectedSceneID" value="@Model.Filter.SelectedSceneID" />
<input type="hidden" name="ReturnFilter.ShowAcknowledgedWarnings" value="@Model.Filter.ShowAcknowledgedWarnings" />
<button class="btn btn-outline-secondary btn-sm" type="submit">Restore</button>
</form>
}
else
{
<form asp-action="AcknowledgeWarning" method="post" class="d-flex gap-1 align-items-center">
<input type="hidden" name="WarningType" value="@warning.WarningType" />
<input type="hidden" name="SceneID" value="@warning.SceneID" />
<input type="hidden" name="CharacterID" value="@warning.CharacterID" />
<input type="hidden" name="AssetID" value="@warning.AssetID" />
<input type="hidden" name="ReturnFilter.ProjectID" value="@Model.Filter.ProjectID" />
@foreach (var entityType in Model.Filter.EntityTypes)
{
<input type="hidden" name="ReturnFilter.EntityTypes" value="@entityType" />
}
@foreach (var characterId in Model.Filter.CharacterIDs)
{
<input type="hidden" name="ReturnFilter.CharacterIDs" value="@characterId" />
}
@foreach (var assetId in Model.Filter.AssetIDs)
{
<input type="hidden" name="ReturnFilter.AssetIDs" value="@assetId" />
}
@foreach (var locationId in Model.Filter.LocationIDs)
{
<input type="hidden" name="ReturnFilter.LocationIDs" value="@locationId" />
}
<input type="hidden" name="ReturnFilter.SceneRangeMode" value="@Model.Filter.SceneRangeMode" />
<input type="hidden" name="ReturnFilter.BookID" value="@Model.Filter.BookID" />
<input type="hidden" name="ReturnFilter.ChapterID" value="@Model.Filter.ChapterID" />
<input type="hidden" name="ReturnFilter.StartSceneID" value="@Model.Filter.StartSceneID" />
<input type="hidden" name="ReturnFilter.EndSceneID" value="@Model.Filter.EndSceneID" />
<input type="hidden" name="ReturnFilter.DisplayMode" value="@Model.Filter.DisplayMode" />
<input type="hidden" name="ReturnFilter.SelectedSceneID" value="@Model.Filter.SelectedSceneID" />
<input type="hidden" name="ReturnFilter.ShowAcknowledgedWarnings" value="@Model.Filter.ShowAcknowledgedWarnings" />
<input class="form-control form-control-sm" type="text" name="Notes" maxlength="500" placeholder="Optional note" />
<button class="btn btn-outline-primary btn-sm" type="submit">Acknowledge</button>
</form>
}
</td>
</tr>
}
</tbody>
</table>
</div>
}
</section>
}
else else
{ {
<section class="story-bible-section"> <section class="story-bible-section">

View File

@ -4,6 +4,15 @@
ViewData["ProjectSection"] = "Warnings"; ViewData["ProjectSection"] = "Warnings";
} }
@functions {
private static string SeverityBadgeClass(string severity) => severity switch
{
"Critical" => "bg-danger",
"Warning" => "bg-warning text-dark",
_ => "bg-info text-dark"
};
}
<nav class="breadcrumb-trail" aria-label="Breadcrumb"> <nav class="breadcrumb-trail" aria-label="Breadcrumb">
<a asp-controller="Projects" asp-action="Index">Projects</a> <a asp-controller="Projects" asp-action="Index">Projects</a>
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a> <a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a>
@ -53,6 +62,18 @@
<option value="">All books</option> <option value="">All books</option>
</select> </select>
</div> </div>
<div class="col-md-3">
<label class="form-label">Chapter</label>
<select name="chapterId" asp-items="Model.ChapterOptions" class="form-select form-select-sm">
<option value="">All chapters</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label">Category</label>
<select name="category" asp-items="Model.CategoryOptions" class="form-select form-select-sm">
<option value="">All categories</option>
</select>
</div>
<div class="col-md-3"> <div class="col-md-3">
<label class="form-label">Type</label> <label class="form-label">Type</label>
<select name="warningTypeId" asp-items="Model.WarningTypeOptions" class="form-select form-select-sm"> <select name="warningTypeId" asp-items="Model.WarningTypeOptions" class="form-select form-select-sm">
@ -80,6 +101,10 @@
<input class="form-check-input" name="includeIntentional" type="checkbox" value="true" checked="@Model.IncludeIntentional" /> <input class="form-check-input" name="includeIntentional" type="checkbox" value="true" checked="@Model.IncludeIntentional" />
<span class="form-check-label">Intentional</span> <span class="form-check-label">Intentional</span>
</label> </label>
<label class="form-check">
<input class="form-check-input" name="showAcknowledgedWarnings" type="checkbox" value="true" checked="@Model.ShowAcknowledgedWarnings" />
<span class="form-check-label">Acknowledged</span>
</label>
</div> </div>
<div class="col-12"> <div class="col-12">
<button class="btn btn-outline-primary btn-sm" type="submit">Filter warnings</button> <button class="btn btn-outline-primary btn-sm" type="submit">Filter warnings</button>
@ -112,80 +137,171 @@
<section class="list-section"> <section class="list-section">
<h2>Warnings <help-icon key="warnings.list" /></h2> <h2>Warnings <help-icon key="warnings.list" /></h2>
@if (!Model.Warnings.Any()) @if (!Model.ProjectWarnings.Any())
{ {
<p class="muted">No warnings match this view.</p> <p class="muted">@(Model.HasAnyWarnings ? "No warnings match the current filters." : "No project warnings detected.")</p>
} }
else else
{ {
<div class="warning-list"> <div class="table-responsive">
@foreach (var warning in Model.Warnings) <table class="table table-sm align-middle">
{ <thead>
<article class="warning-card"> <tr>
<div> <th>Severity</th>
<div class="warning-card-title"> <th>Category</th>
<span class="severity-pill @warning.SeverityName.ToLowerInvariant()">@warning.SeverityName</span> <th>Warning Type</th>
<strong>@warning.WarningTypeName</strong> <th>Message</th>
@if (warning.IsDismissed) { <span class="location-badge">dismissed</span> } <th>Book</th>
@if (warning.IsIntentional) { <span class="location-badge">intentional</span> } <th>Chapter</th>
</div> <th>Scene</th>
<p>@warning.Message</p> <th>Status</th>
@if (!string.IsNullOrWhiteSpace(warning.Details)) <th>Actions</th>
{ </tr>
<p class="muted">@warning.Details</p> </thead>
} <tbody>
<p class="muted"> @foreach (var warning in Model.ProjectWarnings)
@(warning.BookTitle ?? "Project") {
@if (warning.SceneID.HasValue) <tr>
{ <td><span class="badge @SeverityBadgeClass(warning.Severity)">@warning.Severity</span></td>
<span>/ Scene @warning.SceneNumber: @warning.SceneTitle</span> <td>@warning.Category</td>
} <td>@warning.WarningTypeDisplayName</td>
<span>/ @warning.EntityType</span> <td>
<span>/ Last seen @warning.LastDetectedDate.ToString("dd MMM yyyy HH:mm")</span> <div>@warning.Message</div>
</p> @if (!string.IsNullOrWhiteSpace(warning.Details))
</div> {
<div class="warning-actions"> <small class="text-muted d-block">@warning.Details</small>
@if (warning.SceneID.HasValue) }
{ @if (!string.IsNullOrWhiteSpace(warning.KnowledgeTitle))
<a class="btn btn-outline-primary btn-sm" {
asp-controller="Timeline" <small class="text-muted d-block">Knowledge: @warning.KnowledgeTitle</small>
asp-action="Index" }
asp-route-ProjectID="@Model.Project.ProjectID" @if (warning.EstablishedSceneId.HasValue)
asp-route-BookID="@warning.BookID" {
asp-route-SelectedSceneID="@warning.SceneID" <small class="text-muted d-block">
asp-route-FocusType="scene" Established:
asp-route-FocusID="@warning.SceneID">Open scene</a> <a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@warning.EstablishedSceneId">@warning.EstablishedSceneDisplayName</a>
} </small>
@if (warning.IsDismissed || warning.IsIntentional) }
{ @if (warning.ReferencedSceneId.HasValue)
<form asp-action="Restore" method="post"> {
<input type="hidden" name="id" value="@warning.ContinuityWarningID" /> <small class="text-muted d-block">
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" /> Earlier reference:
<input type="hidden" name="bookId" value="@Model.BookID" /> <a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@warning.ReferencedSceneId">@warning.ReferencedSceneDisplayName</a>
<input type="hidden" name="sceneId" value="@Model.SceneID" /> </small>
<button class="btn btn-outline-secondary btn-sm" type="submit">Restore</button> }
</form> @if (warning.IsAcknowledged && !string.IsNullOrWhiteSpace(warning.AcknowledgementNotes))
} {
else <small class="text-muted d-block">Note: @warning.AcknowledgementNotes</small>
{ }
<form asp-action="Dismiss" method="post"> </td>
<input type="hidden" name="id" value="@warning.ContinuityWarningID" /> <td>@(string.IsNullOrWhiteSpace(warning.BookDisplayName) ? "Project" : warning.BookDisplayName)</td>
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" /> <td>@(string.IsNullOrWhiteSpace(warning.ChapterDisplayName) ? "-" : warning.ChapterDisplayName)</td>
<input type="hidden" name="bookId" value="@Model.BookID" /> <td>
<input type="hidden" name="sceneId" value="@Model.SceneID" /> @if (warning.SceneID.HasValue)
<button class="btn btn-outline-secondary btn-sm" type="submit">Dismiss</button> {
</form> <a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@warning.SceneID">@warning.SceneDisplayName</a>
<form asp-action="MarkIntentional" method="post"> }
<input type="hidden" name="id" value="@warning.ContinuityWarningID" /> else
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" /> {
<input type="hidden" name="bookId" value="@Model.BookID" /> <span class="muted">Project-wide</span>
<input type="hidden" name="sceneId" value="@Model.SceneID" /> }
<button class="btn btn-outline-secondary btn-sm" type="submit">Intentional</button> </td>
</form> <td>
} @if (warning.IsDismissed)
</div> {
</article> <span class="badge bg-secondary">Dismissed</span>
} }
else if (warning.IsIntentional || warning.IsAcknowledged)
{
<span class="badge bg-secondary">Acknowledged</span>
}
else
{
<span class="badge bg-warning text-dark">Active</span>
}
</td>
<td>
@if (warning.Source == "Persisted" && warning.ContinuityWarningID.HasValue)
{
@if (warning.IsDismissed || warning.IsIntentional)
{
<form asp-action="Restore" method="post" class="d-inline">
<input type="hidden" name="id" value="@warning.ContinuityWarningID" />
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
<input type="hidden" name="bookId" value="@Model.BookID" />
<input type="hidden" name="sceneId" value="@Model.SceneID" />
<button class="btn btn-outline-secondary btn-sm" type="submit">Restore</button>
</form>
}
else
{
<form asp-action="Dismiss" method="post" class="d-inline">
<input type="hidden" name="id" value="@warning.ContinuityWarningID" />
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
<input type="hidden" name="bookId" value="@Model.BookID" />
<input type="hidden" name="sceneId" value="@Model.SceneID" />
<button class="btn btn-outline-secondary btn-sm" type="submit">Dismiss</button>
</form>
<form asp-action="MarkIntentional" method="post" class="d-inline">
<input type="hidden" name="id" value="@warning.ContinuityWarningID" />
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
<input type="hidden" name="bookId" value="@Model.BookID" />
<input type="hidden" name="sceneId" value="@Model.SceneID" />
<button class="btn btn-outline-secondary btn-sm" type="submit">Intentional</button>
</form>
}
}
else
{
@if (warning.IsAcknowledged)
{
<form asp-action="RestoreGeneratedWarning" method="post" class="d-inline">
<input type="hidden" name="WarningType" value="@warning.WarningType" />
<input type="hidden" name="SceneID" value="@warning.SceneID" />
<input type="hidden" name="CharacterID" value="@warning.CharacterID" />
<input type="hidden" name="AssetID" value="@warning.AssetID" />
<input type="hidden" name="ReturnFilter.ProjectID" value="@Model.Project.ProjectID" />
<input type="hidden" name="ReturnFilter.BookID" value="@Model.BookID" />
<input type="hidden" name="ReturnFilter.ChapterID" value="@Model.ChapterID" />
<input type="hidden" name="ReturnFilter.SceneID" value="@Model.SceneID" />
<input type="hidden" name="ReturnFilter.WarningTypeID" value="@Model.WarningTypeID" />
<input type="hidden" name="ReturnFilter.WarningSeverityID" value="@Model.WarningSeverityID" />
<input type="hidden" name="ReturnFilter.EntityType" value="@Model.EntityType" />
<input type="hidden" name="ReturnFilter.Category" value="@Model.Category" />
<input type="hidden" name="ReturnFilter.IncludeDismissed" value="@Model.IncludeDismissed" />
<input type="hidden" name="ReturnFilter.IncludeIntentional" value="@Model.IncludeIntentional" />
<input type="hidden" name="ReturnFilter.ShowAcknowledgedWarnings" value="@Model.ShowAcknowledgedWarnings" />
<button class="btn btn-outline-secondary btn-sm" type="submit">Restore</button>
</form>
}
else
{
<form asp-action="AcknowledgeGeneratedWarning" method="post" class="d-flex gap-1 align-items-center">
<input type="hidden" name="WarningType" value="@warning.WarningType" />
<input type="hidden" name="SceneID" value="@warning.SceneID" />
<input type="hidden" name="CharacterID" value="@warning.CharacterID" />
<input type="hidden" name="AssetID" value="@warning.AssetID" />
<input type="hidden" name="ReturnFilter.ProjectID" value="@Model.Project.ProjectID" />
<input type="hidden" name="ReturnFilter.BookID" value="@Model.BookID" />
<input type="hidden" name="ReturnFilter.ChapterID" value="@Model.ChapterID" />
<input type="hidden" name="ReturnFilter.SceneID" value="@Model.SceneID" />
<input type="hidden" name="ReturnFilter.WarningTypeID" value="@Model.WarningTypeID" />
<input type="hidden" name="ReturnFilter.WarningSeverityID" value="@Model.WarningSeverityID" />
<input type="hidden" name="ReturnFilter.EntityType" value="@Model.EntityType" />
<input type="hidden" name="ReturnFilter.Category" value="@Model.Category" />
<input type="hidden" name="ReturnFilter.IncludeDismissed" value="@Model.IncludeDismissed" />
<input type="hidden" name="ReturnFilter.IncludeIntentional" value="@Model.IncludeIntentional" />
<input type="hidden" name="ReturnFilter.ShowAcknowledgedWarnings" value="@Model.ShowAcknowledgedWarnings" />
<input class="form-control form-control-sm" type="text" name="Notes" maxlength="500" placeholder="Optional note" />
<button class="btn btn-outline-primary btn-sm" type="submit">Acknowledge</button>
</form>
}
}
</td>
</tr>
}
</tbody>
</table>
</div> </div>
} }
</section> </section>