feat: show recent transactions and view all transactions

This commit is contained in:
juancwu 2026-05-03 17:59:41 +00:00
commit 054f1227f0
8 changed files with 313 additions and 12 deletions

View file

@ -0,0 +1,56 @@
package blocks
import "git.juancwu.dev/juancwu/budgit/internal/model"
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/icon"
import "git.juancwu.dev/juancwu/budgit/internal/ui/utils"
templ TransactionList(txns []*model.Transaction) {
if len(txns) == 0 {
<div class="text-sm text-muted-foreground py-6 text-center">
No transactions yet.
</div>
} else {
<ul class="divide-y">
for _, t := range txns {
@transactionRow(t)
}
</ul>
}
}
templ transactionRow(t *model.Transaction) {
{{
isDeposit := t.Type == model.TransactionTypeDeposit
amountClasses := []string{"text-sm font-semibold tabular-nums"}
sign := "-"
if isDeposit {
amountClasses = append(amountClasses, "text-green-600 dark:text-green-400")
sign = "+"
} else {
amountClasses = append(amountClasses, "text-red-600 dark:text-red-400")
}
}}
<li class="flex items-center justify-between gap-4 py-3">
<div class="flex items-center gap-3 min-w-0">
<div class="w-9 h-9 shrink-0 rounded-full bg-muted flex items-center justify-center">
if isDeposit {
@icon.BanknoteArrowDown(icon.Props{Class: "size-4 text-green-600 dark:text-green-400"})
} else {
@icon.HandCoins(icon.Props{Class: "size-4 text-red-600 dark:text-red-400"})
}
</div>
<div class="min-w-0">
<p class="font-medium truncate">{ t.Title }</p>
<p class="text-xs text-muted-foreground">{ t.OccurredAt.Format("Jan 2, 2006") }</p>
</div>
</div>
<div class="text-right shrink-0">
<p class={ utils.TwMerge(amountClasses...) }>
{ sign }${ utils.FormatDecimalWithThousands(t.Value.StringFixedBank(2)) }
</p>
if t.Description != nil && *t.Description != "" {
<p class="text-xs text-muted-foreground truncate max-w-[200px]">{ *t.Description }</p>
}
</div>
</li>
}

View file

@ -1,7 +1,9 @@
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/ui/blocks"
import "git.juancwu.dev/juancwu/budgit/internal/ui/layouts"
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/card"
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/button"
@ -9,10 +11,11 @@ import "git.juancwu.dev/juancwu/budgit/internal/ui/components/icon"
import "git.juancwu.dev/juancwu/budgit/internal/ui/utils"
type SpaceAccountPageProps struct {
SpaceID string
AccountID string
AccountName string
AccountBalance decimal.Decimal
SpaceID string
AccountID string
AccountName string
AccountBalance decimal.Decimal
RecentTransactions []*model.Transaction
}
templ SpaceAccountPage(props SpaceAccountPageProps) {
@ -43,7 +46,7 @@ templ SpaceAccountPage(props SpaceAccountPageProps) {
@card.Card(card.Props{Class: "rounded-sm col-span-full md:col-span-4"}) {
@card.Header() {
@card.Title() {
Quick Actions
Quick Actions
}
}
@card.Content(card.ContentProps{Class: "space-y-4"}) {
@ -77,14 +80,24 @@ templ SpaceAccountPage(props SpaceAccountPageProps) {
@card.Header() {
<div>
@card.Title() {
Transaction History
Recent Transactions
}
@card.Description() {
Overview of your activity for August 2024
Your latest activity on this account.
}
</div>
}
@card.Content() {
@blocks.TransactionList(props.RecentTransactions)
}
@card.Footer(card.FooterProps{Class: "justify-end"}) {
@button.Button(button.Props{
Variant: button.VariantLink,
Href: routeurl.URL("page.app.spaces.space.accounts.account.transactions", "spaceID", props.SpaceID, "accountID", props.AccountID),
}) {
View all transactions
@icon.ChevronRight()
}
}
}
</div>

View file

@ -0,0 +1,118 @@
package pages
import "fmt"
import "git.juancwu.dev/juancwu/budgit/internal/model"
import "git.juancwu.dev/juancwu/budgit/internal/routeurl"
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/button"
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/card"
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/icon"
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/pagination"
type SpaceAccountTransactionsPageProps struct {
SpaceID string
AccountID string
AccountName string
Transactions []*model.Transaction
CurrentPage int
TotalPages int
TotalCount int
PerPage int
}
templ SpaceAccountTransactionsPage(props SpaceAccountTransactionsPageProps) {
@layouts.App("Transactions", spaceOverviewSidebarContent(), spaceSpecificSidebarContent(props.SpaceID)) {
<div class="container px-6 py-8 mx-auto space-y-8">
<div class="flex items-start justify-between flex-wrap gap-4">
<div>
<h1 class="text-3xl font-bold">Transactions</h1>
<p class="text-muted-foreground mt-1">
All activity for { props.AccountName }.
</p>
</div>
<div class="flex gap-2 flex-wrap">
@button.Button(button.Props{
Variant: button.VariantDefault,
Href: routeurl.URL("page.app.spaces.space.accounts.account.bills.create", "spaceID", props.SpaceID, "accountID", props.AccountID),
Class: "flex gap-2 items-center",
}) {
@icon.HandCoins()
Pay Bills
}
@button.Button(button.Props{
Variant: button.VariantSecondary,
Class: "flex gap-2 items-center",
}) {
@icon.BanknoteArrowDown()
Deposit Funds
}
</div>
</div>
@card.Card() {
@card.Header() {
@card.Title() {
All Transactions
}
@card.Description() {
{ transactionsRangeLabel(props) }
}
}
@card.Content() {
@blocks.TransactionList(props.Transactions)
}
if props.TotalPages > 1 {
@card.Footer() {
@transactionsPagination(props)
}
}
}
</div>
}
}
func transactionsRangeLabel(props SpaceAccountTransactionsPageProps) string {
if props.TotalCount == 0 {
return "No transactions yet."
}
start := (props.CurrentPage-1)*props.PerPage + 1
end := start + len(props.Transactions) - 1
return fmt.Sprintf("Showing %d%d of %d", start, end, props.TotalCount)
}
func transactionsPageURL(spaceID, accountID string, page int) string {
base := routeurl.URL("page.app.spaces.space.accounts.account.transactions", "spaceID", spaceID, "accountID", accountID)
return fmt.Sprintf("%s?page=%d", base, page)
}
templ transactionsPagination(props SpaceAccountTransactionsPageProps) {
{{ p := pagination.CreatePagination(props.CurrentPage, props.TotalPages, 5) }}
@pagination.Pagination() {
@pagination.Content() {
@pagination.Item() {
@pagination.Previous(pagination.PreviousProps{
Href: transactionsPageURL(props.SpaceID, props.AccountID, p.CurrentPage-1),
Disabled: !p.HasPrevious,
Label: "Previous",
})
}
for _, page := range p.Pages {
@pagination.Item() {
@pagination.Link(pagination.LinkProps{
Href: transactionsPageURL(props.SpaceID, props.AccountID, page),
IsActive: page == p.CurrentPage,
}) {
{ fmt.Sprintf("%d", page) }
}
}
}
@pagination.Item() {
@pagination.Next(pagination.NextProps{
Href: transactionsPageURL(props.SpaceID, props.AccountID, p.CurrentPage+1),
Disabled: !p.HasNext,
Label: "Next",
})
}
}
}
}