package handlers import ( "net/http" "veola/internal/db" "veola/templates" ) func (a *App) GetDashboard(w http.ResponseWriter, r *http.Request) { d, err := a.dashboardData(r) if err != nil { a.serverError(w, r, err) return } render(w, r, templates.Dashboard(d)) } func (a *App) GetDashboardRefresh(w http.ResponseWriter, r *http.Request) { d, err := a.dashboardData(r) if err != nil { a.serverError(w, r, err) return } // Render ONLY the inner body. The hx-swap="outerHTML" on DashboardBody's // root div replaces it with this fresh copy. Rendering templates.Dashboard // here would return the whole Layout — sidebar included — nested inside // the div, producing a duplicate nav bar on every refresh. render(w, r, templates.DashboardBody(d)) } func (a *App) dashboardData(r *http.Request) (templates.DashboardData, error) { stats, err := a.Store.GetDashboardStats(r.Context()) if err != nil { return templates.DashboardData{}, err } results, err := a.Store.ListResults(r.Context(), db.ResultsQuery{Limit: 8, ExcludeEnded: true}) if err != nil { return templates.DashboardData{}, err } itemNames := map[int64]string{} all, _ := a.Store.ListItems(r.Context()) for _, it := range all { itemNames[it.ID] = it.Name } rrs := make([]templates.ResultRow, 0, len(results)) for _, r := range results { rrs = append(rrs, templates.ResultRow{ ItemID: r.ItemID, ItemName: itemNames[r.ItemID], Title: r.Title, Price: r.Price, Currency: r.Currency, Source: r.Source, URL: r.URL, ImageURL: r.ImageURL, FoundAt: r.FoundAt, Alerted: r.Alerted, }) } alerts, err := alertsRecent(a, r, itemNames) if err != nil { return templates.DashboardData{}, err } apifyToday, apifyMonth, costPerCall, monthlyBudget := a.Scheduler.ApifyUsage(r.Context()) ebayUsed, ebayLimit := a.Scheduler.EbayUsage(r.Context()) return templates.DashboardData{ Page: a.page(r, "Dashboard", "dashboard"), Stats: stats, RecentResults: rrs, RecentAlerts: alerts, ApifyToday: apifyToday, ApifyMonth: apifyMonth, ApifyCostPerCall: costPerCall, MonthlyBudget: monthlyBudget, EbayToday: ebayUsed, EbayLimit: ebayLimit, }, nil } func alertsRecent(a *App, r *http.Request, itemNames map[int64]string) ([]templates.AlertRow, error) { results, err := a.Store.ListResults(r.Context(), db.ResultsQuery{OnlyAlerted: true, Limit: 5}) if err != nil { return nil, err } out := make([]templates.AlertRow, 0, len(results)) for _, res := range results { out = append(out, templates.AlertRow{ ItemName: itemNames[res.ItemID], Title: res.Title, Price: res.Price, Currency: res.Currency, ImageURL: res.ImageURL, FoundAt: res.FoundAt, }) } return out, nil }