Add an edit form for payment methods

I do not particularly enjoy an htmx-only way of doing that, because it
means that it can only work with JavaScript, but i think this is already
a lost cause, unfortunately.  If i have time, i will try to make the
HTML-only form work too.

In this case, i have to put back the same row when updating or
cancelling the form, which is inside index.html.  Instead of moving that
part to a separate file, i tried to define a block as a “template
fragment” and try to render that part only.  Surprisingly, it works;
i am happy.

Closes #74.
This commit is contained in:
jordi fita mas 2024-08-30 02:45:40 +02:00
parent e8a44e480e
commit 292720de28
7 changed files with 276 additions and 133 deletions

View File

@ -719,20 +719,32 @@ func newPaymentMethodsPageWithForm(ctx context.Context, conn *Conn, company *Com
}
func (page *PaymentMethodsPage) MustRender(w http.ResponseWriter, r *http.Request) {
mustRenderMainTemplate(w, r, "company/payment_methods.gohtml", page)
mustRenderMainTemplate(w, r, "payments/methods/index.gohtml", page)
}
func mustCollectPaymentMethods(ctx context.Context, conn *Conn, company *Company) []*PaymentMethod {
rows, err := conn.Query(ctx, `
return mustCollectPaymentMethodsWhere(ctx, conn, "company_id = $1", company.Id)
}
func mustCollectPaymentMethod(ctx context.Context, conn *Conn, paymentMethodId int) *PaymentMethod {
methods := mustCollectPaymentMethodsWhere(ctx, conn, "payment_method_id = $1", paymentMethodId)
if len(methods) == 0 {
return nil
}
return methods[0]
}
func mustCollectPaymentMethodsWhere(ctx context.Context, conn *Conn, where string, value any) []*PaymentMethod {
rows, err := conn.Query(ctx, fmt.Sprintf(`
select payment_method_id
, name
, instructions
, payment_method_id = default_payment_method_id
from payment_method
join company using (company_id)
where company_id = $1
where %s
order by name
`, company.Id)
`, where), value)
if err != nil {
panic(err)
}
@ -755,9 +767,10 @@ func mustCollectPaymentMethods(ctx context.Context, conn *Conn, company *Company
}
type paymentMethodForm struct {
locale *Locale
Name *InputField
Instructions *InputField
locale *Locale
PaymentMethodId int
Name *InputField
Instructions *InputField
}
func newPaymentMethodForm(locale *Locale) *paymentMethodForm {
@ -778,6 +791,25 @@ func newPaymentMethodForm(locale *Locale) *paymentMethodForm {
}
}
func (form *paymentMethodForm) MustRender(w http.ResponseWriter, r *http.Request) {
mustRenderStandaloneTemplate(w, r, "payments/methods/edit.gohtml", form)
}
func (form *paymentMethodForm) MustFillFromDatabase(ctx context.Context, conn *Conn, paymentMethodId int) bool {
if notFoundErrorOrPanic(conn.QueryRow(ctx, `
select name
, instructions
from payment_method
where payment_method_id = $1
`, paymentMethodId).Scan(
form.Name,
form.Instructions)) {
return false
}
form.PaymentMethodId = paymentMethodId
return true
}
func (form *paymentMethodForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil {
return err
@ -822,8 +854,43 @@ func HandleUpdatePaymentMethod(w http.ResponseWriter, r *http.Request, params ht
HandleUpdateDefaultPaymentMethod(w, r)
return
}
http.NotFound(w, r)
return
paymentMethodId, err := strconv.Atoi(params[0].Value)
if err != nil {
http.NotFound(w, r)
return
}
locale := getLocale(r)
conn := getConn(r)
form := newPaymentMethodForm(locale)
if err := form.Parse(r); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if len(r.Form["cancel"]) == 0 {
if err := verifyCsrfTokenValid(r); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
if !form.Validate() {
w.WriteHeader(http.StatusUnprocessableEntity)
form.MustRender(w, r)
return
}
newName := conn.MustGetText(r.Context(), "", "update payment_method set name = $1, instructions = $2 where payment_method_id = $3 returning name", form.Name, form.Instructions, paymentMethodId)
if newName == "" {
http.NotFound(w, r)
return
}
}
paymentMethod := mustCollectPaymentMethod(r.Context(), conn, paymentMethodId)
if paymentMethod == nil {
http.NotFound(w, r)
return
}
mustRenderStandaloneTemplateFragment(w, r, "payments/methods/index.gohtml", "row", paymentMethod)
}
func HandleUpdateDefaultPaymentMethod(w http.ResponseWriter, r *http.Request) {
@ -850,6 +917,25 @@ func HandleUpdateDefaultPaymentMethod(w http.ResponseWriter, r *http.Request) {
htmxRedirect(w, r, companyURI(company, "/payment-methods"))
}
func servePaymentMethodEditForm(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
paymentMethodId, err := strconv.Atoi(params[0].Value)
if err != nil {
http.NotFound(w, r)
return
}
locale := getLocale(r)
form := newPaymentMethodForm(locale)
conn := getConn(r)
if !form.MustFillFromDatabase(r.Context(), conn, paymentMethodId) {
http.NotFound(w, r)
return
}
form.MustRender(w, r)
}
func HandleDeletePaymentMethod(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
paymentMethodId, err := strconv.Atoi(params[0].Value)
if err != nil {

View File

@ -22,6 +22,7 @@ func NewRouter(db *Db, demo bool) http.Handler {
companyRouter.POST("/payment-methods", HandleAddPaymentMethod)
companyRouter.PUT("/payment-methods/:paymentMethodId", HandleUpdatePaymentMethod)
companyRouter.DELETE("/payment-methods/:paymentMethodId", HandleDeletePaymentMethod)
companyRouter.GET("/payment-methods/:paymentMethodId/edit", servePaymentMethodEditForm)
companyRouter.GET("/contacts", IndexContacts)
companyRouter.POST("/contacts", HandleAddContact)
companyRouter.POST("/contacts/import", HandleImportContacts)

View File

@ -21,6 +21,10 @@ func templateFile(name string) string {
}
func mustRenderTemplate(wr io.Writer, r *http.Request, layout string, filename string, data interface{}) {
mustRenderTemplateFragment(wr, r, layout, filename, layout, data)
}
func mustRenderTemplateFragment(wr io.Writer, r *http.Request, layout string, filename string, fragment string, data interface{}) {
locale := getLocale(r)
company := getCompany(r)
user := getUser(r)
@ -109,7 +113,7 @@ func mustRenderTemplate(wr io.Writer, r *http.Request, layout string, filename s
if w, ok := wr.(http.ResponseWriter); ok {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
}
if err := t.ExecuteTemplate(wr, layout, data); err != nil {
if err := t.ExecuteTemplate(wr, fragment, data); err != nil {
panic(err)
}
}
@ -145,7 +149,11 @@ func mustRenderMainTemplate(w io.Writer, r *http.Request, filename string, data
}
func mustRenderStandaloneTemplate(w io.Writer, r *http.Request, filename string, data interface{}) {
mustRenderTemplate(w, r, "standalone.gohtml", filename, data)
mustRenderStandaloneTemplateFragment(w, r, filename, "standalone.gohtml", data)
}
func mustRenderStandaloneTemplateFragment(w io.Writer, r *http.Request, filename string, fragment string, data interface{}) {
mustRenderTemplateFragment(w, r, "standalone.gohtml", filename, fragment, data)
}
func mustRenderWebTemplate(w io.Writer, r *http.Request, filename string, data interface{}) {

100
po/ca.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: numerus\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2024-08-28 11:05+0200\n"
"POT-Creation-Date: 2024-08-30 02:37+0200\n"
"PO-Revision-Date: 2023-01-18 17:08+0100\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Catalan <ca@dodds.net>\n"
@ -35,11 +35,11 @@ msgstr "Afegeix productes a la factura"
#: web/template/expenses/edit.gohtml:10 web/template/company/taxes.gohtml:10
#: web/template/company/switch.gohtml:9
#: web/template/company/tax-details.gohtml:9
#: web/template/company/invoicing.gohtml:10
#: web/template/company/payment_methods.gohtml:10
#: web/template/products/new.gohtml:9 web/template/products/index.gohtml:9
#: web/template/products/edit.gohtml:10 web/template/payments/new.gohtml:14
#: web/template/payments/index.gohtml:14 web/template/payments/edit.gohtml:14
#: web/template/company/invoicing.gohtml:10 web/template/products/new.gohtml:9
#: web/template/products/index.gohtml:9 web/template/products/edit.gohtml:10
#: web/template/payments/new.gohtml:14 web/template/payments/index.gohtml:14
#: web/template/payments/methods/index.gohtml:10
#: web/template/payments/edit.gohtml:14
#: web/template/payments/accounts/new.gohtml:10
#: web/template/payments/accounts/index.gohtml:10
#: web/template/payments/accounts/edit.gohtml:10
@ -126,6 +126,7 @@ msgstr "Total"
#: web/template/invoices/new.gohtml:91 web/template/invoices/edit.gohtml:92
#: web/template/quotes/new.gohtml:92 web/template/quotes/edit.gohtml:93
#: web/template/expenses/new.gohtml:56 web/template/expenses/edit.gohtml:58
#: web/template/payments/methods/edit.gohtml:17
#: web/template/payments/edit.gohtml:55
#: web/template/payments/accounts/edit.gohtml:38
msgctxt "action"
@ -477,7 +478,7 @@ msgctxt "title"
msgid "Taxes"
msgstr "Imposts"
#: web/template/app.gohtml:48 web/template/company/payment_methods.gohtml:3
#: web/template/app.gohtml:48 web/template/payments/methods/index.gohtml:3
msgctxt "title"
msgid "Payment Methods"
msgstr "Mètodes de pagament"
@ -701,7 +702,7 @@ msgid "Class"
msgstr "Classe"
#: web/template/company/taxes.gohtml:29
#: web/template/company/payment_methods.gohtml:29
#: web/template/payments/methods/index.gohtml:34
msgid "Are you sure?"
msgstr "Nesteu segur?"
@ -734,40 +735,6 @@ msgctxt "title"
msgid "Tax Details"
msgstr "Configuració fiscal"
#: web/template/company/payment_methods.gohtml:23
msgctxt "title"
msgid "Default"
msgstr "Per defecte"
#: web/template/company/payment_methods.gohtml:24
msgctxt "title"
msgid "Payment Method"
msgstr "Mètode de pagament"
#: web/template/company/payment_methods.gohtml:25
msgctxt "title"
msgid "Instructions"
msgstr "Instruccions"
#: web/template/company/payment_methods.gohtml:59
msgid "No payment methods added yet."
msgstr "No hi ha cap mètode de pagament."
#: web/template/company/payment_methods.gohtml:77
msgctxt "action"
msgid "Set"
msgstr "Estableix"
#: web/template/company/payment_methods.gohtml:87
msgctxt "title"
msgid "New Payment Method"
msgstr "Nou mètode de pagament"
#: web/template/company/payment_methods.gohtml:96
msgctxt "action"
msgid "Add new payment method"
msgstr "Afegeix nou mètode de pagament"
#: web/template/products/new.gohtml:2 web/template/products/new.gohtml:11
#: web/template/products/new.gohtml:19
msgctxt "title"
@ -863,6 +830,45 @@ msgstr "No hi ha cap cobrament."
msgid "No payments added yet."
msgstr "No hi ha cap pagament."
#: web/template/payments/methods/index.gohtml:23
msgctxt "title"
msgid "Default"
msgstr "Per defecte"
#: web/template/payments/methods/index.gohtml:24
msgctxt "title"
msgid "Payment Method"
msgstr "Mètode de pagament"
#: web/template/payments/methods/index.gohtml:25
msgctxt "title"
msgid "Instructions"
msgstr "Instruccions"
#: web/template/payments/methods/index.gohtml:72
msgid "No payment methods added yet."
msgstr "No hi ha cap mètode de pagament."
#: web/template/payments/methods/index.gohtml:90
msgctxt "action"
msgid "Set"
msgstr "Estableix"
#: web/template/payments/methods/index.gohtml:100
msgctxt "title"
msgid "New Payment Method"
msgstr "Nou mètode de pagament"
#: web/template/payments/methods/index.gohtml:109
msgctxt "action"
msgid "Add new payment method"
msgstr "Afegeix nou mètode de pagament"
#: web/template/payments/methods/edit.gohtml:18
msgctxt "action"
msgid "Cancel"
msgstr "Cancel·la"
#: web/template/payments/edit.gohtml:4
msgctxt "title"
msgid "Edit Payment “%s”"
@ -1208,25 +1214,25 @@ msgstr "No podeu deixar percentatge en blanc."
msgid "Tax rate must be an integer between -99 and 99."
msgstr "El percentatge ha de ser entre -99 i 99."
#: pkg/company.go:768
#: pkg/company.go:781
msgctxt "input"
msgid "Payment method name"
msgstr "Nom del mètode de pagament"
#: pkg/company.go:774
#: pkg/company.go:787
msgctxt "input"
msgid "Instructions"
msgstr "Instruccions"
#: pkg/company.go:792
#: pkg/company.go:824
msgid "Payment method name can not be empty."
msgstr "No podeu deixar el nom del mètode de pagament en blanc."
#: pkg/company.go:793
#: pkg/company.go:825
msgid "Payment instructions can not be empty."
msgstr "No podeu deixar les instruccions de pagament en blanc."
#: pkg/company.go:845 pkg/quote.go:755 pkg/invoices.go:928
#: pkg/company.go:912 pkg/quote.go:755 pkg/invoices.go:928
msgid "Selected payment method is not valid."
msgstr "Heu seleccionat un mètode de pagament que no és vàlid."

100
po/es.po
View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: numerus\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2024-08-28 11:05+0200\n"
"POT-Creation-Date: 2024-08-30 02:37+0200\n"
"PO-Revision-Date: 2023-01-18 17:45+0100\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Spanish <es@tp.org.es>\n"
@ -35,11 +35,11 @@ msgstr "Añadir productos a la factura"
#: web/template/expenses/edit.gohtml:10 web/template/company/taxes.gohtml:10
#: web/template/company/switch.gohtml:9
#: web/template/company/tax-details.gohtml:9
#: web/template/company/invoicing.gohtml:10
#: web/template/company/payment_methods.gohtml:10
#: web/template/products/new.gohtml:9 web/template/products/index.gohtml:9
#: web/template/products/edit.gohtml:10 web/template/payments/new.gohtml:14
#: web/template/payments/index.gohtml:14 web/template/payments/edit.gohtml:14
#: web/template/company/invoicing.gohtml:10 web/template/products/new.gohtml:9
#: web/template/products/index.gohtml:9 web/template/products/edit.gohtml:10
#: web/template/payments/new.gohtml:14 web/template/payments/index.gohtml:14
#: web/template/payments/methods/index.gohtml:10
#: web/template/payments/edit.gohtml:14
#: web/template/payments/accounts/new.gohtml:10
#: web/template/payments/accounts/index.gohtml:10
#: web/template/payments/accounts/edit.gohtml:10
@ -126,6 +126,7 @@ msgstr "Total"
#: web/template/invoices/new.gohtml:91 web/template/invoices/edit.gohtml:92
#: web/template/quotes/new.gohtml:92 web/template/quotes/edit.gohtml:93
#: web/template/expenses/new.gohtml:56 web/template/expenses/edit.gohtml:58
#: web/template/payments/methods/edit.gohtml:17
#: web/template/payments/edit.gohtml:55
#: web/template/payments/accounts/edit.gohtml:38
msgctxt "action"
@ -477,7 +478,7 @@ msgctxt "title"
msgid "Taxes"
msgstr "Impuestos"
#: web/template/app.gohtml:48 web/template/company/payment_methods.gohtml:3
#: web/template/app.gohtml:48 web/template/payments/methods/index.gohtml:3
msgctxt "title"
msgid "Payment Methods"
msgstr "Métodos de pago"
@ -701,7 +702,7 @@ msgid "Class"
msgstr "Clase"
#: web/template/company/taxes.gohtml:29
#: web/template/company/payment_methods.gohtml:29
#: web/template/payments/methods/index.gohtml:34
msgid "Are you sure?"
msgstr "¿Estáis seguro?"
@ -734,40 +735,6 @@ msgctxt "title"
msgid "Tax Details"
msgstr "Configuración fiscal"
#: web/template/company/payment_methods.gohtml:23
msgctxt "title"
msgid "Default"
msgstr "Por defecto"
#: web/template/company/payment_methods.gohtml:24
msgctxt "title"
msgid "Payment Method"
msgstr "Método de pago"
#: web/template/company/payment_methods.gohtml:25
msgctxt "title"
msgid "Instructions"
msgstr "Instrucciones"
#: web/template/company/payment_methods.gohtml:59
msgid "No payment methods added yet."
msgstr "No hay métodos de pago."
#: web/template/company/payment_methods.gohtml:77
msgctxt "action"
msgid "Set"
msgstr "Establecer"
#: web/template/company/payment_methods.gohtml:87
msgctxt "title"
msgid "New Payment Method"
msgstr "Nuevo método de pago"
#: web/template/company/payment_methods.gohtml:96
msgctxt "action"
msgid "Add new payment method"
msgstr "Añadir nuevo método de pago"
#: web/template/products/new.gohtml:2 web/template/products/new.gohtml:11
#: web/template/products/new.gohtml:19
msgctxt "title"
@ -863,6 +830,45 @@ msgstr "No hay cobros."
msgid "No payments added yet."
msgstr "No hay pagos."
#: web/template/payments/methods/index.gohtml:23
msgctxt "title"
msgid "Default"
msgstr "Por defecto"
#: web/template/payments/methods/index.gohtml:24
msgctxt "title"
msgid "Payment Method"
msgstr "Método de pago"
#: web/template/payments/methods/index.gohtml:25
msgctxt "title"
msgid "Instructions"
msgstr "Instrucciones"
#: web/template/payments/methods/index.gohtml:72
msgid "No payment methods added yet."
msgstr "No hay métodos de pago."
#: web/template/payments/methods/index.gohtml:90
msgctxt "action"
msgid "Set"
msgstr "Establecer"
#: web/template/payments/methods/index.gohtml:100
msgctxt "title"
msgid "New Payment Method"
msgstr "Nuevo método de pago"
#: web/template/payments/methods/index.gohtml:109
msgctxt "action"
msgid "Add new payment method"
msgstr "Añadir nuevo método de pago"
#: web/template/payments/methods/edit.gohtml:18
msgctxt "action"
msgid "Cancel"
msgstr "Cancelar"
#: web/template/payments/edit.gohtml:4
msgctxt "title"
msgid "Edit Payment “%s”"
@ -1208,25 +1214,25 @@ msgstr "No podéis dejar el porcentaje en blanco."
msgid "Tax rate must be an integer between -99 and 99."
msgstr "El porcentaje tiene que estar entre -99 y 99."
#: pkg/company.go:768
#: pkg/company.go:781
msgctxt "input"
msgid "Payment method name"
msgstr "Nombre del método de pago"
#: pkg/company.go:774
#: pkg/company.go:787
msgctxt "input"
msgid "Instructions"
msgstr "Instrucciones"
#: pkg/company.go:792
#: pkg/company.go:824
msgid "Payment method name can not be empty."
msgstr "No podéis dejar el nombre del método de pago en blanco."
#: pkg/company.go:793
#: pkg/company.go:825
msgid "Payment instructions can not be empty."
msgstr "No podéis dejar las instrucciones de pago en blanco."
#: pkg/company.go:845 pkg/quote.go:755 pkg/invoices.go:928
#: pkg/company.go:912 pkg/quote.go:755 pkg/invoices.go:928
msgid "Selected payment method is not valid."
msgstr "Habéis escogido un método de pago que no es válido."

View File

@ -0,0 +1,23 @@
{{ define "content" }}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.paymentMethodForm*/ -}}
<tr>
<td colspan="6">
<form method="POST"
action="{{ companyURI "/payment-methods" }}/{{ .PaymentMethodId }}"
data-hx-boost="true"
data-hx-push-url="false"
>
<fieldset>
{{ csrfToken }}
{{ putMethod }}
{{ template "input-field" .Name }}
{{ template "input-field" .Instructions }}
</fieldset>
<footer>
<button>{{( pgettext "Update" "action" )}}</button>
<button name="cancel">{{( pgettext "Cancel" "action" )}}</button>
</footer>
</form>
</td>
</tr>
{{- end }}

View File

@ -24,39 +24,52 @@
<th>{{( pgettext "Payment Method" "title" )}}</th>
<th>{{( pgettext "Instructions" "title" )}}</th>
<th></th>
<th></th>
</tr>
</thead>
{{ $confirm := ( gettext "Are you sure?" )}}
<tbody data-hx-confirm="{{ $confirm }}" data-hx-target="closest tr" data-hx-swap="outerHTML swap:1s">
<tbody data-hx-target="closest tr" data-hx-swap="outerHTML">
{{ with .PaymentMethods }}
{{- range $method := . }}
<tr>
<td>
<span class="description-text"></span>
<input type="radio"
form="default_payment_form"
aria-label="{{ pgettext "Set as default" "action" }}"
name="default_payment_id"
value="{{ .Id }}"
{{ if .IsDefault }} checked{{ end}}
>
</td>
<td>{{ .Name }}</td>
<td>{{ .Instructions }}</td>
<td>
<form method="POST" action="{{ companyURI "/payment-methods"}}/{{ .Id }}"
data-hx-boost="true">
{{ csrfToken }}
{{ deleteMethod }}
<button class="icon" aria-label="{{( gettext "Delete payment method" )}}"
><i class="ri-delete-back-2-line"></i></button>
</form>
</td>
</tr>
{{- range . }}
{{- block "row" . }}
{{ $confirm := ( gettext "Are you sure?" )}}
<tr>
<td>
<span class="description-text"></span>
<input type="radio"
form="default_payment_form"
aria-label="{{ pgettext "Set as default" "action" }}"
name="default_payment_id"
value="{{ .Id }}"
{{ if .IsDefault }} checked{{ end}}
>
</td>
<td>{{ .Name }}</td>
<td>{{ .Instructions }}</td>
<td>
<a href="{{ companyURI "/payment-methods"}}/{{ .Id }}/edit"
aria-label="{{( gettext "Edit payment method" )}}"
data-hx-boost="true"
data-hx-push-url="false"
><i class="ri-edit-2-line"></i></a>
</td>
<td>
<form method="POST" action="{{ companyURI "/payment-methods"}}/{{ .Id }}"
data-hx-boost="true"
data-hx-confirm="{{ $confirm }}"
data-hx-swap="outerHTML swap:1s"
>
{{ csrfToken }}
{{ deleteMethod }}
<button class="icon" aria-label="{{( gettext "Delete payment method" )}}"
><i class="ri-delete-back-2-line"></i></button>
</form>
</td>
</tr>
{{- end }}
{{- end }}
{{ else }}
<tr>
<td colspan="4">{{( gettext "No payment methods added yet." )}}</td>
<td colspan="5">{{( gettext "No payment methods added yet." )}}</td>
</tr>
{{ end }}
</tbody>
@ -77,7 +90,7 @@
<button>{{( pgettext "Set" "action")}}</button>
</form>
</td>
<td colspan="3"></td>
<td colspan="4"></td>
</tr>
</tfoot>
{{- end }}