Phase 12K: Warning UI Refinement.
This commit is contained in:
parent
ecc8d3c76a
commit
1783ab88ee
@ -4052,6 +4052,7 @@ public sealed class ContinuityValidationService(
|
|||||||
ShowAcknowledgedWarnings = showAcknowledgedWarnings,
|
ShowAcknowledgedWarnings = showAcknowledgedWarnings,
|
||||||
Warnings = rows,
|
Warnings = rows,
|
||||||
ProjectWarnings = dashboardWarnings,
|
ProjectWarnings = dashboardWarnings,
|
||||||
|
WarningGroups = BuildWarningSeverityGroups(dashboardWarnings),
|
||||||
BookOptions = projectBooks.Select(x => new SelectListItem($"Book {x.BookNumber}: {x.BookTitle}", x.BookID.ToString(), x.BookID == bookId)).ToList(),
|
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(),
|
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(),
|
WarningTypeOptions = lookupData.WarningTypes.Select(x => new SelectListItem(x.TypeName, x.WarningTypeID.ToString(), x.WarningTypeID == warningTypeId)).ToList(),
|
||||||
@ -4297,6 +4298,33 @@ public sealed class ContinuityValidationService(
|
|||||||
warning.InvestigationSceneId = warning.CurrentSceneId ?? warning.ReferencedSceneId ?? warning.SceneID;
|
warning.InvestigationSceneId = warning.CurrentSceneId ?? warning.ReferencedSceneId ?? warning.SceneID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<WarningSeverityGroupViewModel> BuildWarningSeverityGroups(IReadOnlyList<ProjectWarningViewModel> warnings)
|
||||||
|
{
|
||||||
|
var groups = warnings
|
||||||
|
.GroupBy(x => WarningSeverityGroupLabel(x.Severity))
|
||||||
|
.ToDictionary(x => x.Key, x => x.ToList(), StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
return new[] { "Critical", "Warning", "Information" }
|
||||||
|
.Where(groups.ContainsKey)
|
||||||
|
.Select(severity => new WarningSeverityGroupViewModel
|
||||||
|
{
|
||||||
|
Severity = severity,
|
||||||
|
IsExpanded = severity is "Critical" or "Warning",
|
||||||
|
Warnings = groups[severity]
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string WarningSeverityGroupLabel(string severity)
|
||||||
|
{
|
||||||
|
return severity switch
|
||||||
|
{
|
||||||
|
"Critical" or "Error" => "Critical",
|
||||||
|
"Warning" => "Warning",
|
||||||
|
_ => "Information"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static string WarningCategoryForPersisted(ContinuityWarning warning)
|
private static string WarningCategoryForPersisted(ContinuityWarning warning)
|
||||||
{
|
{
|
||||||
return warning.EntityType switch
|
return warning.EntityType switch
|
||||||
|
|||||||
@ -1671,6 +1671,7 @@ public sealed class WarningDashboardViewModel
|
|||||||
public bool ShowAcknowledgedWarnings { 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<ProjectWarningViewModel> ProjectWarnings { get; set; } = [];
|
||||||
|
public IReadOnlyList<WarningSeverityGroupViewModel> WarningGroups { get; set; } = [];
|
||||||
public IReadOnlyList<SelectListItem> BookOptions { get; set; } = [];
|
public IReadOnlyList<SelectListItem> BookOptions { get; set; } = [];
|
||||||
public IReadOnlyList<SelectListItem> ChapterOptions { get; set; } = [];
|
public IReadOnlyList<SelectListItem> ChapterOptions { get; set; } = [];
|
||||||
public IReadOnlyList<SelectListItem> WarningTypeOptions { get; set; } = [];
|
public IReadOnlyList<SelectListItem> WarningTypeOptions { get; set; } = [];
|
||||||
@ -1683,6 +1684,13 @@ public sealed class WarningDashboardViewModel
|
|||||||
public bool HasAnyWarnings { get; set; }
|
public bool HasAnyWarnings { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class WarningSeverityGroupViewModel
|
||||||
|
{
|
||||||
|
public string Severity { get; set; } = string.Empty;
|
||||||
|
public bool IsExpanded { get; set; }
|
||||||
|
public IReadOnlyList<ProjectWarningViewModel> Warnings { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class ProjectWarningViewModel
|
public sealed class ProjectWarningViewModel
|
||||||
{
|
{
|
||||||
public string Source { get; set; } = "Persisted";
|
public string Source { get; set; } = "Persisted";
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
@functions {
|
@functions {
|
||||||
private static string SeverityBadgeClass(string severity) => severity switch
|
private static string SeverityBadgeClass(string severity) => severity switch
|
||||||
{
|
{
|
||||||
"Critical" => "bg-danger",
|
"Critical" or "Error" => "bg-danger",
|
||||||
"Warning" => "bg-warning text-dark",
|
"Warning" => "bg-warning text-dark",
|
||||||
_ => "bg-info text-dark"
|
_ => "bg-info text-dark"
|
||||||
};
|
};
|
||||||
@ -143,23 +143,33 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
<div class="accordion" id="warning-severity-groups">
|
||||||
|
@foreach (var group in Model.WarningGroups)
|
||||||
|
{
|
||||||
|
var groupId = $"warning-group-{group.Severity.ToLowerInvariant().Replace(" ", "-")}";
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h3 class="accordion-header" id="@($"{groupId}-heading")">
|
||||||
|
<button class="accordion-button @(group.IsExpanded ? "" : "collapsed")" type="button" data-bs-toggle="collapse" data-bs-target="#@groupId" aria-expanded="@group.IsExpanded.ToString().ToLowerInvariant()" aria-controls="@groupId">
|
||||||
|
@group.Severity (@group.Warnings.Count)
|
||||||
|
</button>
|
||||||
|
</h3>
|
||||||
|
<div id="@groupId" class="accordion-collapse collapse @(group.IsExpanded ? "show" : "")" aria-labelledby="@($"{groupId}-heading")">
|
||||||
|
<div class="accordion-body p-0">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-sm align-middle">
|
<table class="table table-sm align-middle mb-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Severity</th>
|
<th>Severity</th>
|
||||||
<th>Category</th>
|
<th>Category</th>
|
||||||
<th>Warning Type</th>
|
<th>Warning Type</th>
|
||||||
<th>Message</th>
|
<th>Message</th>
|
||||||
<th>Book</th>
|
|
||||||
<th>Chapter</th>
|
|
||||||
<th>Scene</th>
|
<th>Scene</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach (var warning in Model.ProjectWarnings)
|
@foreach (var warning in group.Warnings)
|
||||||
{
|
{
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge @SeverityBadgeClass(warning.Severity)">@warning.Severity</span></td>
|
<td><span class="badge @SeverityBadgeClass(warning.Severity)">@warning.Severity</span></td>
|
||||||
@ -171,51 +181,30 @@
|
|||||||
{
|
{
|
||||||
<small class="text-muted d-block">@warning.Details</small>
|
<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 (!string.IsNullOrWhiteSpace(warning.PreviousKnowledgeState) && !string.IsNullOrWhiteSpace(warning.CurrentKnowledgeState))
|
@if (!string.IsNullOrWhiteSpace(warning.PreviousKnowledgeState) && !string.IsNullOrWhiteSpace(warning.CurrentKnowledgeState))
|
||||||
{
|
{
|
||||||
<small class="text-muted d-block">State: @warning.PreviousKnowledgeState → @warning.CurrentKnowledgeState</small>
|
<small class="text-muted d-block">@warning.PreviousKnowledgeState → @warning.CurrentKnowledgeState</small>
|
||||||
}
|
|
||||||
@if (warning.PreviousSceneId.HasValue)
|
|
||||||
{
|
|
||||||
<small class="text-muted d-block">
|
|
||||||
Previous:
|
|
||||||
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@warning.PreviousSceneId">@warning.PreviousContextSceneDisplayName</a>
|
|
||||||
</small>
|
|
||||||
}
|
|
||||||
@if (warning.CurrentSceneId.HasValue)
|
|
||||||
{
|
|
||||||
<small class="text-muted d-block">
|
|
||||||
Current:
|
|
||||||
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@warning.CurrentSceneId">@warning.CurrentContextSceneDisplayName</a>
|
|
||||||
</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">@(string.IsNullOrWhiteSpace(warning.EstablishedContextSceneDisplayName) ? warning.EstablishedSceneDisplayName : warning.EstablishedContextSceneDisplayName)</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">@(string.IsNullOrWhiteSpace(warning.ReferencedContextSceneDisplayName) ? warning.ReferencedSceneDisplayName : warning.ReferencedContextSceneDisplayName)</a>
|
|
||||||
</small>
|
|
||||||
}
|
}
|
||||||
@if (warning.IsAcknowledged && !string.IsNullOrWhiteSpace(warning.AcknowledgementNotes))
|
@if (warning.IsAcknowledged && !string.IsNullOrWhiteSpace(warning.AcknowledgementNotes))
|
||||||
{
|
{
|
||||||
<small class="text-muted d-block">Note: @warning.AcknowledgementNotes</small>
|
<small class="text-muted d-block">Note: @warning.AcknowledgementNotes</small>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>@(string.IsNullOrWhiteSpace(warning.BookDisplayName) ? "Project" : warning.BookDisplayName)</td>
|
|
||||||
<td>@(string.IsNullOrWhiteSpace(warning.ChapterDisplayName) ? "-" : warning.ChapterDisplayName)</td>
|
|
||||||
<td>
|
<td>
|
||||||
@if (warning.SceneID.HasValue)
|
@if (warning.PreviousSceneId.HasValue || warning.CurrentSceneId.HasValue)
|
||||||
|
{
|
||||||
|
@if (warning.PreviousSceneId.HasValue)
|
||||||
|
{
|
||||||
|
<small class="text-muted d-block">Previous</small>
|
||||||
|
<a class="d-block mb-2" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@warning.PreviousSceneId">@warning.PreviousContextSceneDisplayName</a>
|
||||||
|
}
|
||||||
|
@if (warning.CurrentSceneId.HasValue)
|
||||||
|
{
|
||||||
|
<small class="text-muted d-block">Current</small>
|
||||||
|
<a class="d-block" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@warning.CurrentSceneId">@warning.CurrentContextSceneDisplayName</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (warning.SceneID.HasValue)
|
||||||
{
|
{
|
||||||
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@warning.SceneID">@warning.ContextSceneDisplayName</a>
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@warning.SceneID">@warning.ContextSceneDisplayName</a>
|
||||||
}
|
}
|
||||||
@ -239,9 +228,10 @@
|
|||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
<div class="d-grid gap-2" style="min-width: 150px;">
|
||||||
@if (warning.CanInvestigate)
|
@if (warning.CanInvestigate)
|
||||||
{
|
{
|
||||||
<a class="btn btn-outline-primary btn-sm"
|
<a class="btn btn-outline-primary btn-sm w-100"
|
||||||
asp-controller="ContinuityExplorer"
|
asp-controller="ContinuityExplorer"
|
||||||
asp-action="Index"
|
asp-action="Index"
|
||||||
asp-route-projectId="@Model.Project.ProjectID"
|
asp-route-projectId="@Model.Project.ProjectID"
|
||||||
@ -254,37 +244,36 @@
|
|||||||
{
|
{
|
||||||
@if (warning.IsDismissed || warning.IsIntentional)
|
@if (warning.IsDismissed || warning.IsIntentional)
|
||||||
{
|
{
|
||||||
<form asp-action="Restore" method="post" class="d-inline">
|
<form asp-action="Restore" method="post" class="d-grid">
|
||||||
<input type="hidden" name="id" value="@warning.ContinuityWarningID" />
|
<input type="hidden" name="id" value="@warning.ContinuityWarningID" />
|
||||||
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
||||||
<input type="hidden" name="bookId" value="@Model.BookID" />
|
<input type="hidden" name="bookId" value="@Model.BookID" />
|
||||||
<input type="hidden" name="sceneId" value="@Model.SceneID" />
|
<input type="hidden" name="sceneId" value="@Model.SceneID" />
|
||||||
<button class="btn btn-outline-secondary btn-sm" type="submit">Restore</button>
|
<button class="btn btn-outline-secondary btn-sm w-100" type="submit">Restore</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<form asp-action="Dismiss" method="post" class="d-inline">
|
<form asp-action="Dismiss" method="post" class="d-grid">
|
||||||
<input type="hidden" name="id" value="@warning.ContinuityWarningID" />
|
<input type="hidden" name="id" value="@warning.ContinuityWarningID" />
|
||||||
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
||||||
<input type="hidden" name="bookId" value="@Model.BookID" />
|
<input type="hidden" name="bookId" value="@Model.BookID" />
|
||||||
<input type="hidden" name="sceneId" value="@Model.SceneID" />
|
<input type="hidden" name="sceneId" value="@Model.SceneID" />
|
||||||
<button class="btn btn-outline-secondary btn-sm" type="submit">Dismiss</button>
|
<button class="btn btn-outline-secondary btn-sm w-100" type="submit">Dismiss</button>
|
||||||
</form>
|
</form>
|
||||||
<form asp-action="MarkIntentional" method="post" class="d-inline">
|
<form asp-action="MarkIntentional" method="post" class="d-grid">
|
||||||
<input type="hidden" name="id" value="@warning.ContinuityWarningID" />
|
<input type="hidden" name="id" value="@warning.ContinuityWarningID" />
|
||||||
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
||||||
<input type="hidden" name="bookId" value="@Model.BookID" />
|
<input type="hidden" name="bookId" value="@Model.BookID" />
|
||||||
<input type="hidden" name="sceneId" value="@Model.SceneID" />
|
<input type="hidden" name="sceneId" value="@Model.SceneID" />
|
||||||
<button class="btn btn-outline-secondary btn-sm" type="submit">Intentional</button>
|
<button class="btn btn-outline-secondary btn-sm w-100" type="submit">Intentional</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (warning.IsAcknowledged)
|
||||||
{
|
{
|
||||||
@if (warning.IsAcknowledged)
|
<span class="badge bg-secondary justify-self-stretch">Acknowledged</span>
|
||||||
{
|
<form asp-action="RestoreGeneratedWarning" method="post" class="d-grid">
|
||||||
<form asp-action="RestoreGeneratedWarning" method="post" class="d-inline">
|
|
||||||
<input type="hidden" name="WarningType" value="@warning.WarningType" />
|
<input type="hidden" name="WarningType" value="@warning.WarningType" />
|
||||||
<input type="hidden" name="SceneID" value="@warning.SceneID" />
|
<input type="hidden" name="SceneID" value="@warning.SceneID" />
|
||||||
<input type="hidden" name="CharacterID" value="@warning.CharacterID" />
|
<input type="hidden" name="CharacterID" value="@warning.CharacterID" />
|
||||||
@ -300,12 +289,12 @@
|
|||||||
<input type="hidden" name="ReturnFilter.IncludeDismissed" value="@Model.IncludeDismissed" />
|
<input type="hidden" name="ReturnFilter.IncludeDismissed" value="@Model.IncludeDismissed" />
|
||||||
<input type="hidden" name="ReturnFilter.IncludeIntentional" value="@Model.IncludeIntentional" />
|
<input type="hidden" name="ReturnFilter.IncludeIntentional" value="@Model.IncludeIntentional" />
|
||||||
<input type="hidden" name="ReturnFilter.ShowAcknowledgedWarnings" value="@Model.ShowAcknowledgedWarnings" />
|
<input type="hidden" name="ReturnFilter.ShowAcknowledgedWarnings" value="@Model.ShowAcknowledgedWarnings" />
|
||||||
<button class="btn btn-outline-secondary btn-sm" type="submit">Restore</button>
|
<button class="btn btn-outline-secondary btn-sm w-100" type="submit">Restore</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<form asp-action="AcknowledgeGeneratedWarning" method="post" class="d-flex gap-1 align-items-center">
|
<form asp-action="AcknowledgeGeneratedWarning" method="post" class="d-grid gap-2">
|
||||||
<input type="hidden" name="WarningType" value="@warning.WarningType" />
|
<input type="hidden" name="WarningType" value="@warning.WarningType" />
|
||||||
<input type="hidden" name="SceneID" value="@warning.SceneID" />
|
<input type="hidden" name="SceneID" value="@warning.SceneID" />
|
||||||
<input type="hidden" name="CharacterID" value="@warning.CharacterID" />
|
<input type="hidden" name="CharacterID" value="@warning.CharacterID" />
|
||||||
@ -321,16 +310,22 @@
|
|||||||
<input type="hidden" name="ReturnFilter.IncludeDismissed" value="@Model.IncludeDismissed" />
|
<input type="hidden" name="ReturnFilter.IncludeDismissed" value="@Model.IncludeDismissed" />
|
||||||
<input type="hidden" name="ReturnFilter.IncludeIntentional" value="@Model.IncludeIntentional" />
|
<input type="hidden" name="ReturnFilter.IncludeIntentional" value="@Model.IncludeIntentional" />
|
||||||
<input type="hidden" name="ReturnFilter.ShowAcknowledgedWarnings" value="@Model.ShowAcknowledgedWarnings" />
|
<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" />
|
<label class="form-label mb-0 small">Reason (optional)</label>
|
||||||
<button class="btn btn-outline-primary btn-sm" type="submit">Acknowledge</button>
|
<input class="form-control form-control-sm" type="text" name="Notes" maxlength="500" />
|
||||||
|
<button class="btn btn-outline-success btn-sm w-100" type="submit">Acknowledge</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
}
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user