diff --git a/PlotLine.Tests/Program.cs b/PlotLine.Tests/Program.cs
index 33d66de..077d635 100644
--- a/PlotLine.Tests/Program.cs
+++ b/PlotLine.Tests/Program.cs
@@ -282,6 +282,7 @@ var tests = new (string Name, Action Test)[]
("Story Intelligence Review Centre uses bounded summary query", StoryIntelligenceReviewCentreUsesBoundedSummaryQuery),
("Story Intelligence detailed review GETs use selected module paths", StoryIntelligenceDetailedReviewGetsUseSelectedModulePaths),
("Story Intelligence completed Character review remains inspectable", StoryIntelligenceCompletedCharacterReviewRemainsInspectable),
+ ("Story Intelligence Review Centre completed modules use canonical destinations", StoryIntelligenceReviewCentreCompletedModulesUseCanonicalDestinations),
("Story Intelligence detailed reviews read persisted candidate pages", StoryIntelligenceDetailedReviewsReadPersistedCandidatePages),
("Story Intelligence Job 6 uses bounded concurrent chapter execution", StoryIntelligenceJob6UsesBoundedConcurrentChapterExecution),
("Story Intelligence global progress uses whole run scene totals", StoryIntelligenceGlobalProgressUsesWholeRunSceneTotals),
@@ -728,7 +729,8 @@ static void StoryIntelligenceReviewCentreCountsOnlyNeedsAttentionCategories()
Assert(viewModel.Contains("CharacterNeedsAttentionCount => CharacterReview.SummaryNeedsAttentionCount ?? CharacterReview.Candidates.Count", StringComparison.Ordinal), "Character count should prefer lightweight summary counts.");
Assert(viewModel.Contains("LocationNeedsAttentionCount => LocationReview.SummaryNeedsAttentionCount ?? LocationReview.Candidates.Count", StringComparison.Ordinal), "Location count should prefer lightweight summary counts.");
Assert(viewModel.Contains("NarrativeNeedsAttentionCount => NarrativeReview.SummaryNeedsAttentionCount ?? NarrativeReview.Candidates.Count", StringComparison.Ordinal), "Narrative count should prefer lightweight summary counts.");
- Assert(centre.Contains("Scene Metrics", StringComparison.Ordinal) && centre.Contains("TotalAnalysedScenes", StringComparison.Ordinal), "Metrics should report analysed scene count.");
+ Assert(centre.Contains("Scene Metrics", StringComparison.Ordinal) && centre.Contains("MetricValueCount", StringComparison.Ordinal), "Metrics should report actual metric availability.");
+ Assert(!centre.Contains("Review Metrics", StringComparison.Ordinal), "Metrics should not present a fake review workflow.");
Assert(!summary.Contains("Locations identified", StringComparison.Ordinal), "Summary should not present total identified rows as review workload.");
}
@@ -757,7 +759,7 @@ static void StoryIntelligenceReviewCentreUsesIndependentTileGrid()
Assert(centre.Contains("can be reviewed in any order", StringComparison.Ordinal), "Header should communicate independent review order.");
Assert(centre.Contains("Locked until Characters are resolved", StringComparison.Ordinal), "Locked dependent tiles should be visible and clearly locked.");
Assert(centre.Contains(">Locked", StringComparison.Ordinal), "Locked tile action should render as non-clickable text.");
- Assert(centre.Contains("Available", StringComparison.Ordinal) && centre.Contains("View Metrics", StringComparison.Ordinal), "Scene Metrics should be available, not a mandatory review workload.");
+ Assert(centre.Contains("No metrics available", StringComparison.Ordinal) && centre.Contains("View on Timeline", StringComparison.Ordinal), "Scene Metrics should distinguish unavailable metrics from Timeline navigation.");
Assert(!centre.Contains("Next", StringComparison.Ordinal) && !centre.Contains("Previous", StringComparison.Ordinal), "Review Centre should not contain wizard navigation.");
Assert(!centre.Contains("Core Import", StringComparison.Ordinal) && !centre.Contains("Analyse Manuscript", StringComparison.Ordinal), "Review Centre should not show phase panels.");
@@ -1174,6 +1176,53 @@ static void StoryIntelligenceCompletedCharacterReviewRemainsInspectable()
Assert(view.Contains("AliasTargetOptions", StringComparison.Ordinal), "Completed Character page should use canonical Character data when no pending candidates exist.");
}
+static void StoryIntelligenceReviewCentreCompletedModulesUseCanonicalDestinations()
+{
+ var root = PlotLineRoot();
+ var centre = File.ReadAllText(Path.Combine(root, "Views/Onboarding/StoryIntelligenceReviewCentre.cshtml"));
+ var model = File.ReadAllText(Path.Combine(root, "ViewModels/OnboardingViewModels.cs"));
+ var persistence = File.ReadAllText(Path.Combine(root, "Models/StoryIntelligencePersistenceModels.cs"));
+ var service = File.ReadAllText(Path.Combine(root, "Services/OnboardingStoryIntelligenceService.cs"));
+ var metricSql = File.ReadAllText(Path.Combine(root, "Sql/178_Phase27E_ReviewCentreMetricSummary.sql"));
+
+ Assert(centre.Contains("\"Review Characters\"", StringComparison.Ordinal), "Incomplete Characters should use the Story Intelligence review action label.");
+ Assert(centre.Contains("\"StoryIntelligenceCharacters\"", StringComparison.Ordinal), "Incomplete Characters should route to Character review.");
+ Assert(centre.Contains("\"View Characters\"", StringComparison.Ordinal), "Complete Characters should use canonical action label.");
+ Assert(centre.Contains("\"Characters\"", StringComparison.Ordinal) && centre.Contains("new { projectId = Model.ProjectID }", StringComparison.Ordinal), "Complete Characters should route to Characters/Index with project context.");
+
+ Assert(centre.Contains("\"Review Locations\"", StringComparison.Ordinal) && centre.Contains("\"StoryIntelligenceLocations\"", StringComparison.Ordinal), "Incomplete Locations should route to Location review.");
+ Assert(centre.Contains("\"View Locations\"", StringComparison.Ordinal) && centre.Contains("\"Locations\"", StringComparison.Ordinal), "Complete Locations should route to Locations/Index.");
+
+ Assert(centre.Contains("\"Review Assets\"", StringComparison.Ordinal) && centre.Contains("\"StoryIntelligenceAssets\"", StringComparison.Ordinal), "Incomplete Assets should route to Asset review.");
+ Assert(centre.Contains("\"View Assets\"", StringComparison.Ordinal) && centre.Contains("\"StoryAssets\"", StringComparison.Ordinal), "Complete Assets should route to StoryAssets/Index.");
+
+ Assert(centre.Contains("\"Review Relationships\"", StringComparison.Ordinal) && centre.Contains("\"StoryIntelligenceRelationships\"", StringComparison.Ordinal), "Incomplete Relationships should route to Relationship review.");
+ Assert(centre.Contains("\"View Relationships\"", StringComparison.Ordinal) && centre.Contains("\"RelationshipMap\"", StringComparison.Ordinal), "Complete Relationships should route to RelationshipMap/Index.");
+ Assert(centre.Contains("new { projectId = Model.ProjectID, bookId = Model.BookID }", StringComparison.Ordinal), "Book-scoped canonical destinations should include book context.");
+
+ Assert(centre.Contains("\"Review Knowledge\"", StringComparison.Ordinal) && centre.Contains("\"StoryIntelligenceKnowledge\"", StringComparison.Ordinal), "Incomplete Knowledge should route to Knowledge review.");
+ Assert(centre.Contains("\"View Knowledge\"", StringComparison.Ordinal) && centre.Contains("\"StoryBible\"", StringComparison.Ordinal), "Complete Knowledge should route to StoryBible/Index.");
+
+ Assert(centre.Contains("Review Plot Lines & Threads", StringComparison.Ordinal), "Incomplete Plot should route to Plot review.");
+ Assert(centre.Contains("Url.Action(\"StoryIntelligenceNarrative\", \"Onboarding\"", StringComparison.Ordinal), "Incomplete Plot should use the Story Intelligence Plot review route.");
+ Assert(centre.Contains("Url.Action(\"Index\", \"Timeline\", new { projectId = Model.ProjectID, bookId = Model.BookID })", StringComparison.Ordinal), "Complete Plot and available Metrics should route to Timeline.");
+ Assert(centre.Contains("View on Timeline", StringComparison.Ordinal), "Complete Plot should label the canonical Timeline action.");
+
+ Assert(centre.Contains("Model.MetricValueCount > 0", StringComparison.Ordinal), "Metrics availability should be based on actual metric values.");
+ Assert(centre.Contains("No metrics available", StringComparison.Ordinal), "Zero metrics should be clearly identified.");
+ Assert(centre.Contains("No action", StringComparison.Ordinal), "Zero metrics should not expose a misleading Review Metrics action.");
+ Assert(!centre.Contains("Review Metrics", StringComparison.Ordinal), "Review Centre should not advertise a non-existent metrics review workflow.");
+
+ Assert(model.Contains("public int MetricValueCount", StringComparison.Ordinal), "Review Centre model should expose metric availability.");
+ Assert(persistence.Contains("public int MetricValueCount", StringComparison.Ordinal), "Persisted summary should expose metric availability.");
+ Assert(service.Contains("MetricValueCount = summary.MetricValueCount", StringComparison.Ordinal), "Review Centre summary should hydrate metric availability.");
+ Assert(metricSql.Contains("MetricValueSummary", StringComparison.Ordinal) && metricSql.Contains("SceneMetricValues", StringComparison.Ordinal), "Metric summary SQL should count existing metric values.");
+
+ Assert(centre.Contains("var href = complete", StringComparison.Ordinal), "Module helper should switch destination by completion state.");
+ Assert(centre.Contains("Url.Action(reviewAction, \"Onboarding\", new { batchId = Model.BatchID })", StringComparison.Ordinal), "Incomplete modules should still open Story Intelligence review.");
+ Assert(centre.Contains("Url.Action(\"Index\", completeController, completeRouteValues)", StringComparison.Ordinal), "Complete modules should open canonical Index routes.");
+}
+
static void StoryIntelligenceDetailedReviewsReadPersistedCandidatePages()
{
var root = PlotLineRoot();
diff --git a/PlotLine/Models/StoryIntelligencePersistenceModels.cs b/PlotLine/Models/StoryIntelligencePersistenceModels.cs
index 98cefa8..46f14e4 100644
--- a/PlotLine/Models/StoryIntelligencePersistenceModels.cs
+++ b/PlotLine/Models/StoryIntelligencePersistenceModels.cs
@@ -179,6 +179,7 @@ public sealed class StoryIntelligenceReviewCentreSummary
public int NarrativePendingCount { get; init; }
public int NarrativeThreadCount { get; init; }
public int NarrativeEventCount { get; init; }
+ public int MetricValueCount { get; init; }
}
public sealed class StoryIntelligenceCommittedRunSummary
diff --git a/PlotLine/Services/OnboardingStoryIntelligenceService.cs b/PlotLine/Services/OnboardingStoryIntelligenceService.cs
index 92acd68..79f1817 100644
--- a/PlotLine/Services/OnboardingStoryIntelligenceService.cs
+++ b/PlotLine/Services/OnboardingStoryIntelligenceService.cs
@@ -846,6 +846,7 @@ public sealed class OnboardingStoryIntelligenceService(
BookID = summary.BookID,
ProjectName = summary.ProjectName,
BookTitle = summary.BookDisplayTitle,
+ MetricValueCount = summary.MetricValueCount,
CharacterReview = new StoryIntelligenceCharacterReviewViewModel
{
HasCommittedScenes = hasCommittedScenes,
diff --git a/PlotLine/Sql/178_Phase27E_ReviewCentreMetricSummary.sql b/PlotLine/Sql/178_Phase27E_ReviewCentreMetricSummary.sql
new file mode 100644
index 0000000..c9619bc
--- /dev/null
+++ b/PlotLine/Sql/178_Phase27E_ReviewCentreMetricSummary.sql
@@ -0,0 +1,134 @@
+SET ANSI_NULLS ON;
+GO
+SET QUOTED_IDENTIFIER ON;
+GO
+
+CREATE OR ALTER PROCEDURE dbo.StoryIntelligenceReviewCentre_GetSummary
+ @BookID int,
+ @UserID int
+AS
+BEGIN
+ SET NOCOUNT ON;
+
+ ;WITH RunSummary AS
+ (
+ SELECT
+ r.BookID,
+ COUNT_BIG(*) AS RunCount,
+ SUM(CASE WHEN r.Status IN (N'Completed', N'CompletedWithWarnings') THEN 1 ELSE 0 END) AS CompletedRunCount,
+ SUM(CASE WHEN r.Status IN (N'Pending', N'Running') THEN 1 ELSE 0 END) AS ActiveRunCount,
+ SUM(CASE WHEN r.Status IN (N'Failed', N'Cancelled') THEN 1 ELSE 0 END) AS FailedRunCount,
+ SUM(COALESCE(r.TotalDetectedScenes, 0)) AS TotalDetectedScenes,
+ SUM(COALESCE(r.CompletedScenes, 0)) AS CompletedScenes,
+ SUM(COALESCE(r.FailedScenes, 0)) AS FailedScenes
+ FROM dbo.StoryIntelligenceRuns r
+ WHERE r.BookID = @BookID
+ GROUP BY r.BookID
+ ),
+ NarrativeSummary AS
+ (
+ SELECT
+ nc.BookID,
+ COUNT_BIG(*) AS NarrativeCandidateCount,
+ SUM(CASE WHEN nc.Status = N'Pending' THEN 1 ELSE 0 END) AS NarrativePendingCount,
+ SUM(CASE WHEN nc.CandidateType = N'Thread' THEN 1 ELSE 0 END) AS NarrativeThreadCount
+ FROM dbo.StoryIntelligenceNarrativeCandidates nc
+ WHERE nc.BookID = @BookID
+ AND nc.WholeBookPlotSynthesisRunID IS NOT NULL
+ GROUP BY nc.BookID
+ ),
+ NarrativeEventSummary AS
+ (
+ SELECT
+ nc.BookID,
+ COUNT_BIG(*) AS NarrativeEventCount
+ FROM dbo.StoryIntelligenceNarrativeCandidateEvents ev
+ INNER JOIN dbo.StoryIntelligenceNarrativeCandidates nc
+ ON nc.NarrativeCandidateID = ev.NarrativeCandidateID
+ WHERE nc.BookID = @BookID
+ AND nc.WholeBookPlotSynthesisRunID IS NOT NULL
+ GROUP BY nc.BookID
+ ),
+ MetricValueSummary AS
+ (
+ SELECT
+ chapter.BookID,
+ COUNT_BIG(*) AS MetricValueCount
+ FROM dbo.SceneMetricValues metric
+ INNER JOIN dbo.Scenes scene
+ ON scene.SceneID = metric.SceneID
+ INNER JOIN dbo.Chapters chapter
+ ON chapter.ChapterID = scene.ChapterID
+ WHERE chapter.BookID = @BookID
+ GROUP BY chapter.BookID
+ ),
+ CurrentPlotRun AS
+ (
+ SELECT TOP (1)
+ run.BookID,
+ run.WholeBookPlotSynthesisRunID,
+ run.Status AS WholeBookPlotSynthesisStatus,
+ run.ErrorMessage AS WholeBookPlotSynthesisErrorMessage
+ FROM dbo.WholeBookPlotSynthesisRuns run
+ WHERE run.BookID = @BookID
+ AND run.IsCurrent = 1
+ ORDER BY run.WholeBookPlotSynthesisRunID DESC
+ )
+ SELECT
+ p.StoryIntelligenceBookPipelineID,
+ p.ProjectID,
+ project.ProjectName,
+ p.BookID,
+ book.BookTitle,
+ book.Subtitle AS BookSubtitle,
+ p.CurrentStage,
+ p.LastCompletedStage,
+ p.CurrentReviewStage,
+ p.Status,
+ p.CompletedUtc,
+ p.CharacterReviewCompletedUtc,
+ p.LocationReviewCompletedUtc,
+ p.AssetReviewCompletedUtc,
+ p.RelationshipReviewCompletedUtc,
+ p.KnowledgeReviewCompletedUtc,
+ p.NarrativeReviewCompletedUtc,
+ p.LastRunID,
+ CAST(COALESCE(rs.RunCount, 0) AS int) AS RunCount,
+ CAST(COALESCE(rs.CompletedRunCount, 0) AS int) AS CompletedRunCount,
+ CAST(COALESCE(rs.ActiveRunCount, 0) AS int) AS ActiveRunCount,
+ CAST(COALESCE(rs.FailedRunCount, 0) AS int) AS FailedRunCount,
+ CAST(COALESCE(rs.TotalDetectedScenes, 0) AS int) AS TotalDetectedScenes,
+ CAST(COALESCE(rs.CompletedScenes, 0) AS int) AS CompletedScenes,
+ CAST(COALESCE(rs.FailedScenes, 0) AS int) AS FailedScenes,
+ plot.WholeBookPlotSynthesisRunID,
+ plot.WholeBookPlotSynthesisStatus,
+ plot.WholeBookPlotSynthesisErrorMessage,
+ CAST(COALESCE(ns.NarrativeCandidateCount, 0) AS int) AS NarrativeCandidateCount,
+ CAST(COALESCE(ns.NarrativePendingCount, 0) AS int) AS NarrativePendingCount,
+ CAST(COALESCE(ns.NarrativeThreadCount, 0) AS int) AS NarrativeThreadCount,
+ CAST(COALESCE(nes.NarrativeEventCount, 0) AS int) AS NarrativeEventCount,
+ CAST(COALESCE(metrics.MetricValueCount, 0) AS int) AS MetricValueCount
+ FROM dbo.StoryIntelligenceBookPipelines p
+ INNER JOIN dbo.Projects project
+ ON project.ProjectID = p.ProjectID
+ INNER JOIN dbo.Books book
+ ON book.BookID = p.BookID
+ INNER JOIN dbo.ProjectUserAccess access
+ ON access.ProjectID = p.ProjectID
+ AND access.UserID = @UserID
+ AND access.IsActive = 1
+ LEFT JOIN RunSummary rs
+ ON rs.BookID = p.BookID
+ LEFT JOIN NarrativeSummary ns
+ ON ns.BookID = p.BookID
+ LEFT JOIN NarrativeEventSummary nes
+ ON nes.BookID = p.BookID
+ LEFT JOIN MetricValueSummary metrics
+ ON metrics.BookID = p.BookID
+ LEFT JOIN CurrentPlotRun plot
+ ON plot.BookID = p.BookID
+ WHERE p.BookID = @BookID
+ AND book.IsArchived = 0
+ AND project.IsArchived = 0;
+END;
+GO
diff --git a/PlotLine/ViewModels/OnboardingViewModels.cs b/PlotLine/ViewModels/OnboardingViewModels.cs
index 24eb02b..bbcbd87 100644
--- a/PlotLine/ViewModels/OnboardingViewModels.cs
+++ b/PlotLine/ViewModels/OnboardingViewModels.cs
@@ -184,6 +184,7 @@ public sealed class StoryIntelligenceProgressViewModel
public int TotalDetectedScenes => Chapters.Sum(chapter => chapter.Scenes.Count > 0 ? chapter.Scenes.Count : chapter.TotalDetectedScenes ?? 0);
public int TotalCompletedScenes => Chapters.Sum(chapter => chapter.CompletedScenes ?? 0);
public int TotalAnalysedScenes => Math.Max(TotalCompletedScenes, PipelineDashboard.ScenesImported);
+ public int MetricValueCount { get; init; }
public int TotalFailedScenes => Chapters.Sum(chapter => chapter.FailedScenes ?? 0);
public int TotalAnalysedWords => Chapters.Sum(chapter => chapter.SourceWordCount ?? 0);
public decimal? TotalEstimatedCostUSD => Chapters.Any(chapter => chapter.EstimatedCostUSD.HasValue) ? Chapters.Sum(chapter => chapter.EstimatedCostUSD ?? 0m) : null;
diff --git a/PlotLine/Views/Onboarding/StoryIntelligenceReviewCentre.cshtml b/PlotLine/Views/Onboarding/StoryIntelligenceReviewCentre.cshtml
index ad77980..cce32b2 100644
--- a/PlotLine/Views/Onboarding/StoryIntelligenceReviewCentre.cshtml
+++ b/PlotLine/Views/Onboarding/StoryIntelligenceReviewCentre.cshtml
@@ -26,11 +26,14 @@
@ReviewModule(
"Characters",
Model.CharacterNeedsAttentionCount,
- Model.CharacterGateSatisfied,
+ Model.PipelineDashboard.CharacterStageComplete,
false,
"Resolve character identities before reviewing dependent Story Intelligence.",
- Model.CharacterGateSatisfied ? "Review Characters" : "Resolve Characters",
+ "Review Characters",
"StoryIntelligenceCharacters",
+ "View Characters",
+ "Characters",
+ new { projectId = Model.ProjectID },
true)
@ReviewModule(
@@ -39,8 +42,11 @@
Model.PipelineDashboard.AssetStageComplete,
!Model.CharacterGateSatisfied,
"Important objects and possessions found in the manuscript.",
- ModuleAction("Assets", Model.AssetNeedsAttentionCount, Model.PipelineDashboard.AssetStageComplete),
- "StoryIntelligenceAssets")
+ "Review Assets",
+ "StoryIntelligenceAssets",
+ "View Assets",
+ "StoryAssets",
+ new { projectId = Model.ProjectID })
@ReviewModule(
"Knowledge",
@@ -48,8 +54,11 @@
Model.PipelineDashboard.KnowledgeStageComplete,
!Model.CharacterGateSatisfied,
"Facts, discoveries, beliefs, and suspicions found in the manuscript.",
- ModuleAction("Knowledge", Model.KnowledgeNeedsAttentionCount, Model.PipelineDashboard.KnowledgeStageComplete),
- "StoryIntelligenceKnowledge")
+ "Review Knowledge",
+ "StoryIntelligenceKnowledge",
+ "View Knowledge",
+ "StoryBible",
+ new { projectId = Model.ProjectID })
@ReviewModule(
"Locations",
@@ -57,8 +66,11 @@
Model.PipelineDashboard.LocationStageComplete,
!Model.CharacterGateSatisfied,
"Places found in the manuscript.",
- ModuleAction("Locations", Model.LocationNeedsAttentionCount, Model.PipelineDashboard.LocationStageComplete),
- "StoryIntelligenceLocations")
+ "Review Locations",
+ "StoryIntelligenceLocations",
+ "View Locations",
+ "Locations",
+ new { projectId = Model.ProjectID })
@ReviewPlotSynthesis()
@@ -68,8 +80,11 @@
Model.PipelineDashboard.RelationshipStageComplete,
!Model.CharacterGateSatisfied,
"Character relationships found in scene evidence.",
- ModuleAction("Relationships", Model.RelationshipNeedsAttentionCount, Model.PipelineDashboard.RelationshipStageComplete),
- "StoryIntelligenceRelationships")
+ "Review Relationships",
+ "StoryIntelligenceRelationships",
+ "View Relationships",
+ "RelationshipMap",
+ new { projectId = Model.ProjectID, bookId = Model.BookID })
@ReviewMetrics(!Model.CharacterGateSatisfied)
@@ -82,7 +97,10 @@
false,
"Some chapters or scenes reported analysis issues.",
"Return to Book",
- null)
+ "StoryIntelligenceReviewCentre",
+ "Return to Book",
+ "Books",
+ new { id = Model.BookID })
}
@@ -94,9 +112,6 @@
@functions {
- private string ModuleAction(string title, int? count, bool complete)
- => count.GetValueOrDefault() > 0 || (!complete && !count.HasValue) ? $"Review {title}" : $"View {title}";
-
private Microsoft.AspNetCore.Html.IHtmlContent ReviewPlotSynthesis()
{
var blocked = !Model.CharacterGateSatisfied;
@@ -146,8 +161,8 @@
{
status = "Complete";
description = "Plot Lines and Threads review has been saved.";
- var href = Url.Action("StoryIntelligenceNarrative", "Onboarding", new { batchId = Model.BatchID }) ?? "#";
- actionHtml = $"View Plot Lines & Threads";
+ var href = Url.Action("Index", "Timeline", new { projectId = Model.ProjectID, bookId = Model.BookID }) ?? "#";
+ actionHtml = $"View on Timeline";
}
else
{
@@ -177,7 +192,7 @@
if (string.Equals(title, "Scene Metrics", StringComparison.Ordinal))
{
- return "Available";
+ return Model.MetricValueCount > 0 ? "Available" : "No metrics available";
}
if (complete)
@@ -200,13 +215,19 @@
private Microsoft.AspNetCore.Html.IHtmlContent ReviewMetrics(bool blocked)
{
- var status = StatusText("Scene Metrics", Model.TotalAnalysedScenes, false, blocked);
+ var hasMetrics = Model.MetricValueCount > 0;
+ var status = StatusText("Scene Metrics", Model.MetricValueCount, false, blocked);
var statusClass = blocked
? "story-review-centre-tile__status story-review-centre-tile__status--locked"
- : "story-review-centre-tile__status story-review-centre-tile__status--ready";
+ : hasMetrics
+ ? "story-review-centre-tile__status story-review-centre-tile__status--ready"
+ : "story-review-centre-tile__status story-review-centre-tile__status--locked";
+ var href = Url.Action("Index", "Timeline", new { projectId = Model.ProjectID, bookId = Model.BookID }) ?? "#";
var actionHtml = blocked
? "Locked"
- : "View Metrics";
+ : hasMetrics
+ ? $"View on Timeline"
+ : "No action";
return new Microsoft.AspNetCore.Html.HtmlString($$"""
@@ -226,8 +247,11 @@
bool complete,
bool blocked,
string description,
- string actionLabel,
- string? action,
+ string reviewActionLabel,
+ string reviewAction,
+ string completeActionLabel,
+ string completeController,
+ object completeRouteValues,
bool primary = false)
{
var status = StatusText(title, count, complete, blocked);
@@ -239,9 +263,10 @@
var tileClass = "story-review-centre-tile"
+ (primary ? " story-review-centre-tile--primary" : string.Empty)
+ (blocked ? " story-review-centre-tile--locked" : string.Empty);
- var href = action is null
- ? Url.Action("Details", "Books", new { id = Model.BookID }) ?? "#"
- : Url.Action(action, "Onboarding", new { batchId = Model.BatchID }) ?? "#";
+ var href = complete
+ ? Url.Action("Index", completeController, completeRouteValues) ?? "#"
+ : Url.Action(reviewAction, "Onboarding", new { batchId = Model.BatchID }) ?? "#";
+ var actionLabel = complete ? completeActionLabel : reviewActionLabel;
var actionHtml = blocked
? "Locked"
: $"{System.Net.WebUtility.HtmlEncode(actionLabel)}";