Add dynamic admin panel for managing entities (#108)

This commit is contained in:
Mike Stefanello 2025-04-22 08:26:35 -04:00 committed by GitHub
parent 60009df0bf
commit 1a6874fd82
47 changed files with 2173 additions and 320 deletions

View file

@ -0,0 +1,125 @@
package forms
import (
"net/http"
"net/url"
"entgo.io/ent/entc/load"
"entgo.io/ent/schema/field"
"github.com/mikestefanello/pagoda/ent/admin"
"github.com/mikestefanello/pagoda/pkg/routenames"
"github.com/mikestefanello/pagoda/pkg/ui"
. "github.com/mikestefanello/pagoda/pkg/ui/components"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
func AdminEntity(r *ui.Request, schema *load.Schema, values url.Values) Node {
// TODO inline validation?
isNew := values == nil
nodes := make(Group, 0)
getValue := func(name string) string {
// Values in the submitted form take precedence.
if value := r.Context.FormValue(name); value != "" {
return value
}
// Fallback to the entity's values, if being edited.
if values != nil && len(values[name]) > 0 {
return values[name][0]
}
return ""
}
// Attempt to add form elements for all editable entity fields.
for _, f := range schema.Fields {
// TODO cardinality?
if !isNew && f.Immutable {
continue
}
switch f.Info.Type {
case field.TypeString:
p := InputFieldParams{
Name: f.Name,
InputType: "text",
Label: admin.FieldLabel(f.Name),
Value: getValue(f.Name),
}
if f.Sensitive {
p.InputType = "password"
if !isNew {
p.Placeholder = "*****"
p.Help = "SENSITIVE: This field will only be updated if a value is provided."
}
}
nodes = append(nodes, InputField(p))
case field.TypeTime:
nodes = append(nodes, InputField(InputFieldParams{
Name: f.Name,
InputType: "datetime-local",
Label: admin.FieldLabel(f.Name),
Value: getValue(f.Name),
}))
case field.TypeInt, field.TypeInt8, field.TypeInt16, field.TypeInt32, field.TypeInt64,
field.TypeUint, field.TypeUint8, field.TypeUint16, field.TypeUint32, field.TypeUint64,
field.TypeFloat32, field.TypeFloat64:
nodes = append(nodes, InputField(InputFieldParams{
Name: f.Name,
InputType: "number",
Label: admin.FieldLabel(f.Name),
Value: getValue(f.Name),
}))
case field.TypeBool:
nodes = append(nodes, Checkbox(CheckboxParams{
Name: f.Name,
Label: admin.FieldLabel(f.Name),
Checked: getValue(f.Name) == "true",
}))
case field.TypeEnum:
options := make([]Choice, 0, len(f.Enums)+1)
if f.Optional {
options = append(options, Choice{
Label: "-",
Value: "",
})
}
for _, enum := range f.Enums {
options = append(options, Choice{
Label: enum.V,
Value: enum.V,
})
}
nodes = append(nodes, SelectList(OptionsParams{
Name: f.Name,
Label: admin.FieldLabel(f.Name),
Value: getValue(f.Name),
Options: options,
}))
default:
nodes = append(nodes, P(Textf("%s not supported", f.Name)))
}
}
return Form(
Method(http.MethodPost),
nodes,
ControlGroup(
FormButton("is-primary", "Submit"),
ButtonLink(
r.Path(routenames.AdminEntityList(schema.Name)),
"is-secondary",
"Cancel",
),
),
CSRF(r),
)
}

View file

@ -0,0 +1,30 @@
package forms
import (
"net/http"
"github.com/mikestefanello/pagoda/pkg/routenames"
"github.com/mikestefanello/pagoda/pkg/ui"
. "github.com/mikestefanello/pagoda/pkg/ui/components"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
func AdminEntityDelete(r *ui.Request, entityTypeName string) Node {
return Form(
Method(http.MethodPost),
P(
Class("subtitle"),
Textf("Are you sure you want to delete this %s?", entityTypeName),
),
ControlGroup(
FormButton("is-link", "Delete"),
ButtonLink(
r.Path(routenames.AdminEntityList(entityTypeName)),
"is-secondary",
"Cancel",
),
),
CSRF(r),
)
}

View file

@ -31,13 +31,13 @@ func (f *Contact) Render(r *ui.Request) Node {
Label: "Email address",
Value: f.Email,
}),
Radios(RadiosParams{
Radios(OptionsParams{
Form: f,
FormField: "Department",
Name: "department",
Label: "Department",
Value: f.Department,
Options: []Radio{
Options: []Choice{
{Value: "sales", Label: "Sales"},
{Value: "marketing", Label: "Marketing"},
{Value: "hr", Label: "HR"},