WIP
This commit is contained in:
1 parent
6f63488c89
commit
64c3eaa473
4 files changed
+86
-39
No files matched your search
@@ -107,31 +107,55 @@ else if (transactions.Count == 0)
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card">
|
||||
<div class="card-header d-flex align-items-center gap-2 flex-wrap">
|
||||
<label class="form-label mb-0 text-nowrap">Account:</label>
|
||||
<select class="form-select form-select-sm" style="max-width: 200px;" title="Filter transactions by account" @onchange="OnAccountFilterChanged">
|
||||
<option value="">All Accounts</option>
|
||||
@if (accountsList is not null)
|
||||
{
|
||||
@foreach (var a in accountsList)
|
||||
{
|
||||
<option value="@a.Id" selected="@(filterAccountId == a.Id)">@a.Name</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
<input type="search" class="form-control form-control-sm" style="max-width: 220px;"
|
||||
title="Search by description, account, category or amount" placeholder="Search..." @bind="searchQuery" @bind:event="oninput" @bind:after="OnSearchChanged" />
|
||||
<div class="ms-auto d-flex align-items-center gap-2">
|
||||
<label class="form-label mb-0 text-nowrap">Show:</label>
|
||||
<select class="form-select form-select-sm" style="max-width: 90px;" @onchange="OnPageSizeChanged">
|
||||
@foreach (var size in pageSizeOptions)
|
||||
{
|
||||
<option value="@size" selected="@(pageSize == size)">@size</option>
|
||||
}
|
||||
</select>
|
||||
<div class="card mb-3">
|
||||
<div class="card-header d-flex flex-wrap align-items-center justify-content-between gap-2">
|
||||
<h5 class="mb-0">Filters</h5>
|
||||
<div class="d-flex flex-wrap align-items-center gap-2">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn @(windowPreset == "7d" ? "btn-primary" : "btn-outline-secondary")" title="Show next 7 days" @onclick="() => SetWindow(7)">7d</button>
|
||||
<button class="btn @(windowPreset == "30d" ? "btn-primary" : "btn-outline-secondary")" title="Show next 30 days" @onclick="() => SetWindow(30)">30d</button>
|
||||
<button class="btn @(windowPreset == "90d" ? "btn-primary" : "btn-outline-secondary")" title="Show next 90 days" @onclick="() => SetWindow(90)">90d</button>
|
||||
<button class="btn @(windowPreset == "1y" ? "btn-primary" : "btn-outline-secondary")" title="Show next year" @onclick="() => SetWindow(365)">1y</button>
|
||||
</div>
|
||||
<input type="date" class="form-control form-control-sm" style="width:auto;"
|
||||
title="Custom start date" @bind="windowStart" @bind:after="OnWindowDateChanged" />
|
||||
<span class="text-muted">to</span>
|
||||
<input type="date" class="form-control form-control-sm" style="width:auto;"
|
||||
title="Custom end date" @bind="windowEnd" @bind:after="OnWindowDateChanged" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body py-2">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-12 col-sm-auto">
|
||||
<select class="form-select form-select-sm" title="Filter transactions by account" @onchange="OnAccountFilterChanged">
|
||||
<option value="">All Accounts</option>
|
||||
@if (accountsList is not null)
|
||||
{
|
||||
@foreach (var a in accountsList)
|
||||
{
|
||||
<option value="@a.Id" selected="@(filterAccountId == a.Id)">@a.Name</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-sm">
|
||||
<input type="search" class="form-control form-control-sm"
|
||||
title="Search by description, account, category or amount" placeholder="Search..." @bind="searchQuery" @bind:event="oninput" @bind:after="OnSearchChanged" />
|
||||
</div>
|
||||
<div class="col-auto d-flex align-items-center gap-2">
|
||||
<label class="form-label mb-0 text-nowrap">Show:</label>
|
||||
<select class="form-select form-select-sm" style="width:auto;" @onchange="OnPageSizeChanged">
|
||||
@foreach (var size in pageSizeOptions)
|
||||
{
|
||||
<option value="@size" selected="@(pageSize == size)">@size</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
@@ -142,8 +166,8 @@ else
|
||||
<th role="button" class="user-select-none" title="Sort by date" @onclick='() => SetSort("date")'>Date @SortIndicator("date")</th>
|
||||
<th role="button" class="user-select-none" title="Sort by description" @onclick='() => SetSort("description")'>Description @SortIndicator("description")</th>
|
||||
<th>Account</th>
|
||||
<th>Category</th>
|
||||
<th>Recurrence</th>
|
||||
<th class="d-none d-md-table-cell">Category</th>
|
||||
<th class="d-none d-md-table-cell">Recurrence</th>
|
||||
<th role="button" class="user-select-none text-end" title="Sort by amount" @onclick='() => SetSort("amount")'>Amount @SortIndicator("amount")</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
@@ -155,14 +179,14 @@ else
|
||||
<td>
|
||||
<input type="checkbox" class="form-check-input" checked="@selectedIds.Contains(t.Id)" @onchange="() => ToggleSelect(t.Id)" />
|
||||
</td>
|
||||
<td>@t.Date.ToString("MMM dd, yyyy")</td>
|
||||
<td class="text-nowrap">@t.Date.ToString("MMM dd, yyyy")</td>
|
||||
<td>@t.Description</td>
|
||||
<td>
|
||||
<span class="badge" style="background-color: @t.Account.Color;">
|
||||
@t.Account.Name
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<td class="d-none d-md-table-cell">
|
||||
@if (t.Category is not null)
|
||||
{
|
||||
@if (!string.IsNullOrEmpty(t.Category.Icon))
|
||||
@@ -176,7 +200,7 @@ else
|
||||
<span>—</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<td class="d-none d-md-table-cell">
|
||||
@if (t.RecurrenceType != RecurrenceType.None)
|
||||
{
|
||||
<span class="badge bg-info">🔁 @t.RecurrenceType</span>
|
||||
@@ -186,16 +210,14 @@ else
|
||||
<span class="badge bg-info">🔁 @(t.ParentTransaction?.RecurrenceType.ToString() ?? "Recurring")</span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end @(t.Type == TransactionType.Income ? "text-success" : "text-danger") fw-semibold">
|
||||
<td class="text-end text-nowrap @(t.Type == TransactionType.Income ? "text-success" : "text-danger") fw-semibold">
|
||||
@(t.Type == TransactionType.Income ? "+" : "-")@t.Account.FormatAmount(t.Amount)
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<button class="btn btn-outline-primary btn-sm" @onclick="() => OpenEditModal(t)">
|
||||
Edit
|
||||
</button>
|
||||
<button class="btn btn-outline-danger btn-sm" @onclick="() => ConfirmDelete(t)">
|
||||
Delete
|
||||
</button>
|
||||
<td class="text-end text-nowrap">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn btn-outline-primary" @onclick="() => OpenEditModal(t)">Edit</button>
|
||||
<button class="btn btn-outline-danger" @onclick="() => ConfirmDelete(t)">Delete</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@@ -204,7 +226,7 @@ else
|
||||
</div>
|
||||
@if (totalPages > 1)
|
||||
{
|
||||
<div class="card-footer d-flex justify-content-between align-items-center">
|
||||
<div class="card-footer d-flex flex-wrap justify-content-between align-items-center gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary" disabled="@(currentPage <= 1 ? true : (bool?)null)" @onclick="() => currentPage--">← Prev</button>
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
@if (selectedIds.Count > 0)
|
||||
@@ -220,7 +242,7 @@ else
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card-footer d-flex justify-content-between align-items-center">
|
||||
<div class="card-footer d-flex flex-wrap justify-content-between align-items-center gap-2">
|
||||
<small class="text-muted">@filteredTransactions.Count transactions</small>
|
||||
@if (selectedIds.Count > 0)
|
||||
{
|
||||
@@ -364,6 +386,11 @@ else
|
||||
private int? filterAccountId;
|
||||
private string searchQuery = string.Empty;
|
||||
|
||||
// Time window state
|
||||
private DateTime windowStart = DateTime.Today;
|
||||
private DateTime windowEnd = DateTime.Today.AddDays(30);
|
||||
private string? windowPreset = "30d";
|
||||
|
||||
// Pagination state
|
||||
private int currentPage = 1;
|
||||
private int pageSize = 25;
|
||||
@@ -382,7 +409,7 @@ else
|
||||
{
|
||||
get
|
||||
{
|
||||
var list = (transactions ?? []).Where(t => t.Date.Date >= DateTime.Today);
|
||||
var list = (transactions ?? []).Where(t => t.Date.Date >= windowStart.Date && t.Date.Date <= windowEnd.Date);
|
||||
if (filterAccountId is not null)
|
||||
list = list.Where(t => t.AccountId == filterAccountId);
|
||||
|
||||
@@ -421,6 +448,20 @@ else
|
||||
|
||||
private void OnSearchChanged() => currentPage = 1;
|
||||
|
||||
private void 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;
|
||||
}
|
||||
|
||||
private void OnWindowDateChanged()
|
||||
{
|
||||
windowPreset = null;
|
||||
currentPage = 1;
|
||||
}
|
||||
|
||||
private void OnPageSizeChanged(ChangeEventArgs e)
|
||||
{
|
||||
if (int.TryParse(e.Value?.ToString(), out var size))
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
@media (max-width: 767.98px) {
|
||||
::deep .table > :not(caption) > * > * {
|
||||
padding: 0.3rem 0.4rem;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user