Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion src/internal/assets/templates/plan_details.html
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ <h3 class="text-lg font-semibold mb-4">Your Payments</h3>

<!-- All Member Payments Section (Only for owner) -->
{{if .is_owner}}
<div class="mb-8">
<div id="member-payments" class="mb-8">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Member Payments</h3>
<button
Expand Down Expand Up @@ -513,9 +513,52 @@ <h3 class="text-lg font-semibold">Member Payments</h3>
</tbody>
</table>
</div>
{{else if gt .member_payments_pagination.CurrentPage 1}}
<p class="text-sm text-gray-500 italic">
No member payments on page {{.member_payments_pagination.CurrentPage}}.
</p>
{{else}}
<p class="text-sm text-gray-500 italic">No member payments yet.</p>
{{end}}

{{if or .member_payments_pagination.HasPrev .member_payments_pagination.HasNext}}
<div class="mt-4 flex items-center justify-between gap-3">
<div class="text-sm text-gray-500">
Page {{.member_payments_pagination.CurrentPage}}
</div>
<div class="flex items-center gap-2">
{{if .member_payments_pagination.HasPrev}}
<a
href="/{{.plan.JoinCode}}?member_payments_page={{.member_payments_pagination.PrevPage}}#member-payments"
class="inline-flex items-center rounded border border-gray-300 px-3 py-1 text-sm font-medium text-gray-700 hover:bg-gray-50"
>
Previous
</a>
{{else}}
<span
class="inline-flex items-center rounded border border-gray-200 px-3 py-1 text-sm font-medium text-gray-400"
>
Previous
</span>
{{end}}

{{if .member_payments_pagination.HasNext}}
<a
href="/{{.plan.JoinCode}}?member_payments_page={{.member_payments_pagination.NextPage}}#member-payments"
class="inline-flex items-center rounded border border-gray-300 px-3 py-1 text-sm font-medium text-gray-700 hover:bg-gray-50"
>
Next
</a>
{{else}}
<span
class="inline-flex items-center rounded border border-gray-200 px-3 py-1 text-sm font-medium text-gray-400"
>
Next
</span>
{{end}}
</div>
</div>
{{end}}
</div>
{{end}}

Expand Down
9 changes: 9 additions & 0 deletions src/internal/domain/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,12 @@ type Payment struct {
Username string `json:"username"`
Name string `json:"name"`
}

// MemberPaymentsPagination describes the owner payments table pagination state.
type MemberPaymentsPagination struct {
CurrentPage int `json:"current_page"`
HasPrev bool `json:"has_prev"`
PrevPage int `json:"prev_page"`
HasNext bool `json:"has_next"`
NextPage int `json:"next_page"`
}
41 changes: 24 additions & 17 deletions src/internal/http/handlers/plans/details.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,20 @@ func HandlePlanDetails(app *pocketbase.PocketBase) echo.HandlerFunc {
pendingPayments := []domain.Payment{}
userPayments := []domain.Payment{}
allPayments := []domain.Payment{}
memberPaymentsPagination := buildMemberPaymentsPagination(1, false)
if isMember {
if isOwner {
pendingPayments, err = loadPendingPayments(app, planRecord.Id)
if err != nil {
return err
}

allPayments, err = loadAllPayments(app, planRecord.Id)
allPayments, memberPaymentsPagination, err = loadAllPaymentsPage(
app,
planRecord.Id,
memberPaymentsPage(c.QueryParam(memberPaymentsPageParam)),
memberPaymentsPageSize,
)
if err != nil {
return err
}
Expand All @@ -97,22 +103,23 @@ func HandlePlanDetails(app *pocketbase.PocketBase) echo.HandlerFunc {
}

return view.RenderPage(c, "plan_details.html", map[string]interface{}{
"title": familyPlan.Name,
"plan": familyPlan,
"is_owner": isOwner,
"is_member": isMember,
"members": members,
"total_members": totalMembers,
"join_requests": joinRequests,
"pending_request": pendingRequest,
"pending_payments": pendingPayments,
"user_payments": userPayments,
"user_balance": userBalance,
"existingMembership": existingMembership,
"all_payments": allPayments,
"total_payments": calculateTotalPayments(app, planRecord.Id),
"total_savings": totalSavings,
"plan_age_days": planAgeDays,
"title": familyPlan.Name,
"plan": familyPlan,
"is_owner": isOwner,
"is_member": isMember,
"members": members,
"total_members": totalMembers,
"join_requests": joinRequests,
"pending_request": pendingRequest,
"pending_payments": pendingPayments,
"user_payments": userPayments,
"user_balance": userBalance,
"existingMembership": existingMembership,
"all_payments": allPayments,
"member_payments_pagination": memberPaymentsPagination,
"total_payments": calculateTotalPayments(app, planRecord.Id),
"total_savings": totalSavings,
"plan_age_days": planAgeDays,
})
}
}
33 changes: 33 additions & 0 deletions src/internal/http/handlers/plans/details_pagination.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package plans

import "familyplan/src/internal/domain"
import "strconv"

const (
memberPaymentsPageParam = "member_payments_page"
memberPaymentsPageSize = 10
)

func memberPaymentsPage(raw string) int {
page, err := strconv.Atoi(raw)
if err != nil || page < 1 {
return 1
}

return page
}

func buildMemberPaymentsPagination(page int, hasNext bool) domain.MemberPaymentsPagination {
prevPage := 1
if page > 1 {
prevPage = page - 1
}

return domain.MemberPaymentsPagination{
CurrentPage: page,
HasPrev: page > 1,
PrevPage: prevPage,
HasNext: hasNext,
NextPage: page + 1,
}
}
60 changes: 60 additions & 0 deletions src/internal/http/handlers/plans/details_pagination_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package plans

import "testing"

func TestMemberPaymentsPageDefaultsToOne(t *testing.T) {
t.Parallel()

tests := []string{"", "0", "-2", "abc"}
for _, raw := range tests {
if got := memberPaymentsPage(raw); got != 1 {
t.Fatalf("memberPaymentsPage(%q) = %d, want 1", raw, got)
}
}
}

func TestMemberPaymentsPageParsesPositiveValue(t *testing.T) {
t.Parallel()

if got := memberPaymentsPage("3"); got != 3 {
t.Fatalf("memberPaymentsPage(3) = %d, want 3", got)
}
}

func TestBuildMemberPaymentsPagination(t *testing.T) {
t.Parallel()

t.Run("first page", func(t *testing.T) {
got := buildMemberPaymentsPagination(1, false)

if got.CurrentPage != 1 {
t.Fatalf("CurrentPage = %d, want 1", got.CurrentPage)
}
if got.HasPrev {
t.Fatalf("HasPrev = %t, want false", got.HasPrev)
}
if got.PrevPage != 1 {
t.Fatalf("PrevPage = %d, want 1", got.PrevPage)
}
if got.HasNext {
t.Fatalf("HasNext = %t, want false", got.HasNext)
}
if got.NextPage != 2 {
t.Fatalf("NextPage = %d, want 2", got.NextPage)
}
})

t.Run("middle page", func(t *testing.T) {
got := buildMemberPaymentsPagination(3, true)

if got.CurrentPage != 3 {
t.Fatalf("CurrentPage = %d, want 3", got.CurrentPage)
}
if !got.HasPrev || got.PrevPage != 2 {
t.Fatalf("unexpected prev page data: %+v", got)
}
if !got.HasNext || got.NextPage != 4 {
t.Fatalf("unexpected next page data: %+v", got)
}
})
}
Loading
Loading