diff --git a/MoneyMap/Pages/Index.cshtml b/MoneyMap/Pages/Index.cshtml
index 6d8efc7..8da2108 100644
--- a/MoneyMap/Pages/Index.cshtml
+++ b/MoneyMap/Pages/Index.cshtml
@@ -1,4 +1,4 @@
-@page
+@page
@model MoneyMap.Pages.IndexModel
@{
ViewData["Title"] = "MoneyMap";
@@ -10,7 +10,7 @@
Transactions
@Model.Stats.TotalTransactions
-
Credits: @Model.Stats.Credits · Debits: @Model.Stats.Debits
+
Credits: @Model.Stats.Credits · Debits: @Model.Stats.Debits
@@ -22,7 +22,7 @@
@if (Model.Stats.Uncategorized > 0)
{
-
View uncategorized →
+
View uncategorized ?
}
else
{
@@ -63,7 +63,7 @@
@@ -125,7 +125,7 @@
@if (t.ReceiptCount > 0)
{
- 📄 @t.ReceiptCount
+ ?? @t.ReceiptCount
}
@@ -152,3 +152,56 @@
+
+
+
+
+
diff --git a/MoneyMap/Pages/Index.cshtml.cs b/MoneyMap/Pages/Index.cshtml.cs
index 7d52af5..5d7658d 100644
--- a/MoneyMap/Pages/Index.cshtml.cs
+++ b/MoneyMap/Pages/Index.cshtml.cs
@@ -21,6 +21,10 @@ namespace MoneyMap.Pages
public DashboardStats Stats { get; set; } = new();
public List
TopCategories { get; set; } = new();
public List Recent { get; set; } = new();
+ public List TrendLabels { get; set; } = new();
+ public List TrendDebitsAbs { get; set; } = new();
+ public List TrendCredits { get; set; } = new();
+ public List TrendNet { get; set; } = new();
public async Task OnGet()
{
@@ -29,6 +33,10 @@ namespace MoneyMap.Pages
Stats = dashboard.Stats;
TopCategories = dashboard.TopCategories;
Recent = dashboard.RecentTransactions;
+ TrendLabels = dashboard.Trends.Labels;
+ TrendDebitsAbs = dashboard.Trends.DebitsAbs;
+ TrendCredits = dashboard.Trends.Credits;
+ TrendNet = dashboard.Trends.Net;
}
public record DashboardStats(
@@ -74,17 +82,20 @@ namespace MoneyMap.Pages
private readonly IDashboardStatsCalculator _statsCalculator;
private readonly ITopCategoriesProvider _topCategoriesProvider;
private readonly IRecentTransactionsProvider _recentTransactionsProvider;
+ private readonly ISpendTrendsProvider _spendTrendsProvider;
public DashboardService(
MoneyMapContext db,
IDashboardStatsCalculator statsCalculator,
ITopCategoriesProvider topCategoriesProvider,
- IRecentTransactionsProvider recentTransactionsProvider)
+ IRecentTransactionsProvider recentTransactionsProvider,
+ ISpendTrendsProvider spendTrendsProvider)
{
_db = db;
_statsCalculator = statsCalculator;
_topCategoriesProvider = topCategoriesProvider;
_recentTransactionsProvider = recentTransactionsProvider;
+ _spendTrendsProvider = spendTrendsProvider;
}
public async Task GetDashboardDataAsync(int topCategoriesCount = 8, int recentTransactionsCount = 20)
@@ -92,12 +103,14 @@ namespace MoneyMap.Pages
var stats = await _statsCalculator.CalculateAsync();
var topCategories = await _topCategoriesProvider.GetTopCategoriesAsync(topCategoriesCount);
var recent = await _recentTransactionsProvider.GetRecentTransactionsAsync(recentTransactionsCount);
+ var trends = await _spendTrendsProvider.GetDailyTrendsAsync(30);
return new DashboardData
{
Stats = stats,
TopCategories = topCategories,
- RecentTransactions = recent
+ RecentTransactions = recent,
+ Trends = trends
};
}
}
@@ -264,5 +277,82 @@ namespace MoneyMap.Pages
public required IndexModel.DashboardStats Stats { get; init; }
public required List TopCategories { get; init; }
public required List RecentTransactions { get; init; }
+ public required SpendTrends Trends { get; init; }
+ }
+
+ // ===== Spend Trends Provider =====
+
+ public interface ISpendTrendsProvider
+ {
+ Task GetDailyTrendsAsync(int lastDays = 30);
+ }
+
+ public class SpendTrendsProvider : ISpendTrendsProvider
+ {
+ private readonly MoneyMapContext _db;
+
+ public SpendTrendsProvider(MoneyMapContext db)
+ {
+ _db = db;
+ }
+
+ public async Task GetDailyTrendsAsync(int lastDays = 30)
+ {
+ var today = DateTime.UtcNow.Date;
+ var since = today.AddDays(-(lastDays - 1));
+
+ var raw = await _db.Transactions
+ .Where(t => t.Date >= since)
+ .ExcludeTransfers()
+ .GroupBy(t => t.Date.Date)
+ .Select(g => new
+ {
+ Date = g.Key,
+ Debits = g.Where(t => t.Amount < 0).Sum(t => t.Amount),
+ Credits = g.Where(t => t.Amount > 0).Sum(t => t.Amount)
+ })
+ .ToListAsync();
+
+ var dict = raw.ToDictionary(x => x.Date, x => x);
+ var labels = new List();
+ var debitsAbs = new List();
+ var credits = new List();
+ var net = new List();
+
+ for (var d = since; d <= today; d = d.AddDays(1))
+ {
+ labels.Add(d.ToString("yyyy-MM-dd"));
+ if (dict.TryGetValue(d, out var v))
+ {
+ var debit = v.Debits;
+ var credit = v.Credits;
+ debitsAbs.Add(Math.Abs(debit));
+ credits.Add(credit);
+ net.Add(credit + debit);
+ }
+ else
+ {
+ debitsAbs.Add(0);
+ credits.Add(0);
+ net.Add(0);
+ }
+ }
+
+ return new SpendTrends
+ {
+ Labels = labels,
+ DebitsAbs = debitsAbs,
+ Credits = credits,
+ Net = net
+ };
+ }
+ }
+
+ public class SpendTrends
+ {
+ public List Labels { get; set; } = new();
+ public List DebitsAbs { get; set; } = new();
+ public List Credits { get; set; } = new();
+ public List Net { get; set; } = new();
}
}
diff --git a/MoneyMap/Program.cs b/MoneyMap/Program.cs
index 54c9b80..d5cc43b 100644
--- a/MoneyMap/Program.cs
+++ b/MoneyMap/Program.cs
@@ -31,6 +31,7 @@ builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
+builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddHttpClient();
@@ -65,4 +66,4 @@ app.UseAuthorization();
app.MapRazorPages();
-app.Run();
\ No newline at end of file
+app.Run();