Private
Public Access
1
0
Files
Purrse/src/Purrse.Api/Services/DashboardService.cs
T
Catherine Renelle dc4fd692ba Limit dashboard spending to top 10 categories and fix nav double-highlight
Show top 10 categories in spending breakdown with label indicating the limit.
Fix account-scoped transactions highlighting both Accounts and Transactions
nav items by excluding sub-route match when account-transactions is active.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:59:27 -05:00

88 lines
4.2 KiB
C#

using Microsoft.EntityFrameworkCore;
using Purrse.Core.DTOs;
using Purrse.Core.Enums;
using Purrse.Core.Interfaces.Services;
using Purrse.Data;
namespace Purrse.Api.Services;
public class DashboardService : IDashboardService
{
private readonly PurrseDbContext _db;
public DashboardService(PurrseDbContext db) => _db = db;
public async Task<DashboardResponse> GetDashboardAsync(Guid userId)
{
var accounts = await _db.Accounts
.Where(a => a.UserId == userId && a.IsActive)
.OrderBy(a => a.SortOrder)
.ToListAsync();
var assetTypes = new[] { AccountType.Checking, AccountType.Savings, AccountType.Cash, AccountType.Brokerage, AccountType.Retirement401k, AccountType.IRA };
var totalAssets = accounts.Where(a => assetTypes.Contains(a.Type)).Sum(a => a.Balance);
var totalLiabilities = accounts.Where(a => !assetTypes.Contains(a.Type)).Sum(a => Math.Abs(a.Balance));
var netWorth = totalAssets - totalLiabilities;
var accountSummaries = accounts.Select(a => new AccountSummaryResponse(a.Id, a.Name, a.Type, a.Institution, a.Balance, a.CreditLimit)).ToList();
var now = DateTime.UtcNow;
var startOfMonth = new DateTime(now.Year, now.Month, 1, 0, 0, 0, DateTimeKind.Utc);
var startOfLastMonth = startOfMonth.AddMonths(-1);
var recentTxns = await _db.Transactions
.Include(t => t.Account).Include(t => t.Category)
.Where(t => t.Account.UserId == userId && !t.IsVoid)
.OrderByDescending(t => t.Date).ThenByDescending(t => t.CreatedAt)
.Take(10)
.Select(t => new TransactionResponse(t.Id, t.AccountId, t.Account.Name, t.Date, t.Amount,
t.PayeeId, t.PayeeName, t.CategoryId, t.Category != null ? t.Category.Name : null,
t.Memo, t.ReferenceNumber, t.CheckNumber, t.Type, t.Status, t.IsVoid,
t.TransferTransactionId, t.ImportBatchId, null, t.CreatedAt))
.ToListAsync();
var thisMonthSpending = await _db.Transactions
.Where(t => t.Account.UserId == userId && t.Date >= startOfMonth && t.Amount < 0 && !t.IsVoid)
.SumAsync(t => Math.Abs(t.Amount));
var lastMonthSpending = await _db.Transactions
.Where(t => t.Account.UserId == userId && t.Date >= startOfLastMonth && t.Date < startOfMonth && t.Amount < 0 && !t.IsVoid)
.SumAsync(t => Math.Abs(t.Amount));
var changePercent = lastMonthSpending == 0 ? 0 : ((thisMonthSpending - lastMonthSpending) / lastMonthSpending) * 100;
var spendingByCategory = await _db.Transactions
.Where(t => t.Account.UserId == userId && t.Date >= startOfMonth && t.Amount < 0 && !t.IsVoid)
.GroupBy(t => t.CategoryId)
.Select(g => new { CategoryId = g.Key, Total = -g.Sum(t => t.Amount) })
.OrderByDescending(x => x.Total)
.Take(10)
.ToListAsync();
var categoryIds = spendingByCategory.Where(x => x.CategoryId.HasValue).Select(x => x.CategoryId!.Value);
var categoryNames = await _db.Categories
.Where(c => categoryIds.Contains(c.Id))
.ToDictionaryAsync(c => c.Id, c => c.Name);
var totalSpending = spendingByCategory.Sum(x => x.Total);
var topCategories = spendingByCategory.Select(x => new CategorySpending(
x.CategoryId.HasValue ? categoryNames.GetValueOrDefault(x.CategoryId.Value, "Unknown") : "Uncategorized",
x.Total,
totalSpending > 0 ? (x.Total / totalSpending) * 100 : 0,
null)).ToList();
var spendingSummary = new SpendingSummary(thisMonthSpending, lastMonthSpending, changePercent, topCategories);
var upcomingBills = await _db.ScheduledTransactions
.Include(s => s.Account)
.Where(s => s.UserId == userId && s.IsActive && s.NextDueDate <= now.AddDays(14))
.OrderBy(s => s.NextDueDate)
.Take(10)
.Select(s => new UpcomingBill(s.Id, s.PayeeName ?? "", s.Amount, s.NextDueDate, s.Account.Name, s.AutoPost))
.ToListAsync();
return new DashboardResponse(netWorth, totalAssets, totalLiabilities, accountSummaries,
recentTxns, spendingSummary, upcomingBills);
}
}