profileShare

rasmusjy / splitapp-backend-clean-onion

Read-only snapshot

No repository description.

main default branch 429 files Expires Sep 13, 2026, 9:06 AM
Index.cshtml 18,984 bytes
1 @model AdminDashboardViewModel
2
3 <div class="admin-page-header">
4 <div>
5 <h1><i class="bi bi-speedometer2 me-2"></i>@Localizer["Dashboard"]</h1>
6 <p class="lead">@Localizer["System overview and real-time statistics"]</p>
7 </div>
8 <div class="text-muted small">
9 <i class="bi bi-clock"></i> @DateTime.UtcNow.ToString("dd MMM yyyy, HH:mm") UTC
10 </div>
11 </div>
12
13 @if (Model.PendingPayments > 0 || Model.MarkedPaidPayments > 0 || Model.PendingInvitations > 0)
14 {
15 <div class="row g-2 mb-3">
16 @if (Model.PendingPayments > 0)
17 {
18 <div class="col-md-4"><div class="alert alert-warning mb-0"><i class="bi bi-exclamation-triangle me-1"></i><strong>@Model.PendingPayments</strong> @Localizer["payments awaiting action"]</div></div>
19 }
20 @if (Model.MarkedPaidPayments > 0)
21 {
22 <div class="col-md-4"><div class="alert alert-info mb-0"><i class="bi bi-info-circle me-1"></i><strong>@Model.MarkedPaidPayments</strong> @Localizer["payments awaiting confirmation"]</div></div>
23 }
24 @if (Model.PendingInvitations > 0)
25 {
26 <div class="col-md-4"><div class="alert alert-info mb-0"><i class="bi bi-envelope me-1"></i><strong>@Model.PendingInvitations</strong> @Localizer["pending invitations"]</div></div>
27 }
28 </div>
29 }
30
31 <!-- Primary metric cards -->
32 <div class="row g-3 mb-4">
33 <div class="col-6 col-md-4 col-xl">
34 <div class="admin-metric">
35 <div class="admin-metric-icon bg-primary"><i class="bi bi-people-fill"></i></div>
36 <div>
37 <div class="admin-metric-value">@Model.UserCount</div>
38 <div class="admin-metric-label">@Localizer["Total Users"]</div>
39 </div>
40 </div>
41 </div>
42 <div class="col-6 col-md-4 col-xl">
43 <div class="admin-metric">
44 <div class="admin-metric-icon bg-success"><i class="bi bi-suitcase-lg-fill"></i></div>
45 <div>
46 <div class="admin-metric-value">@Model.TripCount</div>
47 <div class="admin-metric-label">@Localizer["Trips"]</div>
48 </div>
49 </div>
50 </div>
51 <div class="col-6 col-md-4 col-xl">
52 <div class="admin-metric">
53 <div class="admin-metric-icon bg-warning"><i class="bi bi-cash-coin"></i></div>
54 <div>
55 <div class="admin-metric-value">@Model.ExpenseCount</div>
56 <div class="admin-metric-label">@Localizer["Expenses"]</div>
57 </div>
58 </div>
59 </div>
60 <div class="col-6 col-md-4 col-xl">
61 <div class="admin-metric">
62 <div class="admin-metric-icon bg-info"><i class="bi bi-currency-euro"></i></div>
63 <div>
64 <div class="admin-metric-value">@Model.TotalExpenseAmount.ToString("N2")</div>
65 <div class="admin-metric-label">@Localizer["Total amount"]</div>
66 </div>
67 </div>
68 </div>
69 <div class="col-6 col-md-4 col-xl">
70 <div class="admin-metric">
71 <div class="admin-metric-icon bg-purple"><i class="bi bi-diagram-3-fill"></i></div>
72 <div>
73 <div class="admin-metric-value">@Model.SettlementCount</div>
74 <div class="admin-metric-label">@Localizer["Settlement plans"]</div>
75 </div>
76 </div>
77 </div>
78 <div class="col-6 col-md-4 col-xl">
79 <div class="admin-metric">
80 <div class="admin-metric-icon bg-danger"><i class="bi bi-hourglass-split"></i></div>
81 <div>
82 <div class="admin-metric-value">@Model.PendingPayments</div>
83 <div class="admin-metric-label">@Localizer["Pending payments"]</div>
84 </div>
85 </div>
86 </div>
87 </div>
88
89 <!-- Status breakdowns row -->
90 <div class="row g-3 mb-4">
91 <div class="col-lg-6">
92 <div class="admin-card h-100">
93 <div class="admin-card-header">
94 <span><i class="bi bi-pie-chart-fill"></i>@Localizer["Trip status breakdown"]</span>
95 <span class="text-muted small">@Model.TripCount @Localizer["total"]</span>
96 </div>
97 <div class="admin-card-body">
98 <div class="d-flex flex-wrap gap-2 mb-3">
99 <span class="badge status-active fs-6 px-3 py-2"><i class="bi bi-circle-fill me-1"></i>@Model.ActiveTrips @Localizer["Active"]</span>
100 <span class="badge status-settled fs-6 px-3 py-2"><i class="bi bi-check2-circle me-1"></i>@Model.SettledTrips @Localizer["Settled"]</span>
101 <span class="badge status-archived fs-6 px-3 py-2"><i class="bi bi-archive me-1"></i>@Model.ArchivedTrips @Localizer["Archived"]</span>
102 </div>
103 @if (Model.TripCount > 0)
104 {
105 <div class="progress" style="height: 10px;">
106 <div class="progress-bar bg-success" style="width: @(Model.ActiveTrips * 100 / Model.TripCount)%"></div>
107 <div class="progress-bar bg-primary" style="width: @(Model.SettledTrips * 100 / Model.TripCount)%"></div>
108 <div class="progress-bar bg-secondary" style="width: @(Model.ArchivedTrips * 100 / Model.TripCount)%"></div>
109 </div>
110 }
111 </div>
112 </div>
113 </div>
114 <div class="col-lg-6">
115 <div class="admin-card h-100">
116 <div class="admin-card-header">
117 <span><i class="bi bi-diagram-3"></i>@Localizer["Settlement status"]</span>
118 <span class="text-muted small">@Model.SettlementCount @Localizer["total"]</span>
119 </div>
120 <div class="admin-card-body">
121 <div class="d-flex flex-wrap gap-2 mb-3">
122 <span class="badge status-pending fs-6 px-3 py-2"><i class="bi bi-hourglass me-1"></i>@Model.PendingSettlements @Localizer["Pending"]</span>
123 <span class="badge status-inprogress fs-6 px-3 py-2"><i class="bi bi-arrow-repeat me-1"></i>@Model.InProgressSettlements @Localizer["In progress"]</span>
124 <span class="badge status-completed fs-6 px-3 py-2"><i class="bi bi-check-circle me-1"></i>@Model.CompletedSettlements @Localizer["Completed"]</span>
125 </div>
126 @if (Model.SettlementCount > 0)
127 {
128 <div class="progress" style="height: 10px;">
129 <div class="progress-bar bg-warning" style="width: @(Model.PendingSettlements * 100 / Model.SettlementCount)%"></div>
130 <div class="progress-bar bg-info" style="width: @(Model.InProgressSettlements * 100 / Model.SettlementCount)%"></div>
131 <div class="progress-bar bg-success" style="width: @(Model.CompletedSettlements * 100 / Model.SettlementCount)%"></div>
132 </div>
133 }
134 </div>
135 </div>
136 </div>
137 </div>
138
139 <!-- Top Active Trips + Biggest Expenses -->
140 <div class="row g-3 mb-4">
141 <div class="col-lg-6">
142 <div class="admin-card h-100">
143 <div class="admin-card-header">
144 <span><i class="bi bi-trophy-fill"></i>@Localizer["Top active trips"]</span>
145 <a asp-area="Admin" asp-controller="Trips" asp-action="Index" class="btn btn-sm btn-outline-primary">@Localizer["View all"]</a>
146 </div>
147 <div class="admin-card-body p-0">
148 @if (Model.TopActiveTrips.Any())
149 {
150 <table class="table admin-table mb-0">
151 <thead>
152 <tr>
153 <th>@Localizer["Trip"]</th>
154 <th class="text-center">@Localizer["Participants"]</th>
155 <th class="text-center">@Localizer["Expenses"]</th>
156 <th class="text-end">@Localizer["Total"]</th>
157 </tr>
158 </thead>
159 <tbody>
160 @foreach (var row in Model.TopActiveTrips)
161 {
162 <tr>
163 <td>
164 <a asp-area="Admin" asp-controller="Trips" asp-action="Details" asp-route-id="@row.Trip.Id" class="text-decoration-none fw-semibold">@row.Trip.Name</a>
165 @if (!string.IsNullOrEmpty(row.Trip.Destination))
166 {
167 <br /><small class="text-muted">@row.Trip.Destination</small>
168 }
169 </td>
170 <td class="text-center"><span class="badge bg-light text-dark border">@row.ParticipantCount</span></td>
171 <td class="text-center"><span class="badge bg-light text-dark border">@row.ExpenseCount</span></td>
172 <td class="text-end fw-semibold">@row.ExpenseSum.ToString("N2")</td>
173 </tr>
174 }
175 </tbody>
176 </table>
177 }
178 else
179 {
180 <div class="admin-empty"><i class="bi bi-inbox"></i>@Localizer["No active trips yet"]</div>
181 }
182 </div>
183 </div>
184 </div>
185 <div class="col-lg-6">
186 <div class="admin-card h-100">
187 <div class="admin-card-header">
188 <span><i class="bi bi-graph-up-arrow"></i>@Localizer["Biggest expenses"]</span>
189 <a asp-area="Admin" asp-controller="Expenses" asp-action="Index" class="btn btn-sm btn-outline-primary">@Localizer["View all"]</a>
190 </div>
191 <div class="admin-card-body p-0">
192 @if (Model.BiggestExpenses.Any())
193 {
194 <table class="table admin-table mb-0">
195 <thead>
196 <tr>
197 <th>@Localizer["Description"]</th>
198 <th>@Localizer["Trip"]</th>
199 <th class="text-end">@Localizer["Amount"]</th>
200 </tr>
201 </thead>
202 <tbody>
203 @foreach (var e in Model.BiggestExpenses)
204 {
205 <tr>
206 <td>
207 <a asp-area="Admin" asp-controller="Expenses" asp-action="Details" asp-route-id="@e.Id" class="text-decoration-none">
208 @(string.IsNullOrWhiteSpace(e.Description) ? Localizer["No description"].Value : e.Description)
209 </a>
210 @if (e.PaidByUser != null)
211 {
212 <br /><small class="text-muted">@Localizer["by"] @e.PaidByUser.FirstName @e.PaidByUser.LastName</small>
213 }
214 </td>
215 <td class="text-muted small">@(e.Trip?.Name ?? "—")</td>
216 <td class="text-end fw-bold">@e.Amount.ToString("N2")</td>
217 </tr>
218 }
219 </tbody>
220 </table>
221 }
222 else
223 {
224 <div class="admin-empty"><i class="bi bi-inbox"></i>@Localizer["No expenses yet"]</div>
225 }
226 </div>
227 </div>
228 </div>
229 </div>
230
231 <!-- User stats + Activity Feed -->
232 <div class="row g-3 mb-4">
233 <div class="col-lg-5">
234 <div class="admin-card h-100">
235 <div class="admin-card-header">
236 <span><i class="bi bi-person-lines-fill"></i>@Localizer["User activity"]</span>
237 </div>
238 <div class="admin-card-body">
239 <div class="row g-2 mb-3">
240 <div class="col-4 text-center">
241 <div class="fs-4 fw-bold text-primary">@Model.UserCount</div>
242 <div class="small text-muted">@Localizer["Total"]</div>
243 </div>
244 <div class="col-4 text-center">
245 <div class="fs-4 fw-bold text-success">@Model.NewUsersLast7Days</div>
246 <div class="small text-muted">@Localizer["Active 7d"]</div>
247 </div>
248 <div class="col-4 text-center">
249 <div class="fs-4 fw-bold text-info">@Model.NewUsersLast30Days</div>
250 <div class="small text-muted">@Localizer["Active 30d"]</div>
251 </div>
252 </div>
253 <hr />
254 <div class="small fw-semibold text-muted mb-2 text-uppercase">@Localizer["Most active users"]</div>
255 @if (Model.TopActiveUsers.Any())
256 {
257 <ul class="list-unstyled mb-0">
258 @foreach (var u in Model.TopActiveUsers)
259 {
260 <li class="d-flex justify-content-between align-items-center py-1 border-bottom border-light">
261 <div>
262 <div class="fw-semibold small">@u.FullName</div>
263 <div class="text-muted" style="font-size: 0.78rem;">@u.Email</div>
264 </div>
265 <div class="text-end">
266 <span class="badge bg-primary">@u.ExpenseCount @Localizer["expenses"]</span>
267 </div>
268 </li>
269 }
270 </ul>
271 }
272 else
273 {
274 <div class="admin-empty"><i class="bi bi-person-slash"></i>@Localizer["No activity yet"]</div>
275 }
276 </div>
277 </div>
278 </div>
279 <div class="col-lg-7">
280 <div class="admin-card h-100">
281 <div class="admin-card-header">
282 <span><i class="bi bi-activity"></i>@Localizer["Recent activity"]</span>
283 <span class="text-muted small">@Model.ActivityFeed.Count @Localizer["events"]</span>
284 </div>
285 <div class="admin-card-body">
286 @if (Model.ActivityFeed.Any())
287 {
288 <ul class="admin-feed">
289 @foreach (var item in Model.ActivityFeed)
290 {
291 <li class="admin-feed-item">
292 <div class="admin-feed-icon @item.BadgeCssClass">
293 <i class="bi @item.IconCssClass"></i>
294 </div>
295 <div class="admin-feed-body">
296 <div class="admin-feed-message">@item.Message</div>
297 <div class="admin-feed-date">@item.Date.ToString("dd MMM yyyy, HH:mm") UTC</div>
298 </div>
299 </li>
300 }
301 </ul>
302 }
303 else
304 {
305 <div class="admin-empty"><i class="bi bi-clock-history"></i>@Localizer["No recent activity"]</div>
306 }
307 </div>
308 </div>
309 </div>
310 </div>
311
312 <!-- Secondary counts -->
313 <div class="row g-3 mb-4">
314 <div class="col-6 col-md-4 col-lg-2">
315 <div class="admin-metric">
316 <div class="admin-metric-icon bg-info"><i class="bi bi-people"></i></div>
317 <div><div class="admin-metric-value">@Model.ParticipantCount</div><div class="admin-metric-label">@Localizer["Participants"]</div></div>
318 </div>
319 </div>
320 <div class="col-6 col-md-4 col-lg-2">
321 <div class="admin-metric">
322 <div class="admin-metric-icon bg-warning"><i class="bi bi-tags"></i></div>
323 <div><div class="admin-metric-value">@Model.CategoryCount</div><div class="admin-metric-label">@Localizer["Categories"]</div></div>
324 </div>
325 </div>
326 <div class="col-6 col-md-4 col-lg-2">
327 <div class="admin-metric">
328 <div class="admin-metric-icon bg-success"><i class="bi bi-currency-exchange"></i></div>
329 <div><div class="admin-metric-value">@Model.CurrencyCount</div><div class="admin-metric-label">@Localizer["Currencies"]</div></div>
330 </div>
331 </div>
332 <div class="col-6 col-md-4 col-lg-2">
333 <div class="admin-metric">
334 <div class="admin-metric-icon bg-purple"><i class="bi bi-stars"></i></div>
335 <div><div class="admin-metric-value">@Model.WishlistCount</div><div class="admin-metric-label">@Localizer["Wishlist"]</div></div>
336 </div>
337 </div>
338 <div class="col-6 col-md-4 col-lg-2">
339 <div class="admin-metric">
340 <div class="admin-metric-icon bg-primary"><i class="bi bi-bar-chart"></i></div>
341 <div><div class="admin-metric-value">@Model.PollCount</div><div class="admin-metric-label">@Localizer["Polls"]</div></div>
342 </div>
343 </div>
344 <div class="col-6 col-md-4 col-lg-2">
345 <div class="admin-metric">
346 <div class="admin-metric-icon bg-danger"><i class="bi bi-envelope"></i></div>
347 <div><div class="admin-metric-value">@Model.InvitationCount</div><div class="admin-metric-label">@Localizer["Invitations"]</div></div>
348 </div>
349 </div>
350 </div>
351
352 <!-- Quick actions -->
353 <div class="admin-card">
354 <div class="admin-card-header">
355 <span><i class="bi bi-plus-circle"></i>@Localizer["Quick actions"]</span>
356 </div>
357 <div class="admin-card-body">
358 <div class="row g-2">
359 <div class="col-md-3 col-6">
360 <a asp-area="Admin" asp-controller="Trips" asp-action="Create" class="btn btn-outline-primary w-100"><i class="bi bi-plus-lg me-1"></i>@Localizer["New Trip"]</a>
361 </div>
362 <div class="col-md-3 col-6">
363 <a asp-area="Admin" asp-controller="Expenses" asp-action="Create" class="btn btn-outline-primary w-100"><i class="bi bi-plus-lg me-1"></i>@Localizer["New Expense"]</a>
364 </div>
365 <div class="col-md-3 col-6">
366 <a asp-area="Admin" asp-controller="Currencies" asp-action="Create" class="btn btn-outline-primary w-100"><i class="bi bi-plus-lg me-1"></i>@Localizer["New Currency"]</a>
367 </div>
368 <div class="col-md-3 col-6">
369 <a asp-area="Admin" asp-controller="BudgetCategories" asp-action="Create" class="btn btn-outline-primary w-100"><i class="bi bi-plus-lg me-1"></i>@Localizer["New Category"]</a>
370 </div>
371 </div>
372 </div>
373 </div>
374