namespace SeaHaven.Services.DTOs { /// /// Feed reasons in their fixed tie-break order. Every reason the Notification Center /// can show is listed here so a new producer only adds items, never a contract change. /// public static class NotificationReasons { public const string SeverityCritical = "severity-critical"; public const string Sla = "sla"; public const string Mentions = "mentions"; public const string Unassigned = "unassigned"; public const string DueSoon = "due-soon"; public const string NoVendor = "no-vendor"; public const string AvetaMissing = "aveta-missing"; public const string Activity = "activity"; public const string VendorConflict = "vendor-conflict"; public static readonly IReadOnlyList Order = new[] { SeverityCritical, Sla, Mentions, Unassigned, DueSoon, NoVendor, AvetaMissing, Activity, VendorConflict }; public static readonly IReadOnlyDictionary Labels = new Dictionary { [SeverityCritical] = "Critical severity", [Sla] = "SLA at Risk", [Mentions] = "Mentions", [Unassigned] = "Unassigned", [DueSoon] = "Due Soon", [NoVendor] = "No Vendor", [AvetaMissing] = "Aveta Missing", [Activity] = "Recent activity", [VendorConflict] = "Vendor Conflict" }; } public static class NotificationSeverities { public const string Critical = "Critical"; public const string High = "High"; public const string Medium = "Medium"; public const string Low = "Low"; public static int Rank(string severity) => severity switch { Critical => 3, High => 2, Medium => 1, _ => 0 }; } /// /// How a row leaves the feed. rows are hidden client-side for the /// session; rows (a missed response deadline) are excluded from /// dismiss and dismiss-all and leave only through their own acknowledge action. /// public static class NotificationRowTypes { public const string Dismissable = "dismissable"; public const string Acknowledge = "acknowledge"; } public static class NotificationTargetKinds { public const string WorkOrder = "workOrder"; public const string Queue = "queue"; } /// Named work-order queues a grouped item opens. public static class NotificationQueues { public const string Unassigned = "unassigned"; /// The signed-in user's own open work orders. public const string AssignedToMe = "assigned-to-me"; } /// Work-order detail tabs a single-work-order item can open on. public static class NotificationWorkOrderTabs { public const string Info = "info"; public const string Comments = "comments"; public const string Extras = "extras"; public const string Uplifts = "uplifts"; } public sealed class NotificationTargetDto { public string Kind { get; init; } = NotificationTargetKinds.WorkOrder; public int? WorkOrderId { get; init; } public string? Tab { get; init; } public string? Queue { get; init; } } public sealed class NotificationWorkOrderRefDto { public int Id { get; init; } public string? Number { get; init; } } public sealed class NotificationItemDto { /// Stable across reads while the condition holds; the client dismisses by it. public string Id { get; init; } = ""; public string Reason { get; init; } = ""; public string Severity { get; init; } = NotificationSeverities.Low; public string RowType { get; init; } = NotificationRowTypes.Dismissable; public string Title { get; init; } = ""; /// Work orders this item represents. public int Count { get; init; } /// When the condition started; drives recency ordering. public DateTime TriggeredAt { get; init; } public NotificationTargetDto Target { get; init; } = new(); public IReadOnlyList WorkOrders { get; init; } = Array.Empty(); } public sealed class NotificationSectionDto { public string Reason { get; init; } = ""; public string Label { get; init; } = ""; /// Highest item severity in the section. public string Severity { get; init; } = NotificationSeverities.Low; /// Work orders the whole section represents, including items beyond the returned slice. public int Count { get; init; } public IReadOnlyList Items { get; init; } = Array.Empty(); } /// /// A work order past half of its SEV response window and not yet past the whole window, with the /// server-computed clock. Drives the top-of-app banner and the one-time 50% toast. /// public sealed class NotificationSlaWorkOrderDto { public int Id { get; init; } public string? Number { get; init; } public int Severity { get; init; } /// The work order's creation time; the response clock never starts at the scheduled date. public DateTime StartedAt { get; init; } public DateTime DeadlineAt { get; init; } public int PercentElapsed { get; init; } } public sealed class NotificationFeedDto { public DateTime GeneratedAt { get; init; } public IReadOnlyList Sections { get; init; } = Array.Empty(); /// Every at-risk work order in the feed's scope, oldest first; not capped like section items. public IReadOnlyList SlaAtRisk { get; init; } = Array.Empty(); } }