Phase 16B - Manuscript Binding and Linked Structure Foundation
This commit is contained in:
parent
3c48301732
commit
ced6f4cd0e
@ -63,7 +63,16 @@ public sealed class ChaptersController(IChapterService chapters) : Controller
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Archive(int id, int bookId)
|
||||
{
|
||||
await chapters.ArchiveAsync(id);
|
||||
try
|
||||
{
|
||||
await chapters.ArchiveAsync(id);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
TempData["ArchiveMessage"] = ex.Message;
|
||||
return RedirectToAction(nameof(Details), new { id });
|
||||
}
|
||||
|
||||
TempData["ArchiveMessage"] = "Chapter archived. You can restore it from Archived Items.";
|
||||
return RedirectToAction("Details", "Books", new { id = bookId });
|
||||
}
|
||||
|
||||
@ -94,4 +94,18 @@ public sealed class WordCompanionController(IWordCompanionService wordCompanion)
|
||||
var response = await wordCompanion.UpdateWordCountAsync(sceneId, request);
|
||||
return response is null ? BadRequest() : Ok(response);
|
||||
}
|
||||
|
||||
[HttpGet("manuscript/book/{bookId:int}")]
|
||||
public async Task<IActionResult> GetManuscriptForBook(int bookId)
|
||||
{
|
||||
var response = await wordCompanion.GetManuscriptForBookAsync(bookId);
|
||||
return response is null ? NotFound() : Ok(response);
|
||||
}
|
||||
|
||||
[HttpPost("manuscript/link")]
|
||||
public async Task<IActionResult> LinkManuscript(WordCompanionManuscriptLinkRequest request)
|
||||
{
|
||||
var response = await wordCompanion.LinkManuscriptAsync(request);
|
||||
return response is null ? BadRequest() : Ok(response);
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,6 +60,15 @@ public interface IBookRepository
|
||||
Task ArchiveAsync(int bookId);
|
||||
}
|
||||
|
||||
public interface IManuscriptDocumentRepository
|
||||
{
|
||||
Task<ManuscriptDocumentModel?> GetByBookAsync(int bookId, int userId);
|
||||
Task<ManuscriptDocumentModel?> GetByGuidAsync(Guid documentGuid, int userId);
|
||||
Task<ManuscriptDocumentModel?> SaveAsync(int bookId, Guid documentGuid, int userId);
|
||||
Task UpdateLastSyncAsync(int manuscriptDocumentId, int userId);
|
||||
Task UpdateLastOpenedAsync(int manuscriptDocumentId, int userId);
|
||||
}
|
||||
|
||||
public interface IChapterRepository
|
||||
{
|
||||
Task<IReadOnlyList<Chapter>> ListByBookAsync(int bookId);
|
||||
@ -2250,6 +2259,61 @@ public sealed class BookRepository(ISqlConnectionFactory connectionFactory) : IB
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ManuscriptDocumentRepository(ISqlConnectionFactory connectionFactory) : IManuscriptDocumentRepository
|
||||
{
|
||||
public async Task<ManuscriptDocumentModel?> GetByBookAsync(int bookId, int userId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
return await connection.QuerySingleOrDefaultAsync<ManuscriptDocumentModel>(
|
||||
"dbo.ManuscriptDocument_GetByBook",
|
||||
new { BookID = bookId, UserID = userId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task<ManuscriptDocumentModel?> GetByGuidAsync(Guid documentGuid, int userId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
return await connection.QuerySingleOrDefaultAsync<ManuscriptDocumentModel>(
|
||||
"dbo.ManuscriptDocument_GetByGuid",
|
||||
new { DocumentGuid = documentGuid, UserID = userId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task<ManuscriptDocumentModel?> SaveAsync(int bookId, Guid documentGuid, int userId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
try
|
||||
{
|
||||
return await connection.QuerySingleOrDefaultAsync<ManuscriptDocumentModel>(
|
||||
"dbo.ManuscriptDocument_Save",
|
||||
new { BookID = bookId, DocumentGuid = documentGuid, UserID = userId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
catch (SqlException ex) when (ex.Number == 51000)
|
||||
{
|
||||
throw new InvalidOperationException(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateLastSyncAsync(int manuscriptDocumentId, int userId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
await connection.ExecuteAsync(
|
||||
"dbo.ManuscriptDocument_UpdateLastSync",
|
||||
new { ManuscriptDocumentID = manuscriptDocumentId, UserID = userId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task UpdateLastOpenedAsync(int manuscriptDocumentId, int userId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
await connection.ExecuteAsync(
|
||||
"dbo.ManuscriptDocument_UpdateLastOpened",
|
||||
new { ManuscriptDocumentID = manuscriptDocumentId, UserID = userId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ChapterRepository(ISqlConnectionFactory connectionFactory) : IChapterRepository
|
||||
{
|
||||
public async Task<IReadOnlyList<Chapter>> ListByBookAsync(int bookId)
|
||||
@ -2277,7 +2341,14 @@ public sealed class ChapterRepository(ISqlConnectionFactory connectionFactory) :
|
||||
public async Task ArchiveAsync(int chapterId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
await connection.ExecuteAsync("dbo.Chapter_Archive", new { ChapterID = chapterId }, commandType: CommandType.StoredProcedure);
|
||||
try
|
||||
{
|
||||
await connection.ExecuteAsync("dbo.Chapter_Archive", new { ChapterID = chapterId }, commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
catch (SqlException ex) when (ex.Number == 51000)
|
||||
{
|
||||
throw new InvalidOperationException(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -255,6 +255,20 @@ public sealed class Book
|
||||
: null;
|
||||
}
|
||||
|
||||
public sealed class ManuscriptDocumentModel
|
||||
{
|
||||
public int ManuscriptDocumentID { get; set; }
|
||||
public int BookID { get; set; }
|
||||
public int ProjectID { get; set; }
|
||||
public Guid DocumentGuid { get; set; }
|
||||
public int BindingVersion { get; set; } = 1;
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
public DateTime? LastSyncUtc { get; set; }
|
||||
public DateTime? LastOpenedUtc { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public int? CreatedByUserID { get; set; }
|
||||
}
|
||||
|
||||
public sealed class CharacterAlias
|
||||
{
|
||||
public int CharacterAliasID { get; set; }
|
||||
@ -330,6 +344,10 @@ public sealed class Chapter
|
||||
public bool IsArchived { get; set; }
|
||||
public DateTime? ArchivedDate { get; set; }
|
||||
public string? ArchivedReason { get; set; }
|
||||
public bool IsLinkedToManuscript { get; set; }
|
||||
public int? ManuscriptDocumentID { get; set; }
|
||||
public string? ExternalReference { get; set; }
|
||||
public DateTime? LinkedUtc { get; set; }
|
||||
}
|
||||
|
||||
public sealed class Scene
|
||||
@ -374,6 +392,10 @@ public sealed class Scene
|
||||
public bool IsArchived { get; set; }
|
||||
public DateTime? ArchivedDate { get; set; }
|
||||
public string? ArchivedReason { get; set; }
|
||||
public bool IsLinkedToManuscript { get; set; }
|
||||
public int? ManuscriptDocumentID { get; set; }
|
||||
public string? ExternalReference { get; set; }
|
||||
public DateTime? LinkedUtc { get; set; }
|
||||
public List<int> SelectedPurposeTypeIds { get; set; } = [];
|
||||
public List<ScenePurposeLabel> PurposeLabels { get; set; } = [];
|
||||
public List<SceneMetricValue> Metrics { get; set; } = [];
|
||||
|
||||
@ -217,3 +217,35 @@ public sealed class WordCompanionUpdateWordCountResponse
|
||||
public DateTime? LastWorkedOn { get; init; }
|
||||
public string Message { get; init; } = "Word count updated.";
|
||||
}
|
||||
|
||||
public sealed class WordCompanionManuscriptDocumentDto
|
||||
{
|
||||
public int ManuscriptDocumentId { get; init; }
|
||||
public int BookId { get; init; }
|
||||
public int ProjectId { get; init; }
|
||||
public Guid DocumentGuid { get; init; }
|
||||
public int BindingVersion { get; init; }
|
||||
public DateTime CreatedUtc { get; init; }
|
||||
public DateTime? LastSyncUtc { get; init; }
|
||||
public DateTime? LastOpenedUtc { get; init; }
|
||||
public bool IsActive { get; init; }
|
||||
}
|
||||
|
||||
public sealed class WordCompanionManuscriptBookResponse
|
||||
{
|
||||
public int BookId { get; init; }
|
||||
public WordCompanionManuscriptDocumentDto? ManuscriptDocument { get; init; }
|
||||
}
|
||||
|
||||
public sealed class WordCompanionManuscriptLinkRequest
|
||||
{
|
||||
public int BookId { get; set; }
|
||||
public Guid? DocumentGuid { get; set; }
|
||||
}
|
||||
|
||||
public sealed class WordCompanionManuscriptLinkResponse
|
||||
{
|
||||
public int BookId { get; init; }
|
||||
public WordCompanionManuscriptDocumentDto ManuscriptDocument { get; init; } = new();
|
||||
public string Message { get; init; } = "Manuscript linked.";
|
||||
}
|
||||
|
||||
@ -96,6 +96,7 @@ public class Program
|
||||
builder.Services.AddScoped<IFeatureRequestRepository, FeatureRequestRepository>();
|
||||
builder.Services.AddScoped<IProjectRepository, ProjectRepository>();
|
||||
builder.Services.AddScoped<IBookRepository, BookRepository>();
|
||||
builder.Services.AddScoped<IManuscriptDocumentRepository, ManuscriptDocumentRepository>();
|
||||
builder.Services.AddScoped<IChapterRepository, ChapterRepository>();
|
||||
builder.Services.AddScoped<ISceneRepository, SceneRepository>();
|
||||
builder.Services.AddScoped<ISceneFloorPlanOccupancyRepository, SceneFloorPlanOccupancyRepository>();
|
||||
|
||||
@ -1122,6 +1122,7 @@ public sealed class BookService(
|
||||
IProjectRepository projects,
|
||||
IBookRepository books,
|
||||
IChapterRepository chapters,
|
||||
IManuscriptDocumentRepository manuscriptDocuments,
|
||||
IBookCoverService bookCovers,
|
||||
ISubscriptionService subscriptions,
|
||||
IProjectActivityService activity,
|
||||
@ -1145,6 +1146,9 @@ public sealed class BookService(
|
||||
{
|
||||
Project = project,
|
||||
Book = book,
|
||||
ManuscriptDocument = currentUser.UserId is int userId
|
||||
? await manuscriptDocuments.GetByBookAsync(bookId, userId)
|
||||
: null,
|
||||
Chapters = await chapters.ListByBookAsync(bookId)
|
||||
};
|
||||
}
|
||||
@ -1358,6 +1362,7 @@ public sealed class ChapterService(
|
||||
ChapterTitle = chapter.ChapterTitle,
|
||||
Summary = chapter.Summary,
|
||||
RevisionStatusID = chapter.RevisionStatusID,
|
||||
IsLinkedToManuscript = chapter.IsLinkedToManuscript,
|
||||
Book = book,
|
||||
Project = project,
|
||||
RevisionStatuses = ToSelectList(lookupData.RevisionStatuses, x => x.RevisionStatusID, x => x.StatusName)
|
||||
@ -1486,6 +1491,7 @@ public sealed class SceneService(
|
||||
ScenePurposeNotes = scene.ScenePurposeNotes,
|
||||
SceneOutcomeNotes = scene.SceneOutcomeNotes,
|
||||
RevisionStatusID = scene.RevisionStatusID,
|
||||
IsLinkedToManuscript = scene.IsLinkedToManuscript,
|
||||
PrimaryLocationID = scene.PrimaryLocationID,
|
||||
FloorPlanID = scene.FloorPlanID,
|
||||
InitialFloorPlanFloorID = scene.InitialFloorPlanFloorID,
|
||||
|
||||
@ -17,9 +17,14 @@ public interface IWordCompanionService
|
||||
Task<WordCompanionSyncManuscriptResponse?> SyncManuscriptAsync(int bookId, WordCompanionSyncManuscriptRequest request);
|
||||
Task<WordCompanionUpdateSceneLinksResponse?> UpdateSceneLinksAsync(int sceneId, WordCompanionUpdateSceneLinksRequest request);
|
||||
Task<WordCompanionUpdateWordCountResponse?> UpdateWordCountAsync(int sceneId, WordCompanionUpdateWordCountRequest request);
|
||||
Task<WordCompanionManuscriptBookResponse?> GetManuscriptForBookAsync(int bookId);
|
||||
Task<WordCompanionManuscriptLinkResponse?> LinkManuscriptAsync(WordCompanionManuscriptLinkRequest request);
|
||||
}
|
||||
|
||||
public sealed class WordCompanionService(IWordCompanionRepository repository, ICurrentUserService currentUser) : IWordCompanionService
|
||||
public sealed class WordCompanionService(
|
||||
IWordCompanionRepository repository,
|
||||
IManuscriptDocumentRepository manuscriptDocuments,
|
||||
ICurrentUserService currentUser) : IWordCompanionService
|
||||
{
|
||||
public async Task<WordCompanionProjectsResponse> ListProjectsAsync()
|
||||
{
|
||||
@ -153,6 +158,54 @@ public sealed class WordCompanionService(IWordCompanionRepository repository, IC
|
||||
: repository.UpdateWordCountAsync(sceneId, RequireUserId(), actualWords.Value, request.LastWorkedOn);
|
||||
}
|
||||
|
||||
public async Task<WordCompanionManuscriptBookResponse?> GetManuscriptForBookAsync(int bookId)
|
||||
{
|
||||
if (bookId <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var document = await manuscriptDocuments.GetByBookAsync(bookId, RequireUserId());
|
||||
return new WordCompanionManuscriptBookResponse
|
||||
{
|
||||
BookId = bookId,
|
||||
ManuscriptDocument = document is null ? null : ToDto(document)
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<WordCompanionManuscriptLinkResponse?> LinkManuscriptAsync(WordCompanionManuscriptLinkRequest request)
|
||||
{
|
||||
if (request.BookId <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var documentGuid = request.DocumentGuid.GetValueOrDefault();
|
||||
if (documentGuid == Guid.Empty)
|
||||
{
|
||||
documentGuid = Guid.NewGuid();
|
||||
}
|
||||
|
||||
ManuscriptDocumentModel? document;
|
||||
try
|
||||
{
|
||||
document = await manuscriptDocuments.SaveAsync(request.BookId, documentGuid, RequireUserId());
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return document is null
|
||||
? null
|
||||
: new WordCompanionManuscriptLinkResponse
|
||||
{
|
||||
BookId = document.BookID,
|
||||
ManuscriptDocument = ToDto(document),
|
||||
Message = "Manuscript linked."
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<WordCompanionResolveSceneResponse?> ResolveSceneCoreAsync(int bookId, WordCompanionResolveSceneRequest request)
|
||||
{
|
||||
return await repository.ResolveSceneAsync(bookId, RequireUserId(), request.ChapterTitle!.Trim(), request.SceneTitle!.Trim());
|
||||
@ -164,4 +217,17 @@ public sealed class WordCompanionService(IWordCompanionRepository repository, IC
|
||||
}
|
||||
|
||||
private static bool HasInvalidIds(IEnumerable<int> ids) => ids.Any(id => id <= 0);
|
||||
|
||||
private static WordCompanionManuscriptDocumentDto ToDto(ManuscriptDocumentModel document) => new()
|
||||
{
|
||||
ManuscriptDocumentId = document.ManuscriptDocumentID,
|
||||
BookId = document.BookID,
|
||||
ProjectId = document.ProjectID,
|
||||
DocumentGuid = document.DocumentGuid,
|
||||
BindingVersion = document.BindingVersion,
|
||||
CreatedUtc = document.CreatedUtc,
|
||||
LastSyncUtc = document.LastSyncUtc,
|
||||
LastOpenedUtc = document.LastOpenedUtc,
|
||||
IsActive = document.IsActive
|
||||
};
|
||||
}
|
||||
|
||||
350
PlotLine/Sql/094_Phase16B_ManuscriptBindingLinkedStructures.sql
Normal file
350
PlotLine/Sql/094_Phase16B_ManuscriptBindingLinkedStructures.sql
Normal file
@ -0,0 +1,350 @@
|
||||
SET ANSI_NULLS ON;
|
||||
GO
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
GO
|
||||
|
||||
IF OBJECT_ID(N'dbo.ManuscriptDocuments', N'U') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE dbo.ManuscriptDocuments
|
||||
(
|
||||
ManuscriptDocumentID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_ManuscriptDocuments PRIMARY KEY,
|
||||
BookID int NOT NULL,
|
||||
ProjectID int NOT NULL,
|
||||
DocumentGuid uniqueidentifier NOT NULL,
|
||||
BindingVersion int NOT NULL CONSTRAINT DF_ManuscriptDocuments_BindingVersion DEFAULT (1),
|
||||
CreatedUtc datetime2 NOT NULL CONSTRAINT DF_ManuscriptDocuments_CreatedUtc DEFAULT SYSUTCDATETIME(),
|
||||
LastSyncUtc datetime2 NULL,
|
||||
LastOpenedUtc datetime2 NULL,
|
||||
IsActive bit NOT NULL CONSTRAINT DF_ManuscriptDocuments_IsActive DEFAULT (1),
|
||||
CreatedByUserID int NULL,
|
||||
CONSTRAINT FK_ManuscriptDocuments_Books FOREIGN KEY (BookID) REFERENCES dbo.Books(BookID),
|
||||
CONSTRAINT FK_ManuscriptDocuments_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID),
|
||||
CONSTRAINT FK_ManuscriptDocuments_AppUser FOREIGN KEY (CreatedByUserID) REFERENCES dbo.AppUser(UserID)
|
||||
);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'UX_ManuscriptDocuments_ActiveBook' AND object_id = OBJECT_ID(N'dbo.ManuscriptDocuments'))
|
||||
CREATE UNIQUE INDEX UX_ManuscriptDocuments_ActiveBook ON dbo.ManuscriptDocuments(BookID) WHERE IsActive = 1;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'UX_ManuscriptDocuments_ActiveGuid' AND object_id = OBJECT_ID(N'dbo.ManuscriptDocuments'))
|
||||
CREATE UNIQUE INDEX UX_ManuscriptDocuments_ActiveGuid ON dbo.ManuscriptDocuments(DocumentGuid) WHERE IsActive = 1;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_ManuscriptDocuments_Project' AND object_id = OBJECT_ID(N'dbo.ManuscriptDocuments'))
|
||||
CREATE INDEX IX_ManuscriptDocuments_Project ON dbo.ManuscriptDocuments(ProjectID, IsActive);
|
||||
GO
|
||||
|
||||
IF COL_LENGTH(N'dbo.Chapters', N'IsLinkedToManuscript') IS NULL
|
||||
ALTER TABLE dbo.Chapters ADD IsLinkedToManuscript bit NOT NULL CONSTRAINT DF_Chapters_IsLinkedToManuscript DEFAULT (0);
|
||||
GO
|
||||
|
||||
IF COL_LENGTH(N'dbo.Chapters', N'ManuscriptDocumentID') IS NULL
|
||||
ALTER TABLE dbo.Chapters ADD ManuscriptDocumentID int NULL;
|
||||
GO
|
||||
|
||||
IF COL_LENGTH(N'dbo.Chapters', N'ExternalReference') IS NULL
|
||||
ALTER TABLE dbo.Chapters ADD ExternalReference nvarchar(100) NULL;
|
||||
GO
|
||||
|
||||
IF COL_LENGTH(N'dbo.Chapters', N'LinkedUtc') IS NULL
|
||||
ALTER TABLE dbo.Chapters ADD LinkedUtc datetime2 NULL;
|
||||
GO
|
||||
|
||||
IF COL_LENGTH(N'dbo.Scenes', N'IsLinkedToManuscript') IS NULL
|
||||
ALTER TABLE dbo.Scenes ADD IsLinkedToManuscript bit NOT NULL CONSTRAINT DF_Scenes_IsLinkedToManuscript DEFAULT (0);
|
||||
GO
|
||||
|
||||
IF COL_LENGTH(N'dbo.Scenes', N'ManuscriptDocumentID') IS NULL
|
||||
ALTER TABLE dbo.Scenes ADD ManuscriptDocumentID int NULL;
|
||||
GO
|
||||
|
||||
IF COL_LENGTH(N'dbo.Scenes', N'ExternalReference') IS NULL
|
||||
ALTER TABLE dbo.Scenes ADD ExternalReference nvarchar(100) NULL;
|
||||
GO
|
||||
|
||||
IF COL_LENGTH(N'dbo.Scenes', N'LinkedUtc') IS NULL
|
||||
ALTER TABLE dbo.Scenes ADD LinkedUtc datetime2 NULL;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_Chapters_ManuscriptDocuments')
|
||||
ALTER TABLE dbo.Chapters ADD CONSTRAINT FK_Chapters_ManuscriptDocuments FOREIGN KEY (ManuscriptDocumentID) REFERENCES dbo.ManuscriptDocuments(ManuscriptDocumentID);
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_Scenes_ManuscriptDocuments')
|
||||
ALTER TABLE dbo.Scenes ADD CONSTRAINT FK_Scenes_ManuscriptDocuments FOREIGN KEY (ManuscriptDocumentID) REFERENCES dbo.ManuscriptDocuments(ManuscriptDocumentID);
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Chapters_ManuscriptLink' AND object_id = OBJECT_ID(N'dbo.Chapters'))
|
||||
CREATE INDEX IX_Chapters_ManuscriptLink ON dbo.Chapters(ManuscriptDocumentID, IsLinkedToManuscript, ExternalReference);
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Scenes_ManuscriptLink' AND object_id = OBJECT_ID(N'dbo.Scenes'))
|
||||
CREATE INDEX IX_Scenes_ManuscriptLink ON dbo.Scenes(ManuscriptDocumentID, IsLinkedToManuscript, ExternalReference);
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.ManuscriptDocument_GetByBook
|
||||
@BookID int,
|
||||
@UserID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
SELECT md.ManuscriptDocumentID, md.BookID, md.ProjectID, md.DocumentGuid, md.BindingVersion,
|
||||
md.CreatedUtc, md.LastSyncUtc, md.LastOpenedUtc, md.IsActive, md.CreatedByUserID
|
||||
FROM dbo.ManuscriptDocuments md
|
||||
INNER JOIN dbo.Books b ON b.BookID = md.BookID
|
||||
INNER JOIN dbo.Projects p ON p.ProjectID = b.ProjectID
|
||||
INNER JOIN dbo.ProjectUserAccess pua ON pua.ProjectID = p.ProjectID
|
||||
WHERE md.BookID = @BookID
|
||||
AND md.IsActive = 1
|
||||
AND b.IsArchived = 0
|
||||
AND p.IsArchived = 0
|
||||
AND pua.UserID = @UserID
|
||||
AND pua.IsActive = 1;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.ManuscriptDocument_GetByGuid
|
||||
@DocumentGuid uniqueidentifier,
|
||||
@UserID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
SELECT md.ManuscriptDocumentID, md.BookID, md.ProjectID, md.DocumentGuid, md.BindingVersion,
|
||||
md.CreatedUtc, md.LastSyncUtc, md.LastOpenedUtc, md.IsActive, md.CreatedByUserID
|
||||
FROM dbo.ManuscriptDocuments md
|
||||
INNER JOIN dbo.Books b ON b.BookID = md.BookID
|
||||
INNER JOIN dbo.Projects p ON p.ProjectID = b.ProjectID
|
||||
INNER JOIN dbo.ProjectUserAccess pua ON pua.ProjectID = p.ProjectID
|
||||
WHERE md.DocumentGuid = @DocumentGuid
|
||||
AND md.IsActive = 1
|
||||
AND b.IsArchived = 0
|
||||
AND p.IsArchived = 0
|
||||
AND pua.UserID = @UserID
|
||||
AND pua.IsActive = 1;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.ManuscriptDocument_Save
|
||||
@BookID int,
|
||||
@DocumentGuid uniqueidentifier,
|
||||
@UserID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DECLARE @ProjectID int;
|
||||
SELECT @ProjectID = b.ProjectID
|
||||
FROM dbo.Books b
|
||||
INNER JOIN dbo.Projects p ON p.ProjectID = b.ProjectID
|
||||
INNER JOIN dbo.ProjectUserAccess pua ON pua.ProjectID = p.ProjectID
|
||||
WHERE b.BookID = @BookID
|
||||
AND b.IsArchived = 0
|
||||
AND p.IsArchived = 0
|
||||
AND pua.UserID = @UserID
|
||||
AND pua.IsActive = 1;
|
||||
|
||||
IF @ProjectID IS NULL
|
||||
RETURN;
|
||||
|
||||
IF EXISTS
|
||||
(
|
||||
SELECT 1
|
||||
FROM dbo.ManuscriptDocuments
|
||||
WHERE DocumentGuid = @DocumentGuid
|
||||
AND IsActive = 1
|
||||
AND BookID <> @BookID
|
||||
)
|
||||
THROW 51000, 'This manuscript document is already linked to another book.', 1;
|
||||
|
||||
DECLARE @ManuscriptDocumentID int;
|
||||
SELECT @ManuscriptDocumentID = ManuscriptDocumentID
|
||||
FROM dbo.ManuscriptDocuments
|
||||
WHERE BookID = @BookID
|
||||
AND IsActive = 1;
|
||||
|
||||
IF @ManuscriptDocumentID IS NULL
|
||||
BEGIN
|
||||
INSERT dbo.ManuscriptDocuments (BookID, ProjectID, DocumentGuid, CreatedByUserID)
|
||||
VALUES (@BookID, @ProjectID, @DocumentGuid, @UserID);
|
||||
|
||||
SET @ManuscriptDocumentID = CAST(SCOPE_IDENTITY() AS int);
|
||||
END
|
||||
ELSE
|
||||
BEGIN
|
||||
UPDATE dbo.ManuscriptDocuments
|
||||
SET DocumentGuid = @DocumentGuid,
|
||||
ProjectID = @ProjectID
|
||||
WHERE ManuscriptDocumentID = @ManuscriptDocumentID;
|
||||
END;
|
||||
|
||||
EXEC dbo.ManuscriptDocument_GetByBook @BookID = @BookID, @UserID = @UserID;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.ManuscriptDocument_UpdateLastSync
|
||||
@ManuscriptDocumentID int,
|
||||
@UserID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
UPDATE md
|
||||
SET LastSyncUtc = SYSUTCDATETIME()
|
||||
FROM dbo.ManuscriptDocuments md
|
||||
INNER JOIN dbo.ProjectUserAccess pua ON pua.ProjectID = md.ProjectID
|
||||
WHERE md.ManuscriptDocumentID = @ManuscriptDocumentID
|
||||
AND md.IsActive = 1
|
||||
AND pua.UserID = @UserID
|
||||
AND pua.IsActive = 1;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.ManuscriptDocument_UpdateLastOpened
|
||||
@ManuscriptDocumentID int,
|
||||
@UserID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
UPDATE md
|
||||
SET LastOpenedUtc = SYSUTCDATETIME()
|
||||
FROM dbo.ManuscriptDocuments md
|
||||
INNER JOIN dbo.ProjectUserAccess pua ON pua.ProjectID = md.ProjectID
|
||||
WHERE md.ManuscriptDocumentID = @ManuscriptDocumentID
|
||||
AND md.IsActive = 1
|
||||
AND pua.UserID = @UserID
|
||||
AND pua.IsActive = 1;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.Chapter_ListByBook
|
||||
@BookID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SELECT c.ChapterID, c.BookID, c.ChapterNumber, c.ChapterTitle, c.POVCharacterID, c.Summary,
|
||||
c.SortOrder, c.RevisionStatusID, rs.StatusName AS RevisionStatusName,
|
||||
c.CreatedDate, c.UpdatedDate, c.IsArchived, c.ArchivedDate, c.ArchivedReason,
|
||||
c.IsLinkedToManuscript, c.ManuscriptDocumentID, c.ExternalReference, c.LinkedUtc
|
||||
FROM dbo.Chapters c
|
||||
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = c.RevisionStatusID
|
||||
WHERE c.BookID = @BookID AND c.IsArchived = 0
|
||||
ORDER BY c.SortOrder, c.ChapterNumber, c.ChapterTitle;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.Chapter_Get
|
||||
@ChapterID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SELECT c.ChapterID, c.BookID, c.ChapterNumber, c.ChapterTitle, c.POVCharacterID, c.Summary,
|
||||
c.SortOrder, c.RevisionStatusID, rs.StatusName AS RevisionStatusName,
|
||||
c.CreatedDate, c.UpdatedDate, c.IsArchived, c.ArchivedDate, c.ArchivedReason,
|
||||
c.IsLinkedToManuscript, c.ManuscriptDocumentID, c.ExternalReference, c.LinkedUtc
|
||||
FROM dbo.Chapters c
|
||||
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = c.RevisionStatusID
|
||||
WHERE c.ChapterID = @ChapterID AND c.IsArchived = 0;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.Scene_ListByChapter
|
||||
@ChapterID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SELECT s.SceneID, s.ChapterID, s.SceneNumber, s.SceneTitle, s.Summary, s.POVCharacterID,
|
||||
s.PrimaryLocationID, s.FloorPlanID, s.InitialFloorPlanFloorID, s.SortOrder,
|
||||
s.TimeModeID, tm.TimeModeName, s.StartDateTime, s.EndDateTime, s.DurationAmount,
|
||||
s.DurationUnitID, du.DurationUnitName, s.RelativeTimeText, s.TimeConfidenceID,
|
||||
tc.TimeConfidenceName, s.ScenePurposeNotes, s.SceneOutcomeNotes, s.RevisionStatusID,
|
||||
rs.StatusName AS RevisionStatusName, s.CreatedDate, s.UpdatedDate, s.IsArchived,
|
||||
s.ArchivedDate, s.ArchivedReason, s.IsLinkedToManuscript, s.ManuscriptDocumentID,
|
||||
s.ExternalReference, s.LinkedUtc
|
||||
FROM dbo.Scenes s
|
||||
INNER JOIN dbo.TimeModes tm ON tm.TimeModeID = s.TimeModeID
|
||||
LEFT JOIN dbo.DurationUnits du ON du.DurationUnitID = s.DurationUnitID
|
||||
INNER JOIN dbo.TimeConfidences tc ON tc.TimeConfidenceID = s.TimeConfidenceID
|
||||
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
|
||||
WHERE s.ChapterID = @ChapterID AND s.IsArchived = 0
|
||||
ORDER BY s.SortOrder, s.SceneNumber, s.SceneTitle;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.Scene_Get
|
||||
@SceneID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
EXEC dbo.SceneMetric_EnsureDefaults @SceneID;
|
||||
SELECT s.SceneID, s.ChapterID, s.SceneNumber, s.SceneTitle, s.Summary, s.POVCharacterID, s.PrimaryLocationID,
|
||||
s.FloorPlanID, s.InitialFloorPlanFloorID,
|
||||
location.LocationName AS PrimaryLocationName, location.LocationPath AS PrimaryLocationPath,
|
||||
s.SortOrder, s.TimeModeID, tm.TimeModeName, s.StartDateTime, s.EndDateTime, s.DurationAmount, s.DurationUnitID,
|
||||
du.DurationUnitName, s.RelativeTimeText, s.TimeConfidenceID, tc.TimeConfidenceName, s.ScenePurposeNotes,
|
||||
s.SceneOutcomeNotes, s.RevisionStatusID, rs.StatusName AS RevisionStatusName, s.CreatedDate, s.UpdatedDate, s.IsArchived,
|
||||
s.ArchivedDate, s.ArchivedReason, s.IsLinkedToManuscript, s.ManuscriptDocumentID, s.ExternalReference, s.LinkedUtc
|
||||
FROM dbo.Scenes s
|
||||
INNER JOIN dbo.TimeModes tm ON tm.TimeModeID = s.TimeModeID
|
||||
INNER JOIN dbo.TimeConfidences tc ON tc.TimeConfidenceID = s.TimeConfidenceID
|
||||
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
|
||||
LEFT JOIN dbo.DurationUnits du ON du.DurationUnitID = s.DurationUnitID
|
||||
LEFT JOIN dbo.LocationPaths location ON location.LocationID = s.PrimaryLocationID
|
||||
WHERE s.SceneID = @SceneID AND s.IsArchived = 0;
|
||||
SELECT ScenePurposeTypeID FROM dbo.ScenePurposes WHERE SceneID = @SceneID;
|
||||
EXEC dbo.SceneMetric_ListByScene @SceneID;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.Archive_Entity
|
||||
@EntityType nvarchar(80),
|
||||
@EntityID int,
|
||||
@ArchivedReason nvarchar(500) = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
IF @EntityType = N'Project' UPDATE dbo.Projects SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE ProjectID = @EntityID;
|
||||
ELSE IF @EntityType = N'Book' UPDATE dbo.Books SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE BookID = @EntityID;
|
||||
ELSE IF @EntityType = N'Chapter'
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM dbo.Chapters WHERE ChapterID = @EntityID AND IsLinkedToManuscript = 1)
|
||||
THROW 51000, 'This chapter is linked to a manuscript and cannot be deleted from PlotDirector. Delete the chapter from Microsoft Word to remove it from the manuscript.', 1;
|
||||
|
||||
UPDATE dbo.Chapters SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE ChapterID = @EntityID;
|
||||
END
|
||||
ELSE IF @EntityType = N'Scene'
|
||||
BEGIN
|
||||
DECLARE @ChapterID int;
|
||||
SELECT @ChapterID = ChapterID FROM dbo.Scenes WHERE SceneID = @EntityID AND IsArchived = 0;
|
||||
|
||||
IF EXISTS (SELECT 1 FROM dbo.Scenes WHERE SceneID = @EntityID AND IsLinkedToManuscript = 1)
|
||||
THROW 51000, 'This scene is linked to a manuscript and cannot be deleted from PlotDirector. Delete the scene from Microsoft Word to remove it from the manuscript.', 1;
|
||||
|
||||
IF @ChapterID IS NULL
|
||||
RETURN;
|
||||
|
||||
IF (SELECT COUNT(*) FROM dbo.Scenes WHERE ChapterID = @ChapterID AND IsArchived = 0) <= 1
|
||||
THROW 51000, 'A chapter must contain at least one scene.', 1;
|
||||
|
||||
UPDATE dbo.Scenes SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE SceneID = @EntityID;
|
||||
END
|
||||
ELSE IF @EntityType = N'PlotLine' UPDATE dbo.PlotLines SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE PlotLineID = @EntityID;
|
||||
ELSE IF @EntityType = N'PlotThread' UPDATE dbo.PlotThreads SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE PlotThreadID = @EntityID;
|
||||
ELSE IF @EntityType = N'StoryAsset' UPDATE dbo.StoryAssets SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE StoryAssetID = @EntityID;
|
||||
ELSE IF @EntityType = N'Character' UPDATE dbo.Characters SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE CharacterID = @EntityID;
|
||||
ELSE IF @EntityType = N'Location' UPDATE dbo.Locations SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE LocationID = @EntityID;
|
||||
ELSE IF @EntityType = N'Scenario' UPDATE dbo.Scenarios SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE ScenarioID = @EntityID;
|
||||
ELSE IF @EntityType = N'Relationship' UPDATE dbo.CharacterRelationships SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE CharacterRelationshipID = @EntityID;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.Chapter_Archive @ChapterID int AS BEGIN SET NOCOUNT ON; EXEC dbo.Archive_Entity N'Chapter', @ChapterID, NULL; END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.Scene_Archive @SceneID int AS BEGIN SET NOCOUNT ON; EXEC dbo.Archive_Entity N'Scene', @SceneID, NULL; END;
|
||||
GO
|
||||
@ -232,6 +232,7 @@ public sealed class BookDetailViewModel
|
||||
{
|
||||
public Project Project { get; set; } = new();
|
||||
public Book Book { get; set; } = new();
|
||||
public ManuscriptDocumentModel? ManuscriptDocument { get; set; }
|
||||
public IReadOnlyList<Chapter> Chapters { get; set; } = [];
|
||||
}
|
||||
|
||||
@ -252,6 +253,8 @@ public sealed class ChapterEditViewModel
|
||||
[Display(Name = "Revision status")]
|
||||
public int RevisionStatusID { get; set; }
|
||||
|
||||
public bool IsLinkedToManuscript { get; set; }
|
||||
|
||||
public Book? Book { get; set; }
|
||||
public Project? Project { get; set; }
|
||||
public IReadOnlyList<SelectListItem> RevisionStatuses { get; set; } = [];
|
||||
@ -311,6 +314,8 @@ public sealed class SceneEditViewModel
|
||||
[Display(Name = "Revision status")]
|
||||
public int RevisionStatusID { get; set; }
|
||||
|
||||
public bool IsLinkedToManuscript { get; set; }
|
||||
|
||||
[Display(Name = "Primary location")]
|
||||
public int? PrimaryLocationID { get; set; }
|
||||
|
||||
|
||||
@ -12,6 +12,11 @@
|
||||
|
||||
<partial name="_ProjectSectionNav" model="Model.Project" />
|
||||
|
||||
@if (TempData["ArchiveMessage"] is string archiveMessage)
|
||||
{
|
||||
<div class="alert alert-info">@archiveMessage</div>
|
||||
}
|
||||
|
||||
<div class="page-heading">
|
||||
<div>
|
||||
<p class="eyebrow"><a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a></p>
|
||||
@ -87,6 +92,18 @@
|
||||
<strong>@(Model.Book.PublicationDate?.ToString("dd MMM yyyy") ?? "Not set")</strong>
|
||||
<span class="muted">publication</span>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
@if (Model.ManuscriptDocument is null)
|
||||
{
|
||||
<strong>No manuscript linked</strong>
|
||||
}
|
||||
else
|
||||
{
|
||||
<strong>Manuscript linked</strong>
|
||||
<span class="muted">Last synced: @(Model.ManuscriptDocument.LastSyncUtc?.ToString("dd MMM yyyy HH:mm") ?? "Never")</span>
|
||||
}
|
||||
<span class="muted">Manuscript Status</span>
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(Model.Book.ShortDescription))
|
||||
{
|
||||
<div class="col-12">
|
||||
@ -111,6 +128,7 @@
|
||||
<th>Chapter</th>
|
||||
<th>Title</th>
|
||||
<th>Status</th>
|
||||
<th>Manuscript</th>
|
||||
<th>Summary</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
@ -122,6 +140,7 @@
|
||||
<td class="narrow">@chapter.ChapterNumber</td>
|
||||
<td><a asp-controller="Chapters" asp-action="Details" asp-route-id="@chapter.ChapterID">@chapter.ChapterTitle</a></td>
|
||||
<td><span class="status-pill">@chapter.RevisionStatusName</span></td>
|
||||
<td><span class="status-pill">@(chapter.IsLinkedToManuscript ? "🔗 Linked" : "📝 Planned")</span></td>
|
||||
<td>@chapter.Summary</td>
|
||||
<td class="text-end">
|
||||
<a class="btn btn-outline-secondary btn-sm" asp-controller="Chapters" asp-action="Edit" asp-route-id="@chapter.ChapterID">Edit</a>
|
||||
|
||||
@ -13,6 +13,11 @@
|
||||
|
||||
<partial name="_ProjectSectionNav" model="Model.Project" />
|
||||
|
||||
@if (TempData["ArchiveMessage"] is string archiveMessage)
|
||||
{
|
||||
<div class="alert alert-info">@archiveMessage</div>
|
||||
}
|
||||
|
||||
<div class="page-heading">
|
||||
<div>
|
||||
<p class="eyebrow">
|
||||
@ -20,6 +25,7 @@
|
||||
/ <a asp-controller="Books" asp-action="Details" asp-route-id="@Model.Book.BookID">@Model.Book.BookTitle</a>
|
||||
</p>
|
||||
<h1>Chapter @Model.Chapter.ChapterNumber: @Model.Chapter.ChapterTitle <help-icon key="chapters.overview" /></h1>
|
||||
<span class="status-pill">@(Model.Chapter.IsLinkedToManuscript ? "🔗 Linked" : "📝 Planned")</span>
|
||||
@if (!string.IsNullOrWhiteSpace(Model.Chapter.Summary))
|
||||
{
|
||||
<p class="lead-text">@Model.Chapter.Summary</p>
|
||||
@ -60,6 +66,7 @@
|
||||
<th>Scene</th>
|
||||
<th>Title</th>
|
||||
<th>Status</th>
|
||||
<th>Manuscript</th>
|
||||
<th>Time</th>
|
||||
<th>Summary</th>
|
||||
<th>Order</th>
|
||||
@ -73,6 +80,7 @@
|
||||
<td class="narrow">@scene.SceneNumber</td>
|
||||
<td><a asp-controller="Scenes" asp-action="Edit" asp-route-id="@scene.SceneID">@scene.SceneTitle</a></td>
|
||||
<td><span class="status-pill">@scene.RevisionStatusName</span></td>
|
||||
<td><span class="status-pill">@(scene.IsLinkedToManuscript ? "🔗 Linked" : "📝 Planned")</span></td>
|
||||
<td>@scene.TimeLabel</td>
|
||||
<td>@scene.Summary</td>
|
||||
<td>
|
||||
|
||||
@ -30,6 +30,7 @@
|
||||
<div>
|
||||
<p class="eyebrow">@Model.Book?.BookTitle / Chapter @Model.Chapter?.ChapterNumber</p>
|
||||
<h1>@ViewData["Title"] <help-icon key="scenes.edit" /></h1>
|
||||
<span class="status-pill">@(Model.IsLinkedToManuscript ? "🔗 Linked" : "📝 Planned")</span>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
@if (Model.SceneID > 0)
|
||||
|
||||
@ -46,6 +46,7 @@
|
||||
</div>
|
||||
<div class="overview-chip-grid">
|
||||
<span class="overview-chip">@revisionStatus</span>
|
||||
<span class="overview-chip">@(Model.IsLinkedToManuscript ? "🔗 Linked" : "📝 Planned")</span>
|
||||
<span class="overview-chip">@timeLabel</span>
|
||||
<span class="overview-chip @(totalWarningCount > 0 ? "attention" : "")">@totalWarningCount warning@(totalWarningCount == 1 ? "" : "s")</span>
|
||||
<span class="overview-chip">@Model.SceneCharacters.Count character@(Model.SceneCharacters.Count == 1 ? "" : "s")</span>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user