easy-admin-rails 0.2.6 → 0.2.7

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/easy_admin.base.js +7 -0
  3. data/app/assets/builds/easy_admin.base.js.map +2 -2
  4. data/app/assets/builds/easy_admin.css +207 -35
  5. data/app/components/easy_admin/fields/form/belongs_to_component.rb +0 -1
  6. data/app/components/easy_admin/form_layout_component.rb +553 -0
  7. data/app/components/easy_admin/permissions/user_role_permissions_component.rb +1 -3
  8. data/app/components/easy_admin/show_layout_component.rb +694 -24
  9. data/app/controllers/easy_admin/application_controller.rb +0 -5
  10. data/app/controllers/easy_admin/batch_actions_controller.rb +0 -1
  11. data/app/controllers/easy_admin/concerns/inline_field_editing.rb +4 -11
  12. data/app/controllers/easy_admin/concerns/resource_loading.rb +10 -9
  13. data/app/controllers/easy_admin/concerns/resource_pagination.rb +3 -0
  14. data/app/controllers/easy_admin/dashboards_controller.rb +0 -1
  15. data/app/controllers/easy_admin/resources_controller.rb +1 -5
  16. data/app/controllers/easy_admin/row_actions_controller.rb +1 -4
  17. data/app/helpers/easy_admin/fields_helper.rb +8 -22
  18. data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +12 -0
  19. data/app/views/easy_admin/resources/edit.html.erb +2 -2
  20. data/app/views/easy_admin/resources/new.html.erb +2 -2
  21. data/app/views/easy_admin/resources/show.html.erb +3 -1
  22. data/lib/easy_admin/field.rb +3 -2
  23. data/lib/easy_admin/layouts/builders/base_layout_builder.rb +245 -0
  24. data/lib/easy_admin/layouts/builders/form_layout_builder.rb +208 -0
  25. data/lib/easy_admin/layouts/builders/index_layout_builder.rb +22 -0
  26. data/lib/easy_admin/layouts/builders/show_layout_builder.rb +199 -0
  27. data/lib/easy_admin/layouts/dsl.rb +200 -0
  28. data/lib/easy_admin/layouts/layout_context.rb +189 -0
  29. data/lib/easy_admin/layouts/nodes/base_node.rb +88 -0
  30. data/lib/easy_admin/layouts/nodes/divider.rb +27 -0
  31. data/lib/easy_admin/layouts/nodes/field_node.rb +57 -0
  32. data/lib/easy_admin/layouts/nodes/grid.rb +60 -0
  33. data/lib/easy_admin/layouts/nodes/render_node.rb +41 -0
  34. data/lib/easy_admin/layouts/nodes/root.rb +25 -0
  35. data/lib/easy_admin/layouts/nodes/section.rb +46 -0
  36. data/lib/easy_admin/layouts/nodes/spacer.rb +17 -0
  37. data/lib/easy_admin/layouts/nodes/stubs.rb +109 -0
  38. data/lib/easy_admin/layouts/nodes/tab.rb +40 -0
  39. data/lib/easy_admin/layouts/nodes/tabs.rb +40 -0
  40. data/lib/easy_admin/layouts.rb +28 -0
  41. data/lib/easy_admin/permissions/resource_permissions.rb +1 -5
  42. data/lib/easy_admin/resource/base.rb +2 -2
  43. data/lib/easy_admin/resource/dsl.rb +2 -11
  44. data/lib/easy_admin/resource/field_registry.rb +58 -2
  45. data/lib/easy_admin/resource.rb +0 -9
  46. data/lib/easy_admin/resource_modules.rb +21 -4
  47. data/lib/easy_admin/version.rb +1 -1
  48. data/lib/generators/easy_admin/permissions/install_generator.rb +0 -10
  49. data/lib/generators/easy_admin/permissions/templates/migrations/create_permission_tables.rb +33 -3
  50. metadata +21 -9
  51. data/lib/easy_admin/resource/form_builder.rb +0 -123
  52. data/lib/easy_admin/resource/layout_builder.rb +0 -249
  53. data/lib/easy_admin/resource/show_builder.rb +0 -359
  54. data/lib/generators/easy_admin/permissions/templates/migrations/update_users_for_permissions.rb +0 -6
  55. data/lib/generators/easy_admin/rbac/rbac_generator.rb +0 -244
  56. data/lib/generators/easy_admin/rbac/templates/add_rbac_to_admin_users.rb +0 -23
  57. data/lib/generators/easy_admin/rbac/templates/super_admin.rb +0 -34
