Add account details page with card management
Create a new AccountDetails page that shows account information and all linked cards. This replaces the standalone Cards page and nests card management under accounts, as cards are always linked to accounts. Features: - View account details (institution, type, owner, etc.) - List all cards linked to the account - Add new cards directly from the account page - Edit and delete cards within the account context - Click-through navigation from Accounts list 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
136
MoneyMap/Pages/AccountDetails.cshtml
Normal file
136
MoneyMap/Pages/AccountDetails.cshtml
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
@page "{id:int}"
|
||||||
|
@model MoneyMap.Pages.AccountDetailsModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = $"Account - {Model.Account.DisplayLabel}";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h2>@Model.Account.DisplayLabel</h2>
|
||||||
|
<div>
|
||||||
|
<a asp-page="/EditAccount" asp-route-id="@Model.Account.Id" class="btn btn-outline-primary">Edit Account</a>
|
||||||
|
<a asp-page="/Accounts" class="btn btn-outline-secondary">Back to Accounts</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(Model.SuccessMessage))
|
||||||
|
{
|
||||||
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||||
|
@Model.SuccessMessage
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(Model.ErrorMessage))
|
||||||
|
{
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
@Model.ErrorMessage
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Account Details Card -->
|
||||||
|
<div class="card shadow-sm mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<strong>Account Details</strong>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<dl class="row">
|
||||||
|
<dt class="col-sm-4">Institution:</dt>
|
||||||
|
<dd class="col-sm-8">@Model.Account.Institution</dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-4">Account Type:</dt>
|
||||||
|
<dd class="col-sm-8"><span class="badge bg-info">@Model.Account.AccountType</span></dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-4">Last 4:</dt>
|
||||||
|
<dd class="col-sm-8"><code>@Model.Account.Last4</code></dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<dl class="row">
|
||||||
|
<dt class="col-sm-4">Owner:</dt>
|
||||||
|
<dd class="col-sm-8">@Model.Account.Owner</dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-4">Nickname:</dt>
|
||||||
|
<dd class="col-sm-8">@(string.IsNullOrEmpty(Model.Account.Nickname) ? "-" : Model.Account.Nickname)</dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-4">Transactions:</dt>
|
||||||
|
<dd class="col-sm-8">@Model.TransactionCount</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Linked Cards Section -->
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<strong>Linked Cards (@Model.Cards.Count)</strong>
|
||||||
|
<a asp-page="/EditCard" asp-route-accountId="@Model.Account.Id" class="btn btn-sm btn-primary">+ Add Card</a>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
@if (Model.Cards.Any())
|
||||||
|
{
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Card</th>
|
||||||
|
<th>Owner</th>
|
||||||
|
<th class="text-end">Transactions</th>
|
||||||
|
<th style="width: 150px;">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var item in Model.Cards)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div>
|
||||||
|
<strong>@item.Card.Issuer •••• @item.Card.Last4</strong>
|
||||||
|
@if (!string.IsNullOrEmpty(item.Card.Nickname))
|
||||||
|
{
|
||||||
|
<br />
|
||||||
|
<small class="text-muted">@item.Card.Nickname</small>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>@item.Card.Owner</td>
|
||||||
|
<td class="text-end">@item.TransactionCount</td>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex gap-1">
|
||||||
|
<a asp-page="/EditCard" asp-route-id="@item.Card.Id" class="btn btn-sm btn-outline-primary">
|
||||||
|
Edit
|
||||||
|
</a>
|
||||||
|
@if (item.TransactionCount == 0)
|
||||||
|
{
|
||||||
|
<form method="post" asp-page-handler="DeleteCard" asp-route-cardId="@item.Card.Id"
|
||||||
|
onsubmit="return confirm('Delete this card?')" class="d-inline">
|
||||||
|
<button type="submit" class="btn btn-sm btn-outline-danger">Delete</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary" disabled
|
||||||
|
title="Cannot delete - card has transactions">
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="text-muted mb-2">No cards linked to this account yet.</p>
|
||||||
|
<a asp-page="/EditCard" asp-route-accountId="@Model.Account.Id" class="btn btn-sm btn-primary">Add First Card</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
96
MoneyMap/Pages/AccountDetails.cshtml.cs
Normal file
96
MoneyMap/Pages/AccountDetails.cshtml.cs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using MoneyMap.Data;
|
||||||
|
using MoneyMap.Models;
|
||||||
|
|
||||||
|
namespace MoneyMap.Pages
|
||||||
|
{
|
||||||
|
public class AccountDetailsModel : PageModel
|
||||||
|
{
|
||||||
|
private readonly MoneyMapContext _db;
|
||||||
|
|
||||||
|
public AccountDetailsModel(MoneyMapContext db)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Account Account { get; set; } = null!;
|
||||||
|
public List<CardWithStats> Cards { get; set; } = new();
|
||||||
|
public int TransactionCount { get; set; }
|
||||||
|
|
||||||
|
[TempData]
|
||||||
|
public string? SuccessMessage { get; set; }
|
||||||
|
|
||||||
|
[TempData]
|
||||||
|
public string? ErrorMessage { get; set; }
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnGetAsync(int id)
|
||||||
|
{
|
||||||
|
var account = await _db.Accounts.FindAsync(id);
|
||||||
|
if (account == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
Account = account;
|
||||||
|
|
||||||
|
// Get cards linked to this account
|
||||||
|
var cards = await _db.Cards
|
||||||
|
.Where(c => c.AccountId == id)
|
||||||
|
.OrderBy(c => c.Owner)
|
||||||
|
.ThenBy(c => c.Last4)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var cardStats = new List<CardWithStats>();
|
||||||
|
foreach (var card in cards)
|
||||||
|
{
|
||||||
|
var transactionCount = await _db.Transactions.CountAsync(t => t.CardId == card.Id);
|
||||||
|
cardStats.Add(new CardWithStats
|
||||||
|
{
|
||||||
|
Card = card,
|
||||||
|
TransactionCount = transactionCount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Cards = cardStats;
|
||||||
|
|
||||||
|
// Get transaction count for this account
|
||||||
|
TransactionCount = await _db.Transactions.CountAsync(t => t.AccountId == id);
|
||||||
|
|
||||||
|
return Page();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnPostDeleteCardAsync(int cardId)
|
||||||
|
{
|
||||||
|
var card = await _db.Cards.FindAsync(cardId);
|
||||||
|
if (card == null)
|
||||||
|
{
|
||||||
|
ErrorMessage = "Card not found.";
|
||||||
|
return RedirectToPage(new { id = card?.AccountId });
|
||||||
|
}
|
||||||
|
|
||||||
|
var accountId = card.AccountId;
|
||||||
|
|
||||||
|
var transactionCount = await _db.Transactions.CountAsync(t => t.CardId == card.Id);
|
||||||
|
if (transactionCount > 0)
|
||||||
|
{
|
||||||
|
ErrorMessage = $"Cannot delete card. It has {transactionCount} transaction(s) associated with it.";
|
||||||
|
return RedirectToPage(new { id = accountId });
|
||||||
|
}
|
||||||
|
|
||||||
|
_db.Cards.Remove(card);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
|
||||||
|
SuccessMessage = "Card deleted successfully.";
|
||||||
|
return RedirectToPage(new { id = accountId });
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CardWithStats
|
||||||
|
{
|
||||||
|
public Card Card { get; set; } = null!;
|
||||||
|
public int TransactionCount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user