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,96 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Fields
|
3
|
+
module Index
|
4
|
+
module Filters
|
5
|
+
class BooleanComponent < BaseComponent
|
6
|
+
private
|
7
|
+
|
8
|
+
def render_filter_input
|
9
|
+
div(
|
10
|
+
class: "relative",
|
11
|
+
data: {
|
12
|
+
controller: "select-field",
|
13
|
+
select_field_multiple_value: "false",
|
14
|
+
select_field_placeholder_value: "Select...",
|
15
|
+
field_name: @field[:name]
|
16
|
+
}
|
17
|
+
) do
|
18
|
+
# Display input
|
19
|
+
div(class: "relative") do
|
20
|
+
input(
|
21
|
+
type: "text",
|
22
|
+
class: single_select_input_classes,
|
23
|
+
placeholder: "Select...",
|
24
|
+
readonly: true,
|
25
|
+
value: current_display_value,
|
26
|
+
data: {
|
27
|
+
select_field_target: "display",
|
28
|
+
action: "click->select-field#toggleDropdown"
|
29
|
+
}
|
30
|
+
)
|
31
|
+
# Dropdown arrow
|
32
|
+
div(class: "absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none") do
|
33
|
+
unsafe_raw('<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Dropdown menu
|
38
|
+
div(
|
39
|
+
class: "select-dropdown absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg max-h-60 overflow-auto opacity-0 invisible transform scale-95 transition-all duration-200 ease-out",
|
40
|
+
data: { select_field_target: "dropdown" }
|
41
|
+
) do
|
42
|
+
render_option("", "All")
|
43
|
+
render_option("true", "Yes")
|
44
|
+
render_option("false", "No")
|
45
|
+
end
|
46
|
+
|
47
|
+
# Hidden input for the actual value
|
48
|
+
input(
|
49
|
+
type: "hidden",
|
50
|
+
name: "q[#{@field[:name]}_eq]",
|
51
|
+
value: @search_params["#{@field[:name]}_eq"] || "",
|
52
|
+
data: { select_field_target: "hiddenInput" }
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def render_option(value, text)
|
58
|
+
selected = @search_params["#{@field[:name]}_eq"].to_s == value.to_s
|
59
|
+
|
60
|
+
div(
|
61
|
+
class: "select-option px-3 py-2 text-sm cursor-pointer hover:bg-blue-50 hover:text-blue-900 transition-colors duration-150 #{selected ? 'bg-blue-50 text-blue-900' : 'text-gray-900'}",
|
62
|
+
data: {
|
63
|
+
value: value,
|
64
|
+
action: "click->select-field#selectOption",
|
65
|
+
select_field_target: "option"
|
66
|
+
}
|
67
|
+
) do
|
68
|
+
text
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def current_display_value
|
73
|
+
case @search_params["#{@field[:name]}_eq"]
|
74
|
+
when "true"
|
75
|
+
"Yes"
|
76
|
+
when "false"
|
77
|
+
"No"
|
78
|
+
else
|
79
|
+
"All"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def single_select_input_classes
|
84
|
+
base_classes = "block w-full px-3 py-2 pr-10 text-sm border rounded-md cursor-pointer"
|
85
|
+
state_classes = "border-gray-300 placeholder-gray-400 bg-white"
|
86
|
+
focus_classes = "focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
|
87
|
+
hover_classes = "hover:border-gray-400"
|
88
|
+
transition_classes = "transition-colors duration-200"
|
89
|
+
|
90
|
+
"#{base_classes} #{state_classes} #{focus_classes} #{hover_classes} #{transition_classes}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Fields
|
3
|
+
module Index
|
4
|
+
module Filters
|
5
|
+
class DateComponent < BaseComponent
|
6
|
+
private
|
7
|
+
|
8
|
+
def render_filter_input
|
9
|
+
div(class: "grid grid-cols-2 gap-2") do
|
10
|
+
# From date picker
|
11
|
+
div(class: "relative", data: { controller: "date-picker" }) do
|
12
|
+
div(class: "relative") do
|
13
|
+
input(
|
14
|
+
type: "text",
|
15
|
+
name: "q[#{@field[:name]}_gteq]",
|
16
|
+
value: format_display_value(@search_params["#{@field[:name]}_gteq"]),
|
17
|
+
placeholder: "From date",
|
18
|
+
class: "#{base_input_classes} pr-10",
|
19
|
+
data: {
|
20
|
+
date_picker_target: "input",
|
21
|
+
action: "click->date-picker#toggle focus->date-picker#toggle"
|
22
|
+
},
|
23
|
+
readonly: true
|
24
|
+
)
|
25
|
+
div(class: "absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none text-gray-400") do
|
26
|
+
render_calendar_icon
|
27
|
+
end
|
28
|
+
end
|
29
|
+
render_date_picker_modal("from")
|
30
|
+
end
|
31
|
+
|
32
|
+
# To date picker
|
33
|
+
div(class: "relative", data: { controller: "date-picker" }) do
|
34
|
+
div(class: "relative") do
|
35
|
+
input(
|
36
|
+
type: "text",
|
37
|
+
name: "q[#{@field[:name]}_lteq]",
|
38
|
+
value: format_display_value(@search_params["#{@field[:name]}_lteq"]),
|
39
|
+
placeholder: "To date",
|
40
|
+
class: "#{base_input_classes} pr-10",
|
41
|
+
data: {
|
42
|
+
date_picker_target: "input",
|
43
|
+
action: "click->date-picker#toggle focus->date-picker#toggle"
|
44
|
+
},
|
45
|
+
readonly: true
|
46
|
+
)
|
47
|
+
div(class: "absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none text-gray-400") do
|
48
|
+
render_calendar_icon
|
49
|
+
end
|
50
|
+
end
|
51
|
+
render_date_picker_modal("to")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def format_display_value(value)
|
57
|
+
return "" unless value.present?
|
58
|
+
|
59
|
+
case value
|
60
|
+
when Date
|
61
|
+
value.strftime("%B %d, %Y")
|
62
|
+
when Time
|
63
|
+
value.to_date.strftime("%B %d, %Y")
|
64
|
+
when String
|
65
|
+
begin
|
66
|
+
Date.parse(value).strftime("%B %d, %Y")
|
67
|
+
rescue ArgumentError
|
68
|
+
value
|
69
|
+
end
|
70
|
+
else
|
71
|
+
value.to_s
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def render_calendar_icon
|
76
|
+
unsafe_raw <<~SVG
|
77
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
78
|
+
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
|
79
|
+
<line x1="16" y1="2" x2="16" y2="6"></line>
|
80
|
+
<line x1="8" y1="2" x2="8" y2="6"></line>
|
81
|
+
<line x1="3" y1="10" x2="21" y2="10"></line>
|
82
|
+
</svg>
|
83
|
+
SVG
|
84
|
+
end
|
85
|
+
|
86
|
+
def render_date_picker_modal(suffix)
|
87
|
+
div(
|
88
|
+
class: "absolute z-50 mt-2 hidden opacity-0 scale-95 transition-all duration-200 ease-out left-0 right-0 sm:left-auto sm:right-auto sm:min-w-[320px]",
|
89
|
+
data: {
|
90
|
+
date_picker_target: "modal",
|
91
|
+
action: "click->date-picker#clickOutside"
|
92
|
+
}
|
93
|
+
) do
|
94
|
+
div(
|
95
|
+
class: "bg-white rounded-lg shadow-xl border border-gray-200 p-4 mx-2 sm:mx-0 max-w-sm sm:max-w-none",
|
96
|
+
data: { action: "click->date-picker#preventClose" }
|
97
|
+
) do
|
98
|
+
render_date_picker_header
|
99
|
+
render_date_picker_calendar
|
100
|
+
render_date_picker_footer
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def render_date_picker_header
|
106
|
+
div(class: "flex items-center justify-between mb-4") do
|
107
|
+
button(
|
108
|
+
type: "button",
|
109
|
+
class: "p-2 hover:bg-gray-100 rounded-lg transition-colors group",
|
110
|
+
data: { action: "click->date-picker#previousMonth" }
|
111
|
+
) do
|
112
|
+
unsafe_raw <<~SVG
|
113
|
+
<svg class="w-4 h-4 text-gray-600 group-hover:text-gray-900" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
114
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
115
|
+
</svg>
|
116
|
+
SVG
|
117
|
+
end
|
118
|
+
|
119
|
+
div(
|
120
|
+
class: "text-base font-semibold text-gray-900",
|
121
|
+
data: { date_picker_target: "monthYear" }
|
122
|
+
)
|
123
|
+
|
124
|
+
button(
|
125
|
+
type: "button",
|
126
|
+
class: "p-2 hover:bg-gray-100 rounded-lg transition-colors group",
|
127
|
+
data: { action: "click->date-picker#nextMonth" }
|
128
|
+
) do
|
129
|
+
unsafe_raw <<~SVG
|
130
|
+
<svg class="w-4 h-4 text-gray-600 group-hover:text-gray-900" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
131
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
132
|
+
</svg>
|
133
|
+
SVG
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def render_date_picker_calendar
|
139
|
+
div do
|
140
|
+
# Days of week header
|
141
|
+
div(class: "grid grid-cols-7 gap-1 mb-3") do
|
142
|
+
%w[Sun Mon Tue Wed Thu Fri Sat].each do |day|
|
143
|
+
div(class: "text-xs sm:text-sm text-center text-gray-500 font-medium py-2") { day }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Calendar grid (populated by JavaScript)
|
148
|
+
div(
|
149
|
+
class: "grid grid-cols-7 gap-1 sm:gap-2",
|
150
|
+
data: { date_picker_target: "grid" }
|
151
|
+
)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def render_date_picker_footer
|
156
|
+
div(class: "flex flex-col sm:flex-row sm:items-center sm:justify-between mt-4 pt-4 border-t border-gray-200 space-y-3 sm:space-y-0") do
|
157
|
+
button(
|
158
|
+
type: "button",
|
159
|
+
class: "px-4 py-2.5 sm:px-3 sm:py-1.5 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors touch-manipulation",
|
160
|
+
data: { action: "click->date-picker#today" }
|
161
|
+
) { "Today" }
|
162
|
+
|
163
|
+
div(class: "flex items-center space-x-3 sm:space-x-2") do
|
164
|
+
button(
|
165
|
+
type: "button",
|
166
|
+
class: "flex-1 sm:flex-none px-4 py-2.5 sm:px-3 sm:py-1.5 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors touch-manipulation",
|
167
|
+
data: { action: "click->date-picker#clear" }
|
168
|
+
) { "Clear" }
|
169
|
+
|
170
|
+
button(
|
171
|
+
type: "button",
|
172
|
+
class: "flex-1 sm:flex-none px-4 py-2.5 sm:px-3 sm:py-1.5 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors touch-manipulation",
|
173
|
+
data: { action: "click->date-picker#close" }
|
174
|
+
) { "Done" }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Fields
|
3
|
+
module Index
|
4
|
+
module Filters
|
5
|
+
class NumberComponent < BaseComponent
|
6
|
+
private
|
7
|
+
|
8
|
+
def render_filter_input
|
9
|
+
div(class: "grid grid-cols-2 gap-2") do
|
10
|
+
input(
|
11
|
+
type: "number",
|
12
|
+
name: "q[#{@field[:name]}_gteq]",
|
13
|
+
value: @search_params["#{@field[:name]}_gteq"],
|
14
|
+
placeholder: "Min",
|
15
|
+
class: base_input_classes
|
16
|
+
)
|
17
|
+
input(
|
18
|
+
type: "number",
|
19
|
+
name: "q[#{@field[:name]}_lteq]",
|
20
|
+
value: @search_params["#{@field[:name]}_lteq"],
|
21
|
+
placeholder: "Max",
|
22
|
+
class: base_input_classes
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Fields
|
3
|
+
module Index
|
4
|
+
module Filters
|
5
|
+
class SelectComponent < BaseComponent
|
6
|
+
private
|
7
|
+
|
8
|
+
def render_filter_input
|
9
|
+
div(
|
10
|
+
class: "relative",
|
11
|
+
data: {
|
12
|
+
controller: "select-field",
|
13
|
+
select_field_multiple_value: "false",
|
14
|
+
select_field_placeholder_value: "Select #{@field[:label].downcase}...",
|
15
|
+
field_name: @field[:name]
|
16
|
+
}
|
17
|
+
) do
|
18
|
+
# Display input
|
19
|
+
div(class: "relative") do
|
20
|
+
input(
|
21
|
+
type: "text",
|
22
|
+
class: single_select_input_classes,
|
23
|
+
placeholder: "Select #{@field[:label].downcase}...",
|
24
|
+
readonly: true,
|
25
|
+
value: current_display_value,
|
26
|
+
data: {
|
27
|
+
select_field_target: "display",
|
28
|
+
action: "click->select-field#toggleDropdown"
|
29
|
+
}
|
30
|
+
)
|
31
|
+
# Dropdown arrow
|
32
|
+
div(class: "absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none") do
|
33
|
+
unsafe_raw('<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Dropdown menu
|
38
|
+
div(
|
39
|
+
class: "select-dropdown absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg max-h-60 overflow-auto opacity-0 invisible transform scale-95 transition-all duration-200 ease-out",
|
40
|
+
data: { select_field_target: "dropdown" }
|
41
|
+
) do
|
42
|
+
render_option("", "All")
|
43
|
+
|
44
|
+
if @field[:options]
|
45
|
+
@field[:options].each do |option_value, option_label|
|
46
|
+
render_option(option_value, option_label)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Hidden input for the actual value
|
52
|
+
input(
|
53
|
+
type: "hidden",
|
54
|
+
name: "q[#{@field[:name]}_eq]",
|
55
|
+
value: @search_params["#{@field[:name]}_eq"] || "",
|
56
|
+
data: { select_field_target: "hiddenInput" }
|
57
|
+
)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def render_option(value, text)
|
62
|
+
selected = @search_params["#{@field[:name]}_eq"].to_s == value.to_s
|
63
|
+
|
64
|
+
div(
|
65
|
+
class: "select-option px-3 py-2 text-sm cursor-pointer hover:bg-blue-50 hover:text-blue-900 transition-colors duration-150 #{selected ? 'bg-blue-50 text-blue-900' : 'text-gray-900'}",
|
66
|
+
data: {
|
67
|
+
value: value,
|
68
|
+
action: "click->select-field#selectOption",
|
69
|
+
select_field_target: "option"
|
70
|
+
}
|
71
|
+
) do
|
72
|
+
text.to_s
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def current_display_value
|
77
|
+
return "All" if @search_params["#{@field[:name]}_eq"].blank?
|
78
|
+
|
79
|
+
if @field[:options]
|
80
|
+
@field[:options].each do |option_value, option_label|
|
81
|
+
return option_label.to_s if option_value.to_s == @search_params["#{@field[:name]}_eq"].to_s
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
@search_params["#{@field[:name]}_eq"].to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
def single_select_input_classes
|
89
|
+
base_classes = "block w-full px-3 py-2 pr-10 text-sm border rounded-md cursor-pointer"
|
90
|
+
state_classes = "border-gray-300 placeholder-gray-400 bg-white"
|
91
|
+
focus_classes = "focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
|
92
|
+
hover_classes = "hover:border-gray-400"
|
93
|
+
transition_classes = "transition-colors duration-200"
|
94
|
+
|
95
|
+
"#{base_classes} #{state_classes} #{focus_classes} #{hover_classes} #{transition_classes}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Fields
|
3
|
+
module Index
|
4
|
+
module Filters
|
5
|
+
class StringComponent < BaseComponent
|
6
|
+
private
|
7
|
+
|
8
|
+
def render_filter_input
|
9
|
+
input(
|
10
|
+
type: "text",
|
11
|
+
id: field_id,
|
12
|
+
name: "q[#{@field[:name]}_cont]",
|
13
|
+
value: @search_params["#{@field[:name]}_cont"],
|
14
|
+
placeholder: "Search #{@field[:label].downcase}...",
|
15
|
+
class: input_classes
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def input_classes
|
20
|
+
base_classes = "block w-full px-3 py-2 border rounded-md shadow-sm text-sm"
|
21
|
+
state_classes = "border-gray-300 placeholder-gray-400"
|
22
|
+
focus_classes = "focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
|
23
|
+
hover_classes = "hover:border-gray-400"
|
24
|
+
transition_classes = "transition-colors duration-200"
|
25
|
+
|
26
|
+
"#{base_classes} #{state_classes} #{focus_classes} #{hover_classes} #{transition_classes}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Fields
|
3
|
+
module Index
|
4
|
+
class JsonComponent < Fields::BaseComponent
|
5
|
+
def view_template
|
6
|
+
div(class: "text-center") do
|
7
|
+
if json_value.present?
|
8
|
+
span(class: "text-green-600 font-bold text-lg") { "+" }
|
9
|
+
else
|
10
|
+
span(class: "text-gray-400 font-bold text-lg") { "−" }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def json_value
|
18
|
+
@json_value ||= value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Fields
|
3
|
+
module Index
|
4
|
+
class NumberComponent < BaseComponent
|
5
|
+
def view_template
|
6
|
+
span(class: "ea-cell-value ea-cell-number") do
|
7
|
+
format_value
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def format_value
|
14
|
+
return "" if value.blank?
|
15
|
+
value.to_s
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Fields
|
3
|
+
module Index
|
4
|
+
class SelectComponent < BaseComponent
|
5
|
+
def view_template
|
6
|
+
span(class: "ea-cell-value ea-cell-select") do
|
7
|
+
plain format_value
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def format_value
|
14
|
+
return "" if value.blank?
|
15
|
+
|
16
|
+
if value.is_a?(Array)
|
17
|
+
value.join(", ")
|
18
|
+
else
|
19
|
+
value.to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Fields
|
3
|
+
module Index
|
4
|
+
class TextComponent < BaseComponent
|
5
|
+
def view_template
|
6
|
+
span(class: "ea-cell-value") do
|
7
|
+
truncate(format_value, length: 50)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def truncate(text, length:)
|
14
|
+
return text if text.length <= length
|
15
|
+
"#{text[0, length]}..."
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Fields
|
3
|
+
class InlineEditModalComponent < EasyAdmin::BaseComponent
|
4
|
+
def initialize(record:, field_config:, resource_class:)
|
5
|
+
@record = record
|
6
|
+
@field_config = field_config
|
7
|
+
@resource_class = resource_class
|
8
|
+
end
|
9
|
+
|
10
|
+
def view_template
|
11
|
+
turbo_frame(id: "modal") do
|
12
|
+
div(
|
13
|
+
id: "modal-backdrop",
|
14
|
+
class: "fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50 opacity-100 transition-opacity duration-300",
|
15
|
+
data: {
|
16
|
+
controller: "modal",
|
17
|
+
action: "click->modal#closeOnBackdrop"
|
18
|
+
}
|
19
|
+
) do
|
20
|
+
div(class: "relative top-20 mx-auto p-5 border w-11/12 md:w-3/4 lg:w-1/2 xl:w-2/5 shadow-lg rounded-md bg-white") do
|
21
|
+
div(class: "mt-3") do
|
22
|
+
# Modal header
|
23
|
+
div(class: "flex items-center justify-between mb-4") do
|
24
|
+
h3(class: "text-lg font-medium text-gray-900") do
|
25
|
+
"Edit #{@field_config[:label]}"
|
26
|
+
end
|
27
|
+
button(
|
28
|
+
class: "text-gray-400 hover:text-gray-600 focus:outline-none",
|
29
|
+
data: { action: "click->modal#close" }
|
30
|
+
) do
|
31
|
+
unsafe_raw <<~SVG
|
32
|
+
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
33
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
34
|
+
</svg>
|
35
|
+
SVG
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Modal form
|
40
|
+
form(
|
41
|
+
action: update_field_url,
|
42
|
+
method: "patch",
|
43
|
+
data: {
|
44
|
+
turbo_frame: "_top",
|
45
|
+
controller: "form",
|
46
|
+
action: "submit->form#submit turbo:submit-end->modal#handleSubmitEnd"
|
47
|
+
}
|
48
|
+
) do
|
49
|
+
render_form_field
|
50
|
+
|
51
|
+
# Form actions
|
52
|
+
div(class: "flex justify-end space-x-3 mt-6") do
|
53
|
+
button(
|
54
|
+
type: "button",
|
55
|
+
class: "px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
|
56
|
+
data: { action: "click->modal#close" }
|
57
|
+
) do
|
58
|
+
"Cancel"
|
59
|
+
end
|
60
|
+
|
61
|
+
button(
|
62
|
+
type: "submit",
|
63
|
+
class: "px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
64
|
+
) do
|
65
|
+
"Save Changes"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def render_form_field
|
78
|
+
# For converted belongs_to fields, we need to get the foreign key value
|
79
|
+
if @field_config[:original_type] == :belongs_to
|
80
|
+
current_value = @record.public_send(@field_config[:name]) # This is already the foreign key
|
81
|
+
else
|
82
|
+
current_value = @record.public_send(@field_config[:name])
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
# Create a form builder for the field component
|
87
|
+
# Make sure the record has the current value set for the field
|
88
|
+
if @field_config[:original_type] == :belongs_to
|
89
|
+
# For belongs_to fields, ensure the foreign key is accessible
|
90
|
+
@record.define_singleton_method(@field_config[:name]) { current_value } unless @record.respond_to?(@field_config[:name])
|
91
|
+
end
|
92
|
+
|
93
|
+
form_builder = ActionView::Helpers::FormBuilder.new(
|
94
|
+
@resource_class.param_key,
|
95
|
+
@record,
|
96
|
+
helpers,
|
97
|
+
{}
|
98
|
+
)
|
99
|
+
|
100
|
+
# Get the field component instance and render it
|
101
|
+
field_component = field_component(
|
102
|
+
@field_config,
|
103
|
+
action: :form,
|
104
|
+
value: current_value,
|
105
|
+
record: @record,
|
106
|
+
form: form_builder
|
107
|
+
)
|
108
|
+
|
109
|
+
|
110
|
+
render field_component
|
111
|
+
end
|
112
|
+
|
113
|
+
def field_input_id
|
114
|
+
"field_#{@field_config[:name]}"
|
115
|
+
end
|
116
|
+
|
117
|
+
def update_field_url
|
118
|
+
# For belongs_to fields that were converted to select, we need to use the belongs_to_reattach route
|
119
|
+
if @field_config[:original_type] == :belongs_to
|
120
|
+
easy_admin_url_helpers.belongs_to_reattach_resource_path(
|
121
|
+
@resource_class.route_key,
|
122
|
+
id: @record.id,
|
123
|
+
field: @field_config[:original_name]
|
124
|
+
)
|
125
|
+
else
|
126
|
+
easy_admin_url_helpers.update_field_resource_path(
|
127
|
+
@resource_class.route_key,
|
128
|
+
id: @record.id,
|
129
|
+
field: @field_config[:name]
|
130
|
+
)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|