spree_admin 5.2.0.rc1 → 5.2.0.rc3

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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/spree/admin/components/_alerts.scss +1 -1
  3. data/app/assets/stylesheets/spree/admin/components/_buttons.scss +5 -4
  4. data/app/assets/stylesheets/spree/admin/components/_dialogs.scss +0 -1
  5. data/app/assets/stylesheets/spree/admin/components/_dropdowns.scss +10 -8
  6. data/app/assets/stylesheets/spree/admin/components/_filters.scss +2 -0
  7. data/app/assets/stylesheets/spree/admin/components/_main.scss +7 -7
  8. data/app/assets/stylesheets/spree/admin/components/_tables.scss +2 -2
  9. data/app/assets/stylesheets/spree/admin/components/_variants_form.scss +1 -2
  10. data/app/assets/stylesheets/spree/admin/global/_variables.scss +14 -12
  11. data/app/assets/stylesheets/spree/admin/shared/_base.scss +2 -2
  12. data/app/assets/stylesheets/spree/admin/shared/_forms.scss +5 -6
  13. data/app/controllers/spree/admin/admin_users_controller.rb +0 -2
  14. data/app/controllers/spree/admin/base_controller.rb +1 -0
  15. data/app/controllers/spree/admin/checkouts_controller.rb +1 -4
  16. data/app/controllers/spree/admin/coupon_codes_controller.rb +0 -14
  17. data/app/controllers/spree/admin/customer_returns_controller.rb +0 -13
  18. data/app/controllers/spree/admin/digital_assets_controller.rb +2 -2
  19. data/app/controllers/spree/admin/exports_controller.rb +2 -9
  20. data/app/controllers/spree/admin/gift_cards_controller.rb +7 -14
  21. data/app/controllers/spree/admin/invitations_controller.rb +0 -2
  22. data/app/controllers/spree/admin/oauth_applications_controller.rb +0 -10
  23. data/app/controllers/spree/admin/option_types_controller.rb +0 -10
  24. data/app/controllers/spree/admin/orders_controller.rb +1 -1
  25. data/app/controllers/spree/admin/pages_controller.rb +1 -1
  26. data/app/controllers/spree/admin/payment_methods_controller.rb +0 -10
  27. data/app/controllers/spree/admin/policies_controller.rb +4 -0
  28. data/app/controllers/spree/admin/posts_controller.rb +2 -10
  29. data/app/controllers/spree/admin/properties_controller.rb +0 -12
  30. data/app/controllers/spree/admin/resource_controller.rb +27 -18
  31. data/app/controllers/spree/admin/return_authorizations_controller.rb +0 -10
  32. data/app/controllers/spree/admin/shipping_methods_controller.rb +4 -0
  33. data/app/controllers/spree/admin/stock_items_controller.rb +8 -11
  34. data/app/controllers/spree/admin/stock_locations_controller.rb +1 -1
  35. data/app/controllers/spree/admin/stock_transfers_controller.rb +0 -10
  36. data/app/controllers/spree/admin/store_credits_controller.rb +35 -35
  37. data/app/controllers/spree/admin/taxonomies_controller.rb +0 -10
  38. data/app/controllers/spree/admin/themes_controller.rb +6 -2
  39. data/app/controllers/spree/admin/users_controller.rb +7 -17
  40. data/app/controllers/spree/admin/webhooks_subscribers_controller.rb +0 -10
  41. data/app/controllers/spree/admin/zones_controller.rb +0 -7
  42. data/app/helpers/spree/admin/dropdown_helper.rb +24 -10
  43. data/app/helpers/spree/admin/navigation_helper.rb +1 -1
  44. data/app/helpers/spree/admin/orders_filters_helper.rb +2 -0
  45. data/app/javascript/spree/admin/application.js +2 -2
  46. data/app/javascript/spree/admin/controllers/dropdown_controller.js +154 -0
  47. data/app/models/spree/admin/form_builder.rb +13 -12
  48. data/app/models/spree/admin/updater.rb +2 -2
  49. data/app/views/spree/admin/custom_domains/_custom_domains.html.erb +1 -1
  50. data/app/views/spree/admin/digital_assets/_table.html.erb +1 -1
  51. data/app/views/spree/admin/gift_cards/_filters.html.erb +2 -1
  52. data/app/views/spree/admin/integrations/index.html.erb +20 -8
  53. data/app/views/spree/admin/invitations/new.html.erb +2 -1
  54. data/app/views/spree/admin/metafield_definitions/_filters.html.erb +1 -1
  55. data/app/views/spree/admin/newsletter_subscribers/_filters.html.erb +1 -1
  56. data/app/views/spree/admin/newsletter_subscribers/_table_header.html.erb +2 -2
  57. data/app/views/spree/admin/oauth_applications/_table_header.html.erb +1 -1
  58. data/app/views/spree/admin/orders/_customer.html.erb +1 -1
  59. data/app/views/spree/admin/orders/_filters.html.erb +4 -5
  60. data/app/views/spree/admin/orders/_header.html.erb +0 -5
  61. data/app/views/spree/admin/orders/_list.html.erb +3 -3
  62. data/app/views/spree/admin/page_blocks/edit.html.erb +3 -3
  63. data/app/views/spree/admin/page_builder/_add_block.html.erb +1 -1
  64. data/app/views/spree/admin/page_builder/_header.html.erb +1 -1
  65. data/app/views/spree/admin/page_builder/_pages_dropdown.html.erb +2 -2
  66. data/app/views/spree/admin/page_builder/_sidebar_block.html.erb +1 -1
  67. data/app/views/spree/admin/page_builder/_sidebar_colors.html.erb +2 -2
  68. data/app/views/spree/admin/page_builder/_sidebar_fonts.html.erb +3 -3
  69. data/app/views/spree/admin/page_builder/_sidebar_section.html.erb +1 -1
  70. data/app/views/spree/admin/page_links/_list.html.erb +1 -1
  71. data/app/views/spree/admin/page_links/edit.html.erb +1 -1
  72. data/app/views/spree/admin/page_sections/edit.html.erb +3 -3
  73. data/app/views/spree/admin/page_sections/new.html.erb +1 -1
  74. data/app/views/spree/admin/pages/_table_header.html.erb +3 -3
  75. data/app/views/spree/admin/payment_methods/index.html.erb +5 -1
  76. data/app/views/spree/admin/policies/_filters.html.erb +1 -1
  77. data/app/views/spree/admin/posts/_form.html.erb +0 -2
  78. data/app/views/spree/admin/products/_bulk_operations.html.erb +2 -2
  79. data/app/views/spree/admin/products/edit.html.erb +0 -2
  80. data/app/views/spree/admin/products/form/_status.html.erb +0 -3
  81. data/app/views/spree/admin/products/form/_variants.html.erb +1 -1
  82. data/app/views/spree/admin/promotions/_table_header.html.erb +1 -1
  83. data/app/views/spree/admin/promotions/form/_kind.html.erb +4 -4
  84. data/app/views/spree/admin/refund_reasons/_table_header.html.erb +1 -1
  85. data/app/views/spree/admin/reimbursement_types/_form.html.erb +1 -0
  86. data/app/views/spree/admin/reimbursement_types/_table_header.html.erb +1 -1
  87. data/app/views/spree/admin/return_authorization_reasons/_table_header.html.erb +1 -1
  88. data/app/views/spree/admin/roles/index.html.erb +1 -1
  89. data/app/views/spree/admin/shared/_calendar_range_picker.html.erb +2 -2
  90. data/app/views/spree/admin/shared/_content_header.html.erb +4 -1
  91. data/app/views/spree/admin/shared/_index_table.html.erb +5 -4
  92. data/app/views/spree/admin/shared/_index_table_options.html.erb +1 -1
  93. data/app/views/spree/admin/shared/_new_item_dropdown.html.erb +1 -1
  94. data/app/views/spree/admin/shared/_user_dropdown.html.erb +4 -3
  95. data/app/views/spree/admin/shared/sidebar/_store_dropdown.html.erb +1 -1
  96. data/app/views/spree/admin/shipping_categories/_table_header.html.erb +1 -1
  97. data/app/views/spree/admin/shipping_methods/_table_header.html.erb +1 -1
  98. data/app/views/spree/admin/stock_locations/_table_header.html.erb +2 -2
  99. data/app/views/spree/admin/store_credit_categories/index.html.erb +1 -1
  100. data/app/views/spree/admin/store_credits/_list.html.erb +3 -3
  101. data/app/views/spree/admin/stores/form/_checkout.html.erb +2 -2
  102. data/app/views/spree/admin/stores/form/_checkout_links.html.erb +1 -1
  103. data/app/views/spree/admin/stores/form/_policies.html.erb +5 -5
  104. data/app/views/spree/admin/tax_categories/_table_header.html.erb +2 -2
  105. data/app/views/spree/admin/tax_rates/_table_header.html.erb +2 -2
  106. data/app/views/spree/admin/taxonomies/_table_header.html.erb +1 -1
  107. data/app/views/spree/admin/users/_filters.html.erb +9 -3
  108. data/app/views/spree/admin/users/index.html.erb +1 -1
  109. data/config/importmap.rb +0 -1
  110. data/config/locales/en.yml +2 -0
  111. data/lib/generators/spree/admin/devise/devise_generator.rb +33 -0
  112. data/lib/generators/spree/admin/scaffold/templates/views/_filters.html.erb.tt +1 -1
  113. data/lib/generators/spree/admin/scaffold/templates/views/_table_header.html.erb.tt +2 -2
  114. metadata +10 -9
  115. data/vendor/javascript/@stimulus-components--dropdown.js +0 -4
