WIP
This commit is contained in:
1 parent
38067df8c3
commit
492d86db9e
30 files changed
+2473
-40
No files matched your search
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user