package handler import ( "fmt" "log/slog" "net/http" "spend-sparrow/internal/handler/middleware" "spend-sparrow/internal/service" "spend-sparrow/internal/template/dashboard" "spend-sparrow/internal/types" "spend-sparrow/internal/utils" "time" "github.com/a-h/templ" ) type Dashboard interface { Handle(router *http.ServeMux) } type DashboardImpl struct { r *Render d *service.Dashboard c service.Clock } func NewDashboard(r *Render, d *service.Dashboard, c service.Clock) Dashboard { return DashboardImpl{ r: r, d: d, c: c, } } func (handler DashboardImpl) Handle(router *http.ServeMux) { router.Handle("GET /dashboard", handler.handleDashboard()) router.Handle("GET /dashboard/dataset", handler.handleDashboardDataset()) } func (handler DashboardImpl) handleDashboard() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { updateSpan(r) user := middleware.GetUser(r) if user == nil { utils.DoRedirect(w, r, "/auth/signin") return } var comp templ.Component htmx := utils.IsHtmx(r) var err error comp, err = handler.dashboard(user, r) if err != nil { slog.ErrorContext(r.Context(), "Failed to get dashboard summary", "err", err) } if htmx { handler.r.RenderWithStatus(r, w, comp, http.StatusOK) } else { handler.r.RenderLayoutWithStatus(r, w, comp, user, http.StatusOK) } } } func (handler DashboardImpl) handleDashboardDataset() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { updateSpan(r) user := middleware.GetUser(r) series, err := handler.d.MainChart(r.Context(), user) if err != nil { handleError(w, r, err) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) account := "" savings := "" for _, entry := range series { account += fmt.Sprintf(`["%s",%.2f],`, entry.Day.Format(time.RFC3339), float64(entry.Value)/100) savings += fmt.Sprintf(`["%s",%.2f],`, entry.Day.Format(time.RFC3339), float64(entry.Savings)/100) } account = account[:len(account)-1] savings = savings[:len(savings)-1] _, err = fmt.Fprintf(w, ` { "tooltip": { "trigger": "axis", "formatter": "" }, "xAxis": { "type": "time" }, "yAxis": { "axisLabel": { "formatter": "{value} €" } }, "series": [ { "data": [%s], "type": "line", "name": "Account Value" }, { "data": [%s], "type": "line", "name": "Savings" } ] } `, account, savings) if err != nil { slog.InfoContext(r.Context(), "could not write response", "err", err) } } } func (handler DashboardImpl) dashboard(user *types.User, r *http.Request) (templ.Component, error) { var month time.Time var err error monthStr := r.URL.Query().Get("month") if monthStr != "" { month, err = time.Parse("2006-01-02", monthStr) if err != nil { return nil, fmt.Errorf("could not parse timestamp: %w", service.ErrBadRequest) } } else { month = handler.c.Now() } summary, err := handler.d.Summary(r.Context(), user, month) if err != nil { return nil, err } return dashboard.Dashboard(summary), nil }