- 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
102 lines
3.2 KiB
Go
102 lines
3.2 KiB
Go
// Package handlers provides HTTP request handlers for NextReceipt.
|
|
//
|
|
// This file implements the dashboard handler showing all purchases
|
|
// for the authenticated user.
|
|
package handlers
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/cclohmar/NextReceipt/internal/database"
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// DashboardHandler
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// DashboardHandler groups HTTP handlers related to the main dashboard view.
|
|
type DashboardHandler struct {
|
|
DB *sql.DB
|
|
}
|
|
|
|
// NewDashboardHandler creates a new DashboardHandler with the given database handle.
|
|
func NewDashboardHandler(db *sql.DB) *DashboardHandler {
|
|
return &DashboardHandler{DB: db}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// GET /dashboard — Dashboard
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Dashboard renders the main dashboard page showing all purchases belonging
|
|
// to the authenticated user, along with the receipt upload controls.
|
|
func (h *DashboardHandler) Dashboard(w http.ResponseWriter, r *http.Request) {
|
|
userID := getUserID(r)
|
|
if userID == "" {
|
|
log.Printf("ERROR [%s] handlers: Dashboard: missing user ID", time.Now().Format(time.RFC3339))
|
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
purchases, err := database.GetPurchasesByUser(h.DB, userID)
|
|
if err != nil {
|
|
log.Printf("ERROR [%s] handlers: Dashboard: GetPurchasesByUser: %v",
|
|
time.Now().Format(time.RFC3339), err)
|
|
http.Error(w, "Failed to load purchases", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Normalize ImagePath for all purchases.
|
|
for i := range purchases {
|
|
purchases[i].ImagePath = normalizeImagePath(purchases[i].ImagePath)
|
|
}
|
|
|
|
tmpl := getTemplate("dashboard.html")
|
|
|
|
data := map[string]interface{}{
|
|
"Purchases": purchases,
|
|
"Query": "",
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
if err := tmpl.Execute(w, data); err != nil {
|
|
log.Printf("ERROR [%s] handlers: Dashboard: execute template: %v",
|
|
time.Now().Format(time.RFC3339), err)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// GET /search — SearchPurchases
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// SearchPurchases handles the HTMX search request, returning only the purchase
|
|
// list fragment filtered by the search query.
|
|
func (h *DashboardHandler) SearchPurchases(w http.ResponseWriter, r *http.Request) {
|
|
userID := getUserID(r)
|
|
if userID == "" {
|
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
query := r.URL.Query().Get("q")
|
|
|
|
purchases, err := database.SearchPurchases(h.DB, userID, query)
|
|
if err != nil {
|
|
log.Printf("ERROR [%s] handlers: SearchPurchases: %v",
|
|
time.Now().Format(time.RFC3339), err)
|
|
http.Error(w, "Search failed", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Normalize ImagePath for all purchases.
|
|
for i := range purchases {
|
|
purchases[i].ImagePath = normalizeImagePath(purchases[i].ImagePath)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
fmt.Fprint(w, renderPurchaseList(purchases))
|
|
}
|