@@ -0,0 +1,553 @@
1
+ module EasyAdmin
2
+ class FormLayoutComponent < BaseComponent
3
+ include EasyAdmin::FieldsHelper
4
+
5
+ def initialize(resource_class:, form:, record: nil)
6
+ @resource_class = resource_class
7
+ @form = form
8
+ @record = record
9
+ end
10
+
11
+ def view_template
12
+ has_custom = @resource_class.has_custom_form_layout?
13
+
14
+ if has_custom
15
+ render_custom_layout
16
+ else
17
+ render_default_layout
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def render_custom_layout
24
+ layout_type, layout_content = @resource_class.form_layout_definition
25
+
26
+ case layout_type
27
+ when :ast
28
+ render_ast_layout(layout_content)
29
+ when :component
30
+ render layout_content
31
+ else
32
+ render_default_layout
33
+ end
34
+ end
35
+
36
+ def render_ast_layout(root_node)
37
+ if root_node
38
+ render_children(root_node)
39
+ end
40
+ end
41
+
42
+ def render_children(node)
43
+ return unless node&.children
44
+
45
+ node.children.each do |child|
46
+ render_node(child)
47
+ end
48
+ end
49
+
50
+ def render_node(node)
51
+ case node
52
+ when EasyAdmin::Layouts::Nodes::Row
53
+ render_row_node(node)
54
+ when EasyAdmin::Layouts::Nodes::Column
55
+ render_column_node(node)
56
+ when EasyAdmin::Layouts::Nodes::Card
57
+ render_card_node(node)
58
+ when EasyAdmin::Layouts::Nodes::Section
59
+ render_section_node(node)
60
+ when EasyAdmin::Layouts::Nodes::Grid
61
+ render_grid_node(node)
62
+ when EasyAdmin::Layouts::Nodes::FieldNode
63
+ render_field_node(node)
64
+ when EasyAdmin::Layouts::Nodes::Heading
65
+ render_heading_node(node)
66
+ when EasyAdmin::Layouts::Nodes::Divider
67
+ render_divider_node(node)
68
+ when EasyAdmin::Layouts::Nodes::Tabs
69
+ render_tabs_node(node)
70
+ when EasyAdmin::Layouts::Nodes::Tab
71
+ nil
72
+ when EasyAdmin::Layouts::Nodes::Content
73
+ render_content_node(node)
74
+ when EasyAdmin::Layouts::Nodes::Spacer
75
+ render_spacer_node(node)
76
+ when EasyAdmin::Layouts::Nodes::RenderNode
77
+ render_render_node(node)
78
+ else
79
+ nil
80
+ end
81
+ end
82
+
83
+ def render_row_node(node)
84
+ classes = ["grid", "items-stretch"]
85
+
86
+ # Check if any child column has explicit size - if so, use 12-column grid
87
+ has_sized_columns = node.children.any? { |child|
88
+ child.is_a?(EasyAdmin::Layouts::Nodes::Column) && child.attributes[:size]
89
+ }
90
+
91
+ if has_sized_columns
92
+ # Use 12-column grid for Bootstrap-style column sizing
93
+ classes << "grid-cols-1 md:grid-cols-12"
94
+ elsif node.attributes[:columns]
95
+ # Use simple equal-width columns
96
+ columns = node.attributes[:columns]
97
+ case columns
98
+ when 1
99
+ classes << "grid-cols-1"
100
+ when 2
101
+ classes << "grid-cols-1 md:grid-cols-2"
102
+ when 3
103
+ classes << "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
104
+ when 4
105
+ classes << "grid-cols-1 md:grid-cols-2 lg:grid-cols-4"
106
+ else
107
+ classes << "grid-cols-1 md:grid-cols-#{[columns, 12].min}"
108
+ end
109
+ else
110
+ classes << "grid-cols-1"
111
+ end
112
+
113
+ # Add spacing with Tailwind gap classes
114
+ spacing = node.attributes[:spacing] || "medium"
115
+ case spacing
116
+ when "small", "sm"
117
+ classes << "gap-2"
118
+ when "medium", "md"
119
+ classes << "gap-4"
120
+ when "large", "lg"
121
+ classes << "gap-6"
122
+ else
123
+ classes << "gap-4"
124
+ end
125
+
126
+ div(class: classes.join(" ")) do
127
+ render_children(node)
128
+ end
129
+ end
130
+
131
+ def render_column_node(node)
132
+ classes = ["flex", "flex-col", "h-full"]
133
+
134
+ # Handle column spanning in Tailwind CSS Grid
135
+ if node.attributes[:size]
136
+ size = node.attributes[:size]
137
+ # On mobile, all columns take full width, on desktop use specified size
138
+ case size
139
+ when 1
140
+ classes << "col-span-1 md:col-span-1"
141
+ when 2
142
+ classes << "col-span-1 md:col-span-2"
143
+ when 3
144
+ classes << "col-span-1 md:col-span-3"
145
+ when 4
146
+ classes << "col-span-1 md:col-span-4"
147
+ when 5
148
+ classes << "col-span-1 md:col-span-5"
149
+ when 6
150
+ classes << "col-span-1 md:col-span-6"
151
+ when 7
152
+ classes << "col-span-1 md:col-span-7"
153
+ when 8
154
+ classes << "col-span-1 md:col-span-8"
155
+ when 9
156
+ classes << "col-span-1 md:col-span-9"
157
+ when 10
158
+ classes << "col-span-1 md:col-span-10"
159
+ when 11
160
+ classes << "col-span-1 md:col-span-11"
161
+ when 12
162
+ classes << "col-span-1 md:col-span-12"
163
+ else
164
+ classes << "col-span-full"
165
+ end
166
+ end
167
+
168
+ div(class: classes.join(" ")) do
169
+ render_children(node)
170
+ end
171
+ end
172
+
173
+ def render_card_node(node)
174
+ # Build card classes with Tailwind
175
+ card_classes = ["bg-white", "rounded-lg", "shadow-sm", "border", "mb-4", "flex-1", "flex", "flex-col", "h-full"]
176
+
177
+ # Add color styling with Tailwind
178
+ if node.attributes[:color]
179
+ case node.attributes[:color]
180
+ when "primary"
181
+ card_classes << "border-blue-200"
182
+ when "secondary"
183
+ card_classes << "border-gray-200"
184
+ when "success"
185
+ card_classes << "border-green-200"
186
+ when "info"
187
+ card_classes << "border-cyan-200"
188
+ when "warning"
189
+ card_classes << "border-yellow-200"
190
+ when "danger"
191
+ card_classes << "border-red-200"
192
+ when "light"
193
+ card_classes << "border-gray-100"
194
+ when "dark"
195
+ card_classes << "border-gray-800"
196
+ else
197
+ card_classes << "border-gray-200"
198
+ end
199
+ else
200
+ card_classes << "border-gray-200"
201
+ end
202
+
203
+ div(class: card_classes.join(" ")) do
204
+ # Card header
205
+ if node.attributes[:title]
206
+ header_classes = ["px-6", "py-4", "border-b", "border-gray-200"]
207
+
208
+ # Add header color styling with Tailwind
209
+ if node.attributes[:color]
210
+ case node.attributes[:color]
211
+ when "primary"
212
+ header_classes << "bg-blue-50"
213
+ when "secondary"
214
+ header_classes << "bg-gray-50"
215
+ when "success"
216
+ header_classes << "bg-green-50"
217
+ when "info"
218
+ header_classes << "bg-cyan-50"
219
+ when "warning"
220
+ header_classes << "bg-yellow-50"
221
+ when "danger"
222
+ header_classes << "bg-red-50"
223
+ when "light"
224
+ header_classes << "bg-gray-25"
225
+ when "dark"
226
+ header_classes << "bg-gray-800 text-white"
227
+ else
228
+ header_classes << "bg-gray-50"
229
+ end
230
+ else
231
+ header_classes << "bg-gray-50"
232
+ end
233
+
234
+ div(class: header_classes.join(" ")) do
235
+ h3(class: "text-lg font-semibold text-gray-900") { node.attributes[:title] }
236
+ end
237
+ end
238
+
239
+ # Card body
240
+ div(class: "px-6 py-4 flex-1") do
241
+ render_children(node)
242
+ end
243
+ end
244
+ end
245
+
246
+ def render_section_node(node)
247
+ div(class: "mb-6") do
248
+ if node.attributes[:title]
249
+ h3(class: "text-lg font-medium leading-6 text-gray-900 mb-4") do
250
+ node.attributes[:title]
251
+ end
252
+ end
253
+ render_children(node)
254
+ end
255
+ end
256
+
257
+ def render_grid_node(node)
258
+ columns = node.attributes[:columns] || 2
259
+ classes = ["grid"]
260
+
261
+ # Add responsive column classes with Tailwind CSS Grid
262
+ case columns
263
+ when 1
264
+ classes << "grid-cols-1"
265
+ when 2
266
+ classes << "grid-cols-1 md:grid-cols-2"
267
+ when 3
268
+ classes << "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
269
+ when 4
270
+ classes << "grid-cols-1 md:grid-cols-2 lg:grid-cols-4"
271
+ else
272
+ classes << "grid-cols-1 md:grid-cols-#{[columns, 6].min}"
273
+ end
274
+
275
+ # Add spacing
276
+ spacing = node.attributes[:spacing] || "medium"
277
+ classes << spacing_class(spacing)
278
+
279
+ div(class: classes.join(" ")) do
280
+ render_children(node)
281
+ end
282
+ end
283
+
284
+ def render_field_node(node)
285
+ base_field_config = @resource_class.find_field_config(node.field_name) || {}
286
+ field_config = base_field_config.merge(node.attributes).merge({
287
+ name: node.field_name,
288
+ label: node.attributes[:label] || base_field_config[:label] || node.field_name.to_s.humanize,
289
+ type: node.attributes[:type] || base_field_config[:type]
290
+ })
291
+
292
+ div(class: "mb-6") do
293
+ render_form_field(field_config)
294
+ end
295
+ end
296
+
297
+ def render_form_field(field_config)
298
+ if field_config[:type] == :has_many
299
+ render_has_many_form_field(field_config)
300
+ else
301
+ field_definition = {
302
+ name: field_config[:name],
303
+ type: field_config[:type] || :text,
304
+ field_type: field_config[:type] || :text,
305
+ label: field_config[:label],
306
+ required: field_config[:required],
307
+ help_text: field_config[:help_text],
308
+ options: field_config[:options],
309
+ multiple: field_config[:multiple]
310
+ }.merge(field_config)
311
+
312
+ result = render_field(field_definition, action: :form, form: @form, record: @record)
313
+
314
+ unsafe_raw result
315
+ end
316
+ end
317
+
318
+ def render_has_many_form_field(field_config)
319
+ div(class: "mb-6") do
320
+ div(class: "block text-sm font-medium text-gray-700 mb-2") do
321
+ field_config[:label]
322
+ end
323
+
324
+ if @record
325
+ association = @record.public_send(field_config[:name])
326
+ count = association.respond_to?(:count) ? association.count : 0
327
+
328
+ div(class: "px-4 py-3 bg-gray-50 border border-gray-200 rounded-md") do
329
+ p(class: "text-sm text-gray-600") do
330
+ "#{count} #{field_config[:name].to_s.humanize.downcase} associated with this #{@resource_class.resource_name.humanize.downcase}"
331
+ end
332
+
333
+ if field_config[:help_text]
334
+ p(class: "mt-2 text-xs text-gray-500") { field_config[:help_text] }
335
+ end
336
+
337
+ p(class: "mt-2 text-xs text-gray-400") do
338
+ "Manage #{field_config[:name].to_s.humanize.downcase} through the #{field_config[:name].to_s.humanize} section"
339
+ end
340
+ end
341
+ else
342
+ div(class: "px-4 py-3 bg-gray-50 border border-gray-200 rounded-md") do
343
+ p(class: "text-sm text-gray-600") do
344
+ "#{field_config[:name].to_s.humanize} will be available after saving this #{@resource_class.resource_name.humanize.downcase}"
345
+ end
346
+
347
+ if field_config[:help_text]
348
+ p(class: "mt-2 text-xs text-gray-500") { field_config[:help_text] }
349
+ end
350
+ end
351
+ end
352
+ end
353
+ end
354
+
355
+
356
+ def render_heading_node(node)
357
+ level = node.attributes[:level] || 2
358
+ tag_name = "h#{level}"
359
+ classes = ["mb-3", "font-semibold"]
360
+
361
+ # Add appropriate text sizing based on heading level
362
+ case level
363
+ when 1
364
+ classes << "text-xl text-gray-900"
365
+ when 2
366
+ classes << "text-lg text-gray-900"
367
+ when 3
368
+ classes << "text-base text-gray-800"
369
+ when 4
370
+ classes << "text-sm text-gray-800"
371
+ else
372
+ classes << "text-sm text-gray-700"
373
+ end
374
+
375
+ send(tag_name, class: classes.join(" ")) { node.text }
376
+ end
377
+
378
+ def render_divider_node(node)
379
+ margin = node.attributes[:margin] || 4
380
+ hr(class: "my-#{margin} border-gray-200")
381
+ end
382
+
383
+ def render_tabs_node(node)
384
+ tabs_id = node.attributes[:id] || "form-tabs-#{SecureRandom.hex(4)}"
385
+
386
+ div(class: "form-tabs",
387
+ data: {
388
+ controller: "form-tabs",
389
+ action: "tab:selected@window->form-tabs#handleTabSelection"
390
+ }) do
391
+
392
+ render_tab_navigation(node, tabs_id)
393
+
394
+ render_tab_content(node, tabs_id)
395
+ end
396
+ end
397
+
398
+ def render_tab_navigation(tabs_node, tabs_id)
399
+ nav(class: "border-b border-gray-200 mb-4 sm:mb-6") do
400
+ # Desktop tabs
401
+ ul(class: "flex flex-wrap -mb-px text-sm font-medium text-center", role: "tablist") do
402
+ tabs_node.children.each_with_index do |tab_node, index|
403
+ next unless tab_node.is_a?(EasyAdmin::Layouts::Nodes::Tab)
404
+
405
+ li(class: "mr-2", role: "presentation") do
406
+ button(
407
+ class: tab_button_classes(index == 0),
408
+ id: "#{tab_node.attributes[:name]}-tab",
409
+ data: {
410
+ form_tabs_target: "tabButton",
411
+ tab_id: tab_node.attributes[:name],
412
+ action: "click->form-tabs#switchTab"
413
+ },
414
+ type: "button",
415
+ role: "tab"
416
+ ) do
417
+ if tab_node.attributes[:icon]
418
+ span(class: "inline-flex items-center") do
419
+ unsafe_raw(tab_icon(tab_node.attributes[:icon]))
420
+ span(class: "ml-2") { tab_node.label }
421
+ end
422
+ else
423
+ plain tab_node.label
424
+ end
425
+ end
426
+ end
427
+ end
428
+ end
429
+ end
430
+ end
431
+
432
+ def render_tab_content(tabs_node, tabs_id)
433
+ Rails.logger.info "🔍 [FormLayout] render_tab_content started"
434
+ div(class: "tab-content") do
435
+ tabs_node.children.each_with_index do |tab_node, index|
436
+ next unless tab_node.is_a?(EasyAdmin::Layouts::Nodes::Tab)
437
+
438
+ div(
439
+ class: tab_panel_classes(index == 0),
440
+ id: "#{tab_node.attributes[:name]}-panel",
441
+ data: { form_tabs_target: "tabPanel" },
442
+ role: "tabpanel"
443
+ ) do
444
+ div(class: "bg-white shadow-sm rounded-lg border border-gray-200") do
445
+ div(class: "px-4 py-5 sm:p-6") do
446
+ if tab_node.label && !tab_node.label.empty?
447
+ h3(class: "text-lg font-medium leading-6 text-gray-900 mb-6") do
448
+ tab_node.label
449
+ end
450
+ end
451
+
452
+ div(class: "grid grid-cols-1 gap-6") do
453
+ render_children(tab_node)
454
+ end
455
+ end
456
+ end
457
+ end
458
+ end
459
+ end
460
+ end
461
+
462
+ def render_content_node(node)
463
+ if node.block
464
+ context = EasyAdmin::Layouts::LayoutContext.new(
465
+ record: @record,
466
+ resource_class: @resource_class,
467
+ form_builder: @form
468
+ )
469
+ result = context.instance_exec(&node.block)
470
+ if result.respond_to?(:call)
471
+ render result
472
+ else
473
+ unsafe_raw result.to_s
474
+ end
475
+ end
476
+ end
477
+
478
+ def render_default_layout
479
+ div(class: "bg-white shadow-sm rounded-lg border border-gray-200") do
480
+ div(class: "px-4 py-5 sm:p-6") do
481
+ div(class: "grid grid-cols-1 gap-6") do
482
+ @resource_class.fields_config.each do |field_config|
483
+ next if field_config[:readonly] || field_config[:form] == false
484
+ render_form_field(field_config)
485
+ end
486
+ end
487
+ end
488
+ end
489
+ end
490
+
491
+ def tab_button_classes(active = false)
492
+ base_classes = "inline-flex items-center p-4 border-b-2 rounded-t-lg transition-colors duration-200"
493
+
494
+ if active
495
+ active_classes = "text-blue-600 border-blue-600"
496
+ else
497
+ inactive_classes = "text-gray-500 border-transparent hover:text-gray-600 hover:border-gray-300"
498
+ end
499
+
500
+ "#{base_classes} #{active ? active_classes : inactive_classes}"
501
+ end
502
+
503
+ def tab_panel_classes(active = false)
504
+ base_classes = "tab-panel"
505
+ visibility_classes = active ? "block" : "hidden"
506
+
507
+ "#{base_classes} #{visibility_classes}"
508
+ end
509
+
510
+ def tab_icon(icon_name)
511
+ case icon_name.to_sym
512
+ when :user
513
+ '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>'
514
+ when :security
515
+ '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>'
516
+ when :settings, :info
517
+ '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>'
518
+ else
519
+ '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>'
520
+ end
521
+ end
522
+
523
+ def render_spacer_node(node)
524
+ div(class: "my-#{node.attributes[:size] || 4}")
525
+ end
526
+
527
+ def render_render_node(node)
528
+ if node.component_class
529
+ begin
530
+ component = node.component_class.constantize.new(**node.attributes)
531
+ render component
532
+ rescue => e
533
+ div(class: "bg-yellow-50 border border-yellow-200 text-yellow-800 px-4 py-3 rounded-md") do
534
+ "Component class not found: #{node.component_class}"
535
+ end
536
+ end
537
+ end
538
+ end
539
+
540
+ def spacing_class(spacing)
541
+ case spacing
542
+ when "small", "sm"
543
+ "gap-2"
544
+ when "medium", "md"
545
+ "gap-4"
546
+ when "large", "lg"
547
+ "gap-6"
548
+ else
549
+ "gap-4"
550
+ end
551
+ end
552
+ end
553
+ end
@@ -14,8 +14,6 @@ module EasyAdmin
14
14
  # Get actual permissions from permissions_cache
15
15
  @user_permissions = get_user_permissions_from_cache(user)
16
16
  @available_resources = EasyAdmin::Permissions.available_resources
17
-
18
- Rails.logger.debug "UserRolePermissionsComponent: user=#{@user&.id}, role=#{@current_role&.name}, cached_permissions=#{@user_permissions.size}"
19
17
  end
20
18
 
21
19
  def view_template
@@ -192,4 +190,4 @@ module EasyAdmin
192
190
  end
193
191
  end
194
192
  end
195
- end
193
+ end