package templates import ( "fmt" "time" "veola/internal/db" "veola/internal/models" ) type DashboardData struct { Page Stats *db.DashboardStats RecentResults []ResultRow RecentAlerts []AlertRow } type ResultRow struct { ItemID int64 ItemName string Title string Price *float64 Currency string Source string URL string FoundAt time.Time Alerted bool } type AlertRow struct { ItemName string Price *float64 Currency string FoundAt time.Time } templ dashboardBody(d DashboardData) {

Dashboard

@statCard("Total Items", fmt.Sprintf("%d", d.Stats.TotalItems), "") @statCard("Active", fmt.Sprintf("%d", d.Stats.ActiveItems), "") @statCard("Results Today", fmt.Sprintf("%d", d.Stats.ResultsToday), "") @statCard("Alerts Today", fmt.Sprintf("%d", d.Stats.AlertsToday), "")
Potential Spend
{ fmt.Sprintf("$%.2f", d.Stats.PotentialSpend) }
across { fmt.Sprintf("%d", d.Stats.PricedItemCount) } items
if d.Stats.UnpricedCount > 0 {
{ fmt.Sprintf("%d items not yet priced.", d.Stats.UnpricedCount) }
}
Money Saved
{ fmt.Sprintf("$%.2f", d.Stats.MoneySaved) }
across { fmt.Sprintf("%d", d.Stats.SavedItemCount) } items

Recent Results

if len(d.RecentResults) == 0 {
No results yet.
} else { for _, r := range d.RecentResults { }
ItemPriceSourceFound
{ r.ItemName } { fmtPrice(r.Price, r.Currency) } { r.Source } { humanTime(r.FoundAt) }
}

Recent Alerts

if len(d.RecentAlerts) == 0 {
No alerts sent yet.
} else {
    for _, a := range d.RecentAlerts {
  • { a.ItemName } { fmtPrice(a.Price, a.Currency) }
  • }
}
} templ statCard(label, value, sub string) {
{ label }
{ value }
if sub != "" {
{ sub }
}
} templ Dashboard(d DashboardData) { @Layout(d.Page, dashboardBody(d)) } // Helpers used by multiple templates. func fmtPrice(p *float64, currency string) string { if p == nil { return "—" } sym := currencySymbol(currency) return fmt.Sprintf("%s%.2f", sym, *p) } func currencySymbol(c string) string { switch c { case "USD", "": return "$" case "GBP": return "£" case "EUR": return "€" case "JPY": return "¥" default: return c + " " } } func humanTime(t time.Time) string { if t.IsZero() { return "—" } d := time.Since(t) switch { case d < time.Minute: return "just now" case d < time.Hour: m := int(d.Minutes()) return fmt.Sprintf("%d minutes ago", m) case d < 24*time.Hour: h := int(d.Hours()) return fmt.Sprintf("%d hours ago", h) case d < 30*24*time.Hour: d2 := int(d.Hours() / 24) return fmt.Sprintf("%d days ago", d2) default: return t.Format("2006-01-02") } } // Used by item rendering. var _ = models.Item{}