feat(transaction): #243 add pagination to transactions
Some checks failed
Build Docker Image / Build-Docker-Image (push) Has been cancelled
Some checks failed
Build Docker Image / Build-Docker-Image (push) Has been cancelled
This commit is contained in:
@@ -58,6 +58,7 @@ func (h TransactionImpl) handleTransactionPage() http.HandlerFunc {
|
|||||||
AccountId: r.URL.Query().Get("account-id"),
|
AccountId: r.URL.Query().Get("account-id"),
|
||||||
TreasureChestId: r.URL.Query().Get("treasure-chest-id"),
|
TreasureChestId: r.URL.Query().Get("treasure-chest-id"),
|
||||||
Error: r.URL.Query().Get("error"),
|
Error: r.URL.Query().Get("error"),
|
||||||
|
Page: r.URL.Query().Get("page"),
|
||||||
}
|
}
|
||||||
|
|
||||||
transactions, err := h.s.GetAll(r.Context(), user, filter)
|
transactions, err := h.s.GetAll(r.Context(), user, filter)
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"spend-sparrow/internal/db"
|
"spend-sparrow/internal/db"
|
||||||
"spend-sparrow/internal/types"
|
"spend-sparrow/internal/types"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const page_size = 25
|
||||||
|
|
||||||
type Transaction interface {
|
type Transaction interface {
|
||||||
Add(ctx context.Context, tx *sqlx.Tx, user *types.User, transaction types.Transaction) (*types.Transaction, error)
|
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)
|
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
|
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 = offset * page_size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
transactions := make([]*types.Transaction, 0)
|
transactions := make([]*types.Transaction, 0)
|
||||||
err := s.db.SelectContext(ctx, &transactions, `
|
err = s.db.SelectContext(ctx, &transactions, `
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM "transaction"
|
FROM "transaction"
|
||||||
WHERE user_id = ?
|
WHERE user_id = ?
|
||||||
AND (? = '' OR account_id = ?)
|
AND ($1 = '' OR account_id = $1)
|
||||||
AND (? = '' OR treasure_chest_id = ?)
|
AND ($2 = '' OR treasure_chest_id = $2)
|
||||||
AND (? = ''
|
AND ($3 = ''
|
||||||
OR (? = "true" AND error IS NOT NULL)
|
OR ($3 = "true" AND error IS NOT NULL)
|
||||||
OR (? = "false" AND error IS 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,
|
user.Id,
|
||||||
filter.AccountId, filter.AccountId,
|
filter.AccountId,
|
||||||
filter.TreasureChestId, filter.TreasureChestId,
|
filter.TreasureChestId,
|
||||||
filter.Error, filter.Error, filter.Error)
|
filter.Error,
|
||||||
|
page_size,
|
||||||
|
offset)
|
||||||
err = db.TransformAndLogDbError(ctx, "transaction GetAll", nil, err)
|
err = db.TransformAndLogDbError(ctx, "transaction GetAll", nil, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -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/htmx.min.js"></script>
|
||||||
<script src="/static/js/toast.js"></script>
|
<script src="/static/js/toast.js"></script>
|
||||||
<script src="/static/js/layout.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/time.js"></script>
|
||||||
<script src="/static/js/echarts.min.js"></script>
|
<script src="/static/js/echarts.min.js"></script>
|
||||||
<script src="/static/js/dashboard.js" defer></script>
|
<script src="/static/js/dashboard.js" defer></script>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ templ Transaction(items templ.Component, filter types.TransactionItemsFilter, ac
|
|||||||
<div class="max-w-6xl mt-10 mx-auto">
|
<div class="max-w-6xl mt-10 mx-auto">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<form
|
<form
|
||||||
|
id="transactionFilterForm"
|
||||||
hx-get="/transaction"
|
hx-get="/transaction"
|
||||||
hx-target="#transaction-items"
|
hx-target="#transaction-items"
|
||||||
hx-push-url="true"
|
hx-push-url="true"
|
||||||
@@ -52,6 +53,7 @@ templ Transaction(items templ.Component, filter types.TransactionItemsFilter, ac
|
|||||||
selected?={ filter.Error == "false" }
|
selected?={ filter.Error == "false" }
|
||||||
>Has no Errors</option>
|
>Has no Errors</option>
|
||||||
</select>
|
</select>
|
||||||
|
<input id="page" name="page" type="hidden" value={ filter.Page }/>
|
||||||
</form>
|
</form>
|
||||||
<button
|
<button
|
||||||
hx-get="/transaction/new"
|
hx-get="/transaction/new"
|
||||||
@@ -63,7 +65,25 @@ templ Transaction(items templ.Component, filter types.TransactionItemsFilter, ac
|
|||||||
<p>New Transaction</p>
|
<p>New Transaction</p>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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">
|
||||||
|
<
|
||||||
|
</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">
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
@items
|
@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">
|
||||||
|
<
|
||||||
|
</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">
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,3 +307,11 @@ func formatFloat(balance int64) string {
|
|||||||
euros := float64(balance) / 100
|
euros := float64(balance) / 100
|
||||||
return fmt.Sprintf("%.2f", euros)
|
return fmt.Sprintf("%.2f", euros)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPageNumber(page string) string {
|
||||||
|
if page == "" {
|
||||||
|
return "1"
|
||||||
|
} else {
|
||||||
|
return page
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -51,4 +51,5 @@ type TransactionItemsFilter struct {
|
|||||||
AccountId string
|
AccountId string
|
||||||
TreasureChestId string
|
TreasureChestId string
|
||||||
Error string
|
Error string
|
||||||
|
Page string
|
||||||
}
|
}
|
||||||
|
|||||||
43
static/js/transaction.js
Normal file
43
static/js/transaction.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
if (!page || !page1 || !pagePrev1 || !pageNext1 || !page2 || !pagePrev2 || !pageNext2 || !transactionFilterForm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const scrollToTop = function() {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
};
|
||||||
|
const incPage = function() {
|
||||||
|
const currPage = Number(page.value);
|
||||||
|
var nextPage = currPage
|
||||||
|
if (currPage > 1) {
|
||||||
|
nextPage -= 1;
|
||||||
|
page.value = nextPage;
|
||||||
|
transactionFilterForm.dispatchEvent(new Event('change'));
|
||||||
|
}
|
||||||
|
page1.textContent = nextPage;
|
||||||
|
page2.textContent = nextPage;
|
||||||
|
scrollToTop();
|
||||||
|
};
|
||||||
|
const decPage = function() {
|
||||||
|
const currPage = Number(page.value);
|
||||||
|
var nextPage = currPage + 1;
|
||||||
|
page.value = nextPage;
|
||||||
|
transactionFilterForm.dispatchEvent(new Event('change'));
|
||||||
|
page1.textContent = nextPage;
|
||||||
|
page2.textContent = nextPage;
|
||||||
|
scrollToTop();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pagePrev1.addEventListener("click", incPage);
|
||||||
|
pagePrev2.addEventListener("click", incPage);
|
||||||
|
|
||||||
|
pageNext1.addEventListener("click", decPage);
|
||||||
|
pageNext2.addEventListener("click", decPage);
|
||||||
|
|
||||||
|
console.log("initialized pagination");
|
||||||
|
})
|
||||||
|
|
||||||
Reference in New Issue
Block a user