Phase 12H: Move Continuity Warnings into the Main Warnings Page.
This commit is contained in:
parent
e008da0cf4
commit
f401416fbd
@ -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<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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<IActionResult> 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<IActionResult> 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<IActionResult> 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<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)
|
||||
{
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ public interface ILocationService
|
||||
|
||||
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> ValidateBookAsync(int bookId);
|
||||
Task<ContinuityValidationSummary> 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<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)
|
||||
{
|
||||
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<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
|
||||
{
|
||||
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<ContinuityValidationSummary> 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
|
||||
|
||||
@ -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<ContinuityWarning> Warnings { get; set; } = [];
|
||||
public IReadOnlyList<ProjectWarningViewModel> ProjectWarnings { get; set; } = [];
|
||||
public IReadOnlyList<SelectListItem> BookOptions { get; set; } = [];
|
||||
public IReadOnlyList<SelectListItem> ChapterOptions { get; set; } = [];
|
||||
public IReadOnlyList<SelectListItem> WarningTypeOptions { get; set; } = [];
|
||||
public IReadOnlyList<SelectListItem> SeverityOptions { 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> CountsByType { get; set; } = new Dictionary<string, int>();
|
||||
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
|
||||
|
||||
@ -151,20 +151,12 @@
|
||||
<input class="form-check-input" type="radio" name="DisplayMode" value="LocationActivity" checked="@(Model.Filter.DisplayMode == "LocationActivity")" />
|
||||
<span class="form-check-label">Location Activity</span>
|
||||
</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 class="col-lg-8">
|
||||
<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>
|
||||
<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>
|
||||
|
||||
@ -365,187 +357,6 @@ else if (Model.Filter.DisplayMode == "Journey")
|
||||
}
|
||||
</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
|
||||
{
|
||||
<section class="story-bible-section">
|
||||
|
||||
@ -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"
|
||||
};
|
||||
}
|
||||
|
||||
<nav class="breadcrumb-trail" aria-label="Breadcrumb">
|
||||
<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>
|
||||
@ -53,6 +62,18 @@
|
||||
<option value="">All books</option>
|
||||
</select>
|
||||
</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">
|
||||
<label class="form-label">Type</label>
|
||||
<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" />
|
||||
<span class="form-check-label">Intentional</span>
|
||||
</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 class="col-12">
|
||||
<button class="btn btn-outline-primary btn-sm" type="submit">Filter warnings</button>
|
||||
@ -112,80 +137,171 @@
|
||||
|
||||
<section class="list-section">
|
||||
<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
|
||||
{
|
||||
<div class="warning-list">
|
||||
@foreach (var warning in Model.Warnings)
|
||||
{
|
||||
<article class="warning-card">
|
||||
<div>
|
||||
<div class="warning-card-title">
|
||||
<span class="severity-pill @warning.SeverityName.ToLowerInvariant()">@warning.SeverityName</span>
|
||||
<strong>@warning.WarningTypeName</strong>
|
||||
@if (warning.IsDismissed) { <span class="location-badge">dismissed</span> }
|
||||
@if (warning.IsIntentional) { <span class="location-badge">intentional</span> }
|
||||
</div>
|
||||
<p>@warning.Message</p>
|
||||
@if (!string.IsNullOrWhiteSpace(warning.Details))
|
||||
{
|
||||
<p class="muted">@warning.Details</p>
|
||||
}
|
||||
<p class="muted">
|
||||
@(warning.BookTitle ?? "Project")
|
||||
@if (warning.SceneID.HasValue)
|
||||
{
|
||||
<span>/ Scene @warning.SceneNumber: @warning.SceneTitle</span>
|
||||
}
|
||||
<span>/ @warning.EntityType</span>
|
||||
<span>/ Last seen @warning.LastDetectedDate.ToString("dd MMM yyyy HH:mm")</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="warning-actions">
|
||||
@if (warning.SceneID.HasValue)
|
||||
{
|
||||
<a class="btn btn-outline-primary btn-sm"
|
||||
asp-controller="Timeline"
|
||||
asp-action="Index"
|
||||
asp-route-ProjectID="@Model.Project.ProjectID"
|
||||
asp-route-BookID="@warning.BookID"
|
||||
asp-route-SelectedSceneID="@warning.SceneID"
|
||||
asp-route-FocusType="scene"
|
||||
asp-route-FocusID="@warning.SceneID">Open scene</a>
|
||||
}
|
||||
@if (warning.IsDismissed || warning.IsIntentional)
|
||||
{
|
||||
<form asp-action="Restore" method="post">
|
||||
<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">
|
||||
<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">
|
||||
<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>
|
||||
}
|
||||
</div>
|
||||
</article>
|
||||
}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Severity</th>
|
||||
<th>Category</th>
|
||||
<th>Warning Type</th>
|
||||
<th>Message</th>
|
||||
<th>Book</th>
|
||||
<th>Chapter</th>
|
||||
<th>Scene</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var warning in Model.ProjectWarnings)
|
||||
{
|
||||
<tr>
|
||||
<td><span class="badge @SeverityBadgeClass(warning.Severity)">@warning.Severity</span></td>
|
||||
<td>@warning.Category</td>
|
||||
<td>@warning.WarningTypeDisplayName</td>
|
||||
<td>
|
||||
<div>@warning.Message</div>
|
||||
@if (!string.IsNullOrWhiteSpace(warning.Details))
|
||||
{
|
||||
<small class="text-muted d-block">@warning.Details</small>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(warning.KnowledgeTitle))
|
||||
{
|
||||
<small class="text-muted d-block">Knowledge: @warning.KnowledgeTitle</small>
|
||||
}
|
||||
@if (warning.EstablishedSceneId.HasValue)
|
||||
{
|
||||
<small class="text-muted d-block">
|
||||
Established:
|
||||
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@warning.EstablishedSceneId">@warning.EstablishedSceneDisplayName</a>
|
||||
</small>
|
||||
}
|
||||
@if (warning.ReferencedSceneId.HasValue)
|
||||
{
|
||||
<small class="text-muted d-block">
|
||||
Earlier reference:
|
||||
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@warning.ReferencedSceneId">@warning.ReferencedSceneDisplayName</a>
|
||||
</small>
|
||||
}
|
||||
@if (warning.IsAcknowledged && !string.IsNullOrWhiteSpace(warning.AcknowledgementNotes))
|
||||
{
|
||||
<small class="text-muted d-block">Note: @warning.AcknowledgementNotes</small>
|
||||
}
|
||||
</td>
|
||||
<td>@(string.IsNullOrWhiteSpace(warning.BookDisplayName) ? "Project" : warning.BookDisplayName)</td>
|
||||
<td>@(string.IsNullOrWhiteSpace(warning.ChapterDisplayName) ? "-" : warning.ChapterDisplayName)</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.IsDismissed)
|
||||
{
|
||||
<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>
|
||||
}
|
||||
</section>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user