profileShare

rasmusjy / splitapp-backend-clean-onion

Read-only snapshot

No repository description.

main default branch 429 files Expires Sep 13, 2026, 9:06 AM
AdminStatsService.cs 8,294 bytes
1 using App.BLL.DTO;
2 using App.BLL.Mappers;
3 using App.Domain;
4 using App.Domain.Contracts;
5
6 namespace App.BLL.Services.Admin;
7
8 public class AdminStatsService : IAdminStatsService
9 {
10 private readonly IAppUnitOfWork _uow;
11
12 public AdminStatsService(IAppUnitOfWork uow)
13 {
14 _uow = uow;
15 }
16
17 public async Task<AdminDashboardData> GetDashboardStatsAsync()
18 {
19 var trips = (await _uow.Trips.GetAllAsync()).ToList();
20 var expenses = (await _uow.Expenses.GetAllAsync()).ToList();
21 var budgetCategories = (await _uow.BudgetCategories.GetAllAsync()).ToList();
22 var settlementPlans = (await _uow.SettlementPlans.GetAllAsync()).ToList();
23 var wishlistItems = (await _uow.TripWishlistItems.GetAllAsync()).ToList();
24 var polls = (await _uow.TripPolls.GetAllAsync()).ToList();
25 var invitations = (await _uow.TripInvitations.GetAllAsync()).ToList();
26 var currencies = (await _uow.GetRepository<Currency>().GetAllAsync()).ToList();
27 var participants = (await _uow.TripParticipants.GetAllAsync()).ToList();
28 var settlementPayments = (await _uow.SettlementPayments.GetAllAsync()).ToList();
29 var allUsers = (await _uow.Users.GetAllAsync()).ToList();
30
31 // === Counts ===
32 var tripCount = trips.Count;
33 var userCount = allUsers.Count;
34 var expenseCount = expenses.Count;
35
36 // === Trip status breakdown ===
37 var activeTrips = trips.Count(t => t.Status == ETripStatus.Active);
38 var settledTrips = trips.Count(t => t.Status == ETripStatus.Settled);
39 var archivedTrips = trips.Count(t => t.Status == ETripStatus.Archived);
40
41 var totalExpenseAmount = expenses.Sum(e => e.Amount);
42
43 var pendingSettlements = settlementPlans.Count(s => s.Status == ESettlementStatus.Pending);
44 var inProgressSettlements = settlementPlans.Count(s => s.Status == ESettlementStatus.InProgress);
45 var completedSettlements = settlementPlans.Count(s => s.Status == ESettlementStatus.Completed);
46
47 var recentTrips = TripBllDtoFactory.CreateList(trips.OrderByDescending(t => t.CreatedAt).Take(5));
48 var recentExpenses = ExpenseBllDtoFactory.CreateList(expenses.OrderByDescending(e => e.ExpenseDate).Take(8));
49 var recentUsers = (await _uow.Users.GetRecentAsync(5)).Select(u => new AppUserBllDto
50 {
51 Id = u.Id,
52 FirstName = u.FirstName,
53 LastName = u.LastName,
54 Email = u.Email
55 }).ToList();
56
57 var pendingInvitations = invitations.Count(i => i.Status == EInvitationStatus.Pending);
58 var pendingPayments = settlementPayments.Count(p => p.Status == EPaymentStatus.Pending);
59 var markedPaidPayments = settlementPayments.Count(p => p.Status == EPaymentStatus.MarkedPaid);
60
61 // === Top Active Trips ===
62 var topActiveTrips = trips
63 .Where(t => t.Status == ETripStatus.Active)
64 .Select(t =>
65 {
66 var tripExpenses = expenses.Where(e => e.TripId == t.Id).ToList();
67 return new AdminTopActiveTripItem
68 {
69 Trip = TripBllDtoFactory.Create(t),
70 ParticipantCount = participants.Count(p => p.TripId == t.Id),
71 ExpenseSum = tripExpenses.Sum(e => e.Amount),
72 ExpenseCount = tripExpenses.Count
73 };
74 })
75 .OrderByDescending(x => x.ExpenseSum)
76 .ThenByDescending(x => x.ParticipantCount)
77 .Take(5)
78 .ToList();
79
80 // === Top 10 biggest expenses ===
81 var biggestExpensesEntities = expenses
82 .OrderByDescending(e => e.Amount)
83 .Take(10)
84 .ToList();
85 foreach (var e in biggestExpensesEntities)
86 {
87 e.Trip ??= trips.FirstOrDefault(t => t.Id == e.TripId);
88 e.PaidByUser ??= allUsers.FirstOrDefault(u => u.Id == e.PaidByUserId);
89 }
90 var biggestExpenses = ExpenseBllDtoFactory.CreateList(biggestExpensesEntities);
91
92 // === Active users in last N days ===
93 var now = DateTime.UtcNow;
94 var cutoff7 = now.AddDays(-7);
95 var cutoff30 = now.AddDays(-30);
96
97 var activeUserIds7 = expenses.Where(e => e.CreatedAt >= cutoff7)
98 .Select(e => e.PaidByUserId).Distinct().Count();
99 var activeUserIds30 = expenses.Where(e => e.CreatedAt >= cutoff30)
100 .Select(e => e.PaidByUserId).Distinct().Count();
101
102 // === Top 5 most active users by expense count ===
103 var topActiveUsers = expenses
104 .GroupBy(e => e.PaidByUserId)
105 .Select(g =>
106 {
107 var user = allUsers.FirstOrDefault(u => u.Id == g.Key);
108 return new AdminTopActiveUserItem
109 {
110 UserId = g.Key,
111 Email = user?.Email ?? "—",
112 FullName = user != null ? $"{user.FirstName} {user.LastName}".Trim() : "—",
113 ExpenseCount = g.Count(),
114 TotalAmount = g.Sum(e => e.Amount)
115 };
116 })
117 .OrderByDescending(x => x.ExpenseCount)
118 .Take(5)
119 .ToList();
120
121 // === Activity feed (chronological) ===
122 var feed = new List<AdminActivityFeedItem>();
123 foreach (var t in trips)
124 {
125 feed.Add(new AdminActivityFeedItem
126 {
127 Type = "trip",
128 Date = t.CreatedAt,
129 MessageKey = "Trip \"{0}\" created",
130 MessageArgs = new object[] { t.Name },
131 IconCssClass = "bi-suitcase-lg",
132 BadgeCssClass = "bg-primary"
133 });
134 }
135 foreach (var e in expenses.OrderByDescending(x => x.CreatedAt).Take(30))
136 {
137 var tripName = trips.FirstOrDefault(t => t.Id == e.TripId)?.Name ?? "?";
138 feed.Add(new AdminActivityFeedItem
139 {
140 Type = "expense",
141 Date = e.CreatedAt,
142 MessageKey = "Expense {0:0.00} added to \"{1}\"",
143 MessageArgs = new object[] { e.Amount, tripName },
144 IconCssClass = "bi-cash-coin",
145 BadgeCssClass = "bg-success"
146 });
147 }
148 foreach (var s in settlementPlans)
149 {
150 var tripName = trips.FirstOrDefault(t => t.Id == s.TripId)?.Name ?? "?";
151 feed.Add(new AdminActivityFeedItem
152 {
153 Type = "settlement",
154 Date = s.CreatedAt,
155 MessageKey = "Settlement plan for \"{0}\" ({1})",
156 MessageArgs = new object[] { tripName, s.Status },
157 IconCssClass = "bi-diagram-3",
158 BadgeCssClass = "bg-info"
159 });
160 }
161 var activityFeed = feed.OrderByDescending(f => f.Date).Take(15).ToList();
162
163 return new AdminDashboardData
164 {
165 TripCount = tripCount,
166 UserCount = userCount,
167 ExpenseCount = expenseCount,
168 CategoryCount = budgetCategories.Count,
169 SettlementCount = settlementPlans.Count,
170 WishlistCount = wishlistItems.Count,
171 PollCount = polls.Count,
172 InvitationCount = invitations.Count,
173 CurrencyCount = currencies.Count,
174 ParticipantCount = participants.Count,
175
176 ActiveTrips = activeTrips,
177 SettledTrips = settledTrips,
178 ArchivedTrips = archivedTrips,
179 TotalExpenseAmount = totalExpenseAmount,
180 PendingSettlements = pendingSettlements,
181 InProgressSettlements = inProgressSettlements,
182 CompletedSettlements = completedSettlements,
183 PendingInvitations = pendingInvitations,
184 PendingPayments = pendingPayments,
185 MarkedPaidPayments = markedPaidPayments,
186
187 RecentTrips = recentTrips,
188 RecentExpenses = recentExpenses,
189 RecentUsers = recentUsers,
190
191 TopActiveTrips = topActiveTrips,
192 BiggestExpenses = biggestExpenses,
193 NewUsersLast7Days = activeUserIds7,
194 NewUsersLast30Days = activeUserIds30,
195 TopActiveUsers = topActiveUsers,
196 ActivityFeed = activityFeed
197 };
198 }
199 }
200