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,188 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
class PaginationComponent < Phlex::HTML
|
3
|
+
def initialize(pagy:, request_params: nil)
|
4
|
+
@pagy = pagy
|
5
|
+
@request_params = request_params || ActionController::Parameters.new.permit!
|
6
|
+
end
|
7
|
+
|
8
|
+
def view_template
|
9
|
+
return unless @pagy.pages > 1
|
10
|
+
|
11
|
+
nav(class: "flex items-center justify-between bg-white px-4 py-3 sm:px-6 border-t border-gray-200") do
|
12
|
+
render_mobile_pagination
|
13
|
+
render_desktop_pagination
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def render_mobile_pagination
|
20
|
+
div(class: "flex flex-1 justify-between sm:hidden") do
|
21
|
+
render_prev_link_mobile
|
22
|
+
render_page_info
|
23
|
+
render_next_link_mobile
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def render_desktop_pagination
|
28
|
+
div(class: "hidden sm:flex sm:flex-1 sm:items-center sm:justify-between") do
|
29
|
+
render_page_stats
|
30
|
+
render_desktop_nav
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def render_prev_link_mobile
|
35
|
+
if @pagy.prev
|
36
|
+
a(
|
37
|
+
href: page_url(@pagy.prev),
|
38
|
+
class: "relative inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-medium text-gray-700 border border-gray-300 hover:bg-gray-50 transition-colors duration-200"
|
39
|
+
) { "Previous" }
|
40
|
+
else
|
41
|
+
span(
|
42
|
+
class: "relative inline-flex items-center rounded-md bg-gray-100 px-3 py-2 text-sm font-medium text-gray-400 border border-gray-300 cursor-not-allowed"
|
43
|
+
) { "Previous" }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def render_next_link_mobile
|
48
|
+
if @pagy.next
|
49
|
+
a(
|
50
|
+
href: page_url(@pagy.next),
|
51
|
+
class: "relative ml-3 inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-medium text-gray-700 border border-gray-300 hover:bg-gray-50 transition-colors duration-200"
|
52
|
+
) { "Next" }
|
53
|
+
else
|
54
|
+
span(
|
55
|
+
class: "relative ml-3 inline-flex items-center rounded-md bg-gray-100 px-3 py-2 text-sm font-medium text-gray-400 border border-gray-300 cursor-not-allowed"
|
56
|
+
) { "Next" }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def render_page_info
|
61
|
+
div(class: "flex items-center justify-center flex-1") do
|
62
|
+
p(class: "text-sm text-gray-700 text-center") do
|
63
|
+
plain "Page "
|
64
|
+
span(class: "font-medium") { @pagy.page.to_s }
|
65
|
+
# Only show "of X" if we have total pages (not in countless mode)
|
66
|
+
if @pagy.respond_to?(:pages) && @pagy.pages > 0
|
67
|
+
plain " of "
|
68
|
+
span(class: "font-medium") { @pagy.pages.to_s }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def render_page_stats
|
75
|
+
# Removed record count display for performance
|
76
|
+
# Just show simple page info instead
|
77
|
+
p(class: "text-sm text-gray-700") do
|
78
|
+
plain "Page "
|
79
|
+
span(class: "font-medium") { @pagy.page.to_s }
|
80
|
+
# Only show "of X" if we have total pages (not in countless mode)
|
81
|
+
if @pagy.respond_to?(:pages) && @pagy.pages > 0
|
82
|
+
plain " of "
|
83
|
+
span(class: "font-medium") { @pagy.pages.to_s }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def render_desktop_nav
|
89
|
+
div(class: "flex items-center space-x-1") do
|
90
|
+
# Previous button
|
91
|
+
if @pagy.prev
|
92
|
+
a(
|
93
|
+
href: page_url(@pagy.prev),
|
94
|
+
class: "relative inline-flex items-center justify-center w-10 h-10 rounded-lg bg-white text-gray-500 border border-gray-300 hover:bg-gray-50 hover:text-gray-700 transition-all duration-200"
|
95
|
+
) do
|
96
|
+
unsafe_raw('<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>')
|
97
|
+
end
|
98
|
+
else
|
99
|
+
span(
|
100
|
+
class: "relative inline-flex items-center justify-center w-10 h-10 rounded-lg bg-gray-100 text-gray-400 border border-gray-300 cursor-not-allowed"
|
101
|
+
) do
|
102
|
+
unsafe_raw('<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>')
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Page numbers
|
107
|
+
render_page_links
|
108
|
+
|
109
|
+
# Next button
|
110
|
+
if @pagy.next
|
111
|
+
a(
|
112
|
+
href: page_url(@pagy.next),
|
113
|
+
class: "relative inline-flex items-center justify-center w-10 h-10 rounded-lg bg-white text-gray-500 border border-gray-300 hover:bg-gray-50 hover:text-gray-700 transition-all duration-200"
|
114
|
+
) do
|
115
|
+
unsafe_raw('<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>')
|
116
|
+
end
|
117
|
+
else
|
118
|
+
span(
|
119
|
+
class: "relative inline-flex items-center justify-center w-10 h-10 rounded-lg bg-gray-100 text-gray-400 border border-gray-300 cursor-not-allowed"
|
120
|
+
) do
|
121
|
+
unsafe_raw('<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>')
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def render_page_links
|
128
|
+
# Handle both regular and countless pagination modes
|
129
|
+
series = begin
|
130
|
+
if @pagy.respond_to?(:series) && (@pagy.respond_to?(:pages) ? @pagy.pages > 0 : true)
|
131
|
+
@pagy.series(size: 7)
|
132
|
+
else
|
133
|
+
# Fallback: just show current page
|
134
|
+
[@pagy.page]
|
135
|
+
end
|
136
|
+
rescue => e
|
137
|
+
# If series fails, fall back to current page only
|
138
|
+
[@pagy.page]
|
139
|
+
end
|
140
|
+
|
141
|
+
# Ensure we have a valid array
|
142
|
+
series = [@pagy.page] if series.nil? || !series.respond_to?(:each)
|
143
|
+
|
144
|
+
series.each do |item|
|
145
|
+
case item
|
146
|
+
when Integer
|
147
|
+
if item == @pagy.page
|
148
|
+
# Current page
|
149
|
+
span(
|
150
|
+
class: "relative inline-flex items-center justify-center w-10 h-10 rounded-lg bg-blue-600 text-white font-semibold border border-blue-600 shadow-sm"
|
151
|
+
) { item.to_s }
|
152
|
+
else
|
153
|
+
# Other pages
|
154
|
+
a(
|
155
|
+
href: page_url(item),
|
156
|
+
class: "relative inline-flex items-center justify-center w-10 h-10 rounded-lg bg-white text-gray-700 font-medium border border-gray-300 hover:bg-blue-50 hover:text-blue-700 hover:border-blue-300 transition-all duration-200"
|
157
|
+
) { item.to_s }
|
158
|
+
end
|
159
|
+
when String
|
160
|
+
# Gap
|
161
|
+
span(
|
162
|
+
class: "relative inline-flex items-center justify-center w-10 h-10 text-gray-500"
|
163
|
+
) { "..." }
|
164
|
+
when :gap
|
165
|
+
# Gap (symbol version)
|
166
|
+
span(
|
167
|
+
class: "relative inline-flex items-center justify-center w-10 h-10 text-gray-500"
|
168
|
+
) { "..." }
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def page_url(page)
|
174
|
+
# Permit all parameters that we want to preserve
|
175
|
+
permitted_params = @request_params.permit(:resource_name, :scope, :search, :sort, :direction, :period, :date_from, :date_to, :segment, :refresh)
|
176
|
+
current_params = permitted_params.except(:page).to_h
|
177
|
+
current_params[:page] = page if page > 1
|
178
|
+
|
179
|
+
query_string = current_params.any? ? "?#{current_params.to_query}" : ""
|
180
|
+
"#{current_url_path}#{query_string}"
|
181
|
+
end
|
182
|
+
|
183
|
+
def current_url_path
|
184
|
+
# This will be overridden by the specific implementation
|
185
|
+
"/"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
class QuickFiltersComponent < Phlex::HTML
|
3
|
+
def initialize(resource_class:, current_period: nil, current_params: nil)
|
4
|
+
@resource_class = resource_class
|
5
|
+
@current_period = current_period
|
6
|
+
@current_params = current_params || ActionController::Parameters.new.permit!
|
7
|
+
end
|
8
|
+
|
9
|
+
def view_template
|
10
|
+
div(class: "bg-white rounded-xl shadow-sm border border-gray-200 mb-4 sm:mb-6") do
|
11
|
+
div(class: "px-4 sm:px-6 py-3 border-b border-gray-100") do
|
12
|
+
h3(class: "text-sm font-semibold text-gray-900") { "Quick Filters" }
|
13
|
+
end
|
14
|
+
div(class: "p-4 sm:p-6") do
|
15
|
+
div(class: "flex flex-wrap gap-2") do
|
16
|
+
render_quick_filter_link("Today", "today")
|
17
|
+
render_quick_filter_link("Last 7 Days", "7d")
|
18
|
+
render_quick_filter_link("Last 30 Days", "30d")
|
19
|
+
render_quick_filter_link("Last 90 Days", "90d")
|
20
|
+
render_quick_filter_link("Last Year", "1y")
|
21
|
+
render_clear_filter_link
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def render_quick_filter_link(label, period)
|
30
|
+
active = @current_period == period
|
31
|
+
css_classes = if active
|
32
|
+
"inline-flex items-center px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200 bg-blue-600 text-white shadow-sm"
|
33
|
+
else
|
34
|
+
"inline-flex items-center px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200 bg-white text-gray-700 border border-gray-300 hover:bg-gray-50"
|
35
|
+
end
|
36
|
+
|
37
|
+
a(href: build_filter_url(period), class: css_classes, data: { turbo_prefetch: "false" }) { label }
|
38
|
+
end
|
39
|
+
|
40
|
+
def render_clear_filter_link
|
41
|
+
return unless @current_period.present?
|
42
|
+
|
43
|
+
a(
|
44
|
+
href: build_filter_url(nil),
|
45
|
+
class: "inline-flex items-center px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200 bg-white text-red-600 border border-red-300 hover:bg-red-50",
|
46
|
+
data: { turbo_prefetch: "false" }
|
47
|
+
) { "Clear Filter" }
|
48
|
+
end
|
49
|
+
|
50
|
+
def build_filter_url(period)
|
51
|
+
route_helper = EasyAdmin::Engine.routes.url_helpers
|
52
|
+
current_params = @current_params.permit(:scope, :search, :sort, :direction, :page).to_h
|
53
|
+
|
54
|
+
if period
|
55
|
+
current_params[:period] = period
|
56
|
+
else
|
57
|
+
current_params.delete(:period)
|
58
|
+
end
|
59
|
+
|
60
|
+
base_url = route_helper.resources_path(@resource_class.route_key)
|
61
|
+
query_string = current_params.to_query
|
62
|
+
query_string.present? ? "#{base_url}?#{query_string}" : base_url
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
class ResourcePaginationComponent < PaginationComponent
|
3
|
+
def initialize(pagy:, resource_class:, request_params: nil)
|
4
|
+
@resource_class = resource_class
|
5
|
+
super(pagy: pagy, request_params: request_params)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def current_url_path
|
11
|
+
EasyAdmin::Engine.routes.url_helpers.resources_path(@resource_class.route_key)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Resources
|
3
|
+
class IndexComponent < BaseComponent
|
4
|
+
def initialize(resource_class:, current_scope: nil, scope_counts: {}, current_params: {}, search_params: {}, current_period: nil, current_user: nil)
|
5
|
+
@resource_class = resource_class
|
6
|
+
@current_scope = current_scope
|
7
|
+
@scope_counts = scope_counts
|
8
|
+
@current_params = current_params
|
9
|
+
@search_params = search_params
|
10
|
+
@current_period = current_period
|
11
|
+
@current_user = current_user
|
12
|
+
end
|
13
|
+
|
14
|
+
def view_template
|
15
|
+
div do
|
16
|
+
render_page_header
|
17
|
+
render_scopes if @resource_class.has_scopes?
|
18
|
+
render_combined_filters
|
19
|
+
render_search_banner if @current_params[:search].present?
|
20
|
+
render_period_banner if @current_params[:period].present?
|
21
|
+
render_table_frame
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def render_page_header
|
28
|
+
div(class: "mb-6") do
|
29
|
+
div(class: "sm:flex sm:items-center") do
|
30
|
+
div(class: "sm:flex-auto") do
|
31
|
+
h1(class: "text-2xl font-bold leading-6 text-gray-900") { @resource_class.title }
|
32
|
+
p(class: "mt-2 text-sm text-gray-700") do
|
33
|
+
"A list of all #{@resource_class.title.downcase} in your account."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
div(class: "mt-4 sm:ml-16 sm:mt-0 sm:flex-none") do
|
37
|
+
render_new_resource_link
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def render_new_resource_link
|
44
|
+
a(
|
45
|
+
href: easy_admin_url_helpers.new_resource_path(@resource_class.route_key),
|
46
|
+
class: "block rounded-md bg-blue-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
|
47
|
+
) do
|
48
|
+
unsafe_raw <<~SVG
|
49
|
+
<svg class="inline-block w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
50
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
51
|
+
</svg>
|
52
|
+
SVG
|
53
|
+
plain "New #{@resource_class.singular_title}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def render_scopes
|
58
|
+
render EasyAdmin::ScopesComponent.new(
|
59
|
+
resource_class: @resource_class,
|
60
|
+
current_scope: @current_scope,
|
61
|
+
counts: @scope_counts
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
def render_combined_filters
|
66
|
+
render EasyAdmin::CombinedFiltersComponent.new(
|
67
|
+
resource_class: @resource_class,
|
68
|
+
current_params: @current_params,
|
69
|
+
search_params: @search_params,
|
70
|
+
current_period: @current_period
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
def render_search_banner
|
75
|
+
div(class: "mb-4 rounded-md bg-blue-50 p-4") do
|
76
|
+
div(class: "flex") do
|
77
|
+
div(class: "flex-shrink-0") do
|
78
|
+
unsafe_raw <<~SVG
|
79
|
+
<svg class="h-5 w-5 text-blue-400" viewBox="0 0 20 20" fill="currentColor">
|
80
|
+
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"/>
|
81
|
+
</svg>
|
82
|
+
SVG
|
83
|
+
end
|
84
|
+
div(class: "ml-3") do
|
85
|
+
p(class: "text-sm text-blue-700") do
|
86
|
+
"Search results for \""
|
87
|
+
strong { @current_params[:search] }
|
88
|
+
"\""
|
89
|
+
a(
|
90
|
+
href: easy_admin_url_helpers.resources_path(@resource_class.route_key),
|
91
|
+
class: "font-medium text-blue-700 hover:text-blue-600 ml-2"
|
92
|
+
) { "Clear search" }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def render_period_banner
|
100
|
+
div(class: "mb-4 rounded-md bg-green-50 p-4") do
|
101
|
+
div(class: "flex") do
|
102
|
+
div(class: "flex-shrink-0") do
|
103
|
+
unsafe_raw <<~SVG
|
104
|
+
<svg class="h-5 w-5 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
105
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
106
|
+
</svg>
|
107
|
+
SVG
|
108
|
+
end
|
109
|
+
div(class: "ml-3") do
|
110
|
+
p(class: "text-sm text-green-700") do
|
111
|
+
"Showing results for "
|
112
|
+
strong { period_label(@current_params[:period]) }
|
113
|
+
a(
|
114
|
+
href: clear_period_url,
|
115
|
+
class: "font-medium text-green-700 hover:text-green-600 ml-2",
|
116
|
+
data: { turbo_prefetch: "false" }
|
117
|
+
) { "Clear filter" }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def render_table_frame
|
125
|
+
turbo_frame(
|
126
|
+
id: "table-frame",
|
127
|
+
src: frame_src_url,
|
128
|
+
loading: "lazy"
|
129
|
+
) do
|
130
|
+
render_skeleton_table
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def render_skeleton_table
|
135
|
+
div(class: "mt-8 flow-root") do
|
136
|
+
div(class: "mobile-table-wrapper -mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8") do
|
137
|
+
div(class: "inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8") do
|
138
|
+
div(class: "overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg") do
|
139
|
+
table(class: "table-striped min-w-full divide-y divide-gray-300") do
|
140
|
+
render_skeleton_header
|
141
|
+
render_skeleton_body
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def render_skeleton_header
|
150
|
+
thead(class: "bg-gray-50") do
|
151
|
+
tr do
|
152
|
+
@resource_class.index_fields.each_with_index do |field, index|
|
153
|
+
th(
|
154
|
+
scope: "col",
|
155
|
+
class: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wide #{'mobile-hide' if index > 2}"
|
156
|
+
) { field[:label] }
|
157
|
+
end
|
158
|
+
th(scope: "col", class: "relative px-6 py-3") do
|
159
|
+
span(class: "sr-only") { "Actions" }
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def render_skeleton_body
|
166
|
+
tbody(class: "divide-y divide-gray-200 bg-white") do
|
167
|
+
5.times do
|
168
|
+
tr(class: "animate-pulse") do
|
169
|
+
@resource_class.index_fields.each_with_index do |field, index|
|
170
|
+
td(class: "whitespace-nowrap px-6 py-4 text-sm #{'mobile-hide' if index > 2}") do
|
171
|
+
div(class: "h-4 bg-gray-200 rounded")
|
172
|
+
end
|
173
|
+
end
|
174
|
+
td(class: "relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6") do
|
175
|
+
div(class: "flex items-center justify-end space-x-2") do
|
176
|
+
3.times do
|
177
|
+
div(class: "w-8 h-8 bg-gray-200 rounded-lg")
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def period_label(period)
|
187
|
+
case period
|
188
|
+
when 'today' then 'Today'
|
189
|
+
when '7d' then 'Last 7 Days'
|
190
|
+
when '30d' then 'Last 30 Days'
|
191
|
+
when '90d' then 'Last 90 Days'
|
192
|
+
when '1y' then 'Last Year'
|
193
|
+
else period.humanize
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def clear_period_url
|
198
|
+
base_url = easy_admin_url_helpers.resources_path(@resource_class.route_key)
|
199
|
+
clear_params = @current_params.except(:period)
|
200
|
+
query_string = clear_params.present? ? clear_params.to_query : ""
|
201
|
+
query_string.present? ? "#{base_url}?#{query_string}" : base_url
|
202
|
+
end
|
203
|
+
|
204
|
+
def frame_src_url
|
205
|
+
base_url = easy_admin_url_helpers.resources_path(@resource_class.route_key)
|
206
|
+
query_string = @current_params.present? ? @current_params.to_query : ""
|
207
|
+
query_string.present? ? "#{base_url}?#{query_string}" : base_url
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Resources
|
3
|
+
class IndexFrameComponent < BaseComponent
|
4
|
+
def initialize(resource_class:, records:, pagy: nil, current_params: {}, current_path: nil, current_user: nil)
|
5
|
+
@resource_class = resource_class
|
6
|
+
@records = records
|
7
|
+
@pagy = pagy
|
8
|
+
@current_params = current_params
|
9
|
+
@current_path = current_path
|
10
|
+
@current_user = current_user
|
11
|
+
end
|
12
|
+
|
13
|
+
def view_template
|
14
|
+
turbo_frame(id: "table-frame") do
|
15
|
+
if @records.any?
|
16
|
+
render_table_content
|
17
|
+
else
|
18
|
+
render_empty_state
|
19
|
+
end
|
20
|
+
render_pagination if @pagy
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def render_table_content
|
27
|
+
div(class: "mt-8 flow-root") do
|
28
|
+
div(class: "mobile-table-wrapper -mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8") do
|
29
|
+
div(class: "inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8") do
|
30
|
+
div(class: "overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg") do
|
31
|
+
render EasyAdmin::Resources::TableComponent.new(
|
32
|
+
records: @records,
|
33
|
+
resource_class: @resource_class,
|
34
|
+
current_params: @current_params,
|
35
|
+
current_user: @current_user
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def render_empty_state
|
44
|
+
div(class: "text-center py-12") do
|
45
|
+
unsafe_raw <<~SVG
|
46
|
+
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
47
|
+
<path vector-effect="non-scaling-stroke" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2z"/>
|
48
|
+
</svg>
|
49
|
+
SVG
|
50
|
+
h3(class: "mt-2 text-sm font-semibold text-gray-900") { "No #{@resource_class.title.downcase}" }
|
51
|
+
p(class: "mt-1 text-sm text-gray-500") { "Get started by creating a new #{@resource_class.singular_title.downcase}." }
|
52
|
+
div(class: "mt-6") do
|
53
|
+
a(
|
54
|
+
href: easy_admin_url_helpers.new_resource_path(@resource_class.route_key),
|
55
|
+
class: "inline-flex items-center rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
|
56
|
+
) do
|
57
|
+
unsafe_raw <<~SVG
|
58
|
+
<svg class="-ml-0.5 mr-1.5 h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
59
|
+
<path d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"/>
|
60
|
+
</svg>
|
61
|
+
SVG
|
62
|
+
"New #{@resource_class.singular_title}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def render_pagination
|
69
|
+
div(id: "infinite-scroll-container") do
|
70
|
+
if @resource_class.respond_to?(:infinite_scroll_enabled?) && @resource_class.infinite_scroll_enabled?
|
71
|
+
render EasyAdmin::InfiniteScrollComponent.new(
|
72
|
+
pagy: @pagy,
|
73
|
+
resource_class: @resource_class,
|
74
|
+
current_params: @current_params,
|
75
|
+
current_path: @current_path
|
76
|
+
)
|
77
|
+
else
|
78
|
+
render EasyAdmin::ResourcePaginationComponent.new(
|
79
|
+
pagy: @pagy,
|
80
|
+
resource_class: @resource_class,
|
81
|
+
request_params: @current_params
|
82
|
+
)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|