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.
Files changed (203) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/builds/easy_admin.base.js +43505 -0
  6. data/app/assets/builds/easy_admin.base.js.map +7 -0
  7. data/app/assets/builds/easy_admin.css +6141 -0
  8. data/app/assets/config/easy_admin_manifest.js +1 -0
  9. data/app/assets/images/jsoneditor-icons.svg +749 -0
  10. data/app/assets/stylesheets/easy_admin/application.tailwind.css +390 -0
  11. data/app/components/easy_admin/base_component.rb +35 -0
  12. data/app/components/easy_admin/batch_action_bar_component.rb +125 -0
  13. data/app/components/easy_admin/batch_action_form_component.rb +124 -0
  14. data/app/components/easy_admin/combined_filters_component.rb +232 -0
  15. data/app/components/easy_admin/confirmation_modal_component.rb +61 -0
  16. data/app/components/easy_admin/context_menu_component.rb +161 -0
  17. data/app/components/easy_admin/dashboards/base_card_component.rb +152 -0
  18. data/app/components/easy_admin/dashboards/card_error_component.rb +23 -0
  19. data/app/components/easy_admin/dashboards/card_factory.rb +90 -0
  20. data/app/components/easy_admin/dashboards/card_stream_component.rb +22 -0
  21. data/app/components/easy_admin/dashboards/cards/base_card_component.rb +54 -0
  22. data/app/components/easy_admin/dashboards/cards/chart_card_component.rb +175 -0
  23. data/app/components/easy_admin/dashboards/cards/custom_card_component.rb +50 -0
  24. data/app/components/easy_admin/dashboards/cards/metric_card_component.rb +164 -0
  25. data/app/components/easy_admin/dashboards/cards/table_card_component.rb +148 -0
  26. data/app/components/easy_admin/dashboards/chart_card_component.rb +44 -0
  27. data/app/components/easy_admin/dashboards/metric_card_component.rb +56 -0
  28. data/app/components/easy_admin/dashboards/refresh_stream_component.rb +279 -0
  29. data/app/components/easy_admin/dashboards/show_component.rb +163 -0
  30. data/app/components/easy_admin/dashboards/table_card_component.rb +52 -0
  31. data/app/components/easy_admin/date_picker_component.rb +188 -0
  32. data/app/components/easy_admin/fields/base_component.rb +101 -0
  33. data/app/components/easy_admin/fields/belongs_to_edit_modal_component.rb +117 -0
  34. data/app/components/easy_admin/fields/form/belongs_to_component.rb +82 -0
  35. data/app/components/easy_admin/fields/form/boolean_component.rb +100 -0
  36. data/app/components/easy_admin/fields/form/date_component.rb +55 -0
  37. data/app/components/easy_admin/fields/form/datetime_component.rb +55 -0
  38. data/app/components/easy_admin/fields/form/email_component.rb +55 -0
  39. data/app/components/easy_admin/fields/form/file_component.rb +190 -0
  40. data/app/components/easy_admin/fields/form/has_many_component.rb +416 -0
  41. data/app/components/easy_admin/fields/form/json_component.rb +81 -0
  42. data/app/components/easy_admin/fields/form/number_component.rb +55 -0
  43. data/app/components/easy_admin/fields/form/select_component.rb +326 -0
  44. data/app/components/easy_admin/fields/form/text_component.rb +55 -0
  45. data/app/components/easy_admin/fields/form/textarea_component.rb +54 -0
  46. data/app/components/easy_admin/fields/index/belongs_to_component.rb +93 -0
  47. data/app/components/easy_admin/fields/index/boolean_component.rb +29 -0
  48. data/app/components/easy_admin/fields/index/date_component.rb +13 -0
  49. data/app/components/easy_admin/fields/index/datetime_component.rb +13 -0
  50. data/app/components/easy_admin/fields/index/email_component.rb +24 -0
  51. data/app/components/easy_admin/fields/index/filters/base_component.rb +48 -0
  52. data/app/components/easy_admin/fields/index/filters/boolean_component.rb +96 -0
  53. data/app/components/easy_admin/fields/index/filters/date_component.rb +182 -0
  54. data/app/components/easy_admin/fields/index/filters/number_component.rb +30 -0
  55. data/app/components/easy_admin/fields/index/filters/select_component.rb +101 -0
  56. data/app/components/easy_admin/fields/index/filters/string_component.rb +32 -0
  57. data/app/components/easy_admin/fields/index/json_component.rb +23 -0
  58. data/app/components/easy_admin/fields/index/number_component.rb +20 -0
  59. data/app/components/easy_admin/fields/index/select_component.rb +25 -0
  60. data/app/components/easy_admin/fields/index/text_component.rb +20 -0
  61. data/app/components/easy_admin/fields/inline_edit_modal_component.rb +135 -0
  62. data/app/components/easy_admin/fields/inline_edit_trigger_component.rb +144 -0
  63. data/app/components/easy_admin/fields/show/belongs_to_component.rb +93 -0
  64. data/app/components/easy_admin/fields/show/boolean_component.rb +21 -0
  65. data/app/components/easy_admin/fields/show/date_component.rb +13 -0
  66. data/app/components/easy_admin/fields/show/datetime_component.rb +13 -0
  67. data/app/components/easy_admin/fields/show/email_component.rb +19 -0
  68. data/app/components/easy_admin/fields/show/file_component.rb +304 -0
  69. data/app/components/easy_admin/fields/show/has_many_component.rb +192 -0
  70. data/app/components/easy_admin/fields/show/json_component.rb +45 -0
  71. data/app/components/easy_admin/fields/show/number_component.rb +20 -0
  72. data/app/components/easy_admin/fields/show/select_component.rb +25 -0
  73. data/app/components/easy_admin/fields/show/text_component.rb +17 -0
  74. data/app/components/easy_admin/fields/show/textarea_component.rb +26 -0
  75. data/app/components/easy_admin/filters_component.rb +120 -0
  76. data/app/components/easy_admin/form_tabs_component.rb +166 -0
  77. data/app/components/easy_admin/infinite_scroll_component.rb +82 -0
  78. data/app/components/easy_admin/lazy_chart_card_component.rb +128 -0
  79. data/app/components/easy_admin/lazy_metric_card_component.rb +76 -0
  80. data/app/components/easy_admin/modal_frame_component.rb +26 -0
  81. data/app/components/easy_admin/navbar_component.rb +226 -0
  82. data/app/components/easy_admin/notification_component.rb +83 -0
  83. data/app/components/easy_admin/pagination_component.rb +188 -0
  84. data/app/components/easy_admin/quick_filters_component.rb +65 -0
  85. data/app/components/easy_admin/resource_pagination_component.rb +14 -0
  86. data/app/components/easy_admin/resources/index_component.rb +211 -0
  87. data/app/components/easy_admin/resources/index_frame_component.rb +88 -0
  88. data/app/components/easy_admin/resources/show_page_actions_component.rb +324 -0
  89. data/app/components/easy_admin/resources/table_cell_component.rb +145 -0
  90. data/app/components/easy_admin/resources/table_component.rb +206 -0
  91. data/app/components/easy_admin/resources/table_row_component.rb +160 -0
  92. data/app/components/easy_admin/row_action_form_component.rb +127 -0
  93. data/app/components/easy_admin/scopes_component.rb +224 -0
  94. data/app/components/easy_admin/settings_sidebar_component.rb +140 -0
  95. data/app/components/easy_admin/show_layout_component.rb +600 -0
  96. data/app/components/easy_admin/sidebar_component.rb +174 -0
  97. data/app/components/easy_admin/turbo/response_component.rb +40 -0
  98. data/app/components/easy_admin/turbo/stream_component.rb +28 -0
  99. data/app/controllers/easy_admin/application_controller.rb +66 -0
  100. data/app/controllers/easy_admin/batch_actions_controller.rb +166 -0
  101. data/app/controllers/easy_admin/confirmation_modal_controller.rb +20 -0
  102. data/app/controllers/easy_admin/dashboard_controller.rb +6 -0
  103. data/app/controllers/easy_admin/dashboards_controller.rb +123 -0
  104. data/app/controllers/easy_admin/passwords_controller.rb +15 -0
  105. data/app/controllers/easy_admin/registrations_controller.rb +52 -0
  106. data/app/controllers/easy_admin/resources_controller.rb +907 -0
  107. data/app/controllers/easy_admin/row_actions_controller.rb +216 -0
  108. data/app/controllers/easy_admin/sessions_controller.rb +32 -0
  109. data/app/controllers/easy_admin/settings_controller.rb +94 -0
  110. data/app/helpers/easy_admin/application_helper.rb +4 -0
  111. data/app/helpers/easy_admin/dashboards_helper.rb +121 -0
  112. data/app/helpers/easy_admin/fields_helper.rb +27 -0
  113. data/app/helpers/easy_admin/pagy_helper.rb +30 -0
  114. data/app/helpers/easy_admin/resources_helper.rb +39 -0
  115. data/app/javascript/easy_admin/application.js +12 -0
  116. data/app/javascript/easy_admin/controllers/batch_modal_controller.js +66 -0
  117. data/app/javascript/easy_admin/controllers/batch_selection_controller.js +223 -0
  118. data/app/javascript/easy_admin/controllers/chart_controller.js +216 -0
  119. data/app/javascript/easy_admin/controllers/collapsible_filters_controller.js +118 -0
  120. data/app/javascript/easy_admin/controllers/confirmation_modal_controller.js +64 -0
  121. data/app/javascript/easy_admin/controllers/context_menu_controller.js +227 -0
  122. data/app/javascript/easy_admin/controllers/date_picker_controller.js +309 -0
  123. data/app/javascript/easy_admin/controllers/dropdown_controller.js +63 -0
  124. data/app/javascript/easy_admin/controllers/event_emitter_controller.js +19 -0
  125. data/app/javascript/easy_admin/controllers/file_controller.js +121 -0
  126. data/app/javascript/easy_admin/controllers/form_tabs_controller.js +100 -0
  127. data/app/javascript/easy_admin/controllers/has_many_search_controller.js +76 -0
  128. data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +174 -0
  129. data/app/javascript/easy_admin/controllers/ios_alert_controller.js +195 -0
  130. data/app/javascript/easy_admin/controllers/jsoneditor_controller.js +88 -0
  131. data/app/javascript/easy_admin/controllers/modal_controller.js +75 -0
  132. data/app/javascript/easy_admin/controllers/navbar_scroll_controller.js +76 -0
  133. data/app/javascript/easy_admin/controllers/notification_controller.js +48 -0
  134. data/app/javascript/easy_admin/controllers/row_action_controller.js +124 -0
  135. data/app/javascript/easy_admin/controllers/row_modal_controller.js +59 -0
  136. data/app/javascript/easy_admin/controllers/select_field_controller.js +618 -0
  137. data/app/javascript/easy_admin/controllers/settings_button_controller.js +8 -0
  138. data/app/javascript/easy_admin/controllers/settings_sidebar_controller.js +186 -0
  139. data/app/javascript/easy_admin/controllers/sidebar_controller.js +102 -0
  140. data/app/javascript/easy_admin/controllers/sidebar_mobile_controller.js +23 -0
  141. data/app/javascript/easy_admin/controllers/sidebar_nav_controller.js +96 -0
  142. data/app/javascript/easy_admin/controllers/table_controller.js +28 -0
  143. data/app/javascript/easy_admin/controllers/table_row_controller.js +16 -0
  144. data/app/javascript/easy_admin/controllers/toggle_switch_controller.js +22 -0
  145. data/app/javascript/easy_admin/controllers/turbo_stream_redirect.js +9 -0
  146. data/app/javascript/easy_admin/controllers.js +54 -0
  147. data/app/javascript/easy_admin.base.js +4 -0
  148. data/app/models/easy_admin/admin_user.rb +53 -0
  149. data/app/models/easy_admin/application_record.rb +5 -0
  150. data/app/views/easy_admin/dashboard/index.html.erb +3 -0
  151. data/app/views/easy_admin/dashboards/show.html.erb +7 -0
  152. data/app/views/easy_admin/passwords/edit.html.erb +42 -0
  153. data/app/views/easy_admin/passwords/new.html.erb +41 -0
  154. data/app/views/easy_admin/registrations/new.html.erb +65 -0
  155. data/app/views/easy_admin/resources/_redirect.turbo_stream.erb +3 -0
  156. data/app/views/easy_admin/resources/_table_rows.html.erb +46 -0
  157. data/app/views/easy_admin/resources/edit.html.erb +151 -0
  158. data/app/views/easy_admin/resources/index.html.erb +12 -0
  159. data/app/views/easy_admin/resources/index.turbo_stream.erb +139 -0
  160. data/app/views/easy_admin/resources/index_frame.html.erb +142 -0
  161. data/app/views/easy_admin/resources/new.html.erb +100 -0
  162. data/app/views/easy_admin/resources/show.html.erb +31 -0
  163. data/app/views/easy_admin/sessions/new.html.erb +55 -0
  164. data/app/views/easy_admin/settings/_form.html.erb +51 -0
  165. data/app/views/easy_admin/settings/index.html.erb +53 -0
  166. data/app/views/layouts/easy_admin/application.html.erb +48 -0
  167. data/app/views/layouts/easy_admin/auth.html.erb +34 -0
  168. data/config/initializers/easy_admin_card_factory.rb +27 -0
  169. data/config/initializers/pagy.rb +15 -0
  170. data/config/initializers/rack_mini_profiler.rb +67 -0
  171. data/config/routes.rb +70 -0
  172. data/db/migrate/20250101000001_create_easy_admin_admin_users.rb +45 -0
  173. data/lib/easy-admin.rb +32 -0
  174. data/lib/easy_admin/action.rb +159 -0
  175. data/lib/easy_admin/batch_action.rb +134 -0
  176. data/lib/easy_admin/configuration.rb +75 -0
  177. data/lib/easy_admin/dashboard.rb +110 -0
  178. data/lib/easy_admin/dashboard_registry.rb +30 -0
  179. data/lib/easy_admin/delete_action.rb +22 -0
  180. data/lib/easy_admin/engine.rb +54 -0
  181. data/lib/easy_admin/field.rb +118 -0
  182. data/lib/easy_admin/resource.rb +806 -0
  183. data/lib/easy_admin/resource_registry.rb +22 -0
  184. data/lib/easy_admin/types/json_type.rb +25 -0
  185. data/lib/easy_admin/version.rb +3 -0
  186. data/lib/generators/easy_admin/auth_generator.rb +69 -0
  187. data/lib/generators/easy_admin/card/card_generator.rb +94 -0
  188. data/lib/generators/easy_admin/card/templates/card_component.rb.erb +127 -0
  189. data/lib/generators/easy_admin/card/templates/card_component_spec.rb.erb +122 -0
  190. data/lib/generators/easy_admin/install/templates/easy_admin.rb +31 -0
  191. data/lib/generators/easy_admin/install_generator.rb +25 -0
  192. data/lib/generators/easy_admin/rbac/rbac_generator.rb +244 -0
  193. data/lib/generators/easy_admin/rbac/templates/add_rbac_to_admin_users.rb +23 -0
  194. data/lib/generators/easy_admin/rbac/templates/super_admin.rb +34 -0
  195. data/lib/generators/easy_admin/resource_generator.rb +43 -0
  196. data/lib/generators/easy_admin/templates/AUTH_README +35 -0
  197. data/lib/generators/easy_admin/templates/README +27 -0
  198. data/lib/generators/easy_admin/templates/create_easy_admin_admin_users.rb +45 -0
  199. data/lib/generators/easy_admin/templates/devise.rb +267 -0
  200. data/lib/generators/easy_admin/templates/easy_admin.rb +24 -0
  201. data/lib/generators/easy_admin/templates/resource.rb +29 -0
  202. data/lib/tasks/easy_admin_tasks.rake +4 -0
  203. metadata +445 -0
