Phase 6B Plot Line Forms

This commit is contained in:
Nick Beckley 2026-06-11 19:46:03 +01:00
parent fb5352ab8f
commit 023ca1404e
4 changed files with 42 additions and 9 deletions

View File

@ -32,7 +32,8 @@ public sealed class PlotLinesController(IPlotService plots) : Controller
{
if (!ModelState.IsValid)
{
return View("Edit", model);
var editModel = await plots.PopulatePlotLineFormAsync(model);
return editModel is null ? NotFound() : View("Edit", editModel);
}
await plots.SavePlotLineAsync(model);

View File

@ -113,6 +113,7 @@ public interface IPlotService
Task<PlotLineListViewModel?> GetPlotLinesAsync(int projectId);
Task<PlotLineEditViewModel?> GetCreatePlotLineAsync(int projectId);
Task<PlotLineEditViewModel?> GetEditPlotLineAsync(int plotLineId);
Task<PlotLineEditViewModel?> PopulatePlotLineFormAsync(PlotLineEditViewModel model);
Task<int> SavePlotLineAsync(PlotLineEditViewModel model);
Task ArchivePlotLineAsync(int plotLineId);
Task<PlotThreadListViewModel?> GetPlotThreadsAsync(int projectId);
@ -2099,6 +2100,27 @@ public sealed class PlotService(IProjectRepository projects, IBookRepository boo
}, lookupData, plotLines.Where(x => x.PlotLineID != plotLine.PlotLineID));
}
public async Task<PlotLineEditViewModel?> PopulatePlotLineFormAsync(PlotLineEditViewModel model)
{
var project = await projects.GetAsync(model.ProjectID);
if (project is null)
{
return null;
}
var lookupData = await plots.GetLookupsAsync();
var plotLines = await plots.ListPlotLinesAsync(model.ProjectID);
model.Project = project;
if (model.PlotLineTypeID == 0)
{
model.PlotLineTypeID = lookupData.PlotLineTypes.FirstOrDefault(x => x.TypeName == "Side Plot")?.PlotLineTypeID
?? lookupData.PlotLineTypes.First().PlotLineTypeID;
}
return await PopulatePlotLineListsAsync(model, lookupData, plotLines.Where(x => x.PlotLineID != model.PlotLineID));
}
public async Task<int> SavePlotLineAsync(PlotLineEditViewModel model)
{
var isNew = model.PlotLineID == 0;
@ -2301,7 +2323,6 @@ public sealed class PlotService(IProjectRepository projects, IBookRepository boo
private async Task<PlotLineEditViewModel> PopulatePlotLineListsAsync(PlotLineEditViewModel model, PlotLookupData lookupData, IEnumerable<PlotLineItem> parentCandidates)
{
model.PlotLineTypes = ChapterService.ToSelectList(lookupData.PlotLineTypes, x => x.PlotLineTypeID, x => x.TypeName);
model.PlotImportance = ChapterService.ToSelectList(lookupData.PlotImportance, x => x.PlotImportanceID, x => x.ImportanceName);
model.BookOptions = (await books.ListByProjectAsync(model.ProjectID))
.Select(x => new SelectListItem($"Book {x.BookNumber}: {x.BookTitle}", x.BookID.ToString()))

View File

@ -692,7 +692,7 @@ public sealed class MetricGraphPointViewModel
public int Percent { get; set; }
}
public sealed class PlotLineEditViewModel
public sealed class PlotLineEditViewModel : IValidatableObject
{
public int PlotLineID { get; set; }
public int ProjectID { get; set; }
@ -704,6 +704,7 @@ public sealed class PlotLineEditViewModel
[Display(Name = "Type")]
public int PlotLineTypeID { get; set; }
[Range(1, int.MaxValue, ErrorMessage = "Choose a plot importance.")]
[Display(Name = "Importance")]
public int PlotImportanceID { get; set; } = 2;
@ -726,10 +727,19 @@ public sealed class PlotLineEditViewModel
public bool IsVisibleOnTimeline { get; set; } = true;
public Project? Project { get; set; }
public IReadOnlyList<SelectListItem> PlotLineTypes { get; set; } = [];
public IReadOnlyList<SelectListItem> PlotImportance { get; set; } = [];
public IReadOnlyList<SelectListItem> BookOptions { get; set; } = [];
public IReadOnlyList<SelectListItem> ParentPlotLineOptions { get; set; } = [];
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (PlotLineID > 0 && EmergesFromPlotLineID == PlotLineID)
{
yield return new ValidationResult(
"A plot line cannot emerge from itself.",
[nameof(EmergesFromPlotLineID)]);
}
}
}
public sealed class PlotLineListViewModel

View File

@ -37,6 +37,7 @@
<form asp-action="Save" method="post" class="edit-panel">
<input asp-for="PlotLineID" type="hidden" />
<input asp-for="ProjectID" type="hidden" />
<input asp-for="PlotLineTypeID" type="hidden" />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="row g-3">
<div class="col-md-8">
@ -44,13 +45,12 @@
<input asp-for="PlotLineName" class="form-control" />
<span asp-validation-for="PlotLineName" class="text-danger"></span>
</div>
<div class="col-md-4">
<label asp-for="PlotLineTypeID" class="form-label">Type <help-icon key="plotLines.fields.type" mode="inline" /></label>
<select asp-for="PlotLineTypeID" asp-items="Model.PlotLineTypes" class="form-select"></select>
</div>
<div class="col-md-4">
<label asp-for="PlotImportanceID" class="form-label">Importance</label>
<select asp-for="PlotImportanceID" asp-items="Model.PlotImportance" class="form-select"></select>
<select asp-for="PlotImportanceID" asp-items="Model.PlotImportance" class="form-select">
<option value="">Choose importance</option>
</select>
<span asp-validation-for="PlotImportanceID" class="text-danger"></span>
</div>
<div class="col-md-4">
<label asp-for="BookID" class="form-label">Book / scope <help-icon key="plotLines.fields.scope" mode="inline" /></label>
@ -69,6 +69,7 @@
<select asp-for="EmergesFromPlotLineID" asp-items="Model.ParentPlotLineOptions" class="form-select">
<option value="">None</option>
</select>
<span asp-validation-for="EmergesFromPlotLineID" class="text-danger"></span>
</div>
<div class="col-md-2">
<label asp-for="SortOrder" class="form-label"></label>