feat(transaction): #243 add pagination to transactions
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 2m28s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 2m34s

This commit was merged in pull request #249.
This commit is contained in:
2025-08-08 23:44:29 +02:00
parent 867c0ca1cd
commit 0517e7ec89
7 changed files with 106 additions and 11 deletions

View File

@@ -15,7 +15,6 @@ import (
type migrationLogger struct{}
func (l migrationLogger) Printf(format string, v ...any) {
//nolint:noctx
slog.Info(format, v...)
}
func (l migrationLogger) Verbose() bool {

View File

@@ -58,6 +58,7 @@ func (h TransactionImpl) handleTransactionPage() http.HandlerFunc {
AccountId: r.URL.Query().Get("account-id"),
TreasureChestId: r.URL.Query().Get("treasure-chest-id"),
Error: r.URL.Query().Get("error"),
Page: r.URL.Query().Get("page"),
}
transactions, err := h.s.GetAll(r.Context(), user, filter)

View File

@@ -7,12 +7,15 @@ import (
"log/slog"
"spend-sparrow/internal/db"
"spend-sparrow/internal/types"
"strconv"
"time"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
)
const page_size = 25
type Transaction interface {
Add(ctx context.Context, tx *sqlx.Tx, user *types.User, transaction types.Transaction) (*types.Transaction, error)
Update(ctx context.Context, user *types.User, transaction types.Transaction) (*types.Transaction, error)
@@ -231,22 +234,41 @@ func (s TransactionImpl) GetAll(ctx context.Context, user *types.User, filter ty
return nil, ErrUnauthorized
}
var (
page int64
offset int64
err error
)
if filter.Page != "" {
page, err = strconv.ParseInt(filter.Page, 10, 64)
if err != nil {
offset = 0
} else {
offset = page - 1
offset *= page_size
}
}
transactions := make([]*types.Transaction, 0)
err := s.db.SelectContext(ctx, &transactions, `
err = s.db.SelectContext(ctx, &transactions, `
SELECT *
FROM "transaction"
WHERE user_id = ?
AND (? = '' OR account_id = ?)
AND (? = '' OR treasure_chest_id = ?)
AND (? = ''
OR (? = "true" AND error IS NOT NULL)
OR (? = "false" AND error IS NULL)
AND ($1 = '' OR account_id = $1)
AND ($2 = '' OR treasure_chest_id = $2)
AND ($3 = ''
OR ($3 = "true" AND error IS NOT NULL)
OR ($3 = "false" AND error IS NULL)
)
ORDER BY timestamp DESC, created_at DESC`,
ORDER BY timestamp DESC, created_at DESC
LIMIT $4 OFFSET $5
`,
user.Id,
filter.AccountId, filter.AccountId,
filter.TreasureChestId, filter.TreasureChestId,
filter.Error, filter.Error, filter.Error)
filter.AccountId,
filter.TreasureChestId,
filter.Error,
page_size,
offset)
err = db.TransformAndLogDbError(ctx, "transaction GetAll", nil, err)
if err != nil {
return nil, err

View File

@@ -31,6 +31,7 @@ templ Layout(slot templ.Component, user templ.Component, loggedIn bool, path str
<script src="/static/js/htmx.min.js"></script>
<script src="/static/js/toast.js"></script>
<script src="/static/js/layout.js"></script>
<script src="/static/js/transaction.js"></script>
<script src="/static/js/time.js"></script>
<script src="/static/js/echarts.min.js"></script>
<script src="/static/js/dashboard.js" defer></script>

View File

@@ -10,6 +10,7 @@ templ Transaction(items templ.Component, filter types.TransactionItemsFilter, ac
<div class="max-w-6xl mt-10 mx-auto">
<div class="flex items-center gap-4">
<form
id="transactionFilterForm"
hx-get="/transaction"
hx-target="#transaction-items"
hx-push-url="true"
@@ -52,6 +53,7 @@ templ Transaction(items templ.Component, filter types.TransactionItemsFilter, ac
selected?={ filter.Error == "false" }
>Has no Errors</option>
</select>
<input id="page" name="page" type="hidden" value={ filter.Page }/>
</form>
<button
hx-get="/transaction/new"
@@ -63,7 +65,25 @@ templ Transaction(items templ.Component, filter types.TransactionItemsFilter, ac
<p>New Transaction</p>
</button>
</div>
<div class="flex justify-end items-center gap-5 mt-5">
<button id="pagePrev1" class="text-2xl p-2 text-yellow-700 font-black hover:bg-gray-200 rounded-lg decoration-yellow-400 decoration-[0.25rem] hover:underline">
&lt;
</button>
<span class="text-gray-400 text-sm">Page: <span class="text-gray-800 text-xl" id="page1">{ getPageNumber(filter.Page) }</span></span>
<button id="pageNext1" class="text-2xl p-2 text-yellow-700 font-black hover:bg-gray-200 rounded-lg decoration-yellow-400 decoration-[0.25rem] hover:underline">
&gt;
</button>
</div>
@items
<div class="flex justify-end items-center gap-5 mt-5">
<button id="pagePrev2" class="text-2xl p-2 text-yellow-700 font-black hover:bg-gray-200 rounded-lg decoration-yellow-400 decoration-[0.25rem] hover:underline">
&lt;
</button>
<span class="text-gray-400 text-sm">Page: <span class="text-gray-800 text-xl" id="page2">{ getPageNumber(filter.Page) }</span></span>
<button id="pageNext2" class="text-2xl p-2 text-yellow-700 font-black hover:bg-gray-200 rounded-lg decoration-yellow-400 decoration-[0.25rem] hover:underline">
&gt;
</button>
</div>
</div>
}
@@ -287,3 +307,11 @@ func formatFloat(balance int64) string {
euros := float64(balance) / 100
return fmt.Sprintf("%.2f", euros)
}
func getPageNumber(page string) string {
if page == "" {
return "1"
} else {
return page
}
}

View File

@@ -51,4 +51,5 @@ type TransactionItemsFilter struct {
AccountId string
TreasureChestId string
Error string
Page string
}