namespace PlotLine.Models; public static class FeatureRequestStatuses { public const string New = "New"; public const string NeedsDetail = "Needs Detail"; public const string UnderReview = "Under Review"; public const string Candidate = "Candidate"; public const string Planned = "Planned"; public const string InProgress = "In Progress"; public const string Shipped = "Shipped"; public const string NotPlanned = "Not Planned"; public static IReadOnlyList All { get; } = [ New, NeedsDetail, UnderReview, Candidate, Planned, InProgress, Shipped, NotPlanned ]; public static bool IsValid(string? status) => All.Contains(status ?? string.Empty, StringComparer.OrdinalIgnoreCase); public static bool IsOpen(string? status) => !string.Equals(status, Shipped, StringComparison.OrdinalIgnoreCase) && !string.Equals(status, NotPlanned, StringComparison.OrdinalIgnoreCase); public static string Explanation(string? status) => status switch { New => "Your suggestion has been received and is waiting for review.", NeedsDetail => "We need a little more information before we can evaluate this suggestion.", UnderReview => "We are currently evaluating this idea.", Candidate => "This suggestion is being considered for a future release.", Planned => "This suggestion has been accepted and is planned for development.", InProgress => "Work has started on this feature.", Shipped => "This feature has been completed and released.", NotPlanned => "This suggestion does not currently fit PlotDirector's roadmap.", _ => "This suggestion is being reviewed." }; } public static class FeatureRequestSortOptions { public const string LatestActivity = "LatestActivity"; public const string NewestFirst = "NewestFirst"; public const string OldestFirst = "OldestFirst"; public static IReadOnlyList<(string Value, string Label)> All { get; } = [ (LatestActivity, "Latest activity"), (NewestFirst, "Newest first"), (OldestFirst, "Oldest first") ]; public static bool IsValid(string? sort) => All.Any(option => string.Equals(option.Value, sort, StringComparison.OrdinalIgnoreCase)); } public static class FeatureRequestOptions { public static IReadOnlyList AppAreas { get; } = [ "Timeline", "Characters", "Locations", "Assets", "Plot Lines", "Continuity", "Writing Schedule", "Word Companion", "Import / Export", "Billing / Account", "Other" ]; public static IReadOnlyList ImportanceOptions { get; } = [ "Nice to have", "Would save me time", "Blocking my workflow" ]; } public sealed class FeatureRequest { public int FeatureRequestID { get; set; } public int UserID { get; set; } public string? UserEmail { get; set; } public string? UserDisplayName { get; set; } public string Title { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public string? AppArea { get; set; } public string? Importance { get; set; } public string Status { get; set; } = FeatureRequestStatuses.New; public bool IsPublicCandidate { get; set; } public string? AdminNotes { get; set; } public DateTime CreatedUtc { get; set; } public DateTime UpdatedUtc { get; set; } public DateTime? ClosedUtc { get; set; } public DateTime? ShippedUtc { get; set; } public DateTime? LatestActivityUtc { get; set; } public bool? LatestMessageIsAdmin { get; set; } public string? LatestMessagePreview { get; set; } public int MessageCount { get; set; } public bool IsOpen => FeatureRequestStatuses.IsOpen(Status); public string StatusExplanation => FeatureRequestStatuses.Explanation(Status); public string ConversationState { get { if ((LatestMessageIsAdmin is null || LatestMessageIsAdmin == false) && IsOpen) { return FeatureRequestConversationStates.WaitingForAdminReply; } if (LatestMessageIsAdmin == true && string.Equals(Status, FeatureRequestStatuses.NeedsDetail, StringComparison.OrdinalIgnoreCase)) { return FeatureRequestConversationStates.WaitingForUserReply; } return FeatureRequestConversationStates.NoActionNeeded; } } } public static class FeatureRequestConversationStates { public const string WaitingForAdminReply = "Waiting for Admin Reply"; public const string WaitingForUserReply = "Waiting for User Reply"; public const string NoActionNeeded = "Up to date"; } public sealed class FeatureRequestAdminSummary { public IReadOnlyList Statuses { get; set; } = []; public int TotalOpenRequests { get; set; } public int AwaitingAdminReply { get; set; } public int AwaitingUserReply { get; set; } } public sealed class FeatureRequestStatusSummary { public string Status { get; set; } = string.Empty; public int Count { get; set; } } public sealed class FeatureRequestMessage { public int FeatureRequestMessageID { get; set; } public int FeatureRequestID { get; set; } public int? UserID { get; set; } public string? UserEmail { get; set; } public string? UserDisplayName { get; set; } public bool IsAdmin { get; set; } public string MessageBody { get; set; } = string.Empty; public DateTime CreatedUtc { get; set; } }