94 lines
3.7 KiB
Plaintext

@model FeatureRequestRoadmapViewModel
@using Microsoft.AspNetCore.Html
@using System.Text.Encodings.Web
@{
ViewData["Title"] = "Feature Roadmap";
}
<div class="page-heading compact">
<div>
<p class="eyebrow">Feedback</p>
<h1>Feature Roadmap</h1>
<p class="lead-text">Here are some improvements currently being considered, planned, in progress, or recently shipped. This is not a complete list of every suggestion, but it shows where PlotDirector is actively evolving.</p>
</div>
<div class="button-row">
<a class="btn btn-outline-secondary" asp-action="Index">My Requests</a>
<a class="btn btn-primary" asp-action="Create">Submit Feature Request</a>
</div>
</div>
<nav class="mb-3" aria-label="Breadcrumb">
<a asp-action="Index">Feature Requests</a>
<span class="muted"> / Roadmap</span>
</nav>
@foreach (var roadmapSection in Model.Sections)
{
<section class="edit-panel">
<h2>@roadmapSection.Heading</h2>
@if (!roadmapSection.Requests.Any())
{
<p class="muted mb-0">@EmptyMessage(roadmapSection.Status)</p>
}
else
{
<div class="d-flex flex-column gap-3">
@foreach (var request in roadmapSection.Requests)
{
<article class="border rounded p-3">
<div class="d-flex justify-content-between gap-3 flex-wrap mb-2">
<div>
<h3 class="fs-5 mb-1">@request.Title</h3>
<div class="d-flex flex-wrap gap-2">
@StatusBadge(request.Status)
@if (!string.IsNullOrWhiteSpace(request.AppArea))
{
<span class="badge text-bg-secondary">@request.AppArea</span>
}
</div>
</div>
@if (string.Equals(request.Status, FeatureRequestStatuses.Shipped, StringComparison.OrdinalIgnoreCase) && request.ShippedUtc.HasValue)
{
<span class="muted">Shipped @FormatDate(request.ShippedUtc.Value)</span>
}
</div>
<p class="mb-0">@Excerpt(request.Description)</p>
</article>
}
</div>
}
</section>
}
@functions {
private static string FormatDate(DateTime value) => value.ToLocalTime().ToString("yyyy-MM-dd");
private static string Excerpt(string value)
{
var cleaned = value.ReplaceLineEndings(" ").Trim();
return cleaned.Length <= 220 ? cleaned : $"{cleaned[..220]}...";
}
private static string EmptyMessage(string status) => status switch
{
FeatureRequestStatuses.Shipped => "No shipped roadmap items yet.",
FeatureRequestStatuses.InProgress => "No roadmap items are currently in progress.",
FeatureRequestStatuses.Planned => "No planned roadmap items yet.",
_ => "No roadmap items in this section yet."
};
private static IHtmlContent StatusBadge(string status)
{
var css = status switch
{
FeatureRequestStatuses.Candidate => "text-bg-secondary",
FeatureRequestStatuses.Planned => "text-bg-success",
FeatureRequestStatuses.InProgress => "text-bg-success",
FeatureRequestStatuses.Shipped => "text-bg-dark",
_ => "text-bg-secondary"
};
return new HtmlString($"<span class=\"badge {css}\">{HtmlEncoder.Default.Encode(status)}</span>");
}
}