package templates import ( "fmt" "math" "strings" "veola/internal/models" ) type ItemsData struct { Page Items []models.Item Categories []string SelectedCategory string // PriceHistory holds the last ~20 points per item id, ascending in time. // Nil/missing entries render an empty sparkline cell. PriceHistory map[int64][]models.PricePoint } // sparklinePoints turns a price-history slice into an SVG polyline "points" // attribute, normalized to a 80x24 viewBox with 2px padding so endpoints // don't clip the stroke. Returns "" when there isn't enough history to draw. func sparklinePoints(history []models.PricePoint) string { if len(history) < 2 { return "" } const w, h, pad = 80.0, 24.0, 2.0 minP, maxP := math.Inf(1), math.Inf(-1) for _, p := range history { if p.Price < minP { minP = p.Price } if p.Price > maxP { maxP = p.Price } } span := maxP - minP if span == 0 { // All-equal series: draw a flat line through the middle. span = 1 } step := (w - 2*pad) / float64(len(history)-1) parts := make([]string, len(history)) for i, p := range history { x := pad + float64(i)*step y := h - pad - ((p.Price-minP)/span)*(h-2*pad) parts[i] = fmt.Sprintf("%.1f,%.1f", x, y) } return strings.Join(parts, " ") } // sparklineTrendClass compares the last point to the average of the prior // points and returns a CSS class so the sparkline tints green (price down), // red (up), or neutral. Threshold is ±3% to ignore tiny wobbles. func sparklineTrendClass(history []models.PricePoint) string { if len(history) < 2 { return "" } last := history[len(history)-1].Price var sum float64 for _, p := range history[:len(history)-1] { sum += p.Price } avg := sum / float64(len(history)-1) if avg == 0 { return "v-spark-flat" } switch { case last <= avg*0.97: return "v-spark-down" case last >= avg*1.03: return "v-spark-up" } return "v-spark-flat" } // trendArrow returns the unicode arrow + a CSS class describing the direction. // Same threshold logic as the sparkline. Empty when there's no trend signal. func trendArrow(history []models.PricePoint) (glyph, class string) { switch sparklineTrendClass(history) { case "v-spark-down": return "↓", "v-trend-down" case "v-spark-up": return "↑", "v-trend-up" case "v-spark-flat": return "→", "v-trend-flat" } return "", "" } // isDeal is the gate for the mascot "deal" moment on a row. func isDeal(it models.Item) bool { return it.BestPrice != nil && it.TargetPrice != nil && *it.BestPrice <= *it.TargetPrice } templ itemsBody(d ItemsData) {
| Name | Category | Target | Best Price | Trend | Last Polled | Status |
|---|
}
{ it.Name }