@@ -0,0 +1,88 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import JSONEditor from "jsoneditor"
3
+
4
+ export default class extends Controller {
5
+ static values = {
6
+ mode: String,
7
+ data: Object
8
+ }
9
+ static targets = ["hiddenField", "editor"]
10
+
11
+ connect() {
12
+ this.initializeEditor()
13
+
14
+ // Listen for form submission to ensure hidden field is updated
15
+ const form = this.element.closest('form')
16
+ if (form) {
17
+ form.addEventListener('submit', () => {
18
+ this.updateHiddenField()
19
+ })
20
+ }
21
+ }
22
+
23
+ disconnect() {
24
+ if (this.editor) {
25
+ this.editor.destroy()
26
+ this.editor = null
27
+ }
28
+ }
29
+
30
+ initializeEditor() {
31
+ const options = {
32
+ mode: this.modeValue || 'view',
33
+ modes: this.modeValue === 'code' ? ['code', 'tree', 'view'] : ['view'],
34
+ search: this.modeValue !== 'view',
35
+ history: this.modeValue === 'code',
36
+ navigationBar: this.modeValue !== 'view',
37
+ enableSort: true,
38
+ enableTransform: true,
39
+ onChange: () => {
40
+ this.updateHiddenField()
41
+ },
42
+ onModeChange: (newMode, oldMode) => {
43
+ // Update when switching between modes
44
+ setTimeout(() => this.updateHiddenField(), 100)
45
+ }
46
+ }
47
+
48
+ const container = this.hasEditorTarget ? this.editorTarget : this.element
49
+ this.editor = new JSONEditor(container, options)
50
+
51
+ if (this.hasDataValue) {
52
+ this.editor.set(this.dataValue)
53
+ }
54
+ }
55
+
56
+ updateHiddenField() {
57
+ if (this.editor && this.hasHiddenFieldTarget) {
58
+ try {
59
+ const jsonData = this.editor.get()
60
+ const jsonString = JSON.stringify(jsonData)
61
+ this.hiddenFieldTarget.value = jsonString
62
+ console.log('JSON Editor: Updated hidden field with:', jsonString)
63
+ } catch (error) {
64
+ console.warn('JSON Editor: Invalid JSON, not updating hidden field:', error)
65
+ // In case of error, try to get the text content
66
+ try {
67
+ if (this.editor.mode === 'code') {
68
+ const text = this.editor.getText()
69
+ this.hiddenFieldTarget.value = text
70
+ console.log('JSON Editor: Updated hidden field with raw text:', text)
71
+ }
72
+ } catch (textError) {
73
+ console.warn('JSON Editor: Could not get text content:', textError)
74
+ }
75
+ }
76
+ }
77
+ }
78
+
79
+ getData() {
80
+ return this.editor ? this.editor.get() : null
81
+ }
82
+
83
+ setData(data) {
84
+ if (this.editor) {
85
+ this.editor.set(data)
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,75 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["backdrop", "content"]
5
+
6
+ connect() {
7
+ // Show modal when turbo-frame loads content
8
+ this.element.addEventListener("turbo:frame-load", this.show.bind(this))
9
+
10
+ // Hide modal on successful form submission
11
+ this.element.addEventListener("turbo:submit-end", this.handleSubmitEnd.bind(this))
12
+
13
+ // Handle escape key
14
+ this.handleKeydown = this.handleKeydown.bind(this)
15
+ document.addEventListener("keydown", this.handleKeydown)
16
+ }
17
+
18
+ disconnect() {
19
+ document.removeEventListener("keydown", this.handleKeydown)
20
+ }
21
+
22
+ show() {
23
+ const backdrop = document.getElementById("modal-backdrop")
24
+ if (backdrop) {
25
+ backdrop.style.display = "block"
26
+
27
+ // Trigger animation after display
28
+ requestAnimationFrame(() => {
29
+ backdrop.classList.remove("opacity-0")
30
+ backdrop.classList.add("opacity-100")
31
+ })
32
+
33
+ // Prevent body scroll
34
+ document.body.style.overflow = "hidden"
35
+ }
36
+ }
37
+
38
+ close() {
39
+ const backdrop = document.getElementById("modal-backdrop")
40
+ if (backdrop) {
41
+ backdrop.classList.remove("opacity-100")
42
+ backdrop.classList.add("opacity-0")
43
+
44
+ // Hide after animation and clear turbo frame
45
+ setTimeout(() => {
46
+ // Clear the modal turbo frame
47
+ const modalFrame = document.getElementById("modal")
48
+ if (modalFrame) {
49
+ modalFrame.innerHTML = ""
50
+ }
51
+ document.body.style.overflow = ""
52
+ }, 300)
53
+ }
54
+ }
55
+
56
+ closeOnBackdrop(event) {
57
+ // Only close if clicked on backdrop, not on modal content
58
+ if (event.target === event.currentTarget) {
59
+ this.close()
60
+ }
61
+ }
62
+
63
+ handleKeydown(event) {
64
+ if (event.key === "Escape") {
65
+ this.close()
66
+ }
67
+ }
68
+
69
+ handleSubmitEnd(event) {
70
+ // Close modal on successful form submission
71
+ if (event.detail.success) {
72
+ this.close()
73
+ }
74
+ }
75
+ }
@@ -0,0 +1,76 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["navbar", "content", "body"]
5
+
6
+ connect() {
7
+ this.scrollThreshold = 60
8
+ this.isScrolled = false
9
+ this.handleScroll = this.handleScroll.bind(this)
10
+ window.addEventListener('scroll', this.handleScroll, { passive: true })
11
+ }
12
+
13
+ disconnect() {
14
+ window.removeEventListener('scroll', this.handleScroll)
15
+ }
16
+
17
+ handleScroll() {
18
+ const currentScrollY = window.scrollY
19
+
20
+ if (currentScrollY > this.scrollThreshold && !this.isScrolled) {
21
+ this.shrinkNavbar()
22
+ } else if (currentScrollY <= this.scrollThreshold && this.isScrolled) {
23
+ this.expandNavbar()
24
+ }
25
+ }
26
+
27
+ shrinkNavbar() {
28
+ this.isScrolled = true
29
+ // Reduce navbar padding from py-4 to py-2
30
+ this.navbarTarget.classList.remove('py-4')
31
+ this.navbarTarget.classList.add('py-2')
32
+
33
+ // Reduce body padding from pt-20 to pt-16
34
+ if (this.hasBodyTarget) {
35
+ this.bodyTarget.classList.remove('pt-20')
36
+ this.bodyTarget.classList.add('pt-16')
37
+ }
38
+
39
+ // Smoothly hide content elements with width transition
40
+ this.contentTargets.forEach(content => {
41
+ // Add transition classes first
42
+ content.classList.add('transition-all', 'duration-300', 'overflow-hidden')
43
+
44
+ // Then animate to zero width and opacity
45
+ setTimeout(() => {
46
+ content.classList.add('w-0', 'opacity-0', 'max-w-0')
47
+ content.classList.remove('md:block')
48
+ }, 10)
49
+ })
50
+ }
51
+
52
+ expandNavbar() {
53
+ this.isScrolled = false
54
+ // Restore original navbar padding
55
+ this.navbarTarget.classList.remove('py-2')
56
+ this.navbarTarget.classList.add('py-4')
57
+
58
+ // Restore original body padding
59
+ if (this.hasBodyTarget) {
60
+ this.bodyTarget.classList.remove('pt-16')
61
+ this.bodyTarget.classList.add('pt-20')
62
+ }
63
+
64
+ // Smoothly show content elements
65
+ this.contentTargets.forEach(content => {
66
+ // Remove width constraints and restore opacity
67
+ content.classList.remove('w-0', 'opacity-0', 'max-w-0')
68
+ content.classList.add('md:block')
69
+
70
+ // Clean up transition classes after animation
71
+ setTimeout(() => {
72
+ content.classList.remove('transition-all', 'duration-300', 'overflow-hidden')
73
+ }, 300)
74
+ })
75
+ }
76
+ }
@@ -0,0 +1,48 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["notification"]
5
+ static values = {
6
+ type: String,
7
+ message: String,
8
+ duration: { type: Number, default: 4000 }
9
+ }
10
+
11
+ connect() {
12
+ this.show()
13
+ }
14
+
15
+ show() {
16
+ // Add the notification to the page
17
+ this.element.classList.remove('translate-x-full', 'opacity-0')
18
+ this.element.classList.add('translate-x-0', 'opacity-100')
19
+
20
+ // Auto-hide after duration
21
+ if (this.durationValue > 0) {
22
+ this.timeout = setTimeout(() => {
23
+ this.hide()
24
+ }, this.durationValue)
25
+ }
26
+ }
27
+
28
+ hide() {
29
+ if (this.timeout) {
30
+ clearTimeout(this.timeout)
31
+ }
32
+
33
+ // Animate out
34
+ this.element.classList.remove('translate-x-0', 'opacity-100')
35
+ this.element.classList.add('translate-x-full', 'opacity-0')
36
+
37
+ // Remove from DOM after animation
38
+ setTimeout(() => {
39
+ if (this.element.parentNode) {
40
+ this.element.remove()
41
+ }
42
+ }, 300)
43
+ }
44
+
45
+ close() {
46
+ this.hide()
47
+ }
48
+ }
@@ -0,0 +1,124 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { get, post } from "@rails/request.js"
3
+
4
+ export default class extends Controller {
5
+ static values = {
6
+ actionClass: String,
7
+ executionMode: String,
8
+ recordId: String,
9
+ resourceName: String,
10
+ confirm: String
11
+ }
12
+
13
+ execute(event) {
14
+ event.preventDefault()
15
+ event.stopPropagation()
16
+
17
+ // Hide the context menu first
18
+ this.hideContextMenu()
19
+
20
+ // Handle based on execution mode
21
+ if (this.executionModeValue === 'modal') {
22
+ this.showModal()
23
+ } else if (this.executionModeValue === 'instant') {
24
+ this.executeInstant()
25
+ }
26
+ }
27
+
28
+ executeInstant() {
29
+ // Show confirmation modal if confirm message is provided
30
+ if (this.confirmValue) {
31
+ this.showConfirmationModal()
32
+ } else {
33
+ // Execute immediately if no confirmation needed
34
+ this.performAction()
35
+ }
36
+ }
37
+
38
+ showModal() {
39
+ this.loadModalForm()
40
+ }
41
+
42
+ async showConfirmationModal() {
43
+ try {
44
+ // Store action details for when confirmation is received
45
+ this.pendingAction = {
46
+ resourceName: this.resourceNameValue,
47
+ recordId: this.recordIdValue,
48
+ actionClass: this.actionClassValue
49
+ }
50
+
51
+ // Listen for confirmation modal events
52
+ document.addEventListener('confirmation-modal:confirmed', this.handleConfirmation.bind(this), { once: true })
53
+ document.addEventListener('confirmation-modal:cancelled', this.handleCancellation.bind(this), { once: true })
54
+
55
+ // Load confirmation modal
56
+ const response = await get('/admin/confirmation_modal', {
57
+ responseKind: 'turbo-stream',
58
+ query: {
59
+ message: this.confirmValue,
60
+ title: 'Confirm Action',
61
+ confirm_text: 'Execute',
62
+ danger: 'true'
63
+ }
64
+ })
65
+ } catch (error) {
66
+ console.error('Failed to load confirmation modal:', error)
67
+ }
68
+ }
69
+
70
+ handleConfirmation() {
71
+ if (this.pendingAction) {
72
+ const { resourceName, recordId, actionClass } = this.pendingAction
73
+ this.performActionWithDetails(resourceName, recordId, actionClass)
74
+ this.pendingAction = null
75
+ }
76
+ }
77
+
78
+ handleCancellation() {
79
+ this.pendingAction = null
80
+ }
81
+
82
+ async performAction() {
83
+ this.performActionWithDetails(
84
+ this.resourceNameValue,
85
+ this.recordIdValue,
86
+ this.actionClassValue
87
+ )
88
+ }
89
+
90
+ async performActionWithDetails(resourceName, recordId, actionClass) {
91
+ try {
92
+ const formData = new FormData()
93
+
94
+ // Add CSRF token
95
+ const csrfToken = document.querySelector('[name="csrf-token"]').content
96
+ formData.append('authenticity_token', csrfToken)
97
+
98
+ const response = await post(`/admin/${resourceName}/${recordId}/actions/${actionClass}`, {
99
+ responseKind: 'turbo-stream',
100
+ body: formData
101
+ })
102
+ } catch (error) {
103
+ console.error('Failed to execute action:', error)
104
+ }
105
+ }
106
+
107
+ async loadModalForm() {
108
+ try {
109
+ const response = await get(`/admin/${this.resourceNameValue}/${this.recordIdValue}/actions/${this.actionClassValue}/form`, {
110
+ responseKind: 'turbo-stream'
111
+ })
112
+ } catch (error) {
113
+ console.error('Failed to load modal form:', error)
114
+ }
115
+ }
116
+
117
+ hideContextMenu() {
118
+ // Find and hide any existing context menu
119
+ const contextMenu = document.getElementById('context-menu-container')
120
+ if (contextMenu) {
121
+ contextMenu.remove()
122
+ }
123
+ }
124
+ }
@@ -0,0 +1,59 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { post } from "@rails/request.js"
3
+
4
+ export default class extends Controller {
5
+ static targets = ["form"]
6
+ static values = {
7
+ submitUrl: String,
8
+ recordId: String,
9
+ actionClass: String
10
+ }
11
+
12
+ close() {
13
+ this.element.remove()
14
+ }
15
+
16
+ submit() {
17
+ const formData = this.collectFormData()
18
+
19
+ post(this.submitUrlValue, {
20
+ body: formData,
21
+ responseKind: 'turbo-stream'
22
+ }).then(response => {
23
+ if (response.ok) {
24
+ this.close()
25
+ }
26
+ }).catch(error => {
27
+ console.error('Row action submission failed:', error)
28
+ })
29
+ }
30
+
31
+ collectFormData() {
32
+ const formData = new FormData()
33
+
34
+ // Add CSRF token
35
+ const csrfToken = document.querySelector('[name="csrf-token"]').content
36
+ formData.append('authenticity_token', csrfToken)
37
+
38
+ // Add action class and execution mode
39
+ formData.append('action_class', this.actionClassValue)
40
+ formData.append('execution_mode', 'modal')
41
+
42
+ // Collect form field data from the rendered form components
43
+ const formElement = this.formTarget
44
+ const inputs = formElement.querySelectorAll('input, select, textarea')
45
+
46
+ inputs.forEach(input => {
47
+ const name = input.name
48
+ if (name && name.startsWith('row_action[')) {
49
+ if (input.type === 'checkbox') {
50
+ formData.append(name, input.checked ? '1' : '0')
51
+ } else {
52
+ formData.append(name, input.value || '')
53
+ }
54
+ }
55
+ })
56
+
57
+ return formData
58
+ }
59
+ }