Bifrost/src/Bifrost.Web/Features/Shared/DashboardSnapshotService.cs
2026-04-21 20:08:49 +01:00

92 lines
3.7 KiB
C#

using Bifrost.Web.Data;
using Bifrost.Web.Domain;
using Microsoft.EntityFrameworkCore;
namespace Bifrost.Web.Features.Shared;
public sealed class DashboardSnapshotService(BifrostDbContext dbContext, TimeProvider timeProvider)
{
public async Task<DashboardSnapshot> GetAsync(Guid? currentUserAccountId, CancellationToken cancellationToken)
{
var recentWorkItems = await dbContext.WorkItems
.AsNoTracking()
.Include(x => x.Project)
.OrderByDescending(x => x.UpdatedAtUtc)
.Take(5)
.Select(x => new DashboardWorkItem(
x.Title,
x.Project.Name,
x.Status.ToString(),
x.Id))
.ToListAsync(cancellationToken);
var openMotions = await dbContext.Motions
.AsNoTracking()
.Include(x => x.Votes)
.OrderBy(x => x.ClosesAtUtc)
.Take(5)
.Select(x => new DashboardMotion(
x.Title,
x.Scope.ToString(),
x.Votes.Sum(v => v.Choice == VoteChoice.For ? v.Weight : 0),
x.Votes.Sum(v => v.Choice == VoteChoice.Against ? v.Weight : 0),
x.ClosesAtUtc,
x.Id))
.ToListAsync(cancellationToken);
var outstandingApprovals = await dbContext.Memberships
.AsNoTracking()
.CountAsync(x => x.Status == MembershipStatus.PendingApproval, cancellationToken);
var assignedToMe = currentUserAccountId is null
? 0
: await dbContext.WorkItems
.AsNoTracking()
.CountAsync(
x => x.AssignedToUserAccountId == currentUserAccountId &&
x.Status != WorkItemStatus.Done &&
x.Status != WorkItemStatus.Archived,
cancellationToken);
var payoutPreviewNominal = await dbContext.LedgerEntries
.AsNoTracking()
.Where(x => x.Status == LedgerEntryStatus.Approved)
.SumAsync(x => x.NominalAmount, cancellationToken);
return new DashboardSnapshot(
ActiveMembers: await dbContext.Memberships.AsNoTracking().CountAsync(x => x.Status == MembershipStatus.Active, cancellationToken),
OpenProjects: await dbContext.Projects.AsNoTracking().CountAsync(x => x.Status == ProjectStatus.Active, cancellationToken),
OpenWorkItems: await dbContext.WorkItems.AsNoTracking().CountAsync(x => x.Status == WorkItemStatus.Open || x.Status == WorkItemStatus.Claimed || x.Status == WorkItemStatus.InProgress, cancellationToken),
OpenMotions: await dbContext.Motions.AsNoTracking().CountAsync(x => x.Status == MotionStatus.Open && x.ClosesAtUtc >= timeProvider.GetUtcNow(), cancellationToken),
PendingMembershipApprovals: outstandingApprovals,
AssignedToCurrentMember: assignedToMe,
ApprovedNominalPayoutValue: payoutPreviewNominal,
RecentWorkItems: recentWorkItems,
OpenMotionHighlights: openMotions);
}
}
public sealed record DashboardSnapshot(
int ActiveMembers,
int OpenProjects,
int OpenWorkItems,
int OpenMotions,
int PendingMembershipApprovals,
int AssignedToCurrentMember,
decimal ApprovedNominalPayoutValue,
IReadOnlyList<DashboardWorkItem> RecentWorkItems,
IReadOnlyList<DashboardMotion> OpenMotionHighlights);
public sealed record DashboardWorkItem(
string Title,
string ProjectName,
string Status,
Guid WorkItemId);
public sealed record DashboardMotion(
string Title,
string Scope,
decimal VotesFor,
decimal VotesAgainst,
DateTimeOffset ClosesAtUtc,
Guid MotionId);