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) 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); await plots.SavePlotLineAsync(model);

View File

@ -113,6 +113,7 @@ public interface IPlotService
Task<PlotLineListViewModel?> GetPlotLinesAsync(int projectId); Task<PlotLineListViewModel?> GetPlotLinesAsync(int projectId);
Task<PlotLineEditViewModel?> GetCreatePlotLineAsync(int projectId); Task<PlotLineEditViewModel?> GetCreatePlotLineAsync(int projectId);
Task<PlotLineEditViewModel?> GetEditPlotLineAsync(int plotLineId); Task<PlotLineEditViewModel?> GetEditPlotLineAsync(int plotLineId);
Task<PlotLineEditViewModel?> PopulatePlotLineFormAsync(PlotLineEditViewModel model);
Task<int> SavePlotLineAsync(PlotLineEditViewModel model); Task<int> SavePlotLineAsync(PlotLineEditViewModel model);
Task ArchivePlotLineAsync(int plotLineId); Task ArchivePlotLineAsync(int plotLineId);
Task<PlotThreadListViewModel?> GetPlotThreadsAsync(int projectId); 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)); }, 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) public async Task<int> SavePlotLineAsync(PlotLineEditViewModel model)
{ {
var isNew = model.PlotLineID == 0; 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) 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.PlotImportance = ChapterService.ToSelectList(lookupData.PlotImportance, x => x.PlotImportanceID, x => x.ImportanceName);
model.BookOptions = (await books.ListByProjectAsync(model.ProjectID)) model.BookOptions = (await books.ListByProjectAsync(model.ProjectID))
.Select(x => new SelectListItem($"Book {x.BookNumber}: {x.BookTitle}", x.BookID.ToString())) .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 int Percent { get; set; }
} }
public sealed class PlotLineEditViewModel public sealed class PlotLineEditViewModel : IValidatableObject
{ {
public int PlotLineID { get; set; } public int PlotLineID { get; set; }
public int ProjectID { get; set; } public int ProjectID { get; set; }
@ -704,6 +704,7 @@ public sealed class PlotLineEditViewModel
[Display(Name = "Type")] [Display(Name = "Type")]
public int PlotLineTypeID { get; set; } public int PlotLineTypeID { get; set; }
[Range(1, int.MaxValue, ErrorMessage = "Choose a plot importance.")]
[Display(Name = "Importance")] [Display(Name = "Importance")]
public int PlotImportanceID { get; set; } = 2; public int PlotImportanceID { get; set; } = 2;
@ -726,10 +727,19 @@ public sealed class PlotLineEditViewModel
public bool IsVisibleOnTimeline { get; set; } = true; public bool IsVisibleOnTimeline { get; set; } = true;
public Project? Project { get; set; } public Project? Project { get; set; }
public IReadOnlyList<SelectListItem> PlotLineTypes { get; set; } = [];
public IReadOnlyList<SelectListItem> PlotImportance { get; set; } = []; public IReadOnlyList<SelectListItem> PlotImportance { get; set; } = [];
public IReadOnlyList<SelectListItem> BookOptions { get; set; } = []; public IReadOnlyList<SelectListItem> BookOptions { get; set; } = [];
public IReadOnlyList<SelectListItem> ParentPlotLineOptions { 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 public sealed class PlotLineListViewModel

View File

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