@@ -1,13 +1,16 @@
1
1
  class Spree::Admin::ResourceController < Spree::Admin::BaseController
2
2
  include Spree::Admin::Callbacks
3
3
 
4
- helper_method :new_object_url, :edit_object_url, :object_url, :collection_url, :model_class, :search_collection, :paginated_collection
4
+ helper_method :new_object_url, :edit_object_url, :object_url, :collection_url, :model_class
5
5
  before_action :load_resource
6
6
  before_action :set_currency, :set_current_store, only: [:new, :create]
7
- after_action :set_return_to, only: [:index]
8
7
 
9
8
  rescue_from ActiveRecord::RecordNotFound, with: :resource_not_found
10
9
 
10
+ def index
11
+ @collection = collection
12
+ end
13
+
11
14
  # GET /admin/<resource_name>/new
12
15
  def new
13
16
  invoke_callbacks(:new_action, :before)
@@ -218,7 +221,7 @@ class Spree::Admin::ResourceController < Spree::Admin::BaseController
218
221
 
219
222
  # Returns the collection of resources (as a scope)
220
223
  # @return [ActiveRecord::Relation]
221
- def collection
224
+ def scope
222
225
  return parent.send(controller_name) if parent_data.present?
223
226
 
224
227
  base_scope = model_class.try(:for_store, current_store) || model_class
