Files
MoneyMap/MoneyMap/Pages/ViewReceipt.cshtml
T
aj 4be5658d32 Improve: Overhaul navigation with grouped dropdowns, breadcrumbs, and quick-actions
Restructure the flat 7-item navbar into logical dropdown groups (Transactions,
Receipts, Accounts), add a prominent Upload button, settings gear icon, breadcrumb
navigation on 11 deep pages, and dashboard quick-action cards with hover effects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 19:41:56 -05:00

230 lines
10 KiB
Plaintext

@page "{id:long}"
@model MoneyMap.Pages.ViewReceiptModel
@{
ViewData["Title"] = "View Receipt";
ViewData["Breadcrumbs"] = new List<(string Label, string? Url)>
{
("Transactions", Url.Page("/Transactions")),
($"Transaction #{Model.Receipt.TransactionId}", Url.Page("/EditTransaction", new { id = Model.Receipt.TransactionId })),
("Receipt", null)
};
}
<div class="d-flex justify-content-between align-items-center mb-3">
<h2>Receipt Details</h2>
<a asp-page="/EditTransaction" asp-route-id="@Model.Receipt.TransactionId" class="btn btn-outline-secondary">
Back to Transaction
</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>
}
<div class="row">
<!-- Left column - Receipt Image/PDF -->
<div class="col-lg-8">
<div class="card shadow-sm mb-3">
<div class="card-header">
<strong>@Model.Receipt.FileName</strong>
</div>
<div class="card-body text-center">
@if (Model.Receipt.ContentType == "application/pdf")
{
<iframe src="@Model.ReceiptUrl"
style="width: 100%; height: 800px; border: 1px solid #ddd;"
title="Receipt PDF">
</iframe>
}
else
{
<img src="@Model.ReceiptUrl"
alt="Receipt"
class="img-fluid"
style="max-height: 800px; border: 1px solid #ddd;" />
}
</div>
</div>
<!-- Line Items - Moved here for more space -->
@if (Model.LineItems.Any())
{
<div class="card shadow-sm mb-3">
<div class="card-header">
<strong>Line Items (@Model.LineItems.Count)</strong>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-sm table-hover mb-0">
<thead>
<tr>
<th style="width: 50px;">#</th>
<th>Description</th>
<th class="text-center" style="width: 80px;">Qty</th>
<th class="text-end" style="width: 100px;">Unit Price</th>
<th class="text-end" style="width: 100px;">Total</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.LineItems)
{
<tr>
<td class="text-muted">@item.LineNumber</td>
<td>@item.Description</td>
<td class="text-center">
@(item.Quantity?.ToString("0.##") ?? "-")
</td>
<td class="text-end">
@(item.UnitPrice?.ToString("C") ?? "-")
</td>
<td class="text-end">
<strong>@(item.LineTotal?.ToString("C") ?? "-")</strong>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
}
</div>
<!-- Right column - Metadata -->
<div class="col-lg-4">
<!-- Receipt Info -->
<div class="card shadow-sm mb-3">
<div class="card-header">
<strong>Receipt Information</strong>
</div>
<div class="card-body">
<dl class="row mb-0">
@if (!string.IsNullOrWhiteSpace(Model.Receipt.Merchant))
{
<dt class="col-sm-4">Merchant</dt>
<dd class="col-sm-8">@Model.Receipt.Merchant</dd>
}
@if (Model.Receipt.ReceiptDate.HasValue)
{
<dt class="col-sm-4">Date</dt>
<dd class="col-sm-8">@Model.Receipt.ReceiptDate.Value.ToString("MMM d, yyyy")</dd>
}
@if (Model.Receipt.Subtotal.HasValue)
{
<dt class="col-sm-4">Subtotal</dt>
<dd class="col-sm-8">@Model.Receipt.Subtotal.Value.ToString("C")</dd>
}
@if (Model.Receipt.Tax.HasValue)
{
<dt class="col-sm-4">Tax</dt>
<dd class="col-sm-8">@Model.Receipt.Tax.Value.ToString("C")</dd>
}
@if (Model.Receipt.Total.HasValue)
{
<dt class="col-sm-4">Total</dt>
<dd class="col-sm-8"><strong>@Model.Receipt.Total.Value.ToString("C")</strong></dd>
}
<dt class="col-sm-4">File Size</dt>
<dd class="col-sm-8">@((Model.Receipt.FileSizeBytes / 1024.0).ToString("F1")) KB</dd>
<dt class="col-sm-4">Uploaded</dt>
<dd class="col-sm-8">@Model.Receipt.UploadedAtUtc.ToLocalTime().ToString("MMM d, yyyy h:mm tt")</dd>
@if (!string.IsNullOrWhiteSpace(Model.Receipt.ParsingNotes))
{
<dt class="col-sm-4">AI Notes</dt>
<dd class="col-sm-8">@Model.Receipt.ParsingNotes</dd>
}
</dl>
</div>
</div>
<!-- Parse Receipt -->
<div class="card shadow-sm mb-3">
<div class="card-header">
<strong>Parse Receipt</strong>
</div>
<div class="card-body">
<form method="post" asp-page-handler="Parse" asp-route-id="@Model.Receipt.Id">
<p class="text-muted small mb-2">
Using: <strong>@Model.SelectedModel</strong>
<a href="/Settings" class="ms-2 small">Change</a>
</p>
<div class="mb-2">
<label for="ParsingNotes" class="form-label small text-muted mb-1">Notes for AI</label>
<textarea asp-for="ParsingNotes" class="form-control form-control-sm" rows="3"
placeholder="Optional hints for parsing (e.g., 'This is a restaurant receipt', 'Ignore the voided items')"></textarea>
</div>
<button type="submit" class="btn btn-primary btn-sm w-100">
Parse Receipt
</button>
</form>
</div>
</div>
<!-- Parse Logs -->
@if (Model.ParseLogs.Any())
{
<div class="card shadow-sm">
<div class="card-header">
<strong>Parse History</strong>
</div>
<div class="card-body">
@foreach (var log in Model.ParseLogs)
{
<div class="border-bottom pb-2 mb-2">
<div class="d-flex justify-content-between">
<span class="fw-bold">@log.Provider (@log.Model)</span>
@if (log.Success)
{
<span class="badge bg-success">Success</span>
}
else
{
<span class="badge bg-danger">Failed</span>
}
</div>
<small class="text-muted">
@log.StartedAtUtc.ToLocalTime().ToString("MMM d, yyyy h:mm tt")
</small>
@if (log.Confidence.HasValue)
{
<div class="small">Confidence: @((log.Confidence.Value * 100).ToString("F1"))%</div>
}
@if (!string.IsNullOrWhiteSpace(log.Error))
{
<div class="small text-danger mt-1">@log.Error</div>
}
@if (!string.IsNullOrWhiteSpace(log.RawProviderPayloadJson) && log.RawProviderPayloadJson != "{}")
{
<a class="small" data-bs-toggle="collapse" href="#rawPayload@(log.Id)" role="button" aria-expanded="false">
Show LLM Response
</a>
<div class="collapse mt-1" id="rawPayload@(log.Id)">
<pre class="bg-body-secondary p-2 rounded small" style="max-height: 400px; overflow: auto; white-space: pre-wrap; word-break: break-word;">@log.RawProviderPayloadJson</pre>
</div>
}
</div>
}
</div>
</div>
}
</div>
</div>