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,232 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
class CombinedFiltersComponent < Phlex::HTML
|
3
|
+
def initialize(resource_class:, current_params: {}, search_params: {}, current_period: nil)
|
4
|
+
@resource_class = resource_class
|
5
|
+
@current_params = current_params
|
6
|
+
@search_params = search_params || {}
|
7
|
+
@current_period = current_period
|
8
|
+
end
|
9
|
+
|
10
|
+
def view_template
|
11
|
+
return unless has_any_filters?
|
12
|
+
|
13
|
+
div(
|
14
|
+
id: "combined-filters",
|
15
|
+
class: "bg-white shadow rounded-lg mb-6",
|
16
|
+
data: { controller: "collapsible-filters" }
|
17
|
+
) do
|
18
|
+
# Header with collapse toggle
|
19
|
+
div(
|
20
|
+
class: "px-6 py-4 border-b border-gray-200 cursor-pointer hover:bg-gray-50 transition-colors duration-150",
|
21
|
+
data: { action: "click->collapsible-filters#toggle" }
|
22
|
+
) do
|
23
|
+
div(class: "flex items-center justify-between") do
|
24
|
+
div(class: "flex items-center space-x-2") do
|
25
|
+
h3(class: "text-lg font-medium text-gray-900") { "Filters" }
|
26
|
+
if has_active_filters?
|
27
|
+
span(class: "inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800") do
|
28
|
+
active_filter_count.to_s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Collapse/Expand icon
|
34
|
+
div(data: { collapsible_filters_target: "icon" }) do
|
35
|
+
unsafe_raw '<svg class="w-5 h-5 text-gray-400 transform transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
36
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
37
|
+
</svg>'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Collapsible content
|
43
|
+
div(
|
44
|
+
class: "transition-all duration-300 ease-in-out",
|
45
|
+
data: { collapsible_filters_target: "content" }
|
46
|
+
) do
|
47
|
+
form(
|
48
|
+
method: "get",
|
49
|
+
action: route_helper.resources_path(@resource_class.route_key),
|
50
|
+
class: "space-y-4"
|
51
|
+
) do
|
52
|
+
# Preserve existing params (except filter params)
|
53
|
+
preserve_params
|
54
|
+
|
55
|
+
# Field Filters (Advanced)
|
56
|
+
if @resource_class.filterable_fields.any?
|
57
|
+
div(class: "px-6 pt-4") do
|
58
|
+
label(class: "block text-sm font-medium text-gray-700 mb-3") { "Advanced Filters" }
|
59
|
+
div(class: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4") do
|
60
|
+
@resource_class.filterable_fields.each do |field|
|
61
|
+
render_filter_field(field)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Quick Period Filters
|
68
|
+
if has_quick_filters?
|
69
|
+
div(class: "px-6 #{@resource_class.filterable_fields.any? ? 'pb-4' : 'py-4'}") do
|
70
|
+
div(class: "mb-3") do
|
71
|
+
label(class: "block text-sm font-medium text-gray-700 mb-2") { "Quick Filters" }
|
72
|
+
render_quick_filters
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Action buttons
|
78
|
+
div(class: "px-6 py-4 bg-gray-50 border-t border-gray-200") do
|
79
|
+
div(class: "flex items-center justify-between") do
|
80
|
+
# Clear filters button
|
81
|
+
if has_active_filters?
|
82
|
+
a(
|
83
|
+
href: build_clear_filters_url,
|
84
|
+
class: "text-sm text-red-600 hover:text-red-800 font-medium"
|
85
|
+
) { "Clear all filters" }
|
86
|
+
else
|
87
|
+
div # Empty div for spacing
|
88
|
+
end
|
89
|
+
|
90
|
+
# Apply button
|
91
|
+
button(
|
92
|
+
type: "submit",
|
93
|
+
class: "inline-flex items-center px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
94
|
+
) do
|
95
|
+
unsafe_raw '<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
96
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707v6.586l-4-4V9.414a1 1 0 00-.293-.707L3.293 6.293A1 1 0 013 5.586V4z"></path>
|
97
|
+
</svg>'
|
98
|
+
span { "Apply Filters" }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def has_any_filters?
|
110
|
+
has_quick_filters? || @resource_class.filterable_fields.any?
|
111
|
+
end
|
112
|
+
|
113
|
+
def has_quick_filters?
|
114
|
+
true # We always show period filters
|
115
|
+
end
|
116
|
+
|
117
|
+
def route_helper
|
118
|
+
@route_helper ||= EasyAdmin::Engine.routes.url_helpers
|
119
|
+
end
|
120
|
+
|
121
|
+
def preserve_params
|
122
|
+
# Preserve non-filter params (scope, sort, direction, page, period)
|
123
|
+
preserved_params = @current_params.except(:q)
|
124
|
+
preserved_params.each do |key, value|
|
125
|
+
input(type: "hidden", name: key, value: value)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def render_quick_filters
|
130
|
+
div(class: "flex flex-wrap gap-2") do
|
131
|
+
render_quick_filter_link("All Time", nil)
|
132
|
+
render_quick_filter_link("Today", "today")
|
133
|
+
render_quick_filter_link("Last 7 Days", "7d")
|
134
|
+
render_quick_filter_link("Last 30 Days", "30d")
|
135
|
+
render_quick_filter_link("Last 90 Days", "90d")
|
136
|
+
render_quick_filter_link("Last Year", "1y")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def render_quick_filter_link(label, period)
|
141
|
+
# Check if active - nil period means "All Time" is active when @current_period is blank
|
142
|
+
active = if period.nil?
|
143
|
+
@current_period.blank?
|
144
|
+
else
|
145
|
+
@current_period.to_s == period.to_s
|
146
|
+
end
|
147
|
+
css_classes = if active
|
148
|
+
"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"
|
149
|
+
else
|
150
|
+
"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"
|
151
|
+
end
|
152
|
+
|
153
|
+
a(href: build_quick_filter_url(period), class: css_classes, data: { turbo_prefetch: "false" }) { label }
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
def build_quick_filter_url(period)
|
158
|
+
current_params = @current_params.dup
|
159
|
+
|
160
|
+
# Set or remove period parameter
|
161
|
+
if period
|
162
|
+
current_params[:period] = period
|
163
|
+
else
|
164
|
+
current_params.delete(:period)
|
165
|
+
end
|
166
|
+
|
167
|
+
base_url = route_helper.resources_path(@resource_class.route_key)
|
168
|
+
query_string = current_params.to_query
|
169
|
+
query_string.present? ? "#{base_url}?#{query_string}" : base_url
|
170
|
+
end
|
171
|
+
|
172
|
+
def render_filter_field(field)
|
173
|
+
component_class = filter_component_for_field(field)
|
174
|
+
render component_class.new(field: field, search_params: @search_params)
|
175
|
+
end
|
176
|
+
|
177
|
+
def filter_component_for_field(field)
|
178
|
+
case field[:type]
|
179
|
+
when :string, :email, :text
|
180
|
+
EasyAdmin::Fields::Index::Filters::StringComponent
|
181
|
+
when :boolean
|
182
|
+
EasyAdmin::Fields::Index::Filters::BooleanComponent
|
183
|
+
when :select, :belongs_to
|
184
|
+
EasyAdmin::Fields::Index::Filters::SelectComponent
|
185
|
+
when :number, :integer
|
186
|
+
EasyAdmin::Fields::Index::Filters::NumberComponent
|
187
|
+
when :date, :datetime
|
188
|
+
EasyAdmin::Fields::Index::Filters::DateComponent
|
189
|
+
else
|
190
|
+
EasyAdmin::Fields::Index::Filters::StringComponent
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def has_active_filters?
|
195
|
+
has_active_search_params? || @current_period.present?
|
196
|
+
end
|
197
|
+
|
198
|
+
def has_active_search_params?
|
199
|
+
return false unless @search_params.present?
|
200
|
+
|
201
|
+
@search_params.each do |key, value|
|
202
|
+
return true if value.present?
|
203
|
+
end
|
204
|
+
|
205
|
+
false
|
206
|
+
end
|
207
|
+
|
208
|
+
def active_filter_count
|
209
|
+
count = 0
|
210
|
+
count += 1 if @current_period.present?
|
211
|
+
|
212
|
+
if @search_params.present?
|
213
|
+
@search_params.each do |key, value|
|
214
|
+
count += 1 if value.present?
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
count
|
219
|
+
end
|
220
|
+
|
221
|
+
def build_clear_filters_url
|
222
|
+
base_url = route_helper.resources_path(@resource_class.route_key)
|
223
|
+
clear_params = @current_params.except(:q, :period)
|
224
|
+
|
225
|
+
if clear_params.present? && !clear_params.empty?
|
226
|
+
"#{base_url}?#{clear_params.to_query}"
|
227
|
+
else
|
228
|
+
base_url
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
class ConfirmationModalComponent < BaseComponent
|
3
|
+
def initialize(title:, message:, confirm_text: "Confirm", cancel_text: "Cancel", danger: false)
|
4
|
+
@title = title
|
5
|
+
@message = message
|
6
|
+
@confirm_text = confirm_text
|
7
|
+
@cancel_text = cancel_text
|
8
|
+
@danger = danger
|
9
|
+
end
|
10
|
+
|
11
|
+
def view_template
|
12
|
+
div(
|
13
|
+
id: "confirmation-modal",
|
14
|
+
class: "fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50",
|
15
|
+
data: {
|
16
|
+
controller: "confirmation-modal",
|
17
|
+
action: "click->confirmation-modal#clickOutside"
|
18
|
+
}
|
19
|
+
) do
|
20
|
+
div(
|
21
|
+
class: "relative top-20 mx-auto p-5 border w-11/12 max-w-md shadow-lg rounded-md bg-white",
|
22
|
+
data: { confirmation_modal_target: "modal" }
|
23
|
+
) do
|
24
|
+
# Header
|
25
|
+
div(class: "flex items-center justify-between pb-4 mb-4 border-b border-gray-200") do
|
26
|
+
h3(class: "text-lg font-semibold text-gray-900") { @title }
|
27
|
+
button(
|
28
|
+
type: "button",
|
29
|
+
class: "text-gray-400 hover:text-gray-600 text-xl",
|
30
|
+
data: { action: "click->confirmation-modal#cancel" }
|
31
|
+
) { "×" }
|
32
|
+
end
|
33
|
+
|
34
|
+
# Message
|
35
|
+
p(class: "text-sm text-gray-600 mb-4") { @message }
|
36
|
+
|
37
|
+
# Action buttons
|
38
|
+
div(class: "flex justify-end space-x-3 pt-4 border-t border-gray-200 mt-4") do
|
39
|
+
button(
|
40
|
+
type: "button",
|
41
|
+
class: "px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50",
|
42
|
+
data: { action: "click->confirmation-modal#cancel" }
|
43
|
+
) { @cancel_text }
|
44
|
+
|
45
|
+
confirm_button_class = if @danger
|
46
|
+
"px-4 py-2 text-sm font-medium text-white bg-red-600 border border-transparent rounded-md hover:bg-red-700"
|
47
|
+
else
|
48
|
+
"px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700"
|
49
|
+
end
|
50
|
+
|
51
|
+
button(
|
52
|
+
type: "button",
|
53
|
+
class: confirm_button_class,
|
54
|
+
data: { action: "click->confirmation-modal#confirm" }
|
55
|
+
) { @confirm_text }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
class ContextMenuComponent < BaseComponent
|
3
|
+
def initialize(record:, resource_class:, actions:)
|
4
|
+
@record = record
|
5
|
+
@resource_class = resource_class
|
6
|
+
@actions = actions
|
7
|
+
end
|
8
|
+
|
9
|
+
def view_template
|
10
|
+
div(class: "context-menu bg-white rounded-lg shadow-lg border border-gray-200 py-1 min-w-48") do
|
11
|
+
# Default actions
|
12
|
+
render_default_actions
|
13
|
+
|
14
|
+
# Custom row actions
|
15
|
+
if @actions.any?
|
16
|
+
render_divider
|
17
|
+
render_custom_actions
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def render_default_actions
|
25
|
+
# View action
|
26
|
+
a(
|
27
|
+
href: resource_show_url,
|
28
|
+
class: "context-menu-item w-full text-left block",
|
29
|
+
data: { turbo: "false" }
|
30
|
+
) do
|
31
|
+
div(class: "flex items-center space-x-2") do
|
32
|
+
unsafe_raw <<~SVG
|
33
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
34
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
35
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
36
|
+
</svg>
|
37
|
+
SVG
|
38
|
+
span { "View" }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Edit action
|
43
|
+
a(
|
44
|
+
href: resource_edit_url,
|
45
|
+
class: "context-menu-item w-full text-left block",
|
46
|
+
data: { turbo: "false" }
|
47
|
+
) do
|
48
|
+
div(class: "flex items-center space-x-2") do
|
49
|
+
unsafe_raw <<~SVG
|
50
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
51
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
52
|
+
</svg>
|
53
|
+
SVG
|
54
|
+
span { "Edit" }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Delete action
|
59
|
+
button(
|
60
|
+
type: "button",
|
61
|
+
class: "context-menu-item danger w-full text-left",
|
62
|
+
data: {
|
63
|
+
controller: "row-action",
|
64
|
+
action: "click->row-action#execute",
|
65
|
+
row_action_action_class_value: "EasyAdmin::DeleteAction",
|
66
|
+
row_action_execution_mode_value: "instant",
|
67
|
+
row_action_confirm_value: "Are you sure you want to delete this #{@resource_class.singular_title.downcase}?",
|
68
|
+
row_action_record_id_value: @record.id,
|
69
|
+
row_action_resource_name_value: @resource_class.route_key
|
70
|
+
}
|
71
|
+
) do
|
72
|
+
div(class: "flex items-center space-x-2") do
|
73
|
+
unsafe_raw <<~SVG
|
74
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
75
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
76
|
+
</svg>
|
77
|
+
SVG
|
78
|
+
span { "Delete" }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def render_custom_actions
|
84
|
+
@actions.each do |action_data|
|
85
|
+
action_instance = action_data[:instance]
|
86
|
+
action_class = action_data[:class]
|
87
|
+
|
88
|
+
button(
|
89
|
+
type: "button",
|
90
|
+
class: "context-menu-item w-full text-left #{action_style_class(action_instance)}",
|
91
|
+
data: {
|
92
|
+
controller: "row-action",
|
93
|
+
action: "click->row-action#execute",
|
94
|
+
row_action_action_class_value: action_class.name,
|
95
|
+
row_action_execution_mode_value: action_instance.class.execution_mode.to_s,
|
96
|
+
row_action_confirm_value: action_instance.class.confirm_message,
|
97
|
+
row_action_record_id_value: @record.id,
|
98
|
+
row_action_resource_name_value: @resource_class.route_key
|
99
|
+
}
|
100
|
+
) do
|
101
|
+
div(class: "flex items-center space-x-2") do
|
102
|
+
if action_instance.class.icon
|
103
|
+
span(class: "text-base") { action_instance.class.icon }
|
104
|
+
else
|
105
|
+
render_default_icon(action_instance)
|
106
|
+
end
|
107
|
+
span { action_instance.class.label }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def render_divider
|
114
|
+
div(class: "context-menu-divider")
|
115
|
+
end
|
116
|
+
|
117
|
+
def action_style_class(action_instance)
|
118
|
+
case action_instance.class.style
|
119
|
+
when :danger
|
120
|
+
"danger"
|
121
|
+
when :warning
|
122
|
+
"text-yellow-600 hover:bg-yellow-50 hover:text-yellow-700"
|
123
|
+
when :success
|
124
|
+
"text-green-600 hover:bg-green-50 hover:text-green-700"
|
125
|
+
else
|
126
|
+
""
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def render_default_icon(action_instance)
|
131
|
+
case action_instance.class.style
|
132
|
+
when :danger
|
133
|
+
unsafe_raw <<~SVG
|
134
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
135
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
136
|
+
</svg>
|
137
|
+
SVG
|
138
|
+
when :success
|
139
|
+
unsafe_raw <<~SVG
|
140
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
141
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
142
|
+
</svg>
|
143
|
+
SVG
|
144
|
+
else
|
145
|
+
unsafe_raw <<~SVG
|
146
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
147
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
148
|
+
</svg>
|
149
|
+
SVG
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def resource_show_url
|
154
|
+
easy_admin_url_helpers.resource_path(@resource_class.route_key, @record)
|
155
|
+
end
|
156
|
+
|
157
|
+
def resource_edit_url
|
158
|
+
easy_admin_url_helpers.edit_resource_path(@resource_class.route_key, @record)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Dashboards
|
3
|
+
class BaseCardComponent < EasyAdmin::BaseComponent
|
4
|
+
attr_reader :card, :dashboard_class, :data, :request_params
|
5
|
+
|
6
|
+
def initialize(card:, dashboard_class:, data: nil, request_params: nil)
|
7
|
+
@card = card
|
8
|
+
@dashboard_class = dashboard_class
|
9
|
+
@data = data
|
10
|
+
@request_params = request_params || ActionController::Parameters.new.permit!
|
11
|
+
end
|
12
|
+
|
13
|
+
def view_template
|
14
|
+
div(
|
15
|
+
class: card_classes,
|
16
|
+
role: "region",
|
17
|
+
"aria-labelledby": card_title_id
|
18
|
+
) do
|
19
|
+
render_card_header
|
20
|
+
render_card_body
|
21
|
+
render_card_footer if card[:footer]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def card_classes
|
28
|
+
classes = ["bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden"]
|
29
|
+
classes << "flex flex-col" # Ensure flex layout
|
30
|
+
classes << column_span_class
|
31
|
+
classes << row_span_class if card[:rows] > 1
|
32
|
+
classes.join(" ")
|
33
|
+
end
|
34
|
+
|
35
|
+
def column_span_class
|
36
|
+
cols = card[:cols] || 1
|
37
|
+
case cols
|
38
|
+
when 1
|
39
|
+
"col-span-1"
|
40
|
+
when 2
|
41
|
+
"col-span-1 sm:col-span-2"
|
42
|
+
when 3
|
43
|
+
"col-span-1 sm:col-span-2 lg:col-span-3"
|
44
|
+
when 4
|
45
|
+
"col-span-1 sm:col-span-2 lg:col-span-4"
|
46
|
+
else
|
47
|
+
"col-span-1"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def row_span_class
|
52
|
+
"row-span-#{card[:rows]}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def card_title_id
|
56
|
+
"#{card[:name]}_title"
|
57
|
+
end
|
58
|
+
|
59
|
+
def render_card_header
|
60
|
+
div(class: "px-6 py-4 border-b border-gray-100 bg-gray-50/50 flex items-center justify-between") do
|
61
|
+
h2(id: card_title_id, class: "text-lg font-semibold text-gray-900") { plain card[:title] }
|
62
|
+
div(class: "flex items-center space-x-2") do
|
63
|
+
render_card_actions if card[:actions]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def render_card_body
|
69
|
+
body_classes = card[:type] == :table ? "" : "p-6"
|
70
|
+
div(class: body_classes) do
|
71
|
+
turbo_frame(
|
72
|
+
id: "card_#{card[:name]}",
|
73
|
+
src: card_url,
|
74
|
+
loading: "lazy",
|
75
|
+
data: { controller: "dashboard-card" }
|
76
|
+
) do
|
77
|
+
render_skeleton
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def render_card_footer
|
83
|
+
div(class: "px-6 py-4 bg-gray-50/30 border-t border-gray-100") do
|
84
|
+
plain card[:footer]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def render_card_actions
|
89
|
+
# Override in subclasses
|
90
|
+
end
|
91
|
+
|
92
|
+
def render_skeleton
|
93
|
+
case card[:type]
|
94
|
+
when :metric
|
95
|
+
render_metric_skeleton
|
96
|
+
when :chart
|
97
|
+
render_chart_skeleton
|
98
|
+
when :table
|
99
|
+
render_table_skeleton
|
100
|
+
else
|
101
|
+
render_default_skeleton
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def render_metric_skeleton
|
106
|
+
div(class: "animate-pulse") do
|
107
|
+
div(class: "h-8 bg-gray-200 rounded mb-2")
|
108
|
+
div(class: "h-4 bg-gray-200 rounded w-3/5")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def render_chart_skeleton
|
113
|
+
div(class: "animate-pulse") do
|
114
|
+
div(class: "h-32 bg-gray-200 rounded")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def render_table_skeleton
|
119
|
+
div(class: "animate-pulse space-y-3") do
|
120
|
+
div(class: "grid grid-cols-4 gap-4 mb-4") do
|
121
|
+
4.times do
|
122
|
+
div(class: "h-4 bg-gray-200 rounded")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
5.times do
|
126
|
+
div(class: "grid grid-cols-4 gap-4") do
|
127
|
+
4.times do
|
128
|
+
div(class: "h-4 bg-gray-100 rounded")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def render_default_skeleton
|
136
|
+
div(class: "animate-pulse space-y-3") do
|
137
|
+
div(class: "h-6 bg-gray-200 rounded")
|
138
|
+
div(class: "h-4 bg-gray-200 rounded")
|
139
|
+
div(class: "h-4 bg-gray-200 rounded w-4/5")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def card_url
|
144
|
+
easy_admin_url_helpers.dashboards_dashboard_card_path(
|
145
|
+
dashboard_name: dashboard_class.route_key,
|
146
|
+
card_name: card[:name],
|
147
|
+
**request_params.to_h.symbolize_keys
|
148
|
+
)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Dashboards
|
3
|
+
class CardErrorComponent < BaseComponent
|
4
|
+
def initialize(error:)
|
5
|
+
@error = error
|
6
|
+
end
|
7
|
+
|
8
|
+
def view_template
|
9
|
+
div(class: "p-6 text-center") do
|
10
|
+
div(class: "mb-4") do
|
11
|
+
unsafe_raw <<~SVG
|
12
|
+
<svg class="mx-auto h-12 w-12 text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
13
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
14
|
+
</svg>
|
15
|
+
SVG
|
16
|
+
end
|
17
|
+
div(class: "text-red-600 font-medium mb-2") { "Error loading card" }
|
18
|
+
div(class: "text-sm text-gray-500") { @error }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|