@@ -228,30 +231,36 @@ class Spree::Admin::ResourceController < Spree::Admin::BaseController
228
231
 
229
232
  if model_class.respond_to?(:accessible_by) &&
230
233
  !current_ability.has_block?(params[:action], model_class)
231
- base_scope.accessible_by(current_ability, action)
234
+ base_scope.accessible_by(current_ability, action).includes(collection_includes)
232
235
  else
233
- base_scope
236
+ base_scope.includes(collection_includes)
237
+ end
238
+ end
239
+
240
+ # keeping this as @search for backwards compatibility
241
+ # @return [Ransack::Search]
242
+ def search_collection
243
+ @search ||= begin
244
+ params[:q] ||= {}
245
+ params[:q][:s] ||= collection_default_sort if collection_default_sort.present?
246
+ scope.ransack(params[:q])
234
247
  end
235
248
  end
236
249
 
237
250
  # Returns the filtered and paginated ransack results
238
251
  # @return [ActiveRecord::Relation]
239
- def paginated_collection
240
- @paginated_collection ||= begin
241
- # Check if collection is already a ransack collection and return it
242
- return collection if collection.respond_to?(:current_page)
252
+ def collection
253
+ @collection ||= search_collection.result(distinct: true).page(params[:page]).per(params[:per_page])
254
+ end
243
255
 
244
- search_collection.result(distinct: true).page(params[:page]).per(params[:per_page])
245
- end
256
+ def collection_includes
257
+ []
246
258
  end
247
259
 
