Files
PowderCoatingLogix/src/PowderCoating.Web/Views/Bills/Index.cshtml
T
spouliot 64a9c1531b Fix — HTML entity rendering across 60 views
Razor's @() expression auto-encodes &, turning — into — which
rendered as literal text in the browser. Wrapped all such expressions in
@Html.Raw() so the em-dash entity is passed through unescaped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 09:27:45 -04:00

240 lines
11 KiB
Plaintext

@model List<PowderCoating.Application.DTOs.Accounting.BillExpenseListDto>
@{
ViewData["Title"] = "Bills / Expenses";
ViewData["PageIcon"] = "bi-receipt-cutoff";
}
<div class="d-flex justify-content-between align-items-center mb-4">
<a asp-action="RecurringDetection" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-robot me-1"></i>Detect Recurring Bills
</a>
<div class="btn-group">
<a asp-controller="Bills" asp-action="Create" class="btn btn-primary">
<i class="bi bi-plus-lg me-1"></i>New Bill
</a>
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
<span class="visually-hidden">Toggle dropdown</span>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<a asp-controller="Bills" asp-action="Create" class="dropdown-item">
<i class="bi bi-file-text me-2"></i>New Bill <span class="text-muted small">(pay later)</span>
</a>
</li>
<li>
<a asp-controller="Expenses" asp-action="Create" class="dropdown-item">
<i class="bi bi-receipt me-2"></i>New Expense <span class="text-muted small">(already paid)</span>
</a>
</li>
</ul>
</div>
</div>
@if (TempData["Success"] != null)
{
<div class="alert alert-success alert-dismissible fade show">
<i class="bi bi-check-circle me-2"></i>@TempData["Success"]
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
}
@if (TempData["Error"] != null)
{
<div class="alert alert-danger alert-dismissible fade show">
<i class="bi bi-exclamation-triangle me-2"></i>@TempData["Error"]
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
}
@if ((decimal)ViewBag.TotalOwed > 0)
{
<div class="alert alert-warning alert-permanent d-flex align-items-center gap-2 mb-4">
<i class="bi bi-exclamation-circle fs-5"></i>
<span>Outstanding bills: <strong>@(((decimal)ViewBag.TotalOwed).ToString("C"))</strong></span>
<a asp-action="Index" asp-route-status="Unpaid" class="btn btn-sm btn-warning ms-auto">
<i class="bi bi-funnel me-1"></i>Show unpaid
</a>
</div>
}
<div class="card shadow-sm mb-3">
<div class="card-body py-2">
<form method="get" class="row g-2 align-items-end">
<div class="col-md-4">
<input type="search" name="search" value="@ViewBag.Search" class="form-control"
placeholder="Search by #, vendor, memo, amount&hellip;" />
</div>
<div class="col-md-2">
<select name="type" class="form-select">
<option value="">Bills &amp; Expenses</option>
<option value="Bill" selected="@(ViewBag.TypeFilter == "Bill")">Bills only</option>
<option value="Expense" selected="@(ViewBag.TypeFilter == "Expense")">Expenses only</option>
</select>
</div>
<div class="col-md-2">
<select name="status" class="form-select">
<option value="">All statuses</option>
<option value="Unpaid" selected="@(ViewBag.StatusFilter == "Unpaid")">Unpaid</option>
<option value="Overdue" selected="@(ViewBag.StatusFilter == "Overdue")">Overdue</option>
</select>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-outline-primary">
<i class="bi bi-search me-1"></i>Filter
</button>
<a asp-action="Index" class="btn btn-outline-secondary ms-1">Clear</a>
</div>
</form>
</div>
</div>
@if (Model.Any())
{
<div class="card shadow-sm">
<div class="card-body p-0">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th style="width:90px">Type</th>
<th>Number</th>
<th>Vendor</th>
<th>Memo / Account</th>
<th>Date</th>
<th>Due Date</th>
<th>Status</th>
<th class="text-end">Amount</th>
<th class="text-end">Balance Due</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var entry in Model)
{
<tr class="@(entry.IsOverdue ? "table-warning" : "")">
<td>
@if (entry.EntryType == "Bill")
{
<span class="badge bg-primary-subtle text-primary border border-primary-subtle">
<i class="bi bi-file-text me-1"></i>Bill
</span>
}
else
{
<span class="badge bg-secondary-subtle text-secondary border border-secondary-subtle">
<i class="bi bi-receipt me-1"></i>Expense
</span>
}
</td>
<td>
@if (entry.EntryType == "Bill")
{
<a asp-controller="Bills" asp-action="Details" asp-route-id="@entry.Id"
class="fw-medium text-decoration-none">@entry.Number</a>
}
else
{
<a asp-controller="Expenses" asp-action="Details" asp-route-id="@entry.Id"
class="fw-medium text-decoration-none">@entry.Number</a>
}
</td>
<td>@entry.VendorName</td>
<td class="text-muted small">
@(entry.EntryType == "Bill" ? entry.Memo : entry.AccountName)
@if (entry.HasReceipt)
{
<i class="bi bi-paperclip ms-1" title="Has receipt"></i>
}
</td>
<td>@entry.Date.ToString("MMM d, yyyy")</td>
<td>
@if (entry.DueDate.HasValue)
{
<span class="@(entry.IsOverdue ? "text-danger fw-medium" : "")">
@entry.DueDate.Value.ToString("MMM d, yyyy")
@if (entry.IsOverdue) { <i class="bi bi-exclamation-circle ms-1"></i> }
</span>
}
else if (entry.EntryType == "Expense")
{
<span class="text-muted">&mdash;</span>
}
</td>
<td><span class="badge bg-@entry.StatusColor">@entry.StatusLabel</span></td>
<td class="text-end">@entry.Total.ToString("C")</td>
<td class="text-end fw-medium @(entry.BalanceDue > 0 ? "text-danger" : "text-muted")">
@Html.Raw(entry.EntryType == "Bill" ? entry.BalanceDue.ToString("C") : "&mdash;")
</td>
<td>
@if (entry.EntryType == "Bill")
{
<a asp-controller="Bills" asp-action="Details" asp-route-id="@entry.Id"
class="btn btn-sm btn-outline-primary"><i class="bi bi-eye"></i></a>
}
else
{
<a asp-controller="Expenses" asp-action="Details" asp-route-id="@entry.Id"
class="btn btn-sm btn-outline-secondary"><i class="bi bi-eye"></i></a>
}
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
else
{
<div class="text-center py-5 text-muted">
<i class="bi bi-inbox fs-1 d-block mb-2"></i>
No entries found.
<div class="mt-2">
<a asp-controller="Bills" asp-action="Create" class="btn btn-primary btn-sm me-2">
<i class="bi bi-plus-lg me-1"></i>New Bill
</a>
<a asp-controller="Expenses" asp-action="Create" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-plus-lg me-1"></i>New Expense
</a>
</div>
</div>
}
@if ((int)ViewBag.TotalPages > 1)
{
<nav class="mt-3">
<ul class="pagination justify-content-center">
<li class="page-item @((int)ViewBag.Page <= 1 ? "disabled" : "")">
<a class="page-link" asp-action="Index"
asp-route-type="@ViewBag.TypeFilter"
asp-route-status="@ViewBag.StatusFilter"
asp-route-search="@ViewBag.Search"
asp-route-page="@((int)ViewBag.Page - 1)"
asp-route-pageSize="@ViewBag.PageSize">&lsaquo; Prev</a>
</li>
@for (var p = 1; p <= (int)ViewBag.TotalPages; p++)
{
<li class="page-item @(p == (int)ViewBag.Page ? "active" : "")">
<a class="page-link" asp-action="Index"
asp-route-type="@ViewBag.TypeFilter"
asp-route-status="@ViewBag.StatusFilter"
asp-route-search="@ViewBag.Search"
asp-route-page="@p"
asp-route-pageSize="@ViewBag.PageSize">@p</a>
</li>
}
<li class="page-item @((int)ViewBag.Page >= (int)ViewBag.TotalPages ? "disabled" : "")">
<a class="page-link" asp-action="Index"
asp-route-type="@ViewBag.TypeFilter"
asp-route-status="@ViewBag.StatusFilter"
asp-route-search="@ViewBag.Search"
asp-route-page="@((int)ViewBag.Page + 1)"
asp-route-pageSize="@ViewBag.PageSize">Next &rsaquo;</a>
</li>
</ul>
<p class="text-center text-muted small">
Showing @(((int)ViewBag.Page - 1) * (int)ViewBag.PageSize + 1)&ndash;@(Math.Min((int)ViewBag.Page * (int)ViewBag.PageSize, (int)ViewBag.TotalCount))
of @ViewBag.TotalCount entries
</p>
</nav>
}