From 674cdff87b693ee6a14dd4bb32c443d45190ef78 Mon Sep 17 00:00:00 2001 From: jordi fita mas Date: Fri, 3 May 2024 19:00:02 +0200 Subject: [PATCH] Add a new Cursor form type To hold the common logic of detecting pagination, forming the key, and splitting its values later on. I can take advantage that a form with action="get" already adds its fields to the query string to have a common template for pagination. The only problem is that i have different column spans for different tables, therefore had to add a colspan to the struct. --- pkg/booking/admin.go | 4 +- pkg/booking/filter.go | 37 +++++++++---------- pkg/booking/prebooking.go | 2 +- pkg/customer/admin.go | 4 +- pkg/customer/filter.go | 41 +++++++++++---------- pkg/form/cursor.go | 33 +++++++++++++++++ pkg/template/render.go | 5 +++ web/templates/admin/booking/results.gohtml | 20 +--------- web/templates/admin/customer/results.gohtml | 13 +------ web/templates/admin/form.gohtml | 14 +++++++ 10 files changed, 98 insertions(+), 75 deletions(-) create mode 100644 pkg/form/cursor.go diff --git a/pkg/booking/admin.go b/pkg/booking/admin.go index 5a34d5a..8d6078f 100644 --- a/pkg/booking/admin.go +++ b/pkg/booking/admin.go @@ -172,7 +172,7 @@ func collectBookingEntries(ctx context.Context, conn *database.Conn, lang langua order by lower(stay) desc , booking_id desc LIMIT %d - `, where, filters.perPage+1), args...) + `, where, filters.PerPage()+1), args...) if err != nil { return nil, err } @@ -235,7 +235,7 @@ func (page bookingIndex) MustRender(w http.ResponseWriter, r *http.Request, user } ods.MustWriteResponse(w, table, user.Locale.Pgettext("bookings.ods", "filename")) default: - if httplib.IsHTMxRequest(r) && page.Filters.pagination { + if httplib.IsHTMxRequest(r) && page.Filters.Paginated() { template.MustRenderAdminNoLayout(w, r, user, company, "booking/results.gohtml", page) } else { template.MustRenderAdminFiles(w, r, user, company, page, "booking/index.gohtml", "booking/results.gohtml") diff --git a/pkg/booking/filter.go b/pkg/booking/filter.go index da657f2..1a71113 100644 --- a/pkg/booking/filter.go +++ b/pkg/booking/filter.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "strconv" "strings" "dev.tandem.ws/tandem/camper/pkg/auth" @@ -13,22 +14,17 @@ import ( ) type filterForm struct { - locale *locale.Locale company *auth.Company - perPage int - pagination bool HolderName *form.Input BookingStatus *form.Select FromDate *form.Input ToDate *form.Input - Cursor *form.Input + Cursor *form.Cursor } func newFilterForm(ctx context.Context, conn *database.Conn, company *auth.Company, locale *locale.Locale) *filterForm { return &filterForm{ - locale: locale, company: company, - perPage: 25, HolderName: &form.Input{ Name: "holder_name", }, @@ -42,8 +38,9 @@ func newFilterForm(ctx context.Context, conn *database.Conn, company *auth.Compa ToDate: &form.Input{ Name: "to_date", }, - Cursor: &form.Input{ - Name: "cursor", + Cursor: &form.Cursor{ + Name: "cursor", + PerPage: 25, }, } } @@ -68,7 +65,6 @@ func (f *filterForm) Parse(r *http.Request) error { f.FromDate.FillValue(r) f.ToDate.FillValue(r) f.Cursor.FillValue(r) - f.pagination = f.Cursor.Val != "" return nil } @@ -100,8 +96,8 @@ func (f *filterForm) BuildQuery(args []interface{}) (string, []interface{}) { maybeAppendWhere("lower(stay) >= $%d", f.FromDate.Val, nil) maybeAppendWhere("lower(stay) <= $%d", f.ToDate.Val, nil) - if f.Cursor.Val != "" { - params := strings.Split(f.Cursor.Val, ";") + if f.Paginated() { + params := f.Cursor.Params() if len(params) == 2 { where = append(where, fmt.Sprintf("(lower(stay), booking_id) < ($%d, $%d)", len(args)+1, len(args)+2)) args = append(args, params[0]) @@ -113,14 +109,9 @@ func (f *filterForm) BuildQuery(args []interface{}) (string, []interface{}) { } func (f *filterForm) buildCursor(bookings []*bookingEntry) []*bookingEntry { - if len(bookings) <= f.perPage { - f.Cursor.Val = "" - return bookings - } - bookings = bookings[:f.perPage] - last := bookings[f.perPage-1] - f.Cursor.Val = fmt.Sprintf("%s;%d", last.ArrivalDate.Format(database.ISODateFormat), last.ID) - return bookings + return form.BuildCursor(f.Cursor, bookings, func(entry *bookingEntry) []string { + return []string{entry.ArrivalDate.Format(database.ISODateFormat), strconv.Itoa(entry.ID)} + }) } func (f *filterForm) HasValue() bool { @@ -129,3 +120,11 @@ func (f *filterForm) HasValue() bool { f.FromDate.Val != "" || f.ToDate.Val != "" } + +func (f *filterForm) PerPage() int { + return f.Cursor.PerPage +} + +func (f *filterForm) Paginated() bool { + return f.Cursor.Pagination +} diff --git a/pkg/booking/prebooking.go b/pkg/booking/prebooking.go index 2da3c56..6bc846f 100644 --- a/pkg/booking/prebooking.go +++ b/pkg/booking/prebooking.go @@ -59,7 +59,7 @@ type prebookingIndex struct { } func (page prebookingIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) { - if httplib.IsHTMxRequest(r) && page.Filters.pagination { + if httplib.IsHTMxRequest(r) && page.Filters.Paginated() { template.MustRenderAdminNoLayout(w, r, user, company, "prebooking/results.gohtml", page) } else { template.MustRenderAdminFiles(w, r, user, company, page, "prebooking/index.gohtml", "prebooking/results.gohtml") diff --git a/pkg/customer/admin.go b/pkg/customer/admin.go index fb19109..0d4f0fe 100644 --- a/pkg/customer/admin.go +++ b/pkg/customer/admin.go @@ -109,7 +109,7 @@ func collectCustomerEntries(ctx context.Context, conn *database.Conn, company *a where (%s) order by name, contact_id LIMIT %d - `, where, filters.perPage+1), args...) + `, where, filters.PerPage()+1), args...) if err != nil { return nil, err } @@ -141,7 +141,7 @@ type customerIndex struct { } func (page *customerIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) { - if httplib.IsHTMxRequest(r) && page.Filters.pagination { + if httplib.IsHTMxRequest(r) && page.Filters.Paginated() { template.MustRenderAdminNoLayout(w, r, user, company, "customer/results.gohtml", page) } else { template.MustRenderAdminFiles(w, r, user, company, page, "customer/index.gohtml", "customer/results.gohtml") diff --git a/pkg/customer/filter.go b/pkg/customer/filter.go index 9b00082..9447fe9 100644 --- a/pkg/customer/filter.go +++ b/pkg/customer/filter.go @@ -3,6 +3,7 @@ package customer import ( "fmt" "net/http" + "strconv" "strings" "dev.tandem.ws/tandem/camper/pkg/auth" @@ -10,26 +11,24 @@ import ( ) type filterForm struct { - company *auth.Company - perPage int - pagination bool - Name *form.Input - Email *form.Input - Cursor *form.Input + company *auth.Company + Name *form.Input + Email *form.Input + Cursor *form.Cursor } func newFilterForm(company *auth.Company) *filterForm { return &filterForm{ company: company, - perPage: 25, Name: &form.Input{ Name: "name", }, Email: &form.Input{ Name: "email", }, - Cursor: &form.Input{ - Name: "cursor", + Cursor: &form.Cursor{ + Name: "cursor", + PerPage: 25, }, } } @@ -41,7 +40,6 @@ func (f *filterForm) Parse(r *http.Request) error { f.Name.FillValue(r) f.Email.FillValue(r) f.Cursor.FillValue(r) - f.pagination = f.Cursor.Val != "" return nil } @@ -69,8 +67,8 @@ func (f *filterForm) BuildQuery(args []interface{}) (string, []interface{}) { return "%" + v + "%" }) - if f.Cursor.Val != "" { - params := strings.Split(f.Cursor.Val, ";") + if f.Paginated() { + params := f.Cursor.Params() if len(params) == 2 { where = append(where, fmt.Sprintf("(name, contact_id) > ($%d, $%d)", len(args)+1, len(args)+2)) args = append(args, params[0]) @@ -82,17 +80,20 @@ func (f *filterForm) BuildQuery(args []interface{}) (string, []interface{}) { } func (f *filterForm) buildCursor(customers []*customerEntry) []*customerEntry { - if len(customers) <= f.perPage { - f.Cursor.Val = "" - return customers - } - customers = customers[:f.perPage] - last := customers[f.perPage-1] - f.Cursor.Val = fmt.Sprintf("%s;%d", last.Name, last.ID) - return customers + return form.BuildCursor(f.Cursor, customers, func(entry *customerEntry) []string { + return []string{entry.Name, strconv.Itoa(entry.ID)} + }) } func (f *filterForm) HasValue() bool { return f.Name.Val != "" || f.Email.Val != "" } + +func (f *filterForm) PerPage() int { + return f.Cursor.PerPage +} + +func (f *filterForm) Paginated() bool { + return f.Cursor.Pagination +} diff --git a/pkg/form/cursor.go b/pkg/form/cursor.go new file mode 100644 index 0000000..0f0c4c5 --- /dev/null +++ b/pkg/form/cursor.go @@ -0,0 +1,33 @@ +package form + +import ( + "net/http" + "strings" +) + +type Cursor struct { + PerPage int + Pagination bool + Name string + Val string + Colspan int +} + +func (cursor *Cursor) FillValue(r *http.Request) { + cursor.Val = strings.TrimSpace(r.FormValue(cursor.Name)) + cursor.Pagination = cursor.Val != "" +} + +func (cursor *Cursor) Params() []string { + return strings.Split(cursor.Val, ";") +} + +func BuildCursor[K interface{}](cursor *Cursor, elems []K, build func(K) []string) []K { + if len(elems) <= cursor.PerPage { + cursor.Val = "" + return elems + } + elems = elems[:cursor.PerPage] + cursor.Val = strings.Join(build(elems[cursor.PerPage-1]), ";") + return elems +} diff --git a/pkg/template/render.go b/pkg/template/render.go index b8a0cdb..39d65bf 100644 --- a/pkg/template/render.go +++ b/pkg/template/render.go @@ -24,6 +24,7 @@ import ( "dev.tandem.ws/tandem/camper/pkg/auth" "dev.tandem.ws/tandem/camper/pkg/build" "dev.tandem.ws/tandem/camper/pkg/database" + "dev.tandem.ws/tandem/camper/pkg/form" httplib "dev.tandem.ws/tandem/camper/pkg/http" ) @@ -161,6 +162,10 @@ func mustRenderLayout(w io.Writer, user *auth.User, company *auth.Company, templ return int(num) }, "slugify": Slugify, + "colspan": func(colspan int, cursor *form.Cursor) *form.Cursor { + cursor.Colspan = colspan + return cursor + }, }) templates = append(templates, "form.gohtml") files := make([]string, len(templates)) diff --git a/web/templates/admin/booking/results.gohtml b/web/templates/admin/booking/results.gohtml index 3d3c102..db35cda 100644 --- a/web/templates/admin/booking/results.gohtml +++ b/web/templates/admin/booking/results.gohtml @@ -7,22 +7,4 @@ {{ .StatusLabel }} {{- end }} -{{ if .Filters.Cursor.Val }} - - - {{ with .Filters -}} -
- {{ with .HolderName -}}{{- end }} - {{ with .BookingStatus -}}{{- end }} - {{ with .FromDate -}}{{- end }} - {{ with .ToDate -}}{{- end }} - {{ with .Cursor -}}{{- end }} - -
- {{- end }} - - -{{- end }} +{{ template "pagination" .Filters.Cursor | colspan 5 }} diff --git a/web/templates/admin/customer/results.gohtml b/web/templates/admin/customer/results.gohtml index 26af933..ead5feb 100644 --- a/web/templates/admin/customer/results.gohtml +++ b/web/templates/admin/customer/results.gohtml @@ -5,15 +5,4 @@ {{ .Phone }} {{- end }} -{{ if .Filters.Cursor.Val }} - - - {{ with .Filters -}} -
- {{ with .Cursor -}}{{- end }} - -
- {{- end }} - - -{{- end }} +{{ template "pagination" .Filters.Cursor | colspan 3 }} diff --git a/web/templates/admin/form.gohtml b/web/templates/admin/form.gohtml index 4bfb2f1..095639c 100644 --- a/web/templates/admin/form.gohtml +++ b/web/templates/admin/form.gohtml @@ -82,3 +82,17 @@ @click="document.body.classList.toggle('filters-visible')" type="button">{{(pgettext "Filters" "action")}} {{- end }} + +{{ define "pagination" -}} + {{ if .Val }} + + +
+ + +
+ + + {{- end }} +{{- end }}