Refactor. Add data layer

This commit is contained in:
rossmill76 committed 2026-03-06 11:05:01 +00:00
1 parent 3b7e4e6ec5
commit b6b794f0b4
82 files changed
+2930 -2811

No files matched your search

+18 -144
View File
@@ -1,6 +1,7 @@
@page "/accounts"
@using BudgetApp.Data.Entities
@attribute [Authorize]
@inject AccountService AccountService
@inject IAccountRepository AccountService
@inject AuthenticationStateProvider AuthStateProvider
@inject AppState AppState
@rendermode InteractiveServer
@@ -15,20 +16,20 @@
<h5 class="mb-0">Add Account</h5>
</div>
<div class="card-body">
<EditForm Model="formModel" OnValidSubmit="AddAccount" FormName="addAccount">
<EditForm Model="_formModel" OnValidSubmit="AddAccount" FormName="addAccount">
<ToastValidator />
<div class="row g-3 align-items-end">
<div class="col-md-3">
<label class="form-label">Account Name</label>
<InputText @bind-Value="formModel.Name" class="form-control" placeholder="e.g. Current Account" title="A friendly name for this account" />
<InputText @bind-Value="_formModel.Name" class="form-control" placeholder="e.g. Current Account" title="A friendly name for this account" />
</div>
<div class="col-md-2">
<label class="form-label">Current Balance</label>
<InputNumber @bind-Value="formModel.CurrentBalance" class="form-control numeric-input" title="The opening balance for this account" />
<InputNumber @bind-Value="_formModel.CurrentBalance" class="form-control numeric-input" title="The opening balance for this account" />
</div>
<div class="col-md-2">
<label class="form-label">Currency</label>
<InputSelect @bind-Value="formModel.CurrencyCode" class="form-select" title="Currency used for this account">
<InputSelect @bind-Value="_formModel.CurrencyCode" class="form-select" title="Currency used for this account">
@foreach (var kvp in Account.CurrencySymbols)
{
<option value="@kvp.Key">@kvp.Key (@kvp.Value)</option>
@@ -37,7 +38,7 @@
</div>
<div class="col-md-2">
<label class="form-label">Colour</label>
<InputText @bind-Value="formModel.Color" type="color" class="form-control form-control-color" title="Colour used to identify this account in charts and lists" />
<InputText @bind-Value="_formModel.Color" type="color" class="form-control form-control-color" title="Colour used to identify this account in charts and lists" />
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100">Add</button>
@@ -47,31 +48,31 @@
</div>
</div>
@if (accounts is null)
@if (_accounts is null)
{
<p><em>Loading...</em></p>
}
else if (accounts.Count == 0)
else if (_accounts.Count == 0)
{
<div class="alert alert-info">No accounts yet. Add one above to get started.</div>
}
else
{
<div class="row">
@foreach (var account in accounts)
@foreach (var account in _accounts)
{
<div class="col-md-4 col-sm-6 mb-3">
<div class="card h-100" style="border-left: 4px solid @account.Color;">
<div class="card-body">
@if (editingAccountId == account.Id)
@if (_editingAccountId == account.Id)
{
<div class="mb-2">
<label class="form-label form-label-sm mb-1">Name</label>
<input type="text" class="form-control form-control-sm" @bind="editName" />
<input type="text" class="form-control form-control-sm" @bind="_editName" />
</div>
<div class="mb-2">
<label class="form-label form-label-sm mb-1">Currency</label>
<select class="form-select form-select-sm" @bind="editCurrencyCode">
<select class="form-select form-select-sm" @bind="_editCurrencyCode">
@foreach (var kvp in Account.CurrencySymbols)
{
<option value="@kvp.Key">@kvp.Key (@kvp.Value)</option>
@@ -80,7 +81,7 @@ else
</div>
<div class="mb-2">
<label class="form-label form-label-sm mb-1">Colour</label>
<input type="color" class="form-control form-control-color form-control-sm" @bind="editColor" />
<input type="color" class="form-control form-control-color form-control-sm" @bind="_editColor" />
</div>
<div class="d-flex gap-1">
<button class="btn btn-success btn-sm" @onclick="() => SaveAccountDetails(account.Id)">Save</button>
@@ -100,13 +101,13 @@ else
</div>
</div>
@if (editingBalanceAccountId == account.Id)
@if (_editingBalanceAccountId == account.Id)
{
<div class="mt-2">
<div class="input-group input-group-sm">
<span class="input-group-text">@account.GetSymbol()</span>
<input type="number" class="form-control numeric-input" step="0.01"
@bind="editingBalanceValue" />
@bind="_editingBalanceValue" />
</div>
<div class="d-flex gap-1 mt-2">
<button class="btn btn-success btn-sm flex-fill" @onclick="() => SaveBalance(account.Id)">Save</button>
@@ -131,7 +132,7 @@ else
</div>
}
@if (showDeleteModal && pendingDeleteAccount is not null)
@if (_showDeleteModal && _pendingDeleteAccount is not null)
{
<div class="modal d-block" tabindex="-1" style="background: rgba(0,0,0,.5);">
<div class="modal-dialog">
@@ -141,7 +142,7 @@ else
<button type="button" class="btn-close" @onclick="CancelDeleteAccount"></button>
</div>
<div class="modal-body">
<p>Are you sure you want to delete <strong>@pendingDeleteAccount.Name</strong>?</p>
<p>Are you sure you want to delete <strong>@_pendingDeleteAccount.Name</strong>?</p>
<p class="text-danger mb-0">This will permanently remove the account and all its transactions.</p>
</div>
<div class="modal-footer">
@@ -154,138 +155,11 @@ else
}
@code {
private List<Account>? accounts;
private AccountForm formModel = new();
private string? userId;
// Balance editing state
private int? editingBalanceAccountId;
private decimal editingBalanceValue;
// Account details editing state
private int? editingAccountId;
private string editName = string.Empty;
private string editColor = "#1b6ec2";
private string editCurrencyCode = "GBP";
// Delete confirmation state
private bool showDeleteModal;
private Account? pendingDeleteAccount;
protected override async Task OnInitializedAsync()
{
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
userId = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
await LoadAccounts();
}
private async Task LoadAccounts()
{
if (userId is not null)
{
accounts = await AccountService.GetAccountsAsync(userId);
}
}
private async Task AddAccount()
{
if (userId is null) return;
var account = new Account
{
Name = formModel.Name,
CurrentBalance = formModel.CurrentBalance,
Color = formModel.Color,
CurrencyCode = formModel.CurrencyCode,
UserId = userId
};
await AccountService.CreateAccountAsync(account);
formModel = new AccountForm();
await LoadAccounts();
AppState.NotifyAccountsChanged();
}
private void ConfirmDeleteAccount(Account account)
{
pendingDeleteAccount = account;
showDeleteModal = true;
}
private async Task DeleteAccountConfirmed()
{
if (userId is null || pendingDeleteAccount is null) return;
showDeleteModal = false;
await AccountService.DeleteAccountAsync(pendingDeleteAccount.Id, userId);
pendingDeleteAccount = null;
await LoadAccounts();
AppState.NotifyAccountsChanged();
}
private void CancelDeleteAccount()
{
showDeleteModal = false;
pendingDeleteAccount = null;
}
private void StartBalanceEdit(Account account)
{
editingBalanceAccountId = account.Id;
editingBalanceValue = account.CurrentBalance;
}
private void CancelBalanceEdit()
{
editingBalanceAccountId = null;
}
private async Task SaveBalance(int accountId)
{
if (userId is null) return;
await AccountService.UpdateBalanceAsync(accountId, userId, editingBalanceValue);
editingBalanceAccountId = null;
await LoadAccounts();
AppState.NotifyAccountsChanged();
}
private void StartAccountEdit(Account account)
{
editingAccountId = account.Id;
editName = account.Name;
editColor = account.Color;
editCurrencyCode = account.CurrencyCode;
}
private void CancelAccountEdit()
{
editingAccountId = null;
}
private async Task SaveAccountDetails(int accountId)
{
if (userId is null) return;
await AccountService.UpdateAccountDetailsAsync(accountId, userId, editName, editColor, editCurrencyCode);
editingAccountId = null;
await LoadAccounts();
AppState.NotifyAccountsChanged();
}
private sealed class AccountForm
{
[System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Account name is required")]
[System.ComponentModel.DataAnnotations.MaxLength(100)]
public string Name { get; set; } = string.Empty;
public decimal CurrentBalance { get; set; }
[System.ComponentModel.DataAnnotations.Required]
[System.ComponentModel.DataAnnotations.MaxLength(7)]
public string Color { get; set; } = "#1b6ec2";
[System.ComponentModel.DataAnnotations.Required]
[System.ComponentModel.DataAnnotations.MaxLength(3)]
public string CurrencyCode { get; set; } = "GBP";
}
}