- Search purchasers by product name, store, or notes (case-insensitive) - GET /search?q=... returns purchase list fragment - 300ms debounced keyup trigger on search input - Empty query returns all purchases
127 lines
7.8 KiB
HTML
127 lines
7.8 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
<meta name="theme-color" content="#10b981">
|
|
<title>NextReceipt</title>
|
|
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
|
<link rel="alternate icon" href="/static/icons/icon-192.png">
|
|
<link rel="manifest" href="/manifest.json">
|
|
<link rel="stylesheet" href="/static/css/style.css?v=4">
|
|
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
|
</head>
|
|
<body>
|
|
<div class="app-shell">
|
|
<header class="app-header">
|
|
<h1 class="app-title"><img src="/static/favicon.svg" width="24" height="24" alt="" style="vertical-align: middle; margin-right: 0.5rem;">NextReceipt</h1>
|
|
<button class="btn btn-secondary btn-sm" style="font-size: 0.75rem;"
|
|
hx-post="/logout" hx-target="body" hx-push-url="true">Logout</button>
|
|
</header>
|
|
|
|
<main class="main-content">
|
|
<div class="dashboard-header">
|
|
<h2>My Purchases</h2>
|
|
</div>
|
|
|
|
<!-- Search Bar -->
|
|
<div style="margin-bottom: 1rem;">
|
|
<input type="text" id="search" name="q" placeholder="Search products, stores, notes..."
|
|
value="{{.Query}}"
|
|
hx-get="/search" hx-trigger="keyup changed delay:300ms" hx-target="#purchase-list"
|
|
style="width: 100%%; padding: 0.75rem 1rem; border: 1px solid var(--color-border); border-radius: 0.5rem; background: var(--color-card); color: var(--color-text); font-size: 0.9rem; box-sizing: border-box;">
|
|
</div>
|
|
|
|
<!-- Purchase List -->
|
|
<div id="purchase-list">
|
|
{{if .Purchases}}
|
|
<h3 style="margin-bottom: 1rem; font-size: 1rem; font-weight: 600;">Purchases ({{len .Purchases}})</h3>
|
|
<div style="display: flex; flex-direction: column; gap: 0.5rem;">
|
|
{{range .Purchases}}
|
|
<div style="display: flex; align-items: center; background: var(--color-card); border: 1px solid var(--color-border); border-radius: 0.5rem; padding: 0.75rem;">
|
|
<div style="flex: 1; min-width: 0;">
|
|
<div style="font-weight: 500; color: var(--color-text);">{{.ProductName}}</div>
|
|
<div style="font-size: 0.75rem; color: var(--color-text-muted);">{{.Store}} · {{.Category}} · {{.PurchaseDate}}</div>
|
|
{{if .Notes}}<div style="font-size: 0.75rem; color: var(--color-text-muted);">{{.Notes}}</div>{{end}}
|
|
{{if .WarrantyMonths}}
|
|
<div style="font-size: 0.7rem; color: #6ee7b7;">Warranty: {{.WarrantyMonths}} months</div>
|
|
{{end}}
|
|
{{if .ReturnDays}}
|
|
<div style="font-size: 0.7rem; color: #fcd34d;">Return: {{.ReturnDays}} days</div>
|
|
{{end}}
|
|
</div>
|
|
<div style="text-align: right; margin-right: 0.75rem;">
|
|
<div style="font-weight: 600; color: var(--color-text);">{{printf "%.2f" .Amount}} {{.Currency}}</div>
|
|
</div>
|
|
<div style="display: flex; gap: 0.25rem;">
|
|
{{if .ImagePath}}
|
|
<button class="btn btn-sm" style="background: none; border: 1px solid var(--color-border); border-radius: 0.375rem; padding: 0.25rem 0.5rem; font-size: 0.75rem; cursor: pointer; flex-shrink: 0; color: var(--color-text);"
|
|
onclick="document.getElementById('img-{{.ID}}').classList.toggle('hidden')"
|
|
title="View receipt image">🖼️</button>
|
|
{{end}}
|
|
<button class="btn btn-sm" style="background: none; border: 1px solid var(--color-border); border-radius: 0.375rem; padding: 0.25rem 0.5rem; font-size: 0.75rem; cursor: pointer; flex-shrink: 0; color: var(--color-text);"
|
|
hx-get="/purchases/{{.ID}}/edit"
|
|
hx-target="#receipt-form"
|
|
hx-swap="innerHTML"
|
|
title="Edit purchase">✏️</button>
|
|
<button class="btn btn-sm" style="background: none; border: 1px solid #7f1d1d; border-radius: 0.375rem; padding: 0.25rem 0.5rem; font-size: 0.75rem; cursor: pointer; flex-shrink: 0; color: #fca5a5;"
|
|
onclick="if(confirm('Delete this purchase?')) htmx.trigger('#del-{{.ID}}','click')"
|
|
title="Delete purchase">🗑️</button>
|
|
<div hx-delete="/purchases/{{.ID}}" hx-target="#purchase-list" hx-swap="outerHTML" id="del-{{.ID}}" style="display:none"></div>
|
|
</div>
|
|
</div>
|
|
{{if .ImagePath}}
|
|
<div id="img-{{.ID}}" class="hidden" style="position: fixed; inset: 0; background: rgba(0,0,0,0.85); z-index: 999; align-items: center; justify-content: center; cursor: pointer; padding: 1rem;"
|
|
onclick="this.classList.add('hidden')">
|
|
<span style="position: absolute; top: 1rem; right: 1rem; font-size: 2rem; color: #fff; line-height: 1; cursor: pointer; z-index: 1000;">×</span>
|
|
<img src="/storage/{{.ImagePath}}" alt="Receipt" style="max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 0.5rem;" onclick="event.stopPropagation()">
|
|
</div>
|
|
{{end}}
|
|
{{end}}
|
|
</div>
|
|
{{else}}
|
|
<div style="text-align: center; padding: 3rem; color: var(--color-text-muted); font-size: 0.875rem;">
|
|
<p>No purchases yet. Upload a receipt to get started.</p>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
|
|
<!-- Receipt Form (edit/add) -->
|
|
<div id="receipt-form" style="margin-top: 1.5rem;"></div>
|
|
|
|
<!-- Add Receipt Buttons -->
|
|
<div style="margin-top: 1.5rem; display: flex; gap: 0.75rem;">
|
|
<label for="receipt-camera" class="btn btn-primary" style="flex: 1; text-align: center; cursor: pointer;">
|
|
📷 Camera
|
|
</label>
|
|
<label for="receipt-upload" class="btn btn-secondary" style="flex: 1; text-align: center; cursor: pointer;">
|
|
📁 Upload
|
|
</label>
|
|
</div>
|
|
|
|
<!-- Hidden file input: camera (mobile) -->
|
|
<input type="file" id="receipt-camera" name="receipt" accept="image/*" capture="environment" style="display: none;"
|
|
hx-post="/purchases/upload"
|
|
hx-encoding="multipart/form-data"
|
|
hx-target="#receipt-form"
|
|
hx-swap="innerHTML"
|
|
hx-indicator="#upload-indicator"
|
|
hx-trigger="change">
|
|
|
|
<!-- Hidden file input: gallery / PDF upload -->
|
|
<input type="file" id="receipt-upload" name="receipt" accept="image/*,.pdf" style="display: none;"
|
|
hx-post="/purchases/upload"
|
|
hx-encoding="multipart/form-data"
|
|
hx-target="#receipt-form"
|
|
hx-swap="innerHTML"
|
|
hx-indicator="#upload-indicator"
|
|
hx-trigger="change">
|
|
|
|
<div id="upload-indicator" class="htmx-indicator" style="text-align: center; padding: 1rem;">
|
|
<div class="spinner"></div>
|
|
<p>Analyzing receipt...</p>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</body>
|
|
</html>
|