248
- # Returns the ransack search collection
249
- # @return [Ransack::Search]
250
- def search_collection
251
- @search_collection ||= begin
252
- params[:q] ||= {}
253
- collection.ransack(params[:q])
254
- end
260
+ # Override in child controllers to set default sort order
261
+ # @return [String, nil] Ransack sort string (e.g., "name asc", "created_at desc")
262
+ def collection_default_sort
263
+ nil
255
264
  end
256
265
 
257
266
  # Returns the URL to redirect to after destroying a resource
@@ -21,16 +21,6 @@ module Spree
21
21
  def location_after_destroy
22
22
  spree.edit_admin_order_path(@return_authorization.order)
23
23
  end
24
-
25
- def collection
26
- return @collection if @collection.present?
27
-
28
- params[:q] ||= {}
29
-
30
- # @search needs to be defined as this is passed to search_form_for
31
- @search = current_store.return_authorizations.accessible_by(current_ability, :index).ransack(params[:q])
32
- @collection = @search.result.order(created_at: :desc).page(params[:page]).per(params[:per_page])
33
- end
34
24
  end
35
25
  end
36
26
  end
@@ -8,6 +8,10 @@ module Spree
8
8
 
9
9
  private
10
10
 
11
+ def collection_includes
12
+ [:zones, :calculator]
13
+ end
14
+
11
15
  def set_default_values
12
16
  @shipping_method.display_on = 'both'
13
17
  @shipping_method.shipping_categories = [Spree::ShippingCategory.first]
@@ -11,18 +11,15 @@ module Spree
11
11
  true
12
12
  end
13
13
 
14
- def collection
15
- params[:q] ||= {}
16
- params[:q][:s] ||= 'created_at desc'
14
+ def scope
15
+ super.joins(:variant).where(spree_variants: { track_inventory: true }).merge(current_store.variants.eligible).reorder('')
16
+ end
17
17
 
18
- @search = super.accessible_by(current_ability, :update).ransack(params[:q])
19
- @stock_items = @search.result.
20
- joins(:variant).
21
- where(spree_variants: { track_inventory: true }).
22
- merge(current_store.variants.eligible).
23
- includes(:stock_location, [variant: [product: [variants: [:images], master: [:images]], images: []]]).
24
- page(params[:page]).
25
- per(params[:per_page])
18
+ def collection_includes
19
+ {
20
+ stock_location: [],
21
+ variant: [option_values: :option_type, product: [variant_images: [], variants: [:images], master: [:images]], images: []]
22
+ }
26
23
  end
27
24
 
28
25
  def add_breadcrumbs
@@ -15,7 +15,7 @@ module Spree
15
15
 
16
16
  private
17
17
 
18
- def collection
18
+ def scope
19
19
  super.order_default
20
20
  end
21
21
 
@@ -15,16 +15,6 @@ module Spree
15
15
  spree.admin_stock_transfer_path(@object)
16
16
  end
17
17
 
18
- def collection
19
- params[:q] ||= {}
20
- params[:q][:s] ||= 'created_at desc'
21
-
22
- @search = super.accessible_by(current_ability, :index).ransack(params[:q])
23
- @stock_transfers = @search.result.
24
- page(params[:page]).
25
- per(params[:per_page])
26
- end
27
-
28
18
  def permitted_resource_params
29
19
  params.require(:stock_transfer).permit(permitted_stock_transfer_attributes)
30
20
  end
@@ -2,18 +2,10 @@ module Spree
2
2
  module Admin
3
3
  class StoreCreditError < StandardError; end
4
4
 
5
- class StoreCreditsController < Spree::Admin::BaseController
6
- before_action :load_user
7
- before_action :load_store_credit, only: [:new, :edit, :update, :destroy]
5
+ class StoreCreditsController < Spree::Admin::ResourceController
6
+ before_action :set_breadcrumbs
7
+ before_action :set_user
8
8
  before_action :ensure_unused_store_credit, only: [:update]
9
- helper_method :collection_url
10
-
11
- def index
12
- @store_credits = scope.includes(:created_by).order(created_at: :desc)
13
- @store_credits = @store_credits.page(params[:page]).per(params[:per_page])
14
-
15
- @collection = @store_credits
16
- end
17
9
 
18
10
  def show
19
11
  @store_credit = scope.find(params[:id])
@@ -21,8 +13,8 @@ module Spree
21
13
  end
22
14
 
23
15
  def create
