This commit is contained in:
rossmill76 committed 2026-03-05 18:57:12 +00:00
1 parent 38067df8c3
commit 492d86db9e
30 files changed
+2473 -40

No files matched your search

+62 -17
View File
@@ -2,6 +2,7 @@
@attribute [Authorize]
@inject DashboardService DashboardService
@inject TransactionService TransactionService
@inject SalaryService SalaryService
@inject AuthenticationStateProvider AuthStateProvider
@inject IJSRuntime JS
@implements IAsyncDisposable
@@ -195,6 +196,10 @@ else
{
<span class="badge bg-info bg-opacity-10 text-info" style="font-size: 0.7rem;">🔁 @GetRecurrenceLabel(t)</span>
}
@if (t.IsPaid)
{
<span class="badge bg-success bg-opacity-10 text-success" style="font-size: 0.7rem;">✓ Paid</span>
}
</div>
<div class="d-flex align-items-center gap-2 mt-1 flex-wrap" style="font-size: 0.8rem;">
<span class="text-muted">@t.Date.ToString("MMM dd, yyyy")</span>
@@ -209,20 +214,30 @@ else
<div class="@(t.Type == TransactionType.Income ? "text-success" : "text-danger") fw-semibold" style="font-size: 1.05rem;">
@(t.Type == TransactionType.Income ? "+" : "−")@t.Account.FormatAmount(t.Amount)
</div>
<small class="text-muted" style="font-size: 0.7rem;">@(t.Type == TransactionType.Income ? "Income" : "Expense")</small>
</div>
</div>
@if (selectedTransactionId == t.Id)
{
<div class="mt-2 pt-2 border-top d-flex justify-content-end gap-2">
<button class="btn btn-outline-secondary btn-sm" @onclick="() => OpenEditModal(t)" @onclick:stopPropagation="true">✏️ Edit</button>
<button class="btn btn-outline-danger btn-sm" @onclick="() => ConfirmDelete(t)" @onclick:stopPropagation="true">🗑️ Delete</button>
</div>
}
</div>
}
</div>
@if (RecentTotalPages > 1)
<small class="text-muted" style="font-size: 0.7rem;">@(t.Type == TransactionType.Income ? "Income" : "Expense")</small>
</div>
</div>
@if (selectedTransactionId == t.Id)
{
<div class="mt-2 pt-2 border-top d-flex justify-content-between align-items-center gap-2">
@if (t.IsPaid)
{
<button class="btn btn-outline-secondary btn-sm" title="Revert to upcoming" @onclick="() => MarkAsPaid(t.Id, false)" @onclick:stopPropagation="true">↩ Unpaid</button>
}
else
{
<span></span>
}
<div class="d-flex gap-2">
<button class="btn btn-outline-secondary btn-sm" @onclick="() => OpenEditModal(t)" @onclick:stopPropagation="true">✏️ Edit</button>
<button class="btn btn-outline-danger btn-sm" @onclick="() => ConfirmDelete(t)" @onclick:stopPropagation="true">🗑️ Delete</button>
</div>
</div>
}
</div>
}
</div>
@if (RecentTotalPages > 1)
{
<div class="d-flex justify-content-between align-items-center px-3 py-2 border-top">
<button class="btn btn-sm btn-outline-secondary" disabled="@(recentPage <= 1 ? true : (bool?)null)" @onclick="() => recentPage--">← Prev</button>
@@ -282,9 +297,12 @@ else
</div>
@if (selectedTransactionId == t.Id)
{
<div class="mt-2 pt-2 border-top d-flex justify-content-end gap-2">
<button class="btn btn-outline-secondary btn-sm" @onclick="() => OpenEditModal(t)" @onclick:stopPropagation="true">✏️ Edit</button>
<button class="btn btn-outline-danger btn-sm" @onclick="() => ConfirmDelete(t)" @onclick:stopPropagation="true">🗑️ Delete</button>
<div class="mt-2 pt-2 border-top d-flex justify-content-between align-items-center gap-2">
<button class="btn btn-outline-success btn-sm" title="Mark as paid and move to Recent" @onclick="() => MarkAsPaid(t.Id, true)" @onclick:stopPropagation="true">✅ Mark as Paid</button>
<div class="d-flex gap-2">
<button class="btn btn-outline-secondary btn-sm" @onclick="() => OpenEditModal(t)" @onclick:stopPropagation="true">✏️ Edit</button>
<button class="btn btn-outline-danger btn-sm" @onclick="() => ConfirmDelete(t)" @onclick:stopPropagation="true">🗑️ Delete</button>
</div>
</div>
}
</div>
@@ -446,6 +464,7 @@ else
datasets,
dayLabels,
highlightIndex,
GetSalaryDayIndices(),
data.CurrencySymbol);
}
}
@@ -462,6 +481,23 @@ else
return data.ChartData.Labels.IndexOf(label);
}
private int[] GetSalaryDayIndices()
{
if (data is null) return [];
var salaryDates = data.RecentTransactions
.Concat(data.UpcomingTransactions)
.Where(t => t.SalaryId is not null)
.Select(t => t.Date.ToString("MMM dd"))
.ToHashSet();
return data.ChartData.Labels
.Select((label, index) => (label, index))
.Where(x => salaryDates.Contains(x.label))
.Select(x => x.index)
.ToArray();
}
private async Task SelectTransaction(int id)
{
selectedTransactionId = selectedTransactionId == id ? null : id;
@@ -583,6 +619,14 @@ else
pendingDeleteTransaction = null;
}
private async Task MarkAsPaid(int id, bool isPaid)
{
if (userId is null) return;
await TransactionService.MarkAsPaidAsync(id, userId, isPaid);
selectedTransactionId = null;
await RefreshData();
}
private static string GetRecurrenceLabel(Transaction t)
{
if (t.RecurrenceType != RecurrenceType.None)
@@ -609,6 +653,7 @@ else
private async Task RefreshData()
{
if (userId is null) return;
await SalaryService.MaterializeSalaryTransactionsAsync(userId, windowEnd);
data = await DashboardService.GetDashboardDataAsync(userId, windowStart, windowEnd);
chartDataVersion++;
ComputeProjectedBalances();