Writer Workspace changes.
This commit is contained in:
parent
c130879b27
commit
77d5623e51
@ -1485,6 +1485,8 @@ public sealed class WriterDashboardScene
|
||||
public decimal SceneNumber { get; set; }
|
||||
public string SceneTitle { get; set; } = string.Empty;
|
||||
public string? Summary { get; set; }
|
||||
public int RevisionStatusID { get; set; }
|
||||
public string RevisionStatusName { get; set; } = string.Empty;
|
||||
public string DraftStatus { get; set; } = "Planned";
|
||||
public int? Priority { get; set; }
|
||||
public bool IsBlocked { get; set; }
|
||||
@ -1501,6 +1503,8 @@ public sealed class ChapterWorkflowScene
|
||||
public int SceneID { get; set; }
|
||||
public decimal SceneNumber { get; set; }
|
||||
public string SceneTitle { get; set; } = string.Empty;
|
||||
public int RevisionStatusID { get; set; }
|
||||
public string RevisionStatusName { get; set; } = string.Empty;
|
||||
public string DraftStatus { get; set; } = "Planned";
|
||||
public int? Priority { get; set; }
|
||||
public bool ReadyForDraft { get; set; }
|
||||
|
||||
@ -2896,6 +2896,7 @@ public sealed class WriterWorkspaceService(
|
||||
{
|
||||
ProjectID = projectId,
|
||||
ProjectOptions = projectRows.Select(x => new SelectListItem(x.ProjectName, x.ProjectID.ToString(), x.ProjectID == projectId)).ToList(),
|
||||
WriteNext = await GetDashboardScenesAsync(projectIds, "WriteNext"),
|
||||
ContinueWriting = await GetDashboardScenesAsync(projectIds, "ContinueWriting"),
|
||||
NeedsAttention = await GetDashboardScenesAsync(projectIds, "NeedsAttention"),
|
||||
RevisionQueue = await GetDashboardScenesAsync(projectIds, "RevisionQueue"),
|
||||
|
||||
144
PlotLine/Sql/052_RevisionStatusWriterWorkflow.sql
Normal file
144
PlotLine/Sql/052_RevisionStatusWriterWorkflow.sql
Normal file
@ -0,0 +1,144 @@
|
||||
SET ANSI_NULLS ON;
|
||||
GO
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
GO
|
||||
|
||||
DELETE rs
|
||||
FROM dbo.RevisionStatuses rs
|
||||
WHERE rs.StatusName IN (N'Ready To Draft', N'Drafting', N'Revising', N'Polishing', N'Complete', N'On Hold')
|
||||
AND NOT EXISTS (SELECT 1 FROM dbo.Scenes s WHERE s.RevisionStatusID = rs.RevisionStatusID)
|
||||
AND NOT EXISTS (SELECT 1 FROM dbo.Chapters c WHERE c.RevisionStatusID = rs.RevisionStatusID);
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.ChapterWorkflow_Summary
|
||||
@ChapterID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
SELECT s.SceneID, s.SceneNumber, s.SceneTitle,
|
||||
s.RevisionStatusID,
|
||||
rs.StatusName AS RevisionStatusName,
|
||||
COALESCE(sw.DraftStatus, rs.StatusName, N'Planned') AS DraftStatus,
|
||||
sw.Priority,
|
||||
COALESCE(sw.ReadyForDraft, 0) AS ReadyForDraft,
|
||||
COALESCE(sw.ReadyForRevision, 0) AS ReadyForRevision,
|
||||
COALESCE(sw.ReadyForPolish, 0) AS ReadyForPolish,
|
||||
COALESCE(sw.IsBlocked, 0) AS IsBlocked,
|
||||
sw.BlockedReason,
|
||||
ISNULL(warnings.WarningCount, 0) AS WarningCount,
|
||||
ISNULL(checklist.RemainingChecklistCount, 0) AS RemainingChecklistCount,
|
||||
ISNULL(notes.UnresolvedNoteCount, 0) AS UnresolvedNoteCount
|
||||
FROM dbo.Scenes s
|
||||
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
|
||||
LEFT JOIN dbo.SceneWorkflow sw ON sw.SceneID = s.SceneID
|
||||
OUTER APPLY
|
||||
(
|
||||
SELECT COUNT(*) AS WarningCount
|
||||
FROM dbo.ContinuityWarnings cw
|
||||
WHERE cw.SceneID = s.SceneID AND cw.IsDismissed = 0
|
||||
) warnings
|
||||
OUTER APPLY
|
||||
(
|
||||
SELECT COUNT(*) AS RemainingChecklistCount
|
||||
FROM dbo.SceneChecklistItems sci
|
||||
WHERE sci.SceneID = s.SceneID AND sci.IsCompleted = 0
|
||||
) checklist
|
||||
OUTER APPLY
|
||||
(
|
||||
SELECT COUNT(*) AS UnresolvedNoteCount
|
||||
FROM dbo.SceneNotes sn
|
||||
WHERE sn.SceneID = s.SceneID AND sn.IsResolved = 0
|
||||
) notes
|
||||
WHERE s.ChapterID = @ChapterID AND s.IsArchived = 0
|
||||
ORDER BY s.SortOrder, s.SceneID;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.WriterDashboard_Scenes
|
||||
@ProjectID int = NULL,
|
||||
@Queue nvarchar(50)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
WITH SceneBase AS
|
||||
(
|
||||
SELECT p.ProjectID, p.ProjectName, b.BookID, b.BookTitle, c.ChapterID, c.ChapterNumber, c.ChapterTitle,
|
||||
s.SceneID, s.SceneNumber, s.SceneTitle, s.Summary,
|
||||
s.RevisionStatusID,
|
||||
rs.StatusName AS RevisionStatusName,
|
||||
COALESCE(sw.DraftStatus, rs.StatusName, N'Planned') AS DraftStatus,
|
||||
sw.Priority,
|
||||
COALESCE(sw.IsBlocked, 0) AS IsBlocked,
|
||||
sw.BlockedReason,
|
||||
COALESCE(sw.ReadyForDraft, 0) AS ReadyForDraft,
|
||||
COALESCE(sw.ReadyForRevision, 0) AS ReadyForRevision,
|
||||
COALESCE(sw.ReadyForPolish, 0) AS ReadyForPolish,
|
||||
b.SortOrder AS BookSortOrder,
|
||||
c.SortOrder AS ChapterSortOrder,
|
||||
s.SortOrder AS SceneSortOrder
|
||||
FROM dbo.Projects p
|
||||
INNER JOIN dbo.Books b ON b.ProjectID = p.ProjectID AND b.IsArchived = 0
|
||||
INNER JOIN dbo.Chapters c ON c.BookID = b.BookID AND c.IsArchived = 0
|
||||
INNER JOIN dbo.Scenes s ON s.ChapterID = c.ChapterID AND s.IsArchived = 0
|
||||
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
|
||||
LEFT JOIN dbo.SceneWorkflow sw ON sw.SceneID = s.SceneID
|
||||
WHERE p.IsArchived = 0 AND (@ProjectID IS NULL OR p.ProjectID = @ProjectID)
|
||||
)
|
||||
SELECT TOP (40) sb.ProjectID, sb.ProjectName, sb.BookID, sb.BookTitle, sb.ChapterID, sb.ChapterNumber, sb.ChapterTitle,
|
||||
sb.SceneID, sb.SceneNumber, sb.SceneTitle, sb.Summary, sb.RevisionStatusID, sb.RevisionStatusName,
|
||||
sb.DraftStatus, sb.Priority, sb.IsBlocked, sb.BlockedReason,
|
||||
ISNULL(warnings.WarningCount, 0) AS WarningCount,
|
||||
ISNULL(checklist.RemainingChecklistCount, 0) AS RemainingChecklistCount,
|
||||
ISNULL(notes.UnresolvedNoteCount, 0) AS UnresolvedNoteCount,
|
||||
threads.ImportantThreads,
|
||||
pinned.PinnedNotes
|
||||
FROM SceneBase sb
|
||||
OUTER APPLY
|
||||
(
|
||||
SELECT COUNT(*) AS WarningCount
|
||||
FROM dbo.ContinuityWarnings cw
|
||||
WHERE cw.SceneID = sb.SceneID AND cw.IsDismissed = 0
|
||||
) warnings
|
||||
OUTER APPLY
|
||||
(
|
||||
SELECT COUNT(*) AS RemainingChecklistCount
|
||||
FROM dbo.SceneChecklistItems sci
|
||||
WHERE sci.SceneID = sb.SceneID AND sci.IsCompleted = 0
|
||||
) checklist
|
||||
OUTER APPLY
|
||||
(
|
||||
SELECT COUNT(*) AS UnresolvedNoteCount
|
||||
FROM dbo.SceneNotes sn
|
||||
INNER JOIN dbo.SceneNoteTypes snt ON snt.SceneNoteTypeID = sn.SceneNoteTypeID
|
||||
WHERE sn.SceneID = sb.SceneID AND sn.IsResolved = 0
|
||||
AND (@Queue <> N'NeedsAttention' OR snt.TypeName IN (N'TODO', N'Continuity Note'))
|
||||
) notes
|
||||
OUTER APPLY
|
||||
(
|
||||
SELECT STRING_AGG(CONVERT(nvarchar(max), pt.ThreadTitle), N', ') AS ImportantThreads
|
||||
FROM
|
||||
(
|
||||
SELECT DISTINCT TOP (5) pt.ThreadTitle, pt.Importance
|
||||
FROM dbo.ThreadEvents te
|
||||
INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID
|
||||
WHERE te.SceneID = sb.SceneID
|
||||
ORDER BY pt.Importance DESC, pt.ThreadTitle
|
||||
) pt
|
||||
) threads
|
||||
OUTER APPLY
|
||||
(
|
||||
SELECT STRING_AGG(CONVERT(nvarchar(max), COALESCE(sn.NoteTitle, sn.NoteText)), N' | ') AS PinnedNotes
|
||||
FROM dbo.SceneNotes sn
|
||||
WHERE sn.SceneID = sb.SceneID AND sn.IsPinned = 1 AND sn.IsResolved = 0
|
||||
) pinned
|
||||
WHERE
|
||||
(@Queue = N'WriteNext' AND sb.RevisionStatusName = N'Outlined')
|
||||
OR (@Queue = N'ContinueWriting' AND sb.RevisionStatusName = N'Drafted')
|
||||
OR (@Queue = N'NeedsAttention' AND (sb.IsBlocked = 1 OR ISNULL(warnings.WarningCount, 0) > 0 OR ISNULL(notes.UnresolvedNoteCount, 0) > 0))
|
||||
OR (@Queue = N'RevisionQueue' AND sb.RevisionStatusName = N'Needs Work')
|
||||
OR (@Queue = N'PolishingQueue' AND sb.RevisionStatusName = N'Revised')
|
||||
ORDER BY ISNULL(sb.Priority, 0) DESC, sb.BookSortOrder, sb.ChapterSortOrder, sb.SceneSortOrder, sb.SceneID;
|
||||
END;
|
||||
GO
|
||||
@ -312,6 +312,7 @@ public sealed class WriterDashboardViewModel
|
||||
{
|
||||
public int? ProjectID { get; set; }
|
||||
public IReadOnlyList<SelectListItem> ProjectOptions { get; set; } = [];
|
||||
public IReadOnlyList<WriterDashboardScene> WriteNext { get; set; } = [];
|
||||
public IReadOnlyList<WriterDashboardScene> ContinueWriting { get; set; } = [];
|
||||
public IReadOnlyList<WriterDashboardScene> NeedsAttention { get; set; } = [];
|
||||
public IReadOnlyList<WriterDashboardScene> RevisionQueue { get; set; } = [];
|
||||
|
||||
@ -972,13 +972,16 @@
|
||||
<input type="hidden" name="ReturnProjectID" value="@Model.ReturnProjectID" />
|
||||
<input type="hidden" name="ReturnBookID" value="@Model.ReturnBookID" />
|
||||
<div class="row g-2">
|
||||
<div class="col-sm-6">
|
||||
<label class="form-label" for="DraftStatus">Draft status</label>
|
||||
<select class="form-select form-select-sm" id="DraftStatus" name="DraftStatus" asp-items="Model.DraftStatusOptions"></select>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<input type="hidden" name="DraftStatus" value="@Model.Workflow.DraftStatus" />
|
||||
<input type="hidden" name="ReadyForDraft" value="false" />
|
||||
<input type="hidden" name="ReadyForRevision" value="false" />
|
||||
<input type="hidden" name="ReadyForPolish" value="false" />
|
||||
<div class="col-12">
|
||||
<label class="form-label" for="Priority">Priority</label>
|
||||
<input class="form-control form-control-sm" id="Priority" name="Priority" type="number" min="1" max="10" value="@Model.Workflow.Priority" />
|
||||
<div class="writer-priority-control">
|
||||
<input class="form-range" id="Priority" name="Priority" type="range" min="1" max="10" value="@(Model.Workflow.Priority ?? 5)" data-priority-slider />
|
||||
<span class="status-pill" data-priority-label></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<label class="form-label" for="EstimatedWordCount">Estimated words</label>
|
||||
@ -997,9 +1000,6 @@
|
||||
<input class="form-control form-control-sm" id="LastWorkedOn" name="LastWorkedOn" type="date" value="@Model.Workflow.LastWorkedOn?.ToString("yyyy-MM-dd")" />
|
||||
</div>
|
||||
<div class="col-12 writer-checkbox-grid">
|
||||
<label><input type="checkbox" name="ReadyForDraft" value="true" checked="@Model.Workflow.ReadyForDraft" /> Ready for draft</label>
|
||||
<label><input type="checkbox" name="ReadyForRevision" value="true" checked="@Model.Workflow.ReadyForRevision" /> Ready for revision</label>
|
||||
<label><input type="checkbox" name="ReadyForPolish" value="true" checked="@Model.Workflow.ReadyForPolish" /> Ready for polish</label>
|
||||
<label><input type="checkbox" name="IsBlocked" value="true" checked="@Model.Workflow.IsBlocked" /> Blocked</label>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
@ -1011,6 +1011,26 @@
|
||||
<button class="btn btn-primary btn-sm" type="submit">Save workflow</button>
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
(() => {
|
||||
const slider = document.querySelector('[data-priority-slider]');
|
||||
const label = document.querySelector('[data-priority-label]');
|
||||
if (!slider || !label) return;
|
||||
|
||||
const labelFor = value => {
|
||||
const priority = Number(value);
|
||||
if (priority <= 2) return `${priority} - Very Low`;
|
||||
if (priority <= 4) return `${priority} - Low`;
|
||||
if (priority <= 6) return `${priority} - Medium`;
|
||||
if (priority <= 8) return `${priority} - High`;
|
||||
return `${priority} - Critical`;
|
||||
};
|
||||
|
||||
const update = () => label.textContent = labelFor(slider.value);
|
||||
slider.addEventListener('input', update);
|
||||
update();
|
||||
})();
|
||||
</script>
|
||||
</section>
|
||||
|
||||
<section class="inspector-section writer-checklist-section" data-section-title="Scene Checklist" data-section-id="inspector-checklist">
|
||||
|
||||
@ -2,9 +2,10 @@
|
||||
@{
|
||||
ViewData["Title"] = $"Chapter Workflow - {Model.Chapter.ChapterTitle}";
|
||||
var blockedCount = Model.Scenes.Count(x => x.IsBlocked);
|
||||
var readyDraftCount = Model.Scenes.Count(x => x.ReadyForDraft);
|
||||
var readyRevisionCount = Model.Scenes.Count(x => x.ReadyForRevision);
|
||||
var readyPolishCount = Model.Scenes.Count(x => x.ReadyForPolish);
|
||||
var writeNextCount = Model.Scenes.Count(x => x.RevisionStatusName == "Outlined");
|
||||
var continueWritingCount = Model.Scenes.Count(x => x.RevisionStatusName == "Drafted");
|
||||
var revisionCount = Model.Scenes.Count(x => x.RevisionStatusName == "Needs Work");
|
||||
var polishCount = Model.Scenes.Count(x => x.RevisionStatusName == "Revised");
|
||||
}
|
||||
|
||||
<nav class="breadcrumb-trail" aria-label="Breadcrumb">
|
||||
@ -31,9 +32,10 @@
|
||||
|
||||
<section class="story-bible-section">
|
||||
<div class="story-bible-grid story-bible-grid-compact">
|
||||
<article class="story-bible-card"><h3>Ready to draft</h3><strong class="writer-kpi">@readyDraftCount</strong></article>
|
||||
<article class="story-bible-card"><h3>Ready to revise</h3><strong class="writer-kpi">@readyRevisionCount</strong></article>
|
||||
<article class="story-bible-card"><h3>Ready to polish</h3><strong class="writer-kpi">@readyPolishCount</strong></article>
|
||||
<article class="story-bible-card"><h3>Write next</h3><strong class="writer-kpi">@writeNextCount</strong></article>
|
||||
<article class="story-bible-card"><h3>Continue writing</h3><strong class="writer-kpi">@continueWritingCount</strong></article>
|
||||
<article class="story-bible-card"><h3>Revision</h3><strong class="writer-kpi">@revisionCount</strong></article>
|
||||
<article class="story-bible-card"><h3>Polish</h3><strong class="writer-kpi">@polishCount</strong></article>
|
||||
<article class="story-bible-card"><h3>Blocked</h3><strong class="writer-kpi">@blockedCount</strong></article>
|
||||
</div>
|
||||
</section>
|
||||
@ -75,16 +77,13 @@
|
||||
<article class="writer-queue-card">
|
||||
<div class="writer-queue-title">
|
||||
<h3>Scene @scene.SceneNumber: @scene.SceneTitle</h3>
|
||||
<span class="status-pill">@scene.DraftStatus</span>
|
||||
<span class="status-pill">@(string.IsNullOrWhiteSpace(scene.RevisionStatusName) ? scene.DraftStatus : scene.RevisionStatusName)</span>
|
||||
</div>
|
||||
<div class="story-bible-meta">
|
||||
@if (scene.Priority.HasValue) { <span>Priority @scene.Priority</span> }
|
||||
<span>@scene.WarningCount warnings</span>
|
||||
<span>@scene.RemainingChecklistCount checklist remaining</span>
|
||||
<span>@scene.UnresolvedNoteCount open notes</span>
|
||||
@if (scene.ReadyForDraft) { <span>Ready for draft</span> }
|
||||
@if (scene.ReadyForRevision) { <span>Ready for revision</span> }
|
||||
@if (scene.ReadyForPolish) { <span>Ready for polish</span> }
|
||||
@if (scene.IsBlocked) { <span>Blocked</span> }
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(scene.BlockedReason))
|
||||
|
||||
@ -22,6 +22,17 @@
|
||||
<div class="story-bible-section-heading">
|
||||
<div>
|
||||
<p class="eyebrow">Write Next</p>
|
||||
<h2>Write Next <help-icon key="workspace.writeNext" /></h2>
|
||||
</div>
|
||||
<span class="soft-count">@Model.WriteNext.Count scenes</span>
|
||||
</div>
|
||||
<partial name="_WriterSceneQueue" model="Model.WriteNext" />
|
||||
</section>
|
||||
|
||||
<section class="writer-dashboard-panel">
|
||||
<div class="story-bible-section-heading">
|
||||
<div>
|
||||
<p class="eyebrow">In progress</p>
|
||||
<h2>Continue Writing <help-icon key="workspace.writeNext" /></h2>
|
||||
</div>
|
||||
<span class="soft-count">@Model.ContinueWriting.Count scenes</span>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
@if (!Model.Any())
|
||||
{
|
||||
<p class="muted">Nothing needs your attention here. When scenes are ready for this stage, they will appear in this queue.</p>
|
||||
<p class="muted">Scenes appear here based on Revision Status. Use Outlined for Write Next, Drafted for Continue Writing, Needs Work for Revision, and Revised for Polishing.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -15,7 +15,7 @@ else
|
||||
<span class="eyebrow">@scene.BookTitle / Chapter @scene.ChapterNumber</span>
|
||||
<h3>Scene @scene.SceneNumber: @scene.SceneTitle</h3>
|
||||
</div>
|
||||
<span class="status-pill">@scene.DraftStatus</span>
|
||||
<span class="status-pill">@(string.IsNullOrWhiteSpace(scene.RevisionStatusName) ? scene.DraftStatus : scene.RevisionStatusName)</span>
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(scene.Summary))
|
||||
{
|
||||
|
||||
@ -3146,6 +3146,17 @@ body.dragging-location [data-drag-type="location"] {
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.writer-priority-control {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) max-content;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.writer-priority-control .form-range {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.writer-note-list,
|
||||
.writer-queue-list {
|
||||
display: grid;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user