easy-admin-rails 0.1.14 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/builds/easy_admin.base.js +254 -18
- data/app/assets/builds/easy_admin.base.js.map +4 -4
- data/app/assets/builds/easy_admin.css +112 -18
- data/app/components/easy_admin/base_component.rb +1 -0
- data/app/components/easy_admin/form_tabs_component.rb +5 -2
- data/app/components/easy_admin/navbar_component.rb +5 -1
- data/app/components/easy_admin/permissions/user_role_assignment_component.rb +254 -0
- data/app/components/easy_admin/permissions/user_role_permissions_component.rb +186 -0
- data/app/components/easy_admin/resources/index_component.rb +1 -4
- data/app/components/easy_admin/sidebar_component.rb +67 -2
- data/app/components/easy_admin/versions/diff_modal_component.rb +5 -1
- data/app/controllers/easy_admin/application_controller.rb +131 -1
- data/app/controllers/easy_admin/batch_actions_controller.rb +27 -0
- data/app/controllers/easy_admin/concerns/belongs_to_editing.rb +201 -0
- data/app/controllers/easy_admin/concerns/inline_field_editing.rb +297 -0
- data/app/controllers/easy_admin/concerns/resource_authorization.rb +55 -0
- data/app/controllers/easy_admin/concerns/resource_filtering.rb +178 -0
- data/app/controllers/easy_admin/concerns/resource_loading.rb +149 -0
- data/app/controllers/easy_admin/concerns/resource_pagination.rb +135 -0
- data/app/controllers/easy_admin/dashboard_controller.rb +2 -1
- data/app/controllers/easy_admin/dashboards_controller.rb +6 -40
- data/app/controllers/easy_admin/resources_controller.rb +13 -762
- data/app/controllers/easy_admin/row_actions_controller.rb +25 -0
- data/app/helpers/easy_admin/fields_helper.rb +61 -9
- data/app/javascript/easy_admin/controllers/event_emitter_controller.js +2 -4
- data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +0 -10
- data/app/javascript/easy_admin/controllers/jsoneditor_controller.js +1 -4
- data/app/javascript/easy_admin/controllers/permission_toggle_controller.js +227 -0
- data/app/javascript/easy_admin/controllers/role_preview_controller.js +93 -0
- data/app/javascript/easy_admin/controllers/select_field_controller.js +1 -2
- data/app/javascript/easy_admin/controllers/settings_button_controller.js +1 -2
- data/app/javascript/easy_admin/controllers/settings_sidebar_controller.js +1 -4
- data/app/javascript/easy_admin/controllers/turbo_stream_redirect.js +0 -2
- data/app/javascript/easy_admin/controllers.js +5 -1
- data/app/models/easy_admin/admin_user.rb +6 -0
- data/app/policies/admin_user_policy.rb +36 -0
- data/app/policies/application_policy.rb +83 -0
- data/app/views/easy_admin/application/authorization_failure.turbo_stream.erb +8 -0
- data/app/views/easy_admin/dashboards/card.html.erb +5 -0
- data/app/views/easy_admin/dashboards/card.turbo_stream.erb +7 -0
- data/app/views/easy_admin/dashboards/card_error.html.erb +3 -0
- data/app/views/easy_admin/dashboards/card_error.turbo_stream.erb +5 -0
- data/app/views/easy_admin/dashboards/show.turbo_stream.erb +7 -0
- data/app/views/easy_admin/resources/belongs_to_edit_attached.html.erb +6 -0
- data/app/views/easy_admin/resources/belongs_to_edit_attached.turbo_stream.erb +8 -0
- data/app/views/easy_admin/resources/belongs_to_reattach.html.erb +5 -0
- data/app/views/easy_admin/resources/edit.html.erb +1 -1
- data/app/views/easy_admin/resources/edit_field.html.erb +5 -0
- data/app/views/easy_admin/resources/edit_field.turbo_stream.erb +7 -0
- data/app/views/easy_admin/resources/index.html.erb +1 -1
- data/app/views/easy_admin/resources/index_frame.html.erb +8 -142
- data/app/views/easy_admin/resources/update_belongs_to_attached.turbo_stream.erb +25 -0
- data/app/views/layouts/easy_admin/application.html.erb +15 -2
- data/config/initializers/easy_admin_permissions.rb +73 -0
- data/db/seeds/easy_admin_permissions.rb +121 -0
- data/lib/easy-admin-rails.rb +2 -0
- data/lib/easy_admin/permissions/component.rb +168 -0
- data/lib/easy_admin/permissions/configuration.rb +37 -0
- data/lib/easy_admin/permissions/controller.rb +164 -0
- data/lib/easy_admin/permissions/dsl.rb +180 -0
- data/lib/easy_admin/permissions/models.rb +44 -0
- data/lib/easy_admin/permissions/permission_denied_component.rb +121 -0
- data/lib/easy_admin/permissions/resource_permissions.rb +231 -0
- data/lib/easy_admin/permissions/role_definition.rb +45 -0
- data/lib/easy_admin/permissions/role_denied_component.rb +159 -0
- data/lib/easy_admin/permissions/role_dsl.rb +73 -0
- data/lib/easy_admin/permissions/user_extensions.rb +129 -0
- data/lib/easy_admin/permissions.rb +113 -0
- data/lib/easy_admin/resource/base.rb +119 -0
- data/lib/easy_admin/resource/configuration.rb +148 -0
- data/lib/easy_admin/resource/dsl.rb +117 -0
- data/lib/easy_admin/resource/field_registry.rb +189 -0
- data/lib/easy_admin/resource/form_builder.rb +123 -0
- data/lib/easy_admin/resource/layout_builder.rb +249 -0
- data/lib/easy_admin/resource/scope_manager.rb +252 -0
- data/lib/easy_admin/resource/show_builder.rb +359 -0
- data/lib/easy_admin/resource.rb +8 -835
- data/lib/easy_admin/resource_modules.rb +11 -0
- data/lib/easy_admin/version.rb +1 -1
- data/lib/generators/easy_admin/permissions/install_generator.rb +90 -0
- data/lib/generators/easy_admin/permissions/templates/initializers/permissions.rb +37 -0
- data/lib/generators/easy_admin/permissions/templates/migrations/create_permission_tables.rb +27 -0
- data/lib/generators/easy_admin/permissions/templates/migrations/update_users_for_permissions.rb +6 -0
- data/lib/generators/easy_admin/permissions/templates/models/permission.rb +9 -0
- data/lib/generators/easy_admin/permissions/templates/models/role.rb +9 -0
- data/lib/generators/easy_admin/permissions/templates/models/role_permission.rb +9 -0
- data/lib/generators/easy_admin/permissions/templates/models/user_role.rb +9 -0
- data/lib/generators/easy_admin/permissions/templates/policies/application_policy.rb +47 -0
- data/lib/generators/easy_admin/permissions/templates/policies/user_policy.rb +36 -0
- data/lib/generators/easy_admin/permissions/templates/seeds/permissions.rb +89 -0
- metadata +62 -5
- data/db/migrate/20250101000001_create_easy_admin_admin_users.rb +0 -45
@@ -0,0 +1,189 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module ResourceModules
|
3
|
+
# FieldRegistry module for centralized field management
|
4
|
+
# Handles field registration, categorization, and retrieval
|
5
|
+
module FieldRegistry
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
class_attribute :fields_config
|
10
|
+
|
11
|
+
def self.initialize_field_registry
|
12
|
+
self.fields_config = []
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class_methods do
|
17
|
+
# Core field registration method
|
18
|
+
def register_field(name, type = :string, **options)
|
19
|
+
field_config = build_field_config(name, type, **options)
|
20
|
+
add_field_to_registry(field_config)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Build field configuration hash
|
24
|
+
def build_field_config(name, type, **options)
|
25
|
+
{
|
26
|
+
name: name,
|
27
|
+
type: type,
|
28
|
+
label: options[:label] || name.to_s.humanize,
|
29
|
+
sortable: options.fetch(:sortable, true),
|
30
|
+
searchable: options.fetch(:searchable, false),
|
31
|
+
filterable: options.fetch(:filterable, false),
|
32
|
+
required: options.fetch(:required, false),
|
33
|
+
readonly: options.fetch(:readonly, false),
|
34
|
+
format: options[:format],
|
35
|
+
options: options[:options], # for select fields
|
36
|
+
multiple: options[:multiple], # for select fields
|
37
|
+
placeholder: options[:placeholder], # for select fields
|
38
|
+
suggest: options[:suggest], # for dynamic option loading
|
39
|
+
display_method: options[:display_method], # for association fields
|
40
|
+
help_text: options[:help_text],
|
41
|
+
editable: options[:editable],
|
42
|
+
association: options[:association],
|
43
|
+
foreign_key: options[:foreign_key] # for belongs_to fields
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
# Add field to the registry
|
48
|
+
def add_field_to_registry(field_config)
|
49
|
+
# Prevent duplicate fields by name
|
50
|
+
current_fields = self.fields_config || []
|
51
|
+
unless current_fields.any? { |f| f[:name] == field_config[:name] }
|
52
|
+
self.fields_config = current_fields + [field_config]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Field categorization methods
|
57
|
+
def index_fields
|
58
|
+
fields_config.select { |field| ![:text, :has_many, :file].include?(field[:type]) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def sortable_fields
|
62
|
+
fields_config.select { |field| field[:sortable] }
|
63
|
+
end
|
64
|
+
|
65
|
+
def filterable_fields
|
66
|
+
fields_config.select { |field| field[:filterable] }
|
67
|
+
end
|
68
|
+
|
69
|
+
def searchable_fields
|
70
|
+
fields_config.select { |field| field[:searchable] }
|
71
|
+
end
|
72
|
+
|
73
|
+
def show_fields
|
74
|
+
fields_config
|
75
|
+
end
|
76
|
+
|
77
|
+
def form_fields
|
78
|
+
if has_form_tabs?
|
79
|
+
# If tabs are configured, return fields from all tabs
|
80
|
+
form_tabs_config.flat_map { |tab| tab[:fields] }.reject { |field| field[:readonly] }
|
81
|
+
else
|
82
|
+
# Default behavior: return all non-readonly fields
|
83
|
+
fields_config.reject { |field| field[:readonly] }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Field lookup methods
|
88
|
+
def find_field_config(field_name)
|
89
|
+
fields_config.find { |field| field[:name].to_s == field_name.to_s }
|
90
|
+
end
|
91
|
+
|
92
|
+
def field_exists?(field_name)
|
93
|
+
find_field_config(field_name).present?
|
94
|
+
end
|
95
|
+
|
96
|
+
def field_type(field_name)
|
97
|
+
field_config = find_field_config(field_name)
|
98
|
+
field_config&.dig(:type)
|
99
|
+
end
|
100
|
+
|
101
|
+
def field_editable?(field_name)
|
102
|
+
field_config = find_field_config(field_name)
|
103
|
+
return false unless field_config
|
104
|
+
!field_config[:readonly] && field_config.fetch(:editable, true)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Field validation methods
|
108
|
+
def required_fields
|
109
|
+
fields_config.select { |field| field[:required] }
|
110
|
+
end
|
111
|
+
|
112
|
+
def readonly_fields
|
113
|
+
fields_config.select { |field| field[:readonly] }
|
114
|
+
end
|
115
|
+
|
116
|
+
# Field grouping by type
|
117
|
+
def fields_by_type(type)
|
118
|
+
fields_config.select { |field| field[:type] == type }
|
119
|
+
end
|
120
|
+
|
121
|
+
def association_fields
|
122
|
+
fields_config.select { |field| [:belongs_to, :has_many].include?(field[:type]) }
|
123
|
+
end
|
124
|
+
|
125
|
+
def belongs_to_fields
|
126
|
+
fields_by_type(:belongs_to)
|
127
|
+
end
|
128
|
+
|
129
|
+
def has_many_fields
|
130
|
+
fields_by_type(:has_many)
|
131
|
+
end
|
132
|
+
|
133
|
+
def select_fields
|
134
|
+
fields_by_type(:select)
|
135
|
+
end
|
136
|
+
|
137
|
+
def json_fields
|
138
|
+
fields_by_type(:json)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Field options helpers
|
142
|
+
def field_options(field_name)
|
143
|
+
field_config = find_field_config(field_name)
|
144
|
+
field_config&.dig(:options) || []
|
145
|
+
end
|
146
|
+
|
147
|
+
def field_suggest_config(field_name)
|
148
|
+
field_config = find_field_config(field_name)
|
149
|
+
field_config&.dig(:suggest) || {}
|
150
|
+
end
|
151
|
+
|
152
|
+
def field_display_method(field_name)
|
153
|
+
field_config = find_field_config(field_name)
|
154
|
+
field_config&.dig(:display_method) || :name
|
155
|
+
end
|
156
|
+
|
157
|
+
# Advanced field queries
|
158
|
+
def fields_with_suggest
|
159
|
+
fields_config.select { |field| field[:suggest].present? }
|
160
|
+
end
|
161
|
+
|
162
|
+
def multiple_select_fields
|
163
|
+
fields_config.select { |field| field[:type] == :select && field[:multiple] }
|
164
|
+
end
|
165
|
+
|
166
|
+
def file_fields
|
167
|
+
fields_by_type(:file)
|
168
|
+
end
|
169
|
+
|
170
|
+
def password_fields
|
171
|
+
fields_by_type(:password)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Field count helpers
|
175
|
+
def total_fields_count
|
176
|
+
fields_config.count
|
177
|
+
end
|
178
|
+
|
179
|
+
def editable_fields_count
|
180
|
+
fields_config.count { |field| field_editable?(field[:name]) }
|
181
|
+
end
|
182
|
+
|
183
|
+
def association_fields_count
|
184
|
+
association_fields.count
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module ResourceModules
|
3
|
+
# FormBuilder class for form DSL
|
4
|
+
# Handles all form tab and field definitions within forms
|
5
|
+
class FormBuilder
|
6
|
+
def initialize(resource_class)
|
7
|
+
@resource_class = resource_class
|
8
|
+
@current_tab = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def tab(name = "General", **options, &block)
|
12
|
+
tab_config = {
|
13
|
+
name: name,
|
14
|
+
label: options[:label] || name,
|
15
|
+
icon: options[:icon],
|
16
|
+
fields: []
|
17
|
+
}
|
18
|
+
|
19
|
+
@current_tab = tab_config
|
20
|
+
@resource_class.add_form_tab(tab_config)
|
21
|
+
|
22
|
+
instance_eval(&block) if block_given?
|
23
|
+
@current_tab = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
# Field definition methods within tabs - delegate to resource
|
27
|
+
def field(name, type = :string, **options)
|
28
|
+
field_config = @resource_class.build_field_config(name, type, **options)
|
29
|
+
|
30
|
+
if @current_tab
|
31
|
+
@current_tab[:fields] << field_config
|
32
|
+
end
|
33
|
+
|
34
|
+
# Also add to main fields_config for compatibility
|
35
|
+
@resource_class.add_field_to_registry(field_config)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Convenience methods for form fields
|
39
|
+
def text_field(name, **options)
|
40
|
+
field(name, :string, **options)
|
41
|
+
end
|
42
|
+
|
43
|
+
def textarea_field(name, **options)
|
44
|
+
field(name, :text, **options)
|
45
|
+
end
|
46
|
+
|
47
|
+
def number_field(name, **options)
|
48
|
+
field(name, :number, **options)
|
49
|
+
end
|
50
|
+
|
51
|
+
def email_field(name, **options)
|
52
|
+
field(name, :email, **options)
|
53
|
+
end
|
54
|
+
|
55
|
+
def date_field(name, **options)
|
56
|
+
field(name, :date, **options)
|
57
|
+
end
|
58
|
+
|
59
|
+
def datetime_field(name, **options)
|
60
|
+
field(name, :datetime, **options)
|
61
|
+
end
|
62
|
+
|
63
|
+
def boolean_field(name, **options)
|
64
|
+
field(name, :boolean, **options)
|
65
|
+
end
|
66
|
+
|
67
|
+
def select_field(name, options:, **other_options)
|
68
|
+
field(name, :select, options: options, **other_options)
|
69
|
+
end
|
70
|
+
|
71
|
+
def belongs_to_field(name, **options)
|
72
|
+
field(name, :belongs_to, **options)
|
73
|
+
end
|
74
|
+
|
75
|
+
def has_many_field(name, **options)
|
76
|
+
field(name, :has_many, **options)
|
77
|
+
end
|
78
|
+
|
79
|
+
def file_field(name, **options)
|
80
|
+
field(name, :file, **options)
|
81
|
+
end
|
82
|
+
|
83
|
+
def json_field(name, **options)
|
84
|
+
field(name, :json, **options)
|
85
|
+
end
|
86
|
+
|
87
|
+
def password_field(name = :password, **options)
|
88
|
+
field(name, :password, **options)
|
89
|
+
field("#{name}_confirmation".to_sym, :password,
|
90
|
+
label: options[:confirmation_label] || "Confirm #{name.to_s.humanize}",
|
91
|
+
**options.except(:confirmation_label))
|
92
|
+
end
|
93
|
+
|
94
|
+
# Support for rendering custom components in forms
|
95
|
+
def render(component_class_or_instance, **options)
|
96
|
+
content_config = {
|
97
|
+
type: :custom_component,
|
98
|
+
component_class: component_class_or_instance.class,
|
99
|
+
component_options: options,
|
100
|
+
component_instance: component_class_or_instance
|
101
|
+
}
|
102
|
+
|
103
|
+
if @current_tab
|
104
|
+
@current_tab[:fields] << content_config
|
105
|
+
end
|
106
|
+
|
107
|
+
component_class_or_instance
|
108
|
+
end
|
109
|
+
|
110
|
+
# Support for custom content blocks in forms
|
111
|
+
def content(&block)
|
112
|
+
content_config = {
|
113
|
+
type: :custom_content,
|
114
|
+
block: block
|
115
|
+
}
|
116
|
+
|
117
|
+
if @current_tab
|
118
|
+
@current_tab[:fields] << content_config
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,249 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module ResourceModules
|
3
|
+
# LayoutBuilder module for handling form and show layout configuration
|
4
|
+
# Manages form tabs and show page layout elements
|
5
|
+
module LayoutBuilder
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
class_attribute :form_tabs_config, :show_layout_config
|
10
|
+
|
11
|
+
def self.initialize_layout_builder
|
12
|
+
self.form_tabs_config = []
|
13
|
+
self.show_layout_config = []
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class_methods do
|
18
|
+
# Form tabs management
|
19
|
+
def add_form_tab(tab_config)
|
20
|
+
self.form_tabs_config = (self.form_tabs_config || []) + [tab_config]
|
21
|
+
end
|
22
|
+
|
23
|
+
def form_tabs
|
24
|
+
form_tabs_config
|
25
|
+
end
|
26
|
+
|
27
|
+
def has_form_tabs?
|
28
|
+
form_tabs_config.any?
|
29
|
+
end
|
30
|
+
|
31
|
+
def clear_form_tabs
|
32
|
+
self.form_tabs_config = []
|
33
|
+
end
|
34
|
+
|
35
|
+
# Show layout management
|
36
|
+
def add_show_layout_element(element_config)
|
37
|
+
self.show_layout_config = (self.show_layout_config || []) + [element_config]
|
38
|
+
end
|
39
|
+
|
40
|
+
def show_layout
|
41
|
+
show_layout_config
|
42
|
+
end
|
43
|
+
|
44
|
+
def has_show_layout?
|
45
|
+
show_layout_config.any?
|
46
|
+
end
|
47
|
+
|
48
|
+
def clear_show_layout
|
49
|
+
self.show_layout_config = []
|
50
|
+
end
|
51
|
+
|
52
|
+
# Form tab utilities
|
53
|
+
def find_form_tab(tab_name)
|
54
|
+
form_tabs_config.find { |tab| tab[:name].to_s == tab_name.to_s }
|
55
|
+
end
|
56
|
+
|
57
|
+
def form_tab_names
|
58
|
+
form_tabs_config.map { |tab| tab[:name] }
|
59
|
+
end
|
60
|
+
|
61
|
+
def form_tab_fields(tab_name)
|
62
|
+
tab = find_form_tab(tab_name)
|
63
|
+
tab ? tab[:fields] : []
|
64
|
+
end
|
65
|
+
|
66
|
+
def total_form_tabs_count
|
67
|
+
form_tabs_config.count
|
68
|
+
end
|
69
|
+
|
70
|
+
# Show layout utilities
|
71
|
+
def show_layout_rows
|
72
|
+
show_layout_config.select { |element| element[:type] == :row }
|
73
|
+
end
|
74
|
+
|
75
|
+
def show_layout_elements_count
|
76
|
+
count = 0
|
77
|
+
show_layout_config.each do |element|
|
78
|
+
count += count_nested_elements(element)
|
79
|
+
end
|
80
|
+
count
|
81
|
+
end
|
82
|
+
|
83
|
+
def find_show_layout_element(element_type)
|
84
|
+
find_element_in_layout(show_layout_config, element_type)
|
85
|
+
end
|
86
|
+
|
87
|
+
def has_show_layout_tabs?
|
88
|
+
find_show_layout_element(:tabs).present?
|
89
|
+
end
|
90
|
+
|
91
|
+
def has_show_layout_cards?
|
92
|
+
find_show_layout_element(:card).present?
|
93
|
+
end
|
94
|
+
|
95
|
+
def has_metric_cards?
|
96
|
+
find_show_layout_element(:metric_card).present?
|
97
|
+
end
|
98
|
+
|
99
|
+
def has_chart_cards?
|
100
|
+
find_show_layout_element(:chart_card).present?
|
101
|
+
end
|
102
|
+
|
103
|
+
# Layout validation
|
104
|
+
def validate_form_structure
|
105
|
+
errors = []
|
106
|
+
|
107
|
+
if has_form_tabs?
|
108
|
+
form_tabs_config.each_with_index do |tab, index|
|
109
|
+
unless tab[:name].present?
|
110
|
+
errors << "Form tab at index #{index} is missing a name"
|
111
|
+
end
|
112
|
+
|
113
|
+
unless tab[:fields].is_a?(Array)
|
114
|
+
errors << "Form tab '#{tab[:name]}' fields must be an array"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
errors
|
120
|
+
end
|
121
|
+
|
122
|
+
def validate_show_layout_structure
|
123
|
+
errors = []
|
124
|
+
|
125
|
+
show_layout_config.each_with_index do |element, index|
|
126
|
+
case element[:type]
|
127
|
+
when :row
|
128
|
+
unless element[:columns].is_a?(Array)
|
129
|
+
errors << "Row at index #{index} must have columns array"
|
130
|
+
end
|
131
|
+
|
132
|
+
if element[:columns].empty?
|
133
|
+
errors << "Row at index #{index} cannot have empty columns"
|
134
|
+
end
|
135
|
+
when :column
|
136
|
+
unless element[:elements].is_a?(Array)
|
137
|
+
errors << "Column at index #{index} must have elements array"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
errors
|
143
|
+
end
|
144
|
+
|
145
|
+
# Layout rebuild methods
|
146
|
+
def rebuild_form_from_fields
|
147
|
+
return if fields_config.empty?
|
148
|
+
|
149
|
+
# Create a default tab with all non-readonly fields
|
150
|
+
default_tab = {
|
151
|
+
name: "General",
|
152
|
+
label: "General",
|
153
|
+
icon: nil,
|
154
|
+
fields: fields_config.reject { |field| field[:readonly] }
|
155
|
+
}
|
156
|
+
|
157
|
+
self.form_tabs_config = [default_tab]
|
158
|
+
end
|
159
|
+
|
160
|
+
def rebuild_show_layout_from_fields
|
161
|
+
return if fields_config.empty?
|
162
|
+
|
163
|
+
# Create a simple single-column layout with all fields
|
164
|
+
row_config = {
|
165
|
+
type: :row,
|
166
|
+
columns_count: 1,
|
167
|
+
spacing: "medium",
|
168
|
+
columns: [{
|
169
|
+
type: :column,
|
170
|
+
size: 12,
|
171
|
+
elements: [{
|
172
|
+
type: :card,
|
173
|
+
title: "Details",
|
174
|
+
color: "light",
|
175
|
+
padding: "medium",
|
176
|
+
shadow: "small",
|
177
|
+
elements: fields_config.map { |field|
|
178
|
+
{
|
179
|
+
type: :field,
|
180
|
+
name: field[:name],
|
181
|
+
label: field[:label],
|
182
|
+
field_type: :default,
|
183
|
+
format: field[:format],
|
184
|
+
css_classes: nil,
|
185
|
+
show_label: true
|
186
|
+
}
|
187
|
+
}
|
188
|
+
}]
|
189
|
+
}]
|
190
|
+
}
|
191
|
+
|
192
|
+
self.show_layout_config = [row_config]
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
def count_nested_elements(element)
|
198
|
+
count = 1
|
199
|
+
|
200
|
+
case element[:type]
|
201
|
+
when :row
|
202
|
+
element[:columns]&.each { |column| count += count_nested_elements(column) }
|
203
|
+
when :column
|
204
|
+
element[:elements]&.each { |child| count += count_nested_elements(child) }
|
205
|
+
when :card
|
206
|
+
element[:elements]&.each { |child| count += count_nested_elements(child) }
|
207
|
+
when :tabs
|
208
|
+
element[:tabs]&.each { |tab| count += count_nested_elements(tab) }
|
209
|
+
when :tab
|
210
|
+
element[:elements]&.each { |child| count += count_nested_elements(child) }
|
211
|
+
end
|
212
|
+
|
213
|
+
count
|
214
|
+
end
|
215
|
+
|
216
|
+
def find_element_in_layout(layout, element_type)
|
217
|
+
layout.each do |element|
|
218
|
+
return element if element[:type] == element_type
|
219
|
+
|
220
|
+
case element[:type]
|
221
|
+
when :row
|
222
|
+
element[:columns]&.each do |column|
|
223
|
+
found = find_element_in_layout([column], element_type)
|
224
|
+
return found if found
|
225
|
+
end
|
226
|
+
when :column, :card
|
227
|
+
if element[:elements]
|
228
|
+
found = find_element_in_layout(element[:elements], element_type)
|
229
|
+
return found if found
|
230
|
+
end
|
231
|
+
when :tabs
|
232
|
+
element[:tabs]&.each do |tab|
|
233
|
+
found = find_element_in_layout([tab], element_type)
|
234
|
+
return found if found
|
235
|
+
end
|
236
|
+
when :tab
|
237
|
+
if element[:elements]
|
238
|
+
found = find_element_in_layout(element[:elements], element_type)
|
239
|
+
return found if found
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
nil
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|