Added pages for cards
This commit is contained in:
93
MoneyMap/Pages/Cards.cshtml
Normal file
93
MoneyMap/Pages/Cards.cshtml
Normal file
@@ -0,0 +1,93 @@
|
||||
@page
|
||||
@model MoneyMap.Pages.CardsModel
|
||||
@{
|
||||
ViewData["Title"] = "Manage Cards";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2>Manage Cards</h2>
|
||||
<div>
|
||||
<a asp-page="/EditCard" class="btn btn-primary">Add New Card</a>
|
||||
<a asp-page="/Index" class="btn btn-outline-secondary">Back to Dashboard</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>
|
||||
}
|
||||
|
||||
@if (Model.Cards.Any())
|
||||
{
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header">
|
||||
<strong>Your Cards (@Model.Cards.Count)</strong>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Owner</th>
|
||||
<th>Issuer</th>
|
||||
<th>Last 4</th>
|
||||
<th class="text-end">Transactions</th>
|
||||
<th style="width: 150px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model.Cards)
|
||||
{
|
||||
<tr>
|
||||
<td>@item.Card.Owner</td>
|
||||
<td>@item.Card.Issuer</td>
|
||||
<td>•••• @item.Card.Last4</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="Delete" asp-route-id="@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>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<h5>No cards found</h5>
|
||||
<p>Add your first card to start tracking transactions.</p>
|
||||
<a asp-page="/EditCard" class="btn btn-primary">Add New Card</a>
|
||||
</div>
|
||||
}
|
||||
81
MoneyMap/Pages/Cards.cshtml.cs
Normal file
81
MoneyMap/Pages/Cards.cshtml.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
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 CardsModel : PageModel
|
||||
{
|
||||
private readonly MoneyMapContext _db;
|
||||
|
||||
public CardsModel(MoneyMapContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public List<CardWithStats> Cards { get; set; } = new();
|
||||
|
||||
[TempData]
|
||||
public string? SuccessMessage { get; set; }
|
||||
|
||||
[TempData]
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
public async Task OnGetAsync()
|
||||
{
|
||||
var cards = await _db.Cards
|
||||
.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;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostDeleteAsync(int id)
|
||||
{
|
||||
var card = await _db.Cards.FindAsync(id);
|
||||
if (card == null)
|
||||
{
|
||||
ErrorMessage = "Card not found.";
|
||||
return RedirectToPage();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
_db.Cards.Remove(card);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
SuccessMessage = "Card deleted successfully.";
|
||||
return RedirectToPage();
|
||||
}
|
||||
|
||||
public class CardWithStats
|
||||
{
|
||||
public Card Card { get; set; } = null!;
|
||||
public int TransactionCount { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
81
MoneyMap/Pages/EditCard.cshtml
Normal file
81
MoneyMap/Pages/EditCard.cshtml
Normal file
@@ -0,0 +1,81 @@
|
||||
@page
|
||||
@model MoneyMap.Pages.EditCardModel
|
||||
@{
|
||||
ViewData["Title"] = Model.IsNewCard ? "Add Card" : "Edit Card";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2>@(Model.IsNewCard ? "Add Card" : "Edit Card")</h2>
|
||||
<a asp-page="/Cards" class="btn btn-outline-secondary">Back to Cards</a>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header">
|
||||
<strong>Card Details</strong>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
<input type="hidden" asp-for="Card.Id" />
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Card.Owner" class="form-label">Owner</label>
|
||||
<input asp-for="Card.Owner" class="form-control" placeholder="e.g., John Smith" />
|
||||
<span asp-validation-for="Card.Owner" class="text-danger"></span>
|
||||
<div class="form-text">Who owns this card?</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Card.Issuer" class="form-label">Issuer</label>
|
||||
<input asp-for="Card.Issuer" class="form-control" placeholder="e.g., Chase, Bank of America" />
|
||||
<span asp-validation-for="Card.Issuer" class="text-danger"></span>
|
||||
<div class="form-text">Which bank or institution issued the card?</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Card.Last4" class="form-label">Last 4 Digits</label>
|
||||
<input asp-for="Card.Last4" class="form-control" placeholder="1234" maxlength="4" />
|
||||
<span asp-validation-for="Card.Last4" class="text-danger"></span>
|
||||
<div class="form-text">The last 4 digits of the card number</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
@(Model.IsNewCard ? "Add Card" : "Save Changes")
|
||||
</button>
|
||||
<a asp-page="/Cards" class="btn btn-secondary">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header">
|
||||
<strong>Tips</strong>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="mb-0">
|
||||
<li><strong>Owner:</strong> Use the cardholder's name for easy identification</li>
|
||||
<li><strong>Issuer:</strong> The bank or credit card company (e.g., Chase, Discover, Capital One)</li>
|
||||
<li><strong>Last 4:</strong> These digits help match transactions to the correct card</li>
|
||||
<li>Cards with transactions cannot be deleted, only edited</li>
|
||||
<li>Auto-imported cards will have "Unknown" as the owner - update them here</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!Model.IsNewCard)
|
||||
{
|
||||
<div class="alert alert-info mt-3">
|
||||
<strong>Note:</strong> Updating card details will not affect existing transactions, but will change how the card is displayed going forward.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
}
|
||||
111
MoneyMap/Pages/EditCard.cshtml.cs
Normal file
111
MoneyMap/Pages/EditCard.cshtml.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
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 EditCardModel : PageModel
|
||||
{
|
||||
private readonly MoneyMapContext _db;
|
||||
|
||||
public EditCardModel(MoneyMapContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[BindProperty]
|
||||
public CardEditModel Card { get; set; } = new();
|
||||
|
||||
public bool IsNewCard { get; set; }
|
||||
|
||||
[TempData]
|
||||
public string? SuccessMessage { get; set; }
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(int? id)
|
||||
{
|
||||
if (id.HasValue)
|
||||
{
|
||||
var card = await _db.Cards.FindAsync(id.Value);
|
||||
if (card == null)
|
||||
return NotFound();
|
||||
|
||||
Card = new CardEditModel
|
||||
{
|
||||
Id = card.Id,
|
||||
Owner = card.Owner,
|
||||
Issuer = card.Issuer,
|
||||
Last4 = card.Last4
|
||||
};
|
||||
|
||||
IsNewCard = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsNewCard = true;
|
||||
}
|
||||
|
||||
return Page();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync()
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
IsNewCard = Card.Id == 0;
|
||||
return Page();
|
||||
}
|
||||
|
||||
if (Card.Id == 0)
|
||||
{
|
||||
// Create new card
|
||||
var card = new Card
|
||||
{
|
||||
Owner = Card.Owner.Trim(),
|
||||
Issuer = Card.Issuer.Trim(),
|
||||
Last4 = Card.Last4.Trim()
|
||||
};
|
||||
|
||||
_db.Cards.Add(card);
|
||||
SuccessMessage = "Card added successfully!";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update existing card
|
||||
var card = await _db.Cards.FindAsync(Card.Id);
|
||||
if (card == null)
|
||||
return NotFound();
|
||||
|
||||
card.Owner = Card.Owner.Trim();
|
||||
card.Issuer = Card.Issuer.Trim();
|
||||
card.Last4 = Card.Last4.Trim();
|
||||
|
||||
SuccessMessage = "Card updated successfully!";
|
||||
}
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
return RedirectToPage("/Cards");
|
||||
}
|
||||
|
||||
public class CardEditModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Owner is required")]
|
||||
[StringLength(100)]
|
||||
public string Owner { get; set; } = "";
|
||||
|
||||
[Required(ErrorMessage = "Issuer is required")]
|
||||
[StringLength(100)]
|
||||
public string Issuer { get; set; } = "";
|
||||
|
||||
[Required(ErrorMessage = "Last 4 digits are required")]
|
||||
[StringLength(4, MinimumLength = 4, ErrorMessage = "Last 4 must be exactly 4 digits")]
|
||||
[RegularExpression(@"^\d{4}$", ErrorMessage = "Last 4 must be 4 digits")]
|
||||
public string Last4 { get; set; } = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,9 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-page="/Transactions">Transactions</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-page="/Cards">Cards</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-page="/Upload">Upload CSV</a>
|
||||
</li>
|
||||
|
||||
Reference in New Issue
Block a user