diff --git a/MoneyMap/Pages/Transactions.cshtml b/MoneyMap/Pages/Transactions.cshtml index ba9d51c..e1b2a7d 100644 --- a/MoneyMap/Pages/Transactions.cshtml +++ b/MoneyMap/Pages/Transactions.cshtml @@ -122,6 +122,17 @@ + +@if (Model.CategoryBreakdowns.Any()) +{ +
+
Spending by category
+
+ +
+
+} + @if (Model.Transactions.Any()) { @@ -304,6 +315,31 @@ else } @section Scripts { + + } diff --git a/MoneyMap/Pages/Transactions.cshtml.cs b/MoneyMap/Pages/Transactions.cshtml.cs index 6b954e7..3c7edfb 100644 --- a/MoneyMap/Pages/Transactions.cshtml.cs +++ b/MoneyMap/Pages/Transactions.cshtml.cs @@ -50,6 +50,7 @@ namespace MoneyMap.Pages public List AvailableMerchants { get; set; } = new(); public List AvailableCards { get; set; } = new(); public TransactionStats Stats { get; set; } = new(); + public List CategoryBreakdowns { get; set; } = new(); public async Task OnGetAsync() { @@ -61,15 +62,16 @@ namespace MoneyMap.Pages .Include(t => t.Merchant) .AsQueryable(); - // Apply filters + // Apply filters (case-insensitive search using EF.Functions.Like) if (!string.IsNullOrWhiteSpace(Search)) { + var searchPattern = $"%{Search}%"; query = query.Where(t => - t.Name.Contains(Search) || - (t.Memo != null && t.Memo.Contains(Search)) || - (t.Category != null && t.Category.Contains(Search)) || - (t.Notes != null && t.Notes.Contains(Search)) || - (t.Merchant != null && t.Merchant.Name.Contains(Search))); + EF.Functions.Like(t.Name, searchPattern) || + (t.Memo != null && EF.Functions.Like(t.Memo, searchPattern)) || + (t.Category != null && EF.Functions.Like(t.Category, searchPattern)) || + (t.Notes != null && EF.Functions.Like(t.Notes, searchPattern)) || + (t.Merchant != null && EF.Functions.Like(t.Merchant.Name, searchPattern))); } if (!string.IsNullOrWhiteSpace(Category)) @@ -154,6 +156,21 @@ namespace MoneyMap.Pages // Calculate stats for filtered results (all pages, not just current) Stats = await _statsService.CalculateStatsAsync(query); + // Calculate category breakdown for pie chart (only expenses) + var expenseQuery = query.Where(t => t.Amount < 0).ExcludeTransfers(); + var categoryGroups = await expenseQuery + .GroupBy(t => t.Category ?? "") + .Select(g => new CategoryBreakdown + { + Category = g.Key, + TotalSpend = g.Sum(x => -x.Amount), + Count = g.Count() + }) + .OrderByDescending(x => x.TotalSpend) + .ToListAsync(); + + CategoryBreakdowns = categoryGroups; + // Get available categories for filter dropdown AvailableCategories = await _referenceDataService.GetAvailableCategoriesAsync(); @@ -178,5 +195,12 @@ namespace MoneyMap.Pages public string AccountLabel { get; set; } = ""; public int ReceiptCount { get; set; } } + + public class CategoryBreakdown + { + public string Category { get; set; } = ""; + public decimal TotalSpend { get; set; } + public int Count { get; set; } + } } } \ No newline at end of file