feat: replace overview with reports page

This commit is contained in:
juancwu 2026-02-15 23:00:58 +00:00
commit a7d5f21fe8
5 changed files with 12 additions and 200 deletions

View file

@ -97,40 +97,24 @@ func (h *SpaceHandler) DashboardPage(w http.ResponseWriter, r *http.Request) {
space, err := h.spaceService.GetSpace(spaceID)
if err != nil {
slog.Error("failed to get space", "error", err, "space_id", spaceID)
// The RequireSpaceAccess middleware should prevent this, but as a fallback.
http.Error(w, "Space not found.", http.StatusNotFound)
return
}
lists, err := h.listService.GetListsForSpace(spaceID)
// Default to this month
now := time.Now()
presets := service.GetPresetDateRanges(now)
from := presets[0].From
to := presets[0].To
report, err := h.reportService.GetSpendingReport(spaceID, from, to)
if err != nil {
slog.Error("failed to get shopping lists for space", "error", err, "space_id", spaceID)
slog.Error("failed to get spending report", "error", err, "space_id", spaceID)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
tags, err := h.tagService.GetTagsForSpace(spaceID)
if err != nil {
slog.Error("failed to get tags for space", "error", err, "space_id", spaceID)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
listsWithItems, err := h.listService.GetListsWithUncheckedItems(spaceID)
if err != nil {
slog.Error("failed to get lists with unchecked items", "error", err, "space_id", spaceID)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
accounts, err := h.accountService.GetAccountsForSpace(spaceID)
if err != nil {
slog.Error("failed to get accounts for space", "error", err, "space_id", spaceID)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
ui.Render(w, r, pages.SpaceOverviewPage(space, lists, tags, listsWithItems, accounts))
ui.Render(w, r, pages.SpaceReportsPage(space, report, presets, "this_month"))
}
func (h *SpaceHandler) ListsPage(w http.ResponseWriter, r *http.Request) {
@ -644,12 +628,6 @@ func (h *SpaceHandler) CreateExpense(w http.ResponseWriter, r *http.Request) {
}
balance -= totalAllocated
if r.URL.Query().Get("from") == "overview" {
w.Header().Set("HX-Redirect", "/app/spaces/"+spaceID+"/expenses?created=true")
w.WriteHeader(http.StatusOK)
return
}
// Return the full paginated list for page 1 so the new expense appears
expenses, totalPages, err := h.expenseService.GetExpensesWithTagsAndMethodsForSpacePaginated(spaceID, 1)
if err != nil {
@ -2127,30 +2105,6 @@ func (h *SpaceHandler) GetBudgetsList(w http.ResponseWriter, r *http.Request) {
// --- Reports ---
func (h *SpaceHandler) ReportsPage(w http.ResponseWriter, r *http.Request) {
spaceID := r.PathValue("spaceID")
space, err := h.spaceService.GetSpace(spaceID)
if err != nil {
http.Error(w, "Space not found", http.StatusNotFound)
return
}
// Default to this month
now := time.Now()
presets := service.GetPresetDateRanges(now)
from := presets[0].From
to := presets[0].To
report, err := h.reportService.GetSpendingReport(spaceID, from, to)
if err != nil {
slog.Error("failed to get spending report", "error", err, "space_id", spaceID)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
ui.Render(w, r, pages.SpaceReportsPage(space, report, presets, "this_month"))
}
func (h *SpaceHandler) GetReportCharts(w http.ResponseWriter, r *http.Request) {
spaceID := r.PathValue("spaceID")

View file

@ -212,10 +212,6 @@ func SetupRoutes(a *app.App) http.Handler {
mux.Handle("GET /app/spaces/{spaceID}/components/budgets", budgetsListWithAccess)
// Report routes
reportsPageHandler := middleware.RequireAuth(space.ReportsPage)
reportsPageWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(reportsPageHandler)
mux.Handle("GET /app/spaces/{spaceID}/reports", reportsPageWithAccess)
reportChartsHandler := middleware.RequireAuth(space.GetReportCharts)
reportChartsWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(reportChartsHandler)
mux.Handle("GET /app/spaces/{spaceID}/components/report-charts", reportChartsWithAccess)

View file

@ -22,19 +22,10 @@ type AddExpenseFormProps struct {
ListsWithItems []model.ListWithUncheckedItems
PaymentMethods []*model.PaymentMethod
DialogID string // which dialog to close on success
FromOverview bool // if true, POSTs with ?from=overview; server redirects to expenses page
}
func (p AddExpenseFormProps) formAttrs() templ.Attributes {
closeScript := "on htmx:afterOnLoad if event.detail.xhr.status == 200 then call window.tui.dialog.close('" + p.DialogID + "') then reset() me then show #item-selector-section end"
if p.FromOverview {
return templ.Attributes{
"hx-post": "/app/spaces/" + p.Space.ID + "/expenses?from=overview",
"hx-target": "body",
"hx-swap": "beforeend",
"_": closeScript,
}
}
return templ.Attributes{
"hx-post": "/app/spaces/" + p.Space.ID + "/expenses",
"hx-target": "#expenses-list-wrapper",

View file

@ -44,10 +44,10 @@ templ Space(title string, space *model.Space) {
@sidebar.MenuButton(sidebar.MenuButtonProps{
Href: "/app/spaces/" + space.ID,
IsActive: ctxkeys.URLPath(ctx) == "/app/spaces/"+space.ID,
Tooltip: "Space Dashboard",
Tooltip: "Reports",
}) {
@icon.House(icon.Props{Class: "size-4"})
<span>Overview</span>
@icon.ChartPie(icon.Props{Class: "size-4"})
<span>Reports</span>
}
}
@sidebar.MenuItem() {
@ -80,16 +80,6 @@ templ Space(title string, space *model.Space) {
<span>Budgets</span>
}
}
@sidebar.MenuItem() {
@sidebar.MenuButton(sidebar.MenuButtonProps{
Href: "/app/spaces/" + space.ID + "/reports",
IsActive: ctxkeys.URLPath(ctx) == "/app/spaces/"+space.ID+"/reports",
Tooltip: "Reports",
}) {
@icon.ChartPie(icon.Props{Class: "size-4"})
<span>Reports</span>
}
}
@sidebar.MenuItem() {
@sidebar.MenuButton(sidebar.MenuButtonProps{
Href: "/app/spaces/" + space.ID + "/accounts",

View file

@ -1,119 +0,0 @@
package pages
import (
"fmt"
"git.juancwu.dev/juancwu/budgit/internal/model"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/badge"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/button"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/dialog"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/expense"
"git.juancwu.dev/juancwu/budgit/internal/ui/layouts"
)
templ SpaceOverviewPage(space *model.Space, lists []*model.ShoppingList, tags []*model.Tag, listsWithItems []model.ListWithUncheckedItems, accounts []model.MoneyAccountWithBalance) {
@layouts.Space("Overview", space) {
<div class="space-y-4">
<div class="flex justify-between items-center">
<h1 class="text-2xl font-bold">Welcome to { space.Name }!</h1>
@dialog.Dialog(dialog.Props{ID: "add-expense-overview-dialog"}) {
@dialog.Trigger() {
@button.Button() {
Add Expense
}
}
@dialog.Content() {
@dialog.Header() {
@dialog.Title() {
Add Transaction
}
@dialog.Description() {
Add a new expense or top-up to your space.
}
}
@expense.AddExpenseForm(expense.AddExpenseFormProps{
Space: space,
Tags: tags,
ListsWithItems: listsWithItems,
DialogID: "add-expense-overview-dialog",
FromOverview: true,
})
}
}
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
// Shopping Lists section
<div class="border rounded-lg p-4">
<h2 class="text-lg font-semibold mb-2">Shopping Lists</h2>
if len(lists) > 0 {
<ul class="space-y-1">
for i, list := range lists {
if i < 5 {
<li>
<a
href={ templ.URL("/app/spaces/" + space.ID + "/lists/" + list.ID) }
class="block px-3 py-2 rounded-md hover:bg-muted transition-colors text-sm"
>
{ list.Name }
</a>
</li>
}
}
</ul>
<a
href={ templ.URL("/app/spaces/" + space.ID + "/lists") }
class="block text-sm text-muted-foreground hover:text-foreground transition-colors mt-2 px-3"
>
View all shopping lists
</a>
} else {
<p class="text-sm text-muted-foreground">No shopping lists yet.</p>
}
</div>
// Tags section
<div class="border rounded-lg p-4">
<h2 class="text-lg font-semibold mb-2">Tags</h2>
if len(tags) > 0 {
<div class="flex flex-wrap gap-2">
for _, tag := range tags {
@badge.Badge(badge.Props{Variant: badge.VariantSecondary}) {
{ tag.Name }
}
}
</div>
} else {
<p class="text-sm text-muted-foreground">No tags yet.</p>
}
</div>
// Money Accounts section
<div class="border rounded-lg p-4">
<h2 class="text-lg font-semibold mb-2">Money Accounts</h2>
if len(accounts) > 0 {
<ul class="space-y-1">
for i, account := range accounts {
if i < 5 {
<li>
<a
href={ templ.URL("/app/spaces/" + space.ID + "/accounts") }
class="block px-3 py-2 rounded-md hover:bg-muted transition-colors text-sm flex justify-between"
>
<span>{ account.Name }</span>
<span class="text-muted-foreground">{ fmt.Sprintf("$%.2f", float64(account.BalanceCents)/100) }</span>
</a>
</li>
}
}
</ul>
<a
href={ templ.URL("/app/spaces/" + space.ID + "/accounts") }
class="block text-sm text-muted-foreground hover:text-foreground transition-colors mt-2 px-3"
>
View all accounts
</a>
} else {
<p class="text-sm text-muted-foreground">No accounts yet.</p>
}
</div>
</div>
</div>
}
}