improve dashboard page
This commit is contained in:
parent
dd7f2ebe3e
commit
081499ca59
5 changed files with 89 additions and 55 deletions
|
|
@ -1,18 +1,45 @@
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"git.juancwu.dev/juancwu/budgit/internal/ctxkeys"
|
||||||
|
"git.juancwu.dev/juancwu/budgit/internal/service"
|
||||||
"git.juancwu.dev/juancwu/budgit/internal/ui"
|
"git.juancwu.dev/juancwu/budgit/internal/ui"
|
||||||
"git.juancwu.dev/juancwu/budgit/internal/ui/pages"
|
"git.juancwu.dev/juancwu/budgit/internal/ui/pages"
|
||||||
)
|
)
|
||||||
|
|
||||||
type dashboardHandler struct{}
|
type dashboardHandler struct {
|
||||||
|
spaceService *service.SpaceService
|
||||||
|
expenseService *service.ExpenseService
|
||||||
|
}
|
||||||
|
|
||||||
func NewDashboardHandler() *dashboardHandler {
|
func NewDashboardHandler(ss *service.SpaceService, es *service.ExpenseService) *dashboardHandler {
|
||||||
return &dashboardHandler{}
|
return &dashboardHandler{
|
||||||
|
spaceService: ss,
|
||||||
|
expenseService: es,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *dashboardHandler) DashboardPage(w http.ResponseWriter, r *http.Request) {
|
func (h *dashboardHandler) DashboardPage(w http.ResponseWriter, r *http.Request) {
|
||||||
ui.Render(w, r, pages.Dashboard())
|
user := ctxkeys.User(r.Context())
|
||||||
|
spaces, err := h.spaceService.GetSpacesForUser(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to get spaces for user", "error", err, "user_id", user.ID)
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalBalance int
|
||||||
|
for _, space := range spaces {
|
||||||
|
balance, err := h.expenseService.GetBalanceForSpace(space.ID)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to get balance for space", "error", err, "space_id", space.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
totalBalance += balance
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Render(w, r, pages.Dashboard(spaces, totalBalance))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import (
|
||||||
func SetupRoutes(a *app.App) http.Handler {
|
func SetupRoutes(a *app.App) http.Handler {
|
||||||
auth := handler.NewAuthHandler(a.AuthService, a.InviteService)
|
auth := handler.NewAuthHandler(a.AuthService, a.InviteService)
|
||||||
home := handler.NewHomeHandler()
|
home := handler.NewHomeHandler()
|
||||||
dashboard := handler.NewDashboardHandler()
|
dashboard := handler.NewDashboardHandler(a.SpaceService, a.ExpenseService)
|
||||||
space := handler.NewSpaceHandler(a.SpaceService, a.TagService, a.ShoppingListService, a.ExpenseService, a.InviteService, a.EventBus)
|
space := handler.NewSpaceHandler(a.SpaceService, a.TagService, a.ShoppingListService, a.ExpenseService, a.InviteService, a.EventBus)
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ templ App(title string) {
|
||||||
@icon.LayoutDashboard()
|
@icon.LayoutDashboard()
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="text-sm font-bold">{ cfg.AppName }</span>
|
<span class="text-sm font-bold">{ cfg.AppName }</span>
|
||||||
<span class="text-xs text-muted-foreground">Workspace</span>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -54,34 +53,11 @@ templ App(title string) {
|
||||||
<span>Dashboard</span>
|
<span>Dashboard</span>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@sidebar.MenuItem() {
|
|
||||||
@sidebar.MenuButton(sidebar.MenuButtonProps{
|
|
||||||
Href: "/app/goals",
|
|
||||||
IsActive: ctxkeys.URLPath(ctx) == "/app/goals",
|
|
||||||
Tooltip: "Goals",
|
|
||||||
}) {
|
|
||||||
@icon.FolderOpen(icon.Props{Class: "size-4"})
|
|
||||||
<span>Goals</span>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@sidebar.Footer() {
|
@sidebar.Footer() {
|
||||||
@sidebar.Menu() {
|
@sidebar.Menu() {
|
||||||
@sidebar.MenuItem() {
|
|
||||||
@sidebar.MenuButton(sidebar.MenuButtonProps{
|
|
||||||
Href: "/docs",
|
|
||||||
Size: sidebar.MenuButtonSizeSm,
|
|
||||||
Attributes: templ.Attributes{
|
|
||||||
"target": "_blank",
|
|
||||||
"rel": "noopener noreferrer",
|
|
||||||
},
|
|
||||||
}) {
|
|
||||||
@icon.BookOpen(icon.Props{Class: "size-4"})
|
|
||||||
<span>Documentation</span>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@sidebar.MenuItem() {
|
@sidebar.MenuItem() {
|
||||||
@sidebar.MenuButton(sidebar.MenuButtonProps{
|
@sidebar.MenuButton(sidebar.MenuButtonProps{
|
||||||
Href: "mailto:" + cfg.SupportEmail,
|
Href: "mailto:" + cfg.SupportEmail,
|
||||||
|
|
@ -170,23 +146,14 @@ templ AppSidebarDropdown(user *model.User, profile *model.Profile) {
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@dropdown.Separator()
|
@dropdown.Separator()
|
||||||
@dropdown.Item(dropdown.ItemProps{
|
<!-- @dropdown.Item(dropdown.ItemProps{ -->
|
||||||
Href: "/app/settings",
|
<!-- Href: "/app/settings", -->
|
||||||
}) {
|
<!-- }) { -->
|
||||||
<span class="flex items-center">
|
<!-- <span class="flex items-center"> -->
|
||||||
@icon.Settings(icon.Props{Size: 16, Class: "mr-2"})
|
<!-- @icon.Settings(icon.Props{Size: 16, Class: "mr-2"}) -->
|
||||||
Settings
|
<!-- Settings -->
|
||||||
</span>
|
<!-- </span> -->
|
||||||
}
|
<!-- } -->
|
||||||
@dropdown.Item(dropdown.ItemProps{
|
|
||||||
Href: "/app/billing",
|
|
||||||
}) {
|
|
||||||
<span class="flex items-center">
|
|
||||||
@icon.CreditCard(icon.Props{Size: 16, Class: "mr-2"})
|
|
||||||
Billing
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
@dropdown.Separator()
|
|
||||||
<form action="/auth/logout" method="POST" class="contents">
|
<form action="/auth/logout" method="POST" class="contents">
|
||||||
@csrf.Token()
|
@csrf.Token()
|
||||||
@dropdown.Item(dropdown.ItemProps{
|
@dropdown.Item(dropdown.ItemProps{
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ templ Space(title string, space *model.Space) {
|
||||||
@icon.LayoutDashboard()
|
@icon.LayoutDashboard()
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="text-sm font-bold">{ cfg.AppName }</span>
|
<span class="text-sm font-bold">{ cfg.AppName }</span>
|
||||||
<span class="text-xs text-muted-foreground">Back to Home</span>
|
<span class="text-xs text-muted-foreground">Back to Dashboard</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +103,7 @@ templ Space(title string, space *model.Space) {
|
||||||
@breadcrumb.List() {
|
@breadcrumb.List() {
|
||||||
@breadcrumb.Item() {
|
@breadcrumb.Item() {
|
||||||
@breadcrumb.Link(breadcrumb.LinkProps{Href: "/app/dashboard"}) {
|
@breadcrumb.Link(breadcrumb.LinkProps{Href: "/app/dashboard"}) {
|
||||||
Home
|
Dashboard
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@breadcrumb.Separator()
|
@breadcrumb.Separator()
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,56 @@
|
||||||
package pages
|
package pages
|
||||||
|
|
||||||
import "git.juancwu.dev/juancwu/budgit/internal/ui/layouts"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.juancwu.dev/juancwu/budgit/internal/model"
|
||||||
|
"git.juancwu.dev/juancwu/budgit/internal/ui/layouts"
|
||||||
|
"git.juancwu.dev/juancwu/budgit/internal/ui/components/card"
|
||||||
|
)
|
||||||
|
|
||||||
templ Dashboard() {
|
templ Dashboard(spaces []*model.Space, totalBalance int) {
|
||||||
@layouts.App("Dashboard") {
|
@layouts.App("Dashboard") {
|
||||||
<div class="container max-w-7xl px-6 py-8">
|
<div class="container max-w-7xl px-6 py-8">
|
||||||
<div class="mb-8">
|
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4">
|
||||||
|
<div>
|
||||||
<h1 class="text-3xl font-bold">Dashboard</h1>
|
<h1 class="text-3xl font-bold">Dashboard</h1>
|
||||||
<p class="text-muted-foreground mt-2">
|
<p class="text-muted-foreground mt-2">
|
||||||
Welcome to your dashboard
|
Welcome back! Here's an overview of your spaces.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="bg-card border rounded-lg p-4 shadow-sm min-w-[200px]">
|
||||||
|
<p class="text-sm font-medium text-muted-foreground">Total Balance</p>
|
||||||
|
<p class={ "text-2xl font-bold", templ.KV("text-destructive", totalBalance < 0) }>
|
||||||
|
{ fmt.Sprintf("$%.2f", float64(totalBalance)/100.0) }
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
for _, space := range spaces {
|
||||||
|
<a href={ templ.SafeURL("/app/spaces/" + space.ID) } class="block hover:no-underline group">
|
||||||
|
@card.Card(card.Props{ Class: "h-full transition-colors group-hover:border-primary" }) {
|
||||||
|
@card.Header() {
|
||||||
|
@card.Title() { { space.Name } }
|
||||||
|
@card.Description() {
|
||||||
|
Manage expenses and shopping lists in this space.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@card.Content() {
|
||||||
|
// You could add some summary stats here later
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option to create a new space
|
||||||
|
@card.Card(card.Props{ Class: "h-full border-dashed" }) {
|
||||||
|
@card.Content(card.ContentProps{ Class: "h-full flex flex-col items-center justify-center py-12" }) {
|
||||||
|
<p class="text-muted-foreground mb-4">Need another space?</p>
|
||||||
|
// TODO: Add a button or link to create a new space
|
||||||
|
<span class="text-sm font-medium opacity-50">Create Space (Coming Soon)</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue