feat: savings allocations
All checks were successful
Deploy / build-and-deploy (push) Successful in 1m31s

This commit is contained in:
juancwu 2026-05-04 03:19:36 +00:00
commit 2dac136049
17 changed files with 1140 additions and 4 deletions

View file

@ -3,6 +3,7 @@ package pages
import "github.com/shopspring/decimal"
import "git.juancwu.dev/juancwu/budgit/internal/model"
import "git.juancwu.dev/juancwu/budgit/internal/routeurl"
import "git.juancwu.dev/juancwu/budgit/internal/service"
import "git.juancwu.dev/juancwu/budgit/internal/ui/blocks"
import "git.juancwu.dev/juancwu/budgit/internal/ui/layouts"
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/card"
@ -18,6 +19,7 @@ type SpaceAccountPageProps struct {
AccountBalance decimal.Decimal
RecentTransactions []*model.Transaction
NonEditableTransactionIDs map[string]bool
AllocationSummary *service.AllocationSummary
}
templ SpaceAccountPage(props SpaceAccountPageProps) {
@ -42,7 +44,7 @@ templ SpaceAccountPage(props SpaceAccountPageProps) {
}
@card.Content() {
<h1 class={ utils.TwMerge(balanceTextClasses...) }>${ utils.FormatDecimalWithThousands(props.AccountBalance.StringFixedBank(2)) }</h1>
<p class="text-sm text-muted-foreground">Available Balance</p>
<p class="text-sm text-muted-foreground">Account Balance</p>
}
}
@card.Card(card.Props{Class: "rounded-sm col-span-full md:col-span-4"}) {
@ -79,6 +81,11 @@ templ SpaceAccountPage(props SpaceAccountPageProps) {
}
}
</div>
@blocks.AllocationsSection(blocks.AllocationsSectionProps{
SpaceID: props.SpaceID,
AccountID: props.AccountID,
Summary: props.AllocationSummary,
})
<div>
@card.Card() {
@card.Header() {

View file

@ -2,6 +2,7 @@ package pages
import "encoding/json"
import "fmt"
import "sort"
import "strings"
import "git.juancwu.dev/juancwu/budgit/internal/model"
@ -77,6 +78,12 @@ templ activityIcon(action model.SpaceAuditAction) {
@icon.Pencil(icon.Props{Class: "size-4 text-muted-foreground"})
case model.SpaceAuditActionAccountDeleted:
@icon.Trash2(icon.Props{Class: "size-4 text-destructive"})
case model.SpaceAuditActionAllocationCreated:
@icon.Plus(icon.Props{Class: "size-4 text-muted-foreground"})
case model.SpaceAuditActionAllocationUpdated:
@icon.Pencil(icon.Props{Class: "size-4 text-muted-foreground"})
case model.SpaceAuditActionAllocationDeleted:
@icon.Trash2(icon.Props{Class: "size-4 text-destructive"})
default:
@icon.History(icon.Props{Class: "size-4 text-muted-foreground"})
}
@ -166,6 +173,45 @@ func activityMessage(log *model.SpaceAuditLogWithActor) string {
name = "an account"
}
return fmt.Sprintf("%s deleted the account %s.", actor, bold(name))
case model.SpaceAuditActionAllocationCreated:
var meta struct {
Name string `json:"name"`
Amount string `json:"amount"`
}
_ = json.Unmarshal(log.Metadata, &meta)
name := meta.Name
if name == "" {
name = "a savings goal"
}
if meta.Amount != "" {
return fmt.Sprintf("%s created savings goal %s with $%s.", actor, bold(name), bold(meta.Amount))
}
return fmt.Sprintf("%s created savings goal %s.", actor, bold(name))
case model.SpaceAuditActionAllocationUpdated:
var meta struct {
Changes map[string]map[string]any `json:"changes"`
}
_ = json.Unmarshal(log.Metadata, &meta)
fields := make([]string, 0, len(meta.Changes))
for k := range meta.Changes {
fields = append(fields, k)
}
sort.Strings(fields)
if len(fields) == 0 {
return fmt.Sprintf("%s updated a savings goal.", actor)
}
return fmt.Sprintf("%s updated savings goal (%s).", actor, bold(strings.Join(fields, ", ")))
case model.SpaceAuditActionAllocationDeleted:
var meta struct {
Name string `json:"name"`
Amount string `json:"amount"`
}
_ = json.Unmarshal(log.Metadata, &meta)
name := meta.Name
if name == "" {
name = "a savings goal"
}
return fmt.Sprintf("%s deleted savings goal %s.", actor, bold(name))
default:
return fmt.Sprintf("%s performed %s.", actor, bold(string(log.Action)))
}