feat(transaction-recurring): #100 add summary for recurring transactions #131

Merged
tim merged 1 commits from 100-add-summary into prod 2025-05-26 16:31:51 +00:00
3 changed files with 70 additions and 8 deletions

View File

@@ -10,6 +10,7 @@ import (
"spend-sparrow/utils"
"github.com/a-h/templ"
"github.com/google/uuid"
)
type TreasureChest interface {
@@ -51,7 +52,15 @@ func (h TreasureChestImpl) handleTreasureChestPage() http.HandlerFunc {
return
}
comp := t.TreasureChest(treasureChests)
transactionsRecurring, err := h.transactionRecurring.GetAll(user)
if err != nil {
handleError(w, r, err)
return
}
monthlySums := h.calculateMonthlySums(treasureChests, transactionsRecurring)
comp := t.TreasureChest(treasureChests, monthlySums)
h.r.RenderLayout(r, w, comp, user)
}
}
@@ -94,7 +103,8 @@ func (h TreasureChestImpl) handleTreasureChestItemComp() http.HandlerFunc {
if r.URL.Query().Get("edit") == "true" {
comp = t.EditTreasureChest(treasureChest, treasureChests, transactionsRec)
} else {
comp = t.TreasureChestItem(treasureChest)
monthlySums := h.calculateMonthlySums(treasureChests, transactionsRecurring)
comp = t.TreasureChestItem(treasureChest, monthlySums)
}
h.r.Render(r, w, comp)
}
@@ -129,7 +139,16 @@ func (h TreasureChestImpl) handleUpdateTreasureChest() http.HandlerFunc {
}
}
comp := t.TreasureChestItem(treasureChest)
transactionsRecurring, err := h.transactionRecurring.GetAllByTreasureChest(user, treasureChest.Id.String())
if err != nil {
handleError(w, r, err)
return
}
treasureChests := make([]*types.TreasureChest, 1)
treasureChests[0] = treasureChest
monthlySums := h.calculateMonthlySums(treasureChests, transactionsRecurring)
comp := t.TreasureChestItem(treasureChest, monthlySums)
h.r.Render(r, w, comp)
}
}
@@ -151,3 +170,19 @@ func (h TreasureChestImpl) handleDeleteTreasureChest() http.HandlerFunc {
}
}
}
func (h TreasureChestImpl) calculateMonthlySums(
treasureChests []*types.TreasureChest,
transactionsRecurring []*types.TransactionRecurring,
) map[uuid.UUID]int64 {
monthlySums := make(map[uuid.UUID]int64)
for _, tc := range treasureChests {
monthlySums[tc.Id] = 0
}
for _, t := range transactionsRecurring {
if t.TreasureChestId != nil && t.Value > 0 && t.IntervalMonths > 0 {
monthlySums[*t.TreasureChestId] += t.Value / t.IntervalMonths
}
}
return monthlySums
}

View File

@@ -29,6 +29,7 @@ var (
type TransactionRecurring interface {
Add(user *types.User, transactionRecurring types.TransactionRecurringInput) (*types.TransactionRecurring, error)
Update(user *types.User, transactionRecurring types.TransactionRecurringInput) (*types.TransactionRecurring, error)
GetAll(user *types.User) ([]*types.TransactionRecurring, error)
GetAllByAccount(user *types.User, accountId string) ([]*types.TransactionRecurring, error)
GetAllByTreasureChest(user *types.User, treasureChestId string) ([]*types.TransactionRecurring, error)
Delete(user *types.User, id string) error
@@ -158,6 +159,27 @@ func (s TransactionRecurringImpl) Update(
return transactionRecurring, nil
}
func (s TransactionRecurringImpl) GetAll(user *types.User) ([]*types.TransactionRecurring, error) {
transactionRecurringMetric.WithLabelValues("get_all_by_account").Inc()
if user == nil {
return nil, ErrUnauthorized
}
transactionRecurrings := make([]*types.TransactionRecurring, 0)
err := s.db.Select(&transactionRecurrings, `
SELECT *
FROM transaction_recurring
WHERE user_id = ?
ORDER BY created_at DESC`,
user.Id)
err = db.TransformAndLogDbError("transactionRecurring GetAll", nil, err)
if err != nil {
return nil, err
}
return transactionRecurrings, nil
}
func (s TransactionRecurringImpl) GetAllByAccount(user *types.User, accountId string) ([]*types.TransactionRecurring, error) {
transactionRecurringMetric.WithLabelValues("get_all_by_account").Inc()
if user == nil {

View File

@@ -5,7 +5,7 @@ import "spend-sparrow/template/svg"
import "spend-sparrow/types"
import "github.com/google/uuid"
templ TreasureChest(treasureChests []*types.TreasureChest) {
templ TreasureChest(treasureChests []*types.TreasureChest, monthlySums map[uuid.UUID]int64) {
<div class="max-w-6xl mt-10 mx-auto">
<button
hx-get="/treasurechest/new"
@@ -18,7 +18,7 @@ templ TreasureChest(treasureChests []*types.TreasureChest) {
</button>
<div id="treasurechest-items" class="my-6 flex flex-col">
for _, treasureChest := range treasureChests {
@TreasureChestItem(treasureChest)
@TreasureChestItem(treasureChest, monthlySums)
}
</div>
</div>
@@ -114,7 +114,7 @@ templ EditTreasureChest(treasureChest *types.TreasureChest, parents []*types.Tre
</div>
}
templ TreasureChestItem(treasureChest *types.TreasureChest) {
templ TreasureChestItem(treasureChest *types.TreasureChest, monthlySums map[uuid.UUID]int64) {
{{
var indentation string
viewTransactions := ""
@@ -128,11 +128,16 @@ templ TreasureChestItem(treasureChest *types.TreasureChest) {
<div id={ "treasurechest-" + treasureChest.Id.String() } class={ "border-1 border-gray-300 p-4 bg-gray-50 rounded-lg" + indentation }>
<div class="text-xl flex justify-end items-center gap-4">
<p class="mr-auto">{ treasureChest.Name }</p>
<p class="mr-20 text-gray-600">
if treasureChest.ParentId != nil {
+ { displayBalance(monthlySums[treasureChest.Id]) } <span class="text-gray-500 text-sm">&nbsp;per month</span>
}
</p>
if treasureChest.ParentId != nil {
if treasureChest.CurrentBalance < 0 {
<p class="mr-20 text-red-700">{ displayBalance(treasureChest.CurrentBalance) }</p>
<p class="mr-20 min-w-20 text-right text-red-700">{ displayBalance(treasureChest.CurrentBalance) }</p>
} else {
<p class="mr-20 text-green-700">{ displayBalance(treasureChest.CurrentBalance) }</p>
<p class="mr-20 min-w-20 text-right text-green-700">{ displayBalance(treasureChest.CurrentBalance) }</p>
}
}
<a