easy-admin-rails 0.1.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +8 -0
- data/app/assets/builds/easy_admin.base.js +43505 -0
- data/app/assets/builds/easy_admin.base.js.map +7 -0
- data/app/assets/builds/easy_admin.css +6141 -0
- data/app/assets/config/easy_admin_manifest.js +1 -0
- data/app/assets/images/jsoneditor-icons.svg +749 -0
- data/app/assets/stylesheets/easy_admin/application.tailwind.css +390 -0
- data/app/components/easy_admin/base_component.rb +35 -0
- data/app/components/easy_admin/batch_action_bar_component.rb +125 -0
- data/app/components/easy_admin/batch_action_form_component.rb +124 -0
- data/app/components/easy_admin/combined_filters_component.rb +232 -0
- data/app/components/easy_admin/confirmation_modal_component.rb +61 -0
- data/app/components/easy_admin/context_menu_component.rb +161 -0
- data/app/components/easy_admin/dashboards/base_card_component.rb +152 -0
- data/app/components/easy_admin/dashboards/card_error_component.rb +23 -0
- data/app/components/easy_admin/dashboards/card_factory.rb +90 -0
- data/app/components/easy_admin/dashboards/card_stream_component.rb +22 -0
- data/app/components/easy_admin/dashboards/cards/base_card_component.rb +54 -0
- data/app/components/easy_admin/dashboards/cards/chart_card_component.rb +175 -0
- data/app/components/easy_admin/dashboards/cards/custom_card_component.rb +50 -0
- data/app/components/easy_admin/dashboards/cards/metric_card_component.rb +164 -0
- data/app/components/easy_admin/dashboards/cards/table_card_component.rb +148 -0
- data/app/components/easy_admin/dashboards/chart_card_component.rb +44 -0
- data/app/components/easy_admin/dashboards/metric_card_component.rb +56 -0
- data/app/components/easy_admin/dashboards/refresh_stream_component.rb +279 -0
- data/app/components/easy_admin/dashboards/show_component.rb +163 -0
- data/app/components/easy_admin/dashboards/table_card_component.rb +52 -0
- data/app/components/easy_admin/date_picker_component.rb +188 -0
- data/app/components/easy_admin/fields/base_component.rb +101 -0
- data/app/components/easy_admin/fields/belongs_to_edit_modal_component.rb +117 -0
- data/app/components/easy_admin/fields/form/belongs_to_component.rb +82 -0
- data/app/components/easy_admin/fields/form/boolean_component.rb +100 -0
- data/app/components/easy_admin/fields/form/date_component.rb +55 -0
- data/app/components/easy_admin/fields/form/datetime_component.rb +55 -0
- data/app/components/easy_admin/fields/form/email_component.rb +55 -0
- data/app/components/easy_admin/fields/form/file_component.rb +190 -0
- data/app/components/easy_admin/fields/form/has_many_component.rb +416 -0
- data/app/components/easy_admin/fields/form/json_component.rb +81 -0
- data/app/components/easy_admin/fields/form/number_component.rb +55 -0
- data/app/components/easy_admin/fields/form/select_component.rb +326 -0
- data/app/components/easy_admin/fields/form/text_component.rb +55 -0
- data/app/components/easy_admin/fields/form/textarea_component.rb +54 -0
- data/app/components/easy_admin/fields/index/belongs_to_component.rb +93 -0
- data/app/components/easy_admin/fields/index/boolean_component.rb +29 -0
- data/app/components/easy_admin/fields/index/date_component.rb +13 -0
- data/app/components/easy_admin/fields/index/datetime_component.rb +13 -0
- data/app/components/easy_admin/fields/index/email_component.rb +24 -0
- data/app/components/easy_admin/fields/index/filters/base_component.rb +48 -0
- data/app/components/easy_admin/fields/index/filters/boolean_component.rb +96 -0
- data/app/components/easy_admin/fields/index/filters/date_component.rb +182 -0
- data/app/components/easy_admin/fields/index/filters/number_component.rb +30 -0
- data/app/components/easy_admin/fields/index/filters/select_component.rb +101 -0
- data/app/components/easy_admin/fields/index/filters/string_component.rb +32 -0
- data/app/components/easy_admin/fields/index/json_component.rb +23 -0
- data/app/components/easy_admin/fields/index/number_component.rb +20 -0
- data/app/components/easy_admin/fields/index/select_component.rb +25 -0
- data/app/components/easy_admin/fields/index/text_component.rb +20 -0
- data/app/components/easy_admin/fields/inline_edit_modal_component.rb +135 -0
- data/app/components/easy_admin/fields/inline_edit_trigger_component.rb +144 -0
- data/app/components/easy_admin/fields/show/belongs_to_component.rb +93 -0
- data/app/components/easy_admin/fields/show/boolean_component.rb +21 -0
- data/app/components/easy_admin/fields/show/date_component.rb +13 -0
- data/app/components/easy_admin/fields/show/datetime_component.rb +13 -0
- data/app/components/easy_admin/fields/show/email_component.rb +19 -0
- data/app/components/easy_admin/fields/show/file_component.rb +304 -0
- data/app/components/easy_admin/fields/show/has_many_component.rb +192 -0
- data/app/components/easy_admin/fields/show/json_component.rb +45 -0
- data/app/components/easy_admin/fields/show/number_component.rb +20 -0
- data/app/components/easy_admin/fields/show/select_component.rb +25 -0
- data/app/components/easy_admin/fields/show/text_component.rb +17 -0
- data/app/components/easy_admin/fields/show/textarea_component.rb +26 -0
- data/app/components/easy_admin/filters_component.rb +120 -0
- data/app/components/easy_admin/form_tabs_component.rb +166 -0
- data/app/components/easy_admin/infinite_scroll_component.rb +82 -0
- data/app/components/easy_admin/lazy_chart_card_component.rb +128 -0
- data/app/components/easy_admin/lazy_metric_card_component.rb +76 -0
- data/app/components/easy_admin/modal_frame_component.rb +26 -0
- data/app/components/easy_admin/navbar_component.rb +226 -0
- data/app/components/easy_admin/notification_component.rb +83 -0
- data/app/components/easy_admin/pagination_component.rb +188 -0
- data/app/components/easy_admin/quick_filters_component.rb +65 -0
- data/app/components/easy_admin/resource_pagination_component.rb +14 -0
- data/app/components/easy_admin/resources/index_component.rb +211 -0
- data/app/components/easy_admin/resources/index_frame_component.rb +88 -0
- data/app/components/easy_admin/resources/show_page_actions_component.rb +324 -0
- data/app/components/easy_admin/resources/table_cell_component.rb +145 -0
- data/app/components/easy_admin/resources/table_component.rb +206 -0
- data/app/components/easy_admin/resources/table_row_component.rb +160 -0
- data/app/components/easy_admin/row_action_form_component.rb +127 -0
- data/app/components/easy_admin/scopes_component.rb +224 -0
- data/app/components/easy_admin/settings_sidebar_component.rb +140 -0
- data/app/components/easy_admin/show_layout_component.rb +600 -0
- data/app/components/easy_admin/sidebar_component.rb +174 -0
- data/app/components/easy_admin/turbo/response_component.rb +40 -0
- data/app/components/easy_admin/turbo/stream_component.rb +28 -0
- data/app/controllers/easy_admin/application_controller.rb +66 -0
- data/app/controllers/easy_admin/batch_actions_controller.rb +166 -0
- data/app/controllers/easy_admin/confirmation_modal_controller.rb +20 -0
- data/app/controllers/easy_admin/dashboard_controller.rb +6 -0
- data/app/controllers/easy_admin/dashboards_controller.rb +123 -0
- data/app/controllers/easy_admin/passwords_controller.rb +15 -0
- data/app/controllers/easy_admin/registrations_controller.rb +52 -0
- data/app/controllers/easy_admin/resources_controller.rb +907 -0
- data/app/controllers/easy_admin/row_actions_controller.rb +216 -0
- data/app/controllers/easy_admin/sessions_controller.rb +32 -0
- data/app/controllers/easy_admin/settings_controller.rb +94 -0
- data/app/helpers/easy_admin/application_helper.rb +4 -0
- data/app/helpers/easy_admin/dashboards_helper.rb +121 -0
- data/app/helpers/easy_admin/fields_helper.rb +27 -0
- data/app/helpers/easy_admin/pagy_helper.rb +30 -0
- data/app/helpers/easy_admin/resources_helper.rb +39 -0
- data/app/javascript/easy_admin/application.js +12 -0
- data/app/javascript/easy_admin/controllers/batch_modal_controller.js +66 -0
- data/app/javascript/easy_admin/controllers/batch_selection_controller.js +223 -0
- data/app/javascript/easy_admin/controllers/chart_controller.js +216 -0
- data/app/javascript/easy_admin/controllers/collapsible_filters_controller.js +118 -0
- data/app/javascript/easy_admin/controllers/confirmation_modal_controller.js +64 -0
- data/app/javascript/easy_admin/controllers/context_menu_controller.js +227 -0
- data/app/javascript/easy_admin/controllers/date_picker_controller.js +309 -0
- data/app/javascript/easy_admin/controllers/dropdown_controller.js +63 -0
- data/app/javascript/easy_admin/controllers/event_emitter_controller.js +19 -0
- data/app/javascript/easy_admin/controllers/file_controller.js +121 -0
- data/app/javascript/easy_admin/controllers/form_tabs_controller.js +100 -0
- data/app/javascript/easy_admin/controllers/has_many_search_controller.js +76 -0
- data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +174 -0
- data/app/javascript/easy_admin/controllers/ios_alert_controller.js +195 -0
- data/app/javascript/easy_admin/controllers/jsoneditor_controller.js +88 -0
- data/app/javascript/easy_admin/controllers/modal_controller.js +75 -0
- data/app/javascript/easy_admin/controllers/navbar_scroll_controller.js +76 -0
- data/app/javascript/easy_admin/controllers/notification_controller.js +48 -0
- data/app/javascript/easy_admin/controllers/row_action_controller.js +124 -0
- data/app/javascript/easy_admin/controllers/row_modal_controller.js +59 -0
- data/app/javascript/easy_admin/controllers/select_field_controller.js +618 -0
- data/app/javascript/easy_admin/controllers/settings_button_controller.js +8 -0
- data/app/javascript/easy_admin/controllers/settings_sidebar_controller.js +186 -0
- data/app/javascript/easy_admin/controllers/sidebar_controller.js +102 -0
- data/app/javascript/easy_admin/controllers/sidebar_mobile_controller.js +23 -0
- data/app/javascript/easy_admin/controllers/sidebar_nav_controller.js +96 -0
- data/app/javascript/easy_admin/controllers/table_controller.js +28 -0
- data/app/javascript/easy_admin/controllers/table_row_controller.js +16 -0
- data/app/javascript/easy_admin/controllers/toggle_switch_controller.js +22 -0
- data/app/javascript/easy_admin/controllers/turbo_stream_redirect.js +9 -0
- data/app/javascript/easy_admin/controllers.js +54 -0
- data/app/javascript/easy_admin.base.js +4 -0
- data/app/models/easy_admin/admin_user.rb +53 -0
- data/app/models/easy_admin/application_record.rb +5 -0
- data/app/views/easy_admin/dashboard/index.html.erb +3 -0
- data/app/views/easy_admin/dashboards/show.html.erb +7 -0
- data/app/views/easy_admin/passwords/edit.html.erb +42 -0
- data/app/views/easy_admin/passwords/new.html.erb +41 -0
- data/app/views/easy_admin/registrations/new.html.erb +65 -0
- data/app/views/easy_admin/resources/_redirect.turbo_stream.erb +3 -0
- data/app/views/easy_admin/resources/_table_rows.html.erb +46 -0
- data/app/views/easy_admin/resources/edit.html.erb +151 -0
- data/app/views/easy_admin/resources/index.html.erb +12 -0
- data/app/views/easy_admin/resources/index.turbo_stream.erb +139 -0
- data/app/views/easy_admin/resources/index_frame.html.erb +142 -0
- data/app/views/easy_admin/resources/new.html.erb +100 -0
- data/app/views/easy_admin/resources/show.html.erb +31 -0
- data/app/views/easy_admin/sessions/new.html.erb +55 -0
- data/app/views/easy_admin/settings/_form.html.erb +51 -0
- data/app/views/easy_admin/settings/index.html.erb +53 -0
- data/app/views/layouts/easy_admin/application.html.erb +48 -0
- data/app/views/layouts/easy_admin/auth.html.erb +34 -0
- data/config/initializers/easy_admin_card_factory.rb +27 -0
- data/config/initializers/pagy.rb +15 -0
- data/config/initializers/rack_mini_profiler.rb +67 -0
- data/config/routes.rb +70 -0
- data/db/migrate/20250101000001_create_easy_admin_admin_users.rb +45 -0
- data/lib/easy-admin.rb +32 -0
- data/lib/easy_admin/action.rb +159 -0
- data/lib/easy_admin/batch_action.rb +134 -0
- data/lib/easy_admin/configuration.rb +75 -0
- data/lib/easy_admin/dashboard.rb +110 -0
- data/lib/easy_admin/dashboard_registry.rb +30 -0
- data/lib/easy_admin/delete_action.rb +22 -0
- data/lib/easy_admin/engine.rb +54 -0
- data/lib/easy_admin/field.rb +118 -0
- data/lib/easy_admin/resource.rb +806 -0
- data/lib/easy_admin/resource_registry.rb +22 -0
- data/lib/easy_admin/types/json_type.rb +25 -0
- data/lib/easy_admin/version.rb +3 -0
- data/lib/generators/easy_admin/auth_generator.rb +69 -0
- data/lib/generators/easy_admin/card/card_generator.rb +94 -0
- data/lib/generators/easy_admin/card/templates/card_component.rb.erb +127 -0
- data/lib/generators/easy_admin/card/templates/card_component_spec.rb.erb +122 -0
- data/lib/generators/easy_admin/install/templates/easy_admin.rb +31 -0
- data/lib/generators/easy_admin/install_generator.rb +25 -0
- data/lib/generators/easy_admin/rbac/rbac_generator.rb +244 -0
- data/lib/generators/easy_admin/rbac/templates/add_rbac_to_admin_users.rb +23 -0
- data/lib/generators/easy_admin/rbac/templates/super_admin.rb +34 -0
- data/lib/generators/easy_admin/resource_generator.rb +43 -0
- data/lib/generators/easy_admin/templates/AUTH_README +35 -0
- data/lib/generators/easy_admin/templates/README +27 -0
- data/lib/generators/easy_admin/templates/create_easy_admin_admin_users.rb +45 -0
- data/lib/generators/easy_admin/templates/devise.rb +267 -0
- data/lib/generators/easy_admin/templates/easy_admin.rb +24 -0
- data/lib/generators/easy_admin/templates/resource.rb +29 -0
- data/lib/tasks/easy_admin_tasks.rake +4 -0
- metadata +445 -0
@@ -0,0 +1,600 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
class ShowLayoutComponent < Phlex::HTML
|
3
|
+
def initialize(resource_class:, record:)
|
4
|
+
@resource_class = resource_class
|
5
|
+
@record = record
|
6
|
+
end
|
7
|
+
|
8
|
+
def view_template
|
9
|
+
if @resource_class.has_show_layout?
|
10
|
+
render_custom_layout
|
11
|
+
else
|
12
|
+
render_default_layout
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def render_custom_layout
|
19
|
+
@resource_class.show_layout.each do |element|
|
20
|
+
render_element(element)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def render_default_layout
|
25
|
+
div(class: "row row-sm") do
|
26
|
+
div(class: "col-12") do
|
27
|
+
div(class: "card") do
|
28
|
+
div(class: "card-body") do
|
29
|
+
@resource_class.show_fields.each do |field|
|
30
|
+
render_default_field(field)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def render_element(element)
|
39
|
+
case element[:type]
|
40
|
+
when :row
|
41
|
+
render_row(element)
|
42
|
+
else
|
43
|
+
# Handle unknown element types gracefully
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def render_row(row_config)
|
49
|
+
div(class: row_css_classes(row_config)) do
|
50
|
+
row_config[:columns].each do |column|
|
51
|
+
# Add row context to column config
|
52
|
+
column[:row_columns_count] = row_config[:columns_count]
|
53
|
+
render_column(column)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def render_column(column_config)
|
59
|
+
div(class: column_css_classes(column_config)) do
|
60
|
+
column_config[:elements].each do |element|
|
61
|
+
render_column_element(element)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def render_column_element(element)
|
67
|
+
case element[:type]
|
68
|
+
when :card
|
69
|
+
render_card(element)
|
70
|
+
when :metric_card
|
71
|
+
render_metric_card(element)
|
72
|
+
when :chart_card
|
73
|
+
render_chart_card(element)
|
74
|
+
when :field
|
75
|
+
render_field_element(element)
|
76
|
+
when :content
|
77
|
+
render_content_element(element)
|
78
|
+
when :heading
|
79
|
+
render_heading_element(element)
|
80
|
+
when :divider
|
81
|
+
render_divider_element(element)
|
82
|
+
when :tabs
|
83
|
+
render_tabs_element(element)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def render_card(card_config)
|
88
|
+
div(class: card_css_classes(card_config)) do
|
89
|
+
if card_config[:title]
|
90
|
+
# Title gets standard padding even if card padding is none
|
91
|
+
title_padding = card_config[:padding] == "none" ? "px-4 py-4 md:px-5 md:py-5" : "px-4 py-4 md:px-5 md:py-5"
|
92
|
+
div(class: "#{title_padding} border-b border-gray-50") do
|
93
|
+
title_color = card_config[:color] == "dark" ? "text-white" : "text-gray-900"
|
94
|
+
h5(class: "text-base md:text-lg font-medium #{title_color}") { card_config[:title] }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Content area respects card padding setting
|
99
|
+
if card_config[:padding] == "none"
|
100
|
+
# No additional padding - content fills the card
|
101
|
+
card_config[:elements].each do |element|
|
102
|
+
render_card_element(element)
|
103
|
+
end
|
104
|
+
else
|
105
|
+
# Standard padding for content
|
106
|
+
div(class: "p-4 md:p-5") do
|
107
|
+
card_config[:elements].each do |element|
|
108
|
+
render_card_element(element)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def render_metric_card(metric_config)
|
116
|
+
# Evaluate value if it's a lambda/proc
|
117
|
+
value = if metric_config[:value].respond_to?(:call)
|
118
|
+
metric_config[:value].call(@record)
|
119
|
+
else
|
120
|
+
metric_config[:value].to_s
|
121
|
+
end
|
122
|
+
|
123
|
+
div(class: metric_card_css_classes(metric_config)) do
|
124
|
+
div(class: "p-4 md:p-6 text-center") do
|
125
|
+
if metric_config[:icon]
|
126
|
+
div(class: "mb-2 md:mb-3") do
|
127
|
+
i(class: "#{metric_config[:icon]} text-lg md:text-xl")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
h3(class: "text-xl md:text-2xl font-bold mb-1") { value }
|
132
|
+
p(class: "text-xs md:text-sm opacity-90 mb-0") { metric_config[:title] }
|
133
|
+
|
134
|
+
if metric_config[:trend]
|
135
|
+
small(class: "text-xs opacity-75 block mt-1") do
|
136
|
+
case metric_config[:trend]
|
137
|
+
when "increase"
|
138
|
+
"📈 Trending up"
|
139
|
+
when "decrease"
|
140
|
+
"📉 Trending down"
|
141
|
+
else
|
142
|
+
"📊 Stable"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def render_chart_card(chart_config)
|
151
|
+
# Lazy load chart cards via turbo-frame for performance
|
152
|
+
turbo_frame(
|
153
|
+
id: "chart-#{chart_config.object_id}",
|
154
|
+
src: lazy_chart_path(chart_config),
|
155
|
+
loading: "lazy",
|
156
|
+
class: card_css_classes(chart_config)
|
157
|
+
) do
|
158
|
+
# Loading skeleton
|
159
|
+
div(class: "card") do
|
160
|
+
div(class: "card-header") do
|
161
|
+
h5(class: "card-title") { chart_config[:title] }
|
162
|
+
end
|
163
|
+
div(class: "card-body") do
|
164
|
+
div(class: "animate-pulse") do
|
165
|
+
div(class: "h-64 bg-gray-300 rounded")
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def render_tabs_element(tabs_config)
|
173
|
+
div(data: { controller: "form-tabs" }) do
|
174
|
+
# Mobile-friendly tab navigation with horizontal scroll
|
175
|
+
div(class: "border-b border-gray-200 overflow-x-auto") do
|
176
|
+
nav(class: "-mb-px flex space-x-4 md:space-x-8 min-w-max px-4 md:px-0") do
|
177
|
+
tabs_config[:tabs].each_with_index do |tab, index|
|
178
|
+
button(
|
179
|
+
type: "button",
|
180
|
+
class: "whitespace-nowrap py-2 px-2 md:px-1 border-b-2 font-medium text-sm #{index == 0 || tab[:active] ? 'border-blue-600 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'} flex-shrink-0",
|
181
|
+
data: {
|
182
|
+
form_tabs_target: "tabButton",
|
183
|
+
tab_id: tab[:name].downcase,
|
184
|
+
action: "click->form-tabs#switchTab"
|
185
|
+
}
|
186
|
+
) do
|
187
|
+
if tab[:icon]
|
188
|
+
unsafe_raw "<i class=\"#{tab[:icon]} mr-1 md:mr-2 text-xs md:text-sm\"></i>"
|
189
|
+
end
|
190
|
+
span(class: "text-xs md:text-sm") { tab[:label] }
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Tab content with mobile padding
|
197
|
+
div(class: "mt-4 px-4 md:px-0") do
|
198
|
+
tabs_config[:tabs].each_with_index do |tab, index|
|
199
|
+
div(
|
200
|
+
class: "#{index == 0 || tab[:active] ? 'block' : 'hidden'}",
|
201
|
+
id: "#{tab[:name].downcase}-panel",
|
202
|
+
data: { form_tabs_target: "tabPanel" }
|
203
|
+
) do
|
204
|
+
# Lazy load tab content that contains expensive elements
|
205
|
+
if tab_contains_expensive_elements?(tab)
|
206
|
+
turbo_frame(
|
207
|
+
id: "tab-#{tab[:name].downcase}-content",
|
208
|
+
src: lazy_tab_path(tab),
|
209
|
+
loading: "lazy"
|
210
|
+
) do
|
211
|
+
render_loading_skeleton
|
212
|
+
end
|
213
|
+
else
|
214
|
+
tab[:elements].each do |element|
|
215
|
+
render_card_element(element)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def render_card_element(element)
|
225
|
+
case element[:type]
|
226
|
+
when :field
|
227
|
+
render_field_element(element)
|
228
|
+
when :content
|
229
|
+
render_content_element(element)
|
230
|
+
when :heading
|
231
|
+
render_heading_element(element)
|
232
|
+
when :divider
|
233
|
+
render_divider_element(element)
|
234
|
+
when :tabs
|
235
|
+
render_tabs_element(element)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def render_field_element(field_config)
|
240
|
+
# Skip field if label is explicitly set to false/nil and show_label is false
|
241
|
+
show_label = field_config[:show_label] != false && field_config[:label].present?
|
242
|
+
|
243
|
+
if show_label && field_config[:field_type] != :inline
|
244
|
+
div(class: "mb-3 row") do
|
245
|
+
div(class: "col-sm-3") do
|
246
|
+
strong { "#{field_config[:label]}:" }
|
247
|
+
end
|
248
|
+
div(class: "col-sm-9") do
|
249
|
+
render_field_value(field_config)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
else
|
253
|
+
# Field without label (inline or label: false)
|
254
|
+
div(class: "mb-3") do
|
255
|
+
render_field_value(field_config)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def render_field_value(field_config)
|
261
|
+
# Find the matching field configuration
|
262
|
+
field_definition = @resource_class.fields_config.find { |f| f[:name] == field_config[:name] }
|
263
|
+
|
264
|
+
if field_definition
|
265
|
+
field_value = @record.public_send(field_config[:name])
|
266
|
+
# Pass show_label setting to field component
|
267
|
+
show_label = field_config[:show_label] != false && field_config[:label].present?
|
268
|
+
render_field_component(field_definition, field_value, show_label: show_label)
|
269
|
+
else
|
270
|
+
span(class: "text-muted") { "Field not found" }
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def render_content_element(content_config)
|
275
|
+
if content_config[:block]
|
276
|
+
# Execute the block in the context of this component
|
277
|
+
instance_eval(&content_config[:block])
|
278
|
+
else
|
279
|
+
div(class: content_config[:css_classes]) do
|
280
|
+
unsafe_raw content_config[:html]
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def render_heading_element(heading_config)
|
286
|
+
tag_name = "h#{heading_config[:level]}"
|
287
|
+
public_send(tag_name, class: heading_css_classes(heading_config)) do
|
288
|
+
heading_config[:text]
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def render_divider_element(divider_config)
|
293
|
+
hr(class: divider_css_classes(divider_config))
|
294
|
+
end
|
295
|
+
|
296
|
+
def render_default_field(field)
|
297
|
+
div(class: "mb-3 row") do
|
298
|
+
div(class: "col-sm-3") do
|
299
|
+
strong { "#{field[:label]}:" }
|
300
|
+
end
|
301
|
+
div(class: "col-sm-9") do
|
302
|
+
field_value = @record.public_send(field[:name])
|
303
|
+
render_field_component(field, field_value)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# Render field components directly
|
309
|
+
def render_field_component(field_definition, field_value, show_label: true)
|
310
|
+
component_class = field_component_class(field_definition[:type])
|
311
|
+
|
312
|
+
if component_class
|
313
|
+
# Create a modified field definition with show_label setting
|
314
|
+
modified_field = field_definition.merge(show_label: show_label)
|
315
|
+
|
316
|
+
component_instance = component_class.new(
|
317
|
+
field: modified_field,
|
318
|
+
value: field_value,
|
319
|
+
record: @record
|
320
|
+
)
|
321
|
+
render component_instance
|
322
|
+
else
|
323
|
+
# Fallback rendering for unknown field types
|
324
|
+
render_fallback_field(field_value, field_definition)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
def field_component_class(field_type)
|
329
|
+
case field_type
|
330
|
+
when :string, :text
|
331
|
+
EasyAdmin::Fields::Show::TextComponent
|
332
|
+
when :number
|
333
|
+
EasyAdmin::Fields::Show::NumberComponent
|
334
|
+
when :email
|
335
|
+
EasyAdmin::Fields::Show::EmailComponent
|
336
|
+
when :date
|
337
|
+
EasyAdmin::Fields::Show::DateComponent
|
338
|
+
when :datetime
|
339
|
+
EasyAdmin::Fields::Show::DatetimeComponent
|
340
|
+
when :boolean
|
341
|
+
EasyAdmin::Fields::Show::BooleanComponent
|
342
|
+
when :select
|
343
|
+
EasyAdmin::Fields::Show::SelectComponent
|
344
|
+
when :belongs_to
|
345
|
+
EasyAdmin::Fields::Show::BelongsToComponent
|
346
|
+
when :has_many
|
347
|
+
EasyAdmin::Fields::Show::HasManyComponent
|
348
|
+
when :file
|
349
|
+
EasyAdmin::Fields::Show::FileComponent
|
350
|
+
when :json
|
351
|
+
EasyAdmin::Fields::Show::JsonComponent
|
352
|
+
else
|
353
|
+
nil
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
def render_fallback_field(field_value, field_definition)
|
358
|
+
case field_value
|
359
|
+
when true, false
|
360
|
+
span(class: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium #{'bg-green-100 text-green-800' if field_value} #{'bg-gray-100 text-gray-800' unless field_value}") do
|
361
|
+
field_value ? "Yes" : "No"
|
362
|
+
end
|
363
|
+
when Date, DateTime, Time
|
364
|
+
span(class: "text-gray-900") { field_value.strftime("%B %d, %Y") }
|
365
|
+
when Numeric
|
366
|
+
span(class: "text-gray-900") { field_value.to_s }
|
367
|
+
else
|
368
|
+
span(class: "text-gray-900") { field_value.to_s }
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def render_loading_skeleton
|
373
|
+
div(class: "animate-pulse") do
|
374
|
+
3.times do
|
375
|
+
div(class: "mb-4") do
|
376
|
+
div(class: "h-4 bg-gray-300 rounded w-1/4 mb-2")
|
377
|
+
div(class: "h-6 bg-gray-300 rounded w-3/4")
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
# Helper methods for lazy loading paths
|
384
|
+
def lazy_metric_path(metric_config)
|
385
|
+
# Generate path for lazy loading metric partial
|
386
|
+
EasyAdmin::Engine.routes.url_helpers.resource_metric_path(
|
387
|
+
resource_name: @resource_class.route_key,
|
388
|
+
id: @record.id,
|
389
|
+
metric_id: metric_config.object_id
|
390
|
+
)
|
391
|
+
end
|
392
|
+
|
393
|
+
def lazy_chart_path(chart_config)
|
394
|
+
# Generate path for lazy loading chart partial
|
395
|
+
EasyAdmin::Engine.routes.url_helpers.resource_chart_path(
|
396
|
+
resource_name: @resource_class.route_key,
|
397
|
+
id: @record.id,
|
398
|
+
chart_id: chart_config.object_id
|
399
|
+
)
|
400
|
+
end
|
401
|
+
|
402
|
+
def lazy_tab_path(tab_config)
|
403
|
+
# Generate path for lazy loading tab content
|
404
|
+
EasyAdmin::Engine.routes.url_helpers.resource_tab_content_path(
|
405
|
+
resource_name: @resource_class.route_key,
|
406
|
+
id: @record.id,
|
407
|
+
tab_name: tab_config[:name].downcase
|
408
|
+
)
|
409
|
+
end
|
410
|
+
|
411
|
+
def tab_contains_expensive_elements?(tab)
|
412
|
+
# Check if tab contains charts, metrics, or other expensive elements
|
413
|
+
tab[:elements].any? do |element|
|
414
|
+
[:chart_card, :metric_card, :dashboard].include?(element[:type])
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
# Turbo frame helper (for Phlex compatibility)
|
419
|
+
def turbo_frame(id:, src: nil, loading: nil, **options, &block)
|
420
|
+
turbo_frame_tag(id, src: src, loading: loading, **options, &block)
|
421
|
+
end
|
422
|
+
|
423
|
+
def turbo_frame_tag(id, **options, &block)
|
424
|
+
# Create turbo-frame element
|
425
|
+
element_options = { id: id }
|
426
|
+
element_options[:src] = options[:src] if options[:src]
|
427
|
+
element_options[:loading] = options[:loading] if options[:loading]
|
428
|
+
element_options[:class] = options[:class] if options[:class]
|
429
|
+
|
430
|
+
# Convert to HTML attributes format
|
431
|
+
html_attributes = element_options.map { |k, v| "#{k}=\"#{v}\"" }.join(" ")
|
432
|
+
|
433
|
+
unsafe_raw "<turbo-frame #{html_attributes}>"
|
434
|
+
yield if block_given?
|
435
|
+
unsafe_raw "</turbo-frame>"
|
436
|
+
end
|
437
|
+
|
438
|
+
private
|
439
|
+
|
440
|
+
# CSS class helpers with static classes for Tailwind detection
|
441
|
+
def row_css_classes(row_config)
|
442
|
+
# Only apply negative margins for multi-column layouts
|
443
|
+
has_multiple_columns = row_config[:columns_count] > 1
|
444
|
+
|
445
|
+
base_classes = "flex flex-col md:flex-row md:flex-wrap mb-6"
|
446
|
+
|
447
|
+
case row_config[:spacing]
|
448
|
+
when "none"
|
449
|
+
negative_margin = has_multiple_columns ? "md:-mx-0" : ""
|
450
|
+
"#{base_classes} space-y-0 md:space-y-0 #{negative_margin}"
|
451
|
+
when "small"
|
452
|
+
negative_margin = has_multiple_columns ? "md:-mx-1" : ""
|
453
|
+
"#{base_classes} space-y-2 md:space-y-0 #{negative_margin}"
|
454
|
+
when "medium"
|
455
|
+
negative_margin = has_multiple_columns ? "md:-mx-2" : ""
|
456
|
+
"#{base_classes} space-y-4 md:space-y-0 #{negative_margin}"
|
457
|
+
when "large"
|
458
|
+
negative_margin = has_multiple_columns ? "md:-mx-4" : ""
|
459
|
+
"#{base_classes} space-y-6 md:space-y-0 #{negative_margin}"
|
460
|
+
else
|
461
|
+
negative_margin = has_multiple_columns ? "md:-mx-2" : ""
|
462
|
+
"#{base_classes} space-y-4 md:space-y-0 #{negative_margin}"
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
def column_css_classes(column_config)
|
467
|
+
# Calculate width based on the total number of columns in the row
|
468
|
+
total_columns = column_config[:row_columns_count] || 1
|
469
|
+
|
470
|
+
# Only add horizontal padding for multi-column layouts
|
471
|
+
padding_classes = total_columns > 1 ? "px-0 md:px-2" : "px-0"
|
472
|
+
base_classes = "w-full mb-4 md:mb-0 #{padding_classes}"
|
473
|
+
|
474
|
+
case total_columns
|
475
|
+
when 1
|
476
|
+
"#{base_classes} md:w-full"
|
477
|
+
when 2
|
478
|
+
"#{base_classes} md:w-1/2"
|
479
|
+
when 3
|
480
|
+
"#{base_classes} md:w-1/3"
|
481
|
+
when 4
|
482
|
+
"#{base_classes} md:w-1/4"
|
483
|
+
when 6
|
484
|
+
"#{base_classes} md:w-1/6"
|
485
|
+
else
|
486
|
+
"#{base_classes} md:flex-1"
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
def card_css_classes(card_config)
|
491
|
+
classes = ["rounded-2xl mb-4 md:mb-0 overflow-hidden"]
|
492
|
+
|
493
|
+
# Color classes
|
494
|
+
case card_config[:color]
|
495
|
+
when "primary"
|
496
|
+
classes << "bg-blue-50 border border-blue-100"
|
497
|
+
when "secondary"
|
498
|
+
classes << "bg-gray-50 border border-gray-200"
|
499
|
+
when "success"
|
500
|
+
classes << "bg-green-50 border border-green-100"
|
501
|
+
when "danger"
|
502
|
+
classes << "bg-red-50 border border-red-100"
|
503
|
+
when "warning"
|
504
|
+
classes << "bg-yellow-50 border border-yellow-100"
|
505
|
+
when "info"
|
506
|
+
classes << "bg-cyan-50 border border-cyan-100"
|
507
|
+
when "dark"
|
508
|
+
classes << "bg-gray-800 text-white border border-gray-700"
|
509
|
+
when "white"
|
510
|
+
classes << "bg-white border border-gray-200"
|
511
|
+
when "transparent"
|
512
|
+
classes << "bg-transparent border-0"
|
513
|
+
classes.delete_if { |c| c.include?("rounded") } # Remove rounded corners for transparent
|
514
|
+
classes << "rounded-none"
|
515
|
+
else # light
|
516
|
+
classes << "bg-gray-50 border border-gray-200"
|
517
|
+
end
|
518
|
+
|
519
|
+
# Shadow classes
|
520
|
+
case card_config[:shadow]
|
521
|
+
when "none"
|
522
|
+
# No shadow
|
523
|
+
when "small"
|
524
|
+
classes << "shadow-sm"
|
525
|
+
when "medium"
|
526
|
+
classes << "shadow-md"
|
527
|
+
when "large"
|
528
|
+
classes << "shadow-lg"
|
529
|
+
else
|
530
|
+
classes << "shadow-sm" # default
|
531
|
+
end
|
532
|
+
|
533
|
+
# Padding classes
|
534
|
+
padding = card_config[:padding] || "medium"
|
535
|
+
case padding
|
536
|
+
when "none"
|
537
|
+
classes << "p-0"
|
538
|
+
when "small"
|
539
|
+
classes << "p-2 sm:p-3"
|
540
|
+
when "medium"
|
541
|
+
classes << "p-4 sm:p-6"
|
542
|
+
when "large"
|
543
|
+
classes << "p-6 sm:p-8"
|
544
|
+
else
|
545
|
+
classes << "p-4 sm:p-6" # default
|
546
|
+
end
|
547
|
+
|
548
|
+
classes.join(" ")
|
549
|
+
end
|
550
|
+
|
551
|
+
def metric_card_css_classes(metric_config)
|
552
|
+
case metric_config[:color]
|
553
|
+
when "primary"
|
554
|
+
"rounded-2xl shadow-sm border border-gray-100 h-full mb-4 md:mb-0 overflow-hidden bg-blue-500 text-white"
|
555
|
+
when "secondary"
|
556
|
+
"rounded-2xl shadow-sm border border-gray-100 h-full mb-4 md:mb-0 overflow-hidden bg-gray-500 text-white"
|
557
|
+
when "success"
|
558
|
+
"rounded-2xl shadow-sm border border-gray-100 h-full mb-4 md:mb-0 overflow-hidden bg-green-500 text-white"
|
559
|
+
when "danger"
|
560
|
+
"rounded-2xl shadow-sm border border-gray-100 h-full mb-4 md:mb-0 overflow-hidden bg-red-500 text-white"
|
561
|
+
when "warning"
|
562
|
+
"rounded-2xl shadow-sm border border-gray-100 h-full mb-4 md:mb-0 overflow-hidden bg-orange-500 text-white"
|
563
|
+
when "info"
|
564
|
+
"rounded-2xl shadow-sm border border-gray-100 h-full mb-4 md:mb-0 overflow-hidden bg-blue-400 text-white"
|
565
|
+
when "dark"
|
566
|
+
"rounded-2xl shadow-sm border border-gray-100 h-full mb-4 md:mb-0 overflow-hidden bg-gray-800 text-white"
|
567
|
+
else
|
568
|
+
"rounded-2xl shadow-sm border border-gray-100 h-full mb-4 md:mb-0 overflow-hidden bg-gray-100 text-gray-900"
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
572
|
+
def heading_css_classes(heading_config)
|
573
|
+
case heading_config[:size]
|
574
|
+
when "small"
|
575
|
+
"mb-3 font-semibold text-gray-900 text-sm"
|
576
|
+
when "medium"
|
577
|
+
"mb-3 font-semibold text-gray-900 text-base"
|
578
|
+
when "large"
|
579
|
+
"mb-3 font-semibold text-gray-900 text-lg"
|
580
|
+
else
|
581
|
+
"mb-3 font-semibold text-gray-900 text-base"
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
def divider_css_classes(divider_config)
|
586
|
+
case divider_config[:style]
|
587
|
+
when "default"
|
588
|
+
"border-0 border-t my-4 border-gray-200"
|
589
|
+
when "dashed"
|
590
|
+
"border-0 border-t my-4 border-dashed border-gray-300"
|
591
|
+
when "dotted"
|
592
|
+
"border-0 border-t my-4 border-dotted border-gray-300"
|
593
|
+
when "thick"
|
594
|
+
"border-0 border-t-2 my-4 border-gray-400"
|
595
|
+
else
|
596
|
+
"border-0 border-t my-4 border-gray-200"
|
597
|
+
end
|
598
|
+
end
|
599
|
+
end
|
600
|
+
end
|