feat(09-04): compile switch, date-range, and scope filters

- Filters keep typed values and only registered scope names
- Raw conditions and arbitrary methods fail activation
- Request localization copies labels and leaves identifiers unchanged
This commit is contained in:
Jakub Zych
2026-09-24 18:53:52 +02:00
parent cb832eb70c
commit d3a93073c9
5 changed files with 417 additions and 6 deletions

View File

@@ -11,6 +11,7 @@ import (
"git.golem15.com/golem15/summercms/pact"
"git.golem15.com/golem15/summercms/phrasebook"
"git.golem15.com/golem15/summercms/towel"
"github.com/goccy/go-yaml"
"gorm.io/gorm"
)
@@ -35,6 +36,7 @@ type listDocument struct {
ShowSearch bool `yaml:"showSearch"`
DefaultSort *listSortDocument `yaml:"defaultSort"`
Toolbar *listToolbar `yaml:"toolbar"`
Filter string `yaml:"filter"`
}
type listSortDocument struct {
@@ -145,6 +147,13 @@ func compileList(pluginID string, ctl pact.AdminController, fsys fs.FS) (*ListSc
if doc.ShowCheckboxes {
bulk = append(bulk, BulkAction{Name: "delete", Label: "backend::lang.list.delete_selected"})
}
filters := []ListFilter{}
if strings.TrimSpace(doc.Filter) != "" {
filters, err = compileFilters(pluginID, ctl, fsys, dir, doc.Filter)
if err != nil {
return nil, err
}
}
return &ListSchema{
Title: doc.Title,
ModelClass: doc.ModelClass,
@@ -161,7 +170,7 @@ func compileList(pluginID string, ctl pact.AdminController, fsys fs.FS) (*ListSc
DefaultSort: sort,
ToolbarButtons: buttons,
Columns: columns,
Filters: []ListFilter{},
Filters: filters,
RowActions: rowActions,
BulkActions: bulk,
}, nil
@@ -354,8 +363,8 @@ func isListRelation(t reflect.Type) bool {
return true
}
// Localize copies the cached list schema for one request.
// The RED seam returns source keys; request-time translation replaces this body.
// Localize copies the cached list schema and resolves display strings for one request.
// Column, filter, and scope identifiers and option values are not translated.
func (s *ListSchema) Localize(ctx context.Context, tr *phrasebook.Translator) (*ListSchema, error) {
if s == nil {
return nil, fmt.Errorf("cabana: nil list schema")
@@ -363,7 +372,78 @@ func (s *ListSchema) Localize(ctx context.Context, tr *phrasebook.Translator) (*
if ctx == nil {
ctx = context.Background()
}
locale := schemaLocale(ctx, tr)
ctx = towel.WithLocale(ctx, locale)
out := *s
_ = tr
out.Title = translateKey(ctx, tr, s.Title)
out.NoRecordsMessage = translateKey(ctx, tr, s.NoRecordsMessage)
out.SearchPrompt = translateKey(ctx, tr, s.SearchPrompt)
out.PerPageOptions = append([]int(nil), s.PerPageOptions...)
out.ToolbarButtons = append([]string(nil), s.ToolbarButtons...)
if s.DefaultSort != nil {
sort := *s.DefaultSort
out.DefaultSort = &sort
}
out.Columns = localizeListColumns(ctx, tr, s.Columns)
out.Filters = localizeListFilters(ctx, tr, s.Filters)
out.RowActions = localizeRowActions(ctx, tr, s.RowActions)
out.BulkActions = localizeBulkActions(ctx, tr, s.BulkActions)
out.Meta = &FormMeta{Locale: locale}
return &out, nil
}
func localizeListColumns(ctx context.Context, tr *phrasebook.Translator, cols []ListColumn) []ListColumn {
out := make([]ListColumn, len(cols))
for i, col := range cols {
col.Label = translateKey(ctx, tr, col.Label)
out[i] = col
}
if out == nil {
out = []ListColumn{}
}
return out
}
func localizeListFilters(ctx context.Context, tr *phrasebook.Translator, filters []ListFilter) []ListFilter {
out := make([]ListFilter, len(filters))
for i, filter := range filters {
filter.Label = translateKey(ctx, tr, filter.Label)
if len(filter.Options) > 0 {
opts := make([]FormOption, len(filter.Options))
for j, opt := range filter.Options {
opt.Label = translateKey(ctx, tr, opt.Label)
opts[j] = opt
}
filter.Options = opts
}
out[i] = filter
}
if out == nil {
out = []ListFilter{}
}
return out
}
func localizeRowActions(ctx context.Context, tr *phrasebook.Translator, actions []RowAction) []RowAction {
out := make([]RowAction, len(actions))
for i, action := range actions {
action.Label = translateKey(ctx, tr, action.Label)
out[i] = action
}
if out == nil {
out = []RowAction{}
}
return out
}
func localizeBulkActions(ctx context.Context, tr *phrasebook.Translator, actions []BulkAction) []BulkAction {
out := make([]BulkAction, len(actions))
for i, action := range actions {
action.Label = translateKey(ctx, tr, action.Label)
out[i] = action
}
if out == nil {
out = []BulkAction{}
}
return out
}