feat: view receipt image from expense list
- Added 🖼️ button on each expense — opens full-screen lightbox
- Click anywhere on the overlay to close
- Images served via /storage/{filename} (auth-protected)
- Fixed ImagePath to store only filename (was storage/storage/...)
- Both expense_list.html fragment and event_expenses.html page updated
This commit is contained in:
parent
4593dd1f66
commit
8ff1a4175a
3 changed files with 38 additions and 9 deletions
|
|
@ -136,8 +136,11 @@ func (h *ExpenseHandler) UploadReceipt(w http.ResponseWriter, r *http.Request) {
|
||||||
exchangeRate = 1.0
|
exchangeRate = 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
// 9. Call the Gemini Vision API for AI extraction.
|
// 9. Call the Gemini Vision API for AI extraction (uses full disk path).
|
||||||
receipt, aiErr := ai.ExtractReceipt(storagePath)
|
receipt, aiErr := ai.ExtractReceipt(filepath.Join("storage", filename))
|
||||||
|
|
||||||
|
// Strip the storage/ prefix so the template can build a proper URL: /storage/{file}
|
||||||
|
storagePath = filename
|
||||||
|
|
||||||
// 10. Render the receipt_form.html fragment.
|
// 10. Render the receipt_form.html fragment.
|
||||||
tmpl := getTemplate("receipt_form.html")
|
tmpl := getTemplate("receipt_form.html")
|
||||||
|
|
|
||||||
|
|
@ -43,12 +43,25 @@
|
||||||
<div style="font-size: 0.75rem; color: var(--color-primary);">{{printf "%.2f" .ConvertedAmount}} {{.BaseCurrency}}</div>
|
<div style="font-size: 0.75rem; color: var(--color-primary);">{{printf "%.2f" .ConvertedAmount}} {{.BaseCurrency}}</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</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);"
|
<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="/expenses/{{.ID}}/edit"
|
hx-get="/expenses/{{.ID}}/edit"
|
||||||
hx-target="#receipt-form"
|
hx-target="#receipt-form"
|
||||||
hx-swap="innerHTML"
|
hx-swap="innerHTML"
|
||||||
title="Edit receipt">✏️</button>
|
title="Edit receipt">✏️</button>
|
||||||
</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; display: flex; align-items: center; justify-content: center; cursor: pointer; padding: 1rem;"
|
||||||
|
onclick="this.classList.add('hidden')">
|
||||||
|
<img src="/storage/{{.ImagePath}}" alt="Receipt" style="max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 0.5rem;">
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,15 @@
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; align-items: center; margin-left: 0.5rem;">
|
<div style="display: flex; align-items: center; gap: 0.25rem;">
|
||||||
<button class="btn btn-sm" style="background: none; border: 1px solid #d1d5db; border-radius: 0.375rem; padding: 0.25rem 0.5rem; font-size: 0.75rem; cursor: pointer;"
|
{{if .ImagePath}}
|
||||||
|
<button class="btn btn-sm" style="background: none; border: 1px solid #475569; border-radius: 0.375rem; padding: 0.25rem 0.5rem; font-size: 0.75rem; cursor: pointer;"
|
||||||
|
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 #475569; border-radius: 0.375rem; padding: 0.25rem 0.5rem; font-size: 0.75rem; cursor: pointer;"
|
||||||
hx-get="/expenses/{{.ID}}/edit"
|
hx-get="/expenses/{{.ID}}/edit"
|
||||||
hx-target="#receipt-form"
|
hx-target="#receipt-form"
|
||||||
hx-swap="innerHTML"
|
hx-swap="innerHTML"
|
||||||
|
|
@ -37,6 +44,12 @@
|
||||||
✏️
|
✏️
|
||||||
</button>
|
</button>
|
||||||
</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; display: flex; align-items: center; justify-content: center; cursor: pointer; padding: 1rem;"
|
||||||
|
onclick="this.classList.add('hidden')">
|
||||||
|
<img src="/storage/{{.ImagePath}}" alt="Receipt" style="max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 0.5rem;">
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue