From b813b187d71ba925f61f8489604a08a7cc5bcd4b Mon Sep 17 00:00:00 2001 From: Nick Beckley Date: Sat, 13 Jun 2026 20:10:54 +0100 Subject: [PATCH] Phase 10F: Sync Current Scene Progress --- PlotLine/Data/WordCompanionRepository.cs | 6 +- PlotLine/Models/WordCompanionApiModels.cs | 5 +- PlotLine/Services/WordCompanionService.cs | 6 +- ...hase10F_WordCompanionSceneProgressSync.sql | 52 ++++++++++++++ PlotLine/Views/WordCompanionHost/Index.cshtml | 24 +++++++ PlotLine/wwwroot/css/word-companion.css | 21 ++++++ PlotLine/wwwroot/js/word-companion-host.js | 69 +++++++++++++++++++ 7 files changed, 176 insertions(+), 7 deletions(-) create mode 100644 PlotLine/Sql/059_Phase10F_WordCompanionSceneProgressSync.sql diff --git a/PlotLine/Data/WordCompanionRepository.cs b/PlotLine/Data/WordCompanionRepository.cs index 2c1dcb9..18b0441 100644 --- a/PlotLine/Data/WordCompanionRepository.cs +++ b/PlotLine/Data/WordCompanionRepository.cs @@ -14,7 +14,7 @@ public interface IWordCompanionRepository Task SyncSceneAsync(int bookId, int userId, WordCompanionSyncSceneRequest request); Task SyncManuscriptAsync(int bookId, int userId, WordCompanionSyncManuscriptRequest request); Task UpdateSceneLinksAsync(int sceneId, int userId, WordCompanionUpdateSceneLinksRequest request); - Task UpdateWordCountAsync(int sceneId, int userId, int actualWords); + Task UpdateWordCountAsync(int sceneId, int userId, int actualWords, DateTime? lastWorkedOn); } public sealed class WordCompanionRepository(ISqlConnectionFactory connectionFactory) : IWordCompanionRepository @@ -179,12 +179,12 @@ public sealed class WordCompanionRepository(ISqlConnectionFactory connectionFact commandType: CommandType.StoredProcedure); } - public async Task UpdateWordCountAsync(int sceneId, int userId, int actualWords) + public async Task UpdateWordCountAsync(int sceneId, int userId, int actualWords, DateTime? lastWorkedOn) { using var connection = connectionFactory.CreateConnection(); return await connection.QuerySingleOrDefaultAsync( "dbo.WordCompanion_Scene_UpdateWordCount", - new { SceneID = sceneId, UserID = userId, ActualWords = actualWords }, + new { SceneID = sceneId, UserID = userId, ActualWords = actualWords, LastWorkedOn = lastWorkedOn }, commandType: CommandType.StoredProcedure); } diff --git a/PlotLine/Models/WordCompanionApiModels.cs b/PlotLine/Models/WordCompanionApiModels.cs index 965d0be..8d27a07 100644 --- a/PlotLine/Models/WordCompanionApiModels.cs +++ b/PlotLine/Models/WordCompanionApiModels.cs @@ -163,12 +163,15 @@ public sealed class WordCompanionUpdateSceneLinksResponse public sealed class WordCompanionUpdateWordCountRequest { - public int ActualWords { get; set; } + public int? ActualWords { get; set; } + public int? ActualWordCount { get; set; } + public DateTime? LastWorkedOn { get; set; } } public sealed class WordCompanionUpdateWordCountResponse { public int SceneId { get; init; } public int ActualWords { get; init; } + public DateTime? LastWorkedOn { get; init; } public string Message { get; init; } = "Word count updated."; } diff --git a/PlotLine/Services/WordCompanionService.cs b/PlotLine/Services/WordCompanionService.cs index dfe25a7..4932699 100644 --- a/PlotLine/Services/WordCompanionService.cs +++ b/PlotLine/Services/WordCompanionService.cs @@ -107,9 +107,10 @@ public sealed class WordCompanionService(IWordCompanionRepository repository, IC public Task UpdateWordCountAsync(int sceneId, WordCompanionUpdateWordCountRequest request) { - return sceneId <= 0 || request.ActualWords < 0 + var actualWords = request.ActualWordCount ?? request.ActualWords; + return sceneId <= 0 || actualWords is null or < 0 ? Task.FromResult(null) - : repository.UpdateWordCountAsync(sceneId, RequireUserId(), request.ActualWords); + : repository.UpdateWordCountAsync(sceneId, RequireUserId(), actualWords.Value, request.LastWorkedOn); } private async Task ResolveSceneCoreAsync(int bookId, WordCompanionResolveSceneRequest request) @@ -124,4 +125,3 @@ public sealed class WordCompanionService(IWordCompanionRepository repository, IC private static bool HasInvalidIds(IEnumerable ids) => ids.Any(id => id <= 0); } - diff --git a/PlotLine/Sql/059_Phase10F_WordCompanionSceneProgressSync.sql b/PlotLine/Sql/059_Phase10F_WordCompanionSceneProgressSync.sql new file mode 100644 index 0000000..cb6abcd --- /dev/null +++ b/PlotLine/Sql/059_Phase10F_WordCompanionSceneProgressSync.sql @@ -0,0 +1,52 @@ +CREATE OR ALTER PROCEDURE dbo.WordCompanion_Scene_UpdateWordCount + @SceneID int, + @UserID int, + @ActualWords int, + @LastWorkedOn datetime2 = NULL +AS +BEGIN + SET NOCOUNT ON; + + IF @ActualWords < 0 + RETURN; + + IF NOT EXISTS + ( + SELECT 1 + FROM dbo.Scenes s + INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID + INNER JOIN dbo.Books b ON b.BookID = c.BookID + INNER JOIN dbo.Projects p ON p.ProjectID = b.ProjectID + INNER JOIN dbo.ProjectUserAccess pua ON pua.ProjectID = p.ProjectID + WHERE s.SceneID = @SceneID + AND s.IsArchived = 0 + AND c.IsArchived = 0 + AND b.IsArchived = 0 + AND p.IsArchived = 0 + AND pua.UserID = @UserID + AND pua.IsActive = 1 + ) + RETURN; + + DECLARE @LastWorkedOnDate date = COALESCE(CONVERT(date, @LastWorkedOn), CONVERT(date, SYSUTCDATETIME())); + + IF EXISTS (SELECT 1 FROM dbo.SceneWorkflow WHERE SceneID = @SceneID) + BEGIN + UPDATE dbo.SceneWorkflow + SET ActualWordCount = @ActualWords, + LastWorkedOn = @LastWorkedOnDate, + UpdatedDate = SYSUTCDATETIME() + WHERE SceneID = @SceneID; + END + ELSE + BEGIN + INSERT dbo.SceneWorkflow (SceneID, ActualWordCount, LastWorkedOn) + VALUES (@SceneID, @ActualWords, @LastWorkedOnDate); + END; + + SELECT @SceneID AS SceneId, + @ActualWords AS ActualWords, + @LastWorkedOnDate AS LastWorkedOn, + N'Word count updated.' AS Message; +END; +GO diff --git a/PlotLine/Views/WordCompanionHost/Index.cshtml b/PlotLine/Views/WordCompanionHost/Index.cshtml index b64a73c..21dd790 100644 --- a/PlotLine/Views/WordCompanionHost/Index.cshtml +++ b/PlotLine/Views/WordCompanionHost/Index.cshtml @@ -154,6 +154,30 @@ - +
+
+ Progress Sync + +
+
+
+ Word Document Count + - +
+
+ PlotDirector Count + - +
+
+ Last Synced + - +
+
+ Sync Status + No resolved PlotDirector scene. +
+
+