Files
MoneyMap/MoneyMap/Pages/Budgets.cshtml
AJ Isaacs a3ca358e9a UI: Use dark text on yellow budget progress bars
Improves readability of percentage text when progress bar
is in warning state (80-99% of budget used).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-10 22:59:00 -05:00

317 lines
16 KiB
Plaintext

@page
@model MoneyMap.Pages.BudgetsModel
@using MoneyMap.Models
@{
ViewData["Title"] = "Budgets";
}
<div class="d-flex justify-content-between align-items-center mb-3">
<h2>Budgets</h2>
<a asp-page="/Index" class="btn btn-outline-secondary">Back to Dashboard</a>
</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>
}
<!-- Add New Budget Button -->
<div class="mb-3">
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
+ Add New Budget
</button>
</div>
@if (Model.BudgetStatuses.Any())
{
<!-- Active Budgets -->
var activeBudgets = Model.BudgetStatuses.Where(s => s.Budget.IsActive).ToList();
if (activeBudgets.Any())
{
<div class="card shadow-sm mb-4">
<div class="card-header">
<strong>Active Budgets</strong>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>Category</th>
<th>Period</th>
<th class="text-end">Budget</th>
<th class="text-end">Spent</th>
<th class="text-end">Remaining</th>
<th style="width: 200px;">Progress</th>
<th style="width: 180px;">Actions</th>
</tr>
</thead>
<tbody>
@foreach (var status in activeBudgets)
{
var transactionsUrl = status.Budget.IsTotalBudget
? Url.Page("/Transactions", new { startDate = status.PeriodStart.ToString("yyyy-MM-dd"), endDate = status.PeriodEnd.ToString("yyyy-MM-dd") })
: Url.Page("/Transactions", new { category = status.Budget.Category, startDate = status.PeriodStart.ToString("yyyy-MM-dd"), endDate = status.PeriodEnd.ToString("yyyy-MM-dd") });
<tr style="cursor: pointer;" onclick="window.location.href='@transactionsUrl'">
<td>
<a href="@transactionsUrl" class="text-decoration-none">
<strong>@status.Budget.DisplayName</strong>
</a>
@if (status.Budget.IsTotalBudget)
{
<span class="badge bg-info ms-1">Total</span>
}
@if (status.IsOverBudget)
{
<span class="badge bg-danger ms-1">Over Budget</span>
}
<br />
<small class="text-muted">@status.PeriodDisplay</small>
</td>
<td>@status.Budget.Period</td>
<td class="text-end">@status.Budget.Amount.ToString("C")</td>
<td class="text-end">@status.Spent.ToString("C")</td>
<td class="text-end @(status.IsOverBudget ? "text-danger fw-bold" : "")">
@status.Remaining.ToString("C")
</td>
<td>
<div class="progress" style="height: 20px;">
@{
var percent = Math.Min(status.PercentUsed, 100);
var progressClass = status.StatusClass;
var textClass = progressClass == "warning" ? "text-dark" : "";
}
<div class="progress-bar bg-@progressClass @textClass" role="progressbar"
style="width: @percent%"
aria-valuenow="@status.PercentUsed" aria-valuemin="0" aria-valuemax="100">
@status.PercentUsed.ToString("F0")%
</div>
</div>
@if (status.IsOverBudget)
{
<small class="text-danger">Over by @(Math.Abs(status.Remaining).ToString("C"))</small>
}
</td>
<td onclick="event.stopPropagation()">
<button type="button" class="btn btn-sm btn-outline-primary"
onclick="openEditModal(@status.Budget.Id, '@(status.Budget.Category?.Replace("'", "\\'") ?? "")', @status.Budget.Amount.ToString(System.Globalization.CultureInfo.InvariantCulture), @((int)status.Budget.Period), '@status.Budget.StartDate.ToString("yyyy-MM-dd")', @status.Budget.IsActive.ToString().ToLower(), '@(status.Budget.Notes?.Replace("'", "\\'").Replace("\n", "\\n") ?? "")')">
Edit
</button>
<form method="post" asp-page-handler="ToggleActive" asp-route-id="@status.Budget.Id" class="d-inline">
<button type="submit" class="btn btn-sm btn-outline-secondary" title="Deactivate">
Pause
</button>
</form>
<form method="post" asp-page-handler="DeleteBudget" asp-route-id="@status.Budget.Id"
onsubmit="return confirm('Delete this budget?')" class="d-inline">
<button type="submit" class="btn btn-sm btn-outline-danger">Delete</button>
</form>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
}
<!-- Inactive Budgets -->
var inactiveBudgets = Model.BudgetStatuses.Where(s => !s.Budget.IsActive).ToList();
if (inactiveBudgets.Any())
{
<div class="card shadow-sm">
<div class="card-header">
<strong>Inactive Budgets</strong>
<small class="text-muted ms-2">Paused budgets are not tracked</small>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>Category</th>
<th>Period</th>
<th class="text-end">Budget Amount</th>
<th>Notes</th>
<th style="width: 180px;">Actions</th>
</tr>
</thead>
<tbody>
@foreach (var status in inactiveBudgets)
{
<tr class="text-muted">
<td>
@status.Budget.DisplayName
@if (status.Budget.IsTotalBudget)
{
<span class="badge bg-secondary ms-1">Total</span>
}
</td>
<td>@status.Budget.Period</td>
<td class="text-end">@status.Budget.Amount.ToString("C")</td>
<td>@(status.Budget.Notes ?? "-")</td>
<td>
<form method="post" asp-page-handler="ToggleActive" asp-route-id="@status.Budget.Id" class="d-inline">
<button type="submit" class="btn btn-sm btn-outline-success" title="Activate">
Resume
</button>
</form>
<form method="post" asp-page-handler="DeleteBudget" asp-route-id="@status.Budget.Id"
onsubmit="return confirm('Delete this budget?')" class="d-inline">
<button type="submit" class="btn btn-sm btn-outline-danger">Delete</button>
</form>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
}
}
else
{
<div class="alert alert-info">
<h5>No budgets found</h5>
<p>Click "Add New Budget" to create your first budget. You can create budgets for specific categories or a total spending budget.</p>
</div>
}
<!-- Add Modal -->
<div class="modal fade" id="addModal" tabindex="-1" aria-labelledby="addModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form method="post" asp-page-handler="AddBudget">
<div class="modal-header">
<h5 class="modal-title" id="addModalLabel">Add New Budget</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="addCategory" class="form-label">Category</label>
<select name="model.Category" id="addCategory" class="form-select" asp-items="Model.CategoryOptions">
</select>
<div class="form-text">Select a category or leave as "Total Spending" to track all expenses</div>
</div>
<div class="mb-3">
<label for="addAmount" class="form-label">Budget Amount</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input name="model.Amount" id="addAmount" type="number" step="0.01" min="0.01" class="form-control" required />
</div>
</div>
<div class="mb-3">
<label for="addPeriod" class="form-label">Period</label>
<select name="model.Period" id="addPeriod" class="form-select" asp-items="Model.PeriodOptions">
</select>
</div>
<div class="mb-3">
<label for="addStartDate" class="form-label">Start Date</label>
<input name="model.StartDate" id="addStartDate" type="date" class="form-control" value="@DateTime.Today.ToString("yyyy-MM-dd")" />
<div class="form-text">Used to calculate period boundaries (e.g., if monthly budget starts on the 15th, periods run 15th to 14th)</div>
</div>
<div class="mb-3">
<label for="addNotes" class="form-label">Notes (optional)</label>
<textarea name="model.Notes" id="addNotes" class="form-control" rows="2"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Add Budget</button>
</div>
</form>
</div>
</div>
</div>
<!-- Edit Modal -->
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form method="post" asp-page-handler="UpdateBudget">
<div class="modal-header">
<h5 class="modal-title" id="editModalLabel">Edit Budget</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<input type="hidden" name="model.Id" id="editId" />
<div class="mb-3">
<label for="editCategory" class="form-label">Category</label>
<select name="model.Category" id="editCategory" class="form-select" asp-items="Model.CategoryOptions">
</select>
</div>
<div class="mb-3">
<label for="editAmount" class="form-label">Budget Amount</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input name="model.Amount" id="editAmount" type="number" step="0.01" min="0.01" class="form-control" required />
</div>
</div>
<div class="mb-3">
<label for="editPeriod" class="form-label">Period</label>
<select name="model.Period" id="editPeriod" class="form-select" asp-items="Model.PeriodOptions">
</select>
</div>
<div class="mb-3">
<label for="editStartDate" class="form-label">Start Date</label>
<input name="model.StartDate" id="editStartDate" type="date" class="form-control" required />
</div>
<div class="mb-3">
<label for="editNotes" class="form-label">Notes (optional)</label>
<textarea name="model.Notes" id="editNotes" class="form-control" rows="2"></textarea>
</div>
<div class="form-check mb-3">
<input type="checkbox" name="model.IsActive" id="editIsActive" class="form-check-input" value="true" />
<label for="editIsActive" class="form-check-label">Active</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</form>
</div>
</div>
</div>
@section Scripts {
<script>
function openEditModal(id, category, amount, period, startDate, isActive, notes) {
document.getElementById('editId').value = id;
document.getElementById('editCategory').value = category;
document.getElementById('editAmount').value = amount;
document.getElementById('editPeriod').value = period;
document.getElementById('editStartDate').value = startDate;
document.getElementById('editIsActive').checked = isActive;
document.getElementById('editNotes').value = notes;
var modal = new bootstrap.Modal(document.getElementById('editModal'));
modal.show();
}
</script>
}