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

+61 -11
View File
@@ -3,6 +3,7 @@
@inject TransactionService TransactionService
@inject AccountService AccountService
@inject CategoryService CategoryService
@inject SalaryService SalaryService
@inject AuthenticationStateProvider AuthStateProvider
@rendermode InteractiveServer
@@ -176,11 +177,28 @@ else
@foreach (var t in pagedTransactions)
{
<tr>
<td>
<input type="checkbox" class="form-check-input" checked="@selectedIds.Contains(t.Id)" @onchange="() => ToggleSelect(t.Id)" />
</td>
<td>
@if (t.SalaryId is null)
{
<input type="checkbox" class="form-check-input" checked="@selectedIds.Contains(t.Id)" @onchange="() => ToggleSelect(t.Id)" />
}
else
{
<span class="text-muted" style="font-size: 0.85rem;" title="Salary transactions cannot be bulk deleted — manage them on the Salary page">🔒</span>
}
</td>
<td class="text-nowrap">@t.Date.ToString("MMM dd, yyyy")</td>
<td>@t.Description</td>
<td>
@t.Description
@if (t.SalaryId is not null)
{
<span class="badge bg-primary ms-1" style="font-size: 0.7rem;">💰 Salary</span>
}
@if (t.IsPaid && t.Date.Date > DateTime.Today)
{
<span class="badge bg-success ms-1" style="font-size: 0.7rem;">✓ Paid</span>
}
</td>
<td>
<span class="badge" style="background-color: @t.Account.Color;">
@t.Account.Name
@@ -215,8 +233,21 @@ else
</td>
<td class="text-end text-nowrap">
<div class="btn-group btn-group-sm">
@if (t.Date.Date > DateTime.Today)
{
<button class="btn @(t.IsPaid ? "btn-success" : "btn-outline-success")"
title="@(t.IsPaid ? "Mark as unpaid" : "Mark as paid")"
@onclick="() => TogglePaid(t)">✓</button>
}
<button class="btn btn-outline-primary" @onclick="() => OpenEditModal(t)">Edit</button>
<button class="btn btn-outline-danger" @onclick="() => ConfirmDelete(t)">Delete</button>
@if (t.SalaryId is null)
{
<button class="btn btn-outline-danger" @onclick="() => ConfirmDelete(t)">Delete</button>
}
else
{
<a href="/salary" class="btn btn-outline-secondary" title="Salary transactions are managed on the Salary page">🔒</a>
}
</div>
</td>
</tr>
@@ -448,18 +479,20 @@ else
private void OnSearchChanged() => currentPage = 1;
private void SetWindow(int days)
private async Task SetWindow(int days)
{
windowPreset = days switch { 7 => "7d", 30 => "30d", 90 => "90d", 365 => "1y", _ => null };
windowStart = DateTime.Today;
windowEnd = DateTime.Today.AddDays(days);
currentPage = 1;
await LoadData();
}
private void OnWindowDateChanged()
private async Task OnWindowDateChanged()
{
windowPreset = null;
currentPage = 1;
await LoadData();
}
private void OnPageSizeChanged(ChangeEventArgs e)
@@ -501,6 +534,7 @@ else
{
if (userId is null) return;
await SalaryService.MaterializeSalaryTransactionsAsync(userId, windowEnd);
transactions = await TransactionService.GetTransactionsAsync(userId);
accountsList = await AccountService.GetAccountsAsync(userId);
categoriesList = await CategoryService.GetCategoriesAsync(userId);
@@ -539,22 +573,29 @@ else
private void ToggleSelect(int id)
{
var t = transactions?.FirstOrDefault(x => x.Id == id);
if (t?.SalaryId is not null) return;
if (!selectedIds.Remove(id))
selectedIds.Add(id);
}
private bool IsAllPageSelected() => pagedTransactions.Count > 0 && pagedTransactions.All(t => selectedIds.Contains(t.Id));
private bool IsAllPageSelected()
{
var selectable = pagedTransactions.Where(t => t.SalaryId is null).ToList();
return selectable.Count > 0 && selectable.All(t => selectedIds.Contains(t.Id));
}
private void ToggleSelectAll(ChangeEventArgs e)
{
var selectable = pagedTransactions.Where(t => t.SalaryId is null).ToList();
if (e.Value is true)
{
foreach (var t in pagedTransactions)
foreach (var t in selectable)
selectedIds.Add(t.Id);
}
else
{
foreach (var t in pagedTransactions)
foreach (var t in selectable)
selectedIds.Remove(t.Id);
}
}
@@ -649,8 +690,10 @@ else
{
if (userId is null) return;
showDeleteModal = false;
foreach (var id in selectedIds)
foreach (var id in selectedIds.ToList())
{
var t = transactions?.FirstOrDefault(x => x.Id == id);
if (t?.SalaryId is not null) continue;
await TransactionService.DeleteTransactionAsync(id, userId);
}
selectedIds.Clear();
@@ -664,6 +707,13 @@ else
bulkDeleteCount = 0;
}
private async Task TogglePaid(Transaction t)
{
if (userId is null) return;
await TransactionService.MarkAsPaidAsync(t.Id, userId, !t.IsPaid);
await LoadData();
}
private sealed class TransactionForm : System.ComponentModel.DataAnnotations.IValidatableObject
{
[System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Description is required")]