24
- @store_credit = @user.store_credits.build(
25
- permitted_store_credit_params.merge(
16
+ @store_credit = parent.store_credits.build(
17
+ permitted_resource_params.merge(
26
18
  created_by: try_spree_current_user,
27
19
  action_originator: try_spree_current_user,
28
20
  store: current_store
@@ -31,7 +23,7 @@ module Spree
31
23
 
32
24
  if @store_credit.save
33
25
  flash[:success] = flash_message_for(@store_credit, :successfully_created)
34
- redirect_to spree.admin_user_path(@user)
26
+ redirect_to spree.admin_user_path(parent)
35
27
  else
36
28
  flash[:error] = Spree.t('store_credit.errors.unable_to_create')
37
29
  render :new, status: :unprocessable_entity
@@ -39,11 +31,11 @@ module Spree
39
31
  end
40
32
 
41
33
  def update
42
- @store_credit.assign_attributes(permitted_store_credit_params)
34
+ @store_credit.assign_attributes(permitted_resource_params)
43
35
 
44
36
  if @store_credit.save
45
37
  flash[:success] = flash_message_for(@store_credit, :successfully_updated)
46
- redirect_to spree.admin_user_store_credit_path(@user, @store_credit)
38
+ redirect_to spree.admin_user_store_credit_path(parent, @store_credit)
47
39
  else
48
40
  flash[:error] = Spree.t('store_credit.errors.unable_to_update')
49
41
  render :edit, status: :unprocessable_entity
@@ -59,37 +51,41 @@ module Spree
59
51
  flash[:error] = Spree.t('store_credit.errors.unable_to_delete')
60
52
  end
61
53
 
62
- redirect_to spree.admin_user_path(@user)
54
+ redirect_to spree.admin_user_path(parent)
63
55
  end
64
56
 
65
57
  protected
66
58
 
67
- def permitted_store_credit_params
68
- params.require(:store_credit).permit(permitted_store_credit_attributes)
59
+ def parent
60
+ @parent ||= Spree.user_class.find_by(id: params[:user_id])
69
61
  end
70
62
 
71
- private
63
+ def parent_data
64
+ {
65
+ model_name: 'spree/user',
66
+ model_class: Spree.user_class,
67
+ find_by: :id
68
+ }
69
+ end
72
70
 
73
- def load_user
74
- @user = Spree.user_class.find_by(id: params[:user_id])
71
+ def permitted_resource_params
72
+ params.require(:store_credit).permit(permitted_store_credit_attributes)
73
+ end
75
74
 
76
- unless @user
77
- flash[:error] = Spree.t(:user_not_found)
78
- redirect_to spree.admin_path
79
- end
75
+ private
80
76
 
81
- @breadcrumb_icon = 'users'
82
- add_breadcrumb Spree.t(:customers), :admin_users_path
83
- add_breadcrumb @user.name, spree.admin_user_path(@user)
77
+ def set_user
78
+ @user = parent
84
79
  end
85
80
 
86
- def load_store_credit
87
- @store_credit = scope.find_by(id: params[:id]) || scope.new
88
- @object = @store_credit
81
+ def object_url
82
+ spree.admin_user_store_credit_path(parent, @store_credit)
89
83
  end
90
84
 
91
- def scope
92
- current_store.store_credits.where(user: @user)
85
+ def set_breadcrumbs
86
+ @breadcrumb_icon = 'users'
87
+ add_breadcrumb Spree.t(:customers), :admin_users_path
88
+ add_breadcrumb parent.name, spree.admin_user_path(parent)
93
89
  end
94
90
 
95
91
  def ensure_unused_store_credit
@@ -99,7 +95,11 @@ module Spree
99
95
  end
100
96
 
101
97
  def collection_url
102
- spree.admin_user_store_credits_path(@user)
98
+ spree.admin_user_store_credits_path(parent)
99
+ end
100
+
101
+ def update_turbo_stream_enabled?
102
+ true
103
103
  end
104
104
  end
105
105
  end
@@ -8,16 +8,6 @@ module Spree
8
8
 
9
9
  private
10
10
 
11
- def collection
12
- return @collection if @collection.present?
13
-
14
- @collection = super
15
-
16
- params[:q] ||= {}
17
- @search = @collection.ransack(params[:q])
18
- @collection = @search.result.all
19
- end
20
-
21
11
  def location_after_save
22
12
  spree.admin_taxonomy_path(@taxonomy)
23
13
  end
@@ -70,8 +70,12 @@ module Spree
70
70
  true
71
71
  end
72
72
 
73
- def collection
74
- super.without_previews.order(default: :desc).includes(screenshot_attachment: :blob)
73
+ def scope
74
+ super.without_previews.order(default: :desc)
75
+ end
76
+
77
+ def collection_includes
78
+ { screenshot_attachment: :blob }
75
79
  end
76
80
 
77
81
  def permitted_resource_params
@@ -50,23 +50,13 @@ module Spree
50
50
 
51
51
  protected
52
52
 
53
- def collection
54
- return @collection if @collection.present?
55
-
56
- params[:q] ||= {}
57
- params[:q][:s] ||= 'created_at desc'
58
- params[:q][:created_at_not_null] ||= 1
59
-
60
- @collection = model_class.accessible_by(current_ability, :index)
61
- @search = @collection.ransack(params[:q])
62
- @collection = @search.result(distinct: true).
63
- includes(
64
- addresses: [:country, :state],
65
- ship_address: [:country, :state],
66
- bill_address: [:country, :state],
67
- avatar_attachment: :blob
68
- ).
69
- page(params[:page]).per(params[:per_page])
53
+ def collection_includes
54
+ {
55
+ addresses: [:country, :state],
56
+ ship_address: [:country, :state],
57
+ bill_address: [:country, :state],
58
+ avatar_attachment: :blob
59
+ }
70
60
  end
71
61
 
72
62
  def find_resource
@@ -19,16 +19,6 @@ module Spree
19
19
  @resource ||= Spree::Admin::Resource.new 'spree/admin/webhooks/subscribers', 'subscribers', nil
20
20
  end
21
21
 
22
- def collection
23
- params[:q] ||= {}
24
- params[:q][:s] ||= 'created_at desc'
25
-
26
- @search = Webhooks::Subscriber.accessible_by(current_ability).ransack(params[:q])
27
- @collection = @search.result.
28
- page(params[:page]).
29
- per(params[:per_page])
30
- end
31
-
32
22
  def process_subscriptions
33
23
  return if params[:webhooks_subscriber].blank?
34
24
 
@@ -14,13 +14,6 @@ module Spree
14
14
  edit_object_url(@object, states_country_id: @selected_country&.id)
15
15
  end
16
16
 
17
- def collection
18
- params[:q] ||= {}
19
- params[:q][:s] ||= 'name asc'
20
- @search = super.ransack(params[:q])
21
- @zones = @search.result.page(params[:page]).per(params[:per_page])
22
- end
23
-
24
17
  def load_data
25
18
  @selected_country = if params[:states_country_id]
26
19
  Spree::Country.find_by(id: params[:states_country_id])
@@ -3,28 +3,42 @@ module Spree
3
3
  module DropdownHelper
4
4
  def dropdown(options = {}, &block)
5
5
  options[:class] = ['dropdown'] + Array(options[:class])
6
- options[:data] = { controller: 'dropdown' }.merge(options[:data] || {})
6
+
7
+ # Extract direction option for backward compatibility and convert to Floating UI placement
8
+ placement = case options.delete(:direction)
9
+ when 'left'
10
+ 'bottom-end'
11
+ when 'top'
12
+ 'top-start'
13
+ when 'top-left'
14
+ 'top-end'
15
+ else
16
+ options[:placement] || 'bottom-start'
17
+ end
18
+
19
+ options[:data] = {
20
+ controller: 'dropdown',
21
+ dropdown_placement_value: placement
22
+ }.merge(options[:data] || {})
7
23
  content_tag(:div, options, &block)
8
24
  end
9
25
 
10
26
  def dropdown_toggle(options = {}, &block)
11
27
  options[:type] = 'button'
12
28
  options[:class] = ['btn'] + Array(options[:class])
13
- options[:data] = { action: 'dropdown#toggle click@window->dropdown#hide' }.merge(options[:data] || {})
29
+ options[:data] = {
30
+ action: 'dropdown#toggle click@window->dropdown#hide',
31
+ dropdown_target: 'toggle'
32
+ }.merge(options[:data] || {})
14
33
  button_tag(options, &block)
15
34
  end
16
35
 
17
36
  def dropdown_menu(options = {}, &block)
18
37
  options[:class] = ['dropdown-container hidden'] + Array(options[:class])
38
+ options[:data] = {
39
+ dropdown_target: 'menu'
40
+ }.merge(options[:data] || {})
19
41
 
20
- if options[:direction] == 'left'
21
- options[:class] << 'dropdown-container-left'
22
- elsif options[:direction] == 'top'
23
- options[:class] << 'dropdown-container-top'
24
- elsif options[:direction] == 'top-left'
25
- options[:class] << 'dropdown-container-top dropdown-container-left'
26
- end
27
- options[:data] = { dropdown_target: 'menu' }.merge(options[:data] || {})
28
42
  content_tag(:div, options, &block)
29
43
  end
30
44
  end
@@ -248,7 +248,7 @@ module Spree
248
248
  end
249
249
 
250
250
  link_to url, class: 'd-flex align-items-center text-decoration-none' do
251
- content_tag(:span, icon('chevron-left', class: 'mr-0'), class: 'btn hover-gray px-2 d-flex align-items-center') +
251
+ content_tag(:span, icon('chevron-left', class: 'mr-0'), class: 'btn hover-gray shadow-none px-2 d-flex align-items-center shadow-none') +
252
252
  content_tag(:span, label, class: 'font-size-base text-black')
253
253
  end
254
254
  end
@@ -2,6 +2,8 @@ module Spree
2
2
  module Admin
3
3
  module OrdersFiltersHelper
4
4
  def params_to_filters(search_params:, vendor: nil, user: nil)
5
+ return if search_params.blank?
6
+
5
7
  if search_params.is_a?(String)
6
8
  search_params = JSON.parse(search_params).deep_symbolize_keys
7
9
  end
@@ -35,7 +35,6 @@ if (typeof Stimulus === 'undefined') {
35
35
  import AutoSubmit from '@stimulus-components/auto-submit'
36
36
  import CheckboxSelectAll from 'stimulus-checkbox-select-all'
37
37
  import Dialog from "@stimulus-components/dialog"
38
- import Dropdown from '@stimulus-components/dropdown'
39
38
  import TextareaAutogrow from 'stimulus-textarea-autogrow'
40
39
  import Notification from 'stimulus-notification'
41
40
  import PasswordVisibility from 'stimulus-password-visibility'
@@ -56,6 +55,7 @@ import Clipboard from 'spree/admin/controllers/clipboard_controller'
56
55
  import CodeMirrorController from 'spree/admin/controllers/codemirror_controller'
57
56
  import ColorPaletteController from 'spree/admin/controllers/color_palette_controller'
58
57
  import ColorPickerController from 'spree/admin/controllers/color_picker_controller'
58
+ import DropdownController from 'spree/admin/controllers/dropdown_controller'
59
59
  import FiltersController from 'spree/admin/controllers/filters_controller'
60
60
  import FontPickerController from 'spree/admin/controllers/font_picker_controller'
61
61
  import HighlightController from 'spree/admin/controllers/highlight_controller'
@@ -114,7 +114,7 @@ application.register('color-picker', ColorPickerController)
114
114
  application.register('dialog', Dialog)
115
115
  application.register('drawer', Dialog)
116
116
  application.register('disable-submit-button', DisableSubmitButtonController)
117
- application.register('dropdown', Dropdown)
117
+ application.register('dropdown', DropdownController)
118
118
  application.register('enable-button', EnableButtonController)
119
119
  application.register('export-dialog', Dialog)
120
120
  application.register('filters', FiltersController)
@@ -0,0 +1,154 @@
1
+ import {
2
+ autoUpdate,
3
+ computePosition,
4
+ flip,
5
+ offset,
6
+ shift,
7
+ size,
8
+ } from "@floating-ui/dom"
9
+ import { Controller } from "@hotwired/stimulus"
10
+
11
+ export default class extends Controller {
12
+ static targets = ["menu", "toggle"]
13
+ static values = {
14
+ placement: { type: String, default: "bottom-start" },
15
+ offset: { type: Number, default: 4 },
16
+ }
17
+
18
+ connect() {
19
+ this._cleanup = null
20
+ this.boundUpdate = this.update.bind(this)
21
+ this._isOpen = false
22
+ this._toggleElement = null
23
+ }
24
+
25
+ disconnect() {
26
+ this.stopAutoUpdate()
27
+ }
28
+
29
+ toggle(event) {
30
+ event.preventDefault()
31
+ event.stopPropagation()
32
+
33
+ // Store the toggle element that triggered this action
34
+ this._toggleElement = event.currentTarget
35
+
36
+ if (this._isOpen) {
37
+ this.close()
38
+ } else {
39
+ this.open()
40
+ }
41
+ }
42
+
43
+ open() {
44
+ if (this._isOpen) return
45
+
46
+ this.menuTarget.classList.remove("hidden")
47
+ this._isOpen = true
48
+
49
+ // Start automatic positioning
50
+ this.startAutoUpdate()
51
+
52
+ // Add event listener to close on outside click
53
+ this._outsideClickHandler = this.handleOutsideClick.bind(this)
54
+ document.addEventListener("click", this._outsideClickHandler)
55
+
56
+ // Add event listener to close on escape key
57
+ this._escapeHandler = this.handleEscape.bind(this)
58
+ document.addEventListener("keydown", this._escapeHandler)
59
+ }
60
+
61
+ close() {
62
+ if (!this._isOpen) return
63
+
64
+ this.menuTarget.classList.add("hidden")
65
+ this._isOpen = false
66
+
67
+ // Stop automatic positioning
68
+ this.stopAutoUpdate()
69
+
70
+ // Remove event listeners
71
+ if (this._outsideClickHandler) {
72
+ document.removeEventListener("click", this._outsideClickHandler)
73
+ this._outsideClickHandler = null
74
+ }
75
+
76
+ if (this._escapeHandler) {
77
+ document.removeEventListener("keydown", this._escapeHandler)
78
+ this._escapeHandler = null
79
+ }
80
+ }
81
+
82
+ hide(event) {
83
+ // This method is called from the action: click@window->dropdown#hide
84
+ // Only close if clicking outside the dropdown element
85
+ if (!this.element.contains(event.target)) {
86
+ this.close()
87
+ }
88
+ }
89
+
90
+ handleOutsideClick(event) {
91
+ // Close dropdown if clicking outside
92
+ if (!this.element.contains(event.target)) {
93
+ this.close()
94
+ }
95
+ }
96
+
97
+ handleEscape(event) {
98
+ if (event.key === "Escape") {
99
+ this.close()
100
+ }
101
+ }
102
+
103
+ startAutoUpdate() {
104
+ if (!this._cleanup) {
105
+ const referenceElement = this.hasToggleTarget ? this.toggleTarget : (this._toggleElement || this.element)
106
+ this._cleanup = autoUpdate(
107
+ referenceElement,
108
+ this.menuTarget,
109
+ this.boundUpdate,
110
+ )
111
+ }
112
+ }
113
+
114
+ stopAutoUpdate() {
115
+ if (this._cleanup) {
116
+ this._cleanup()
117
+ this._cleanup = null
118
+ }
119
+ }
120
+
121
+ update() {
122
+ // Use the toggle target if available, or the stored toggle element, or fall back to the controller element
123
+ const referenceElement = this.hasToggleTarget ? this.toggleTarget : (this._toggleElement || this.element)
124
+
125
+ computePosition(referenceElement, this.menuTarget, {
126
+ placement: this.placementValue,
127
+ middleware: [
128
+ offset(this.offsetValue),
129
+ flip({
130
+ fallbackAxisSideDirection: "start",
131
+ padding: 8,
132
+ }),
133
+ shift({ padding: 8 }),
134
+ size({
135
+ apply({ availableWidth, availableHeight, elements }) {
136
+ // Ensure dropdown doesn't exceed viewport
137
+ Object.assign(elements.floating.style, {
138
+ maxWidth: `${availableWidth}px`,
139
+ maxHeight: `${availableHeight}px`,
140
+ overflow: "auto",
141
+ })
142
+ },
143
+ padding: 8,
144
+ }),
145
+ ],
146
+ }).then(({ x, y }) => {
147
+ Object.assign(this.menuTarget.style, {
148
+ left: `${x}px`,
149
+ top: `${y}px`,
150
+ position: "absolute",
151
+ })
152
+ })
153
+ }
154
+ }