avo 2.4.1 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of avo might be problematic. Click here for more details.

Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/components/avo/edit/field_wrapper_component.html.erb +2 -2
  4. data/app/components/avo/edit/field_wrapper_component.rb +6 -1
  5. data/app/components/avo/fields/belongs_to_field/autocomplete_component.html.erb +3 -0
  6. data/app/components/avo/fields/belongs_to_field/autocomplete_component.rb +4 -0
  7. data/app/components/avo/fields/belongs_to_field/edit_component.html.erb +26 -27
  8. data/app/components/avo/filters_component.html.erb +3 -3
  9. data/app/components/avo/filters_component.rb +2 -1
  10. data/app/components/avo/paginator_component.html.erb +3 -3
  11. data/app/components/avo/paginator_component.rb +3 -3
  12. data/app/components/avo/views/resource_index_component.html.erb +3 -3
  13. data/app/components/avo/views/resource_index_component.rb +10 -3
  14. data/app/controllers/avo/associations_controller.rb +6 -1
  15. data/app/controllers/avo/base_controller.rb +24 -1
  16. data/app/controllers/avo/search_controller.rb +22 -1
  17. data/app/helpers/avo/url_helpers.rb +3 -3
  18. data/app/javascript/js/controllers/filter_controller.js +9 -0
  19. data/app/javascript/js/controllers/search_controller.js +9 -2
  20. data/app/views/avo/base/_boolean_filter.html.erb +23 -15
  21. data/app/views/avo/base/_multiple_select_filter.html.erb +5 -1
  22. data/app/views/avo/base/_select_filter.html.erb +5 -1
  23. data/app/views/avo/base/_text_filter.html.erb +5 -1
  24. data/app/views/avo/base/index.html.erb +1 -0
  25. data/app/views/avo/partials/_sidebar_extra.html.erb +2 -0
  26. data/db/factories.rb +5 -3
  27. data/lib/avo/base_resource.rb +1 -0
  28. data/lib/avo/fields/belongs_to_field.rb +11 -1
  29. data/lib/avo/fields/has_base_field.rb +2 -0
  30. data/lib/avo/filters/base_filter.rb +18 -0
  31. data/lib/avo/hosts/association_scope_host.rb +8 -0
  32. data/lib/avo/services/authorization_service.rb +8 -10
  33. data/lib/avo/version.rb +1 -1
  34. data/lib/generators/avo/eject_generator.rb +2 -3
  35. data/lib/generators/avo/templates/locales/avo.en.yml +2 -1
  36. data/lib/generators/avo/templates/locales/avo.nb-NO.yml +2 -1
  37. data/lib/generators/avo/templates/locales/avo.pt-BR.yml +2 -1
  38. data/lib/generators/avo/templates/locales/avo.ro.yml +2 -1
  39. data/public/avo-assets/avo.js +22 -22
  40. data/public/avo-assets/avo.js.map +2 -2
  41. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94051972be378ea5a77b0577e63ed4cfb619f136dbd45945820dff96603dc1cd
4
- data.tar.gz: 25a016f0bdb9028967cedc8c93ffd0d587b310e93d7d476899abdf0e35519f27
3
+ metadata.gz: a1c24962e7af4aae8d474872c00149aac873f23dbbfad3ddf4c8de53cc4c1f75
4
+ data.tar.gz: e356866b46a51297a576a79d77cdc45f29c3482409390a95257a36797f4e759d
5
5
  SHA512:
6
- metadata.gz: 22f72bb7c364b6260b61f2eb87e082fb9ace366c2a2a67999ae17ec72c0a2f9dcde657662d788cedfc965bbbc29fa8bcc261f416f7f4ee5a10eb65710cdd69f9
7
- data.tar.gz: cc1e88105640657fa3e60a0234d56c5e51e851e7e6fc7abbace9c95f1534ee9bfe256bd09e2de929f95d47603ecbb1927589e8e6418e764ccb459d5bf3e94063
6
+ metadata.gz: 5b946132563c3180eefa6eee7417fadb6d85658ffef574560c748ca75da3353c277b978884de218701fedf1e2db732fe129418ae26effad8e4d272223b5aeb25
7
+ data.tar.gz: 25e18c2e1de5509adfe6a051e64195d5522bfdccceecf9208458d0c0603f3452f818759056c2f24fdaedaa89788bcf00a7e17574bde09265b1c2773fece5e152
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (2.4.1)
4
+ avo (2.5.0)
5
5
  active_link_to
6
6
  addressable
7
7
  breadcrumbs_on_rails
@@ -4,8 +4,8 @@
4
4
  <% if @model.present? and @model.errors.include? @field.id %>
5
5
  <div class="text-red-600 mt-2 text-sm"><%= @model.errors.full_messages_for(@field.id).to_sentence %></div>
6
6
  <% end %>
7
- <% if @field.help %>
8
- <div class="text-gray-600 mt-2 text-sm"><%== @field.help %></div>
7
+ <% if help.present? %>
8
+ <div class="text-gray-600 mt-2 text-sm"><%== help %></div>
9
9
  <% end %>
10
10
  </div>
11
11
  <% end %>
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Avo::Edit::FieldWrapperComponent < ViewComponent::Base
4
- def initialize(field: nil, dash_if_blank: true, full_width: false, displayed_in_modal: false, form: nil, resource: {}, label: nil, **args)
4
+ def initialize(field: nil, dash_if_blank: true, full_width: false, displayed_in_modal: false, form: nil, resource: {}, label: nil, help: nil, **args)
5
5
  @field = field
6
6
  @dash_if_blank = dash_if_blank
7
7
  @classes = args[:class].present? ? args[:class] : ""
@@ -12,5 +12,10 @@ class Avo::Edit::FieldWrapperComponent < ViewComponent::Base
12
12
  @model = resource.present? ? resource.model : nil
13
13
  @full_width = full_width
14
14
  @label = label
15
+ @help = help
16
+ end
17
+
18
+ def help
19
+ @help || @field.help
15
20
  end
16
21
  end
@@ -4,6 +4,9 @@
4
4
  data-search-resource="<%= @model_key %>"
5
5
  data-translation-keys='{"no_item_found": "<%= I18n.translate 'avo.no_item_found' %>"}'
6
6
  data-via-association="belongs_to"
7
+ data-via-association-id="<%= @field.id %>"
8
+ data-via-reflection-id="<%= @field.model.id %>"
9
+ data-via-reflection-class="<%= @field.model.class.to_s %>"
7
10
  ></div>
8
11
  <div class="relative w-full" autocomplete="off">
9
12
  <%= @form.text_field @foreign_key,
@@ -34,6 +34,10 @@ class Avo::Fields::BelongsToField::AutocompleteComponent < ViewComponent::Base
34
34
  result
35
35
  end
36
36
 
37
+ def reflection_class
38
+ has_polymorphic_association? ? polymorphic_class : @resource.model_class._reflections[@field.id.to_s].klass
39
+ end
40
+
37
41
  private
38
42
 
39
43
  def should_prefill?
@@ -1,20 +1,19 @@
1
- <%
2
- if is_polymorphic?
3
-
4
- # Set the model keys so we can pass them over
5
- model_keys = @field.types.map do |type|
6
- resource = Avo::App.get_resource_by_model_name(type.to_s)
7
- [type.to_s, resource.model_key]
8
- end.to_h
9
- %>
10
- <div class="divide-y"
1
+ <% if is_polymorphic? %>
2
+ <%
3
+ # Set the model keys so we can pass them over
4
+ model_keys = @field.types.map do |type|
5
+ resource = Avo::App.get_resource_by_model_name(type.to_s)
6
+ [type.to_s, resource.model_key]
7
+ end.to_h
8
+ %>
9
+ <div class="divide-y"
11
10
  data-controller="belongs-to-field"
12
11
  data-searchable="<%= @field.searchable %>"
13
12
  data-association="<%= @field.id %>"
14
13
  data-association-class="<%= @field&.target_resource&.model_class || nil %>"
15
14
  >
16
- <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
17
- <%= @form.select @field.type_input_foreign_key, @field.types.map { |type| [type.to_s.underscore.humanize, type.to_s] },
15
+ <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal, help: @field.polymorphic_help || '' do %>
16
+ <%= @form.select @field.type_input_foreign_key, @field.types.map { |type| [type.to_s.underscore.humanize, type.to_s] },
18
17
  {
19
18
  value: @field.value,
20
19
  include_blank: @field.placeholder,
@@ -26,21 +25,21 @@
26
25
  'data-action': 'change->belongs-to-field#changeType'
27
26
  }
28
27
  %>
29
- <%
28
+ <%
30
29
  # If the select field is disabled, no value will be sent. It's how HTML works.
31
30
  # Thus the extra hidden field to actually send the related id to the server.
32
31
  if disabled %>
33
- <%= @form.hidden_field @field.type_input_foreign_key %>
32
+ <%= @form.hidden_field @field.type_input_foreign_key %>
33
+ <% end %>
34
34
  <% end %>
35
- <% end %>
36
- <% @field.types.each do |type| %>
37
- <div class="hidden"
35
+ <% @field.types.each do |type| %>
36
+ <div class="hidden"
38
37
  data-belongs-to-field-target="type"
39
38
  data-type="<%= type %>"
40
39
  >
41
- <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal, label: type.to_s.underscore.humanize do %>
42
- <% if @field.searchable %>
43
- <%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
40
+ <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal, label: type.to_s.underscore.humanize do %>
41
+ <% if @field.searchable %>
42
+ <%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
44
43
  field: @field,
45
44
  type: type,
46
45
  model_key: model_keys[type.to_s],
@@ -49,8 +48,8 @@
49
48
  disabled: disabled,
50
49
  polymorphic_record: polymorphic_record
51
50
  %>
52
- <% else %>
53
- <%= @form.select @field.id_input_foreign_key,
51
+ <% else %>
52
+ <%= @form.select @field.id_input_foreign_key,
54
53
  options_for_select(@field.values_for_type(type), @resource.present? && @resource.model.present? ? @resource.model[@field.id_input_foreign_key] : nil),
55
54
  {
56
55
  value: @resource.model[@field.id_input_foreign_key].to_s,
@@ -61,17 +60,17 @@
61
60
  disabled: disabled
62
61
  }
63
62
  %>
64
- <%
63
+ <%
65
64
  # If the select field is disabled, no value will be sent. It's how HTML works.
66
65
  # Thus the extra hidden field to actually send the related id to the server.
67
66
  if disabled %>
68
67
  <%= @form.hidden_field @field.id_input_foreign_key %>
69
68
  <% end %>
69
+ <% end %>
70
70
  <% end %>
71
- <% end %>
72
- </div>
73
- <% end %>
74
- </div>
71
+ </div>
72
+ <% end %>
73
+ </div>
75
74
  <% else %>
76
75
  <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
77
76
  <% if @field.searchable %>
@@ -9,12 +9,12 @@
9
9
  'data-action': 'click->toggle-panel#togglePanel',
10
10
  'data-tippy': 'tooltip' do %>
11
11
  <%= t 'avo.filters' %>
12
- <% if params[:filters].present? %>
13
- <span class="ml-1">(<%=JSON.parse(Base64.decode64(params[:filters])).count%> applied)</span>
12
+ <% if @applied_filters.present? %>
13
+ <span class="ml-1">(<%= @applied_filters.count %> applied)</span>
14
14
  <% end %>
15
15
  <% end %>
16
16
  <div
17
- class="absolute block inset-auto sm:right-0 top-full bg-white min-w-[300px] mt-2 z-20 shadow-modal rounded hidden divide-y divide-gray-300"
17
+ class="absolute block inset-auto sm:right-0 top-full bg-white min-w-[300px] mt-2 z-20 shadow-modal rounded divide-y divide-gray-300 <%= 'hidden' unless params[:keep_filters_panel_open] %>"
18
18
  data-toggle-panel-target="panel"
19
19
  >
20
20
  <% @filters.each do |filter| %>
@@ -3,9 +3,10 @@
3
3
  class Avo::FiltersComponent < ViewComponent::Base
4
4
  include Avo::ApplicationHelper
5
5
 
6
- def initialize(filters: [], resource: nil)
6
+ def initialize(filters: [], resource: nil, applied_filters: [])
7
7
  @filters = filters
8
8
  @resource = resource
9
+ @applied_filters = applied_filters
9
10
  end
10
11
 
11
12
  def render?
@@ -1,7 +1,7 @@
1
1
  <%
2
2
  per_page_options = [*Avo.configuration.per_page_steps, Avo.configuration.per_page.to_i, index_params[:per_page].to_i]
3
3
 
4
- if parent_resource.present?
4
+ if parent_model.present?
5
5
  per_page_options.prepend Avo.configuration.via_per_page
6
6
  end
7
7
 
@@ -24,8 +24,8 @@
24
24
  %> <%= t('avo.per_page').downcase %>
25
25
  </div>
26
26
  <% per_page_options.each do |option| %>
27
- <% if parent_resource.present? %>
28
- <%= link_to "Change to #{option} items per page", helpers.related_resources_path(parent_resource.model_class, resource.model_class, per_page: option, keep_query_params: true), class: 'hidden', 'data-per-page-option': option, 'data-turbo-frame': turbo_frame %>
27
+ <% if parent_model.present? %>
28
+ <%= link_to "Change to #{option} items per page", helpers.related_resources_path(parent_model, parent_model, per_page: option, keep_query_params: true), class: 'hidden', 'data-per-page-option': option, 'data-turbo-frame': turbo_frame %>
29
29
  <% else %>
30
30
  <%= link_to "Change to #{option} items per page", helpers.resources_path(resource: resource, per_page: option, keep_query_params: true), class: 'hidden', 'data-per-page-option': option, 'data-turbo-frame': turbo_frame %>
31
31
  <% end %>
@@ -5,13 +5,13 @@ class Avo::PaginatorComponent < ViewComponent::Base
5
5
  attr_reader :turbo_frame
6
6
  attr_reader :index_params
7
7
  attr_reader :resource
8
- attr_reader :parent_resource
8
+ attr_reader :parent_model
9
9
 
10
- def initialize(resource: nil, parent_resource: nil, pagy:, turbo_frame:, index_params:)
10
+ def initialize(resource: nil, parent_model: nil, pagy: nil, turbo_frame: nil, index_params: nil)
11
11
  @pagy = pagy
12
12
  @turbo_frame = turbo_frame
13
13
  @index_params = index_params
14
14
  @resource = resource
15
- @parent_resource = parent_resource
15
+ @parent_model = parent_model
16
16
  end
17
17
  end
@@ -30,7 +30,7 @@
30
30
  <% end %>
31
31
  <% if @filters.present? || available_view_types.count > 1 %>
32
32
  <div class="justify-self-end flex justify-start xs:justify-end items-center px-4 space-x-3">
33
- <%= render Avo::FiltersComponent.new filters: @filters, resource: @resource %>
33
+ <%= render Avo::FiltersComponent.new filters: @filters, resource: @resource, applied_filters: @applied_filters %>
34
34
  <%= render partial: 'avo/partials/view_toggle_button', locals: { available_view_types: available_view_types, view_type: view_type, turbo_frame: @turbo_frame } if available_view_types.count > 1 %>
35
35
  </div>
36
36
  <% end %>
@@ -51,7 +51,7 @@
51
51
  <% if view_type.to_sym == :table %>
52
52
  <% if @models.present? %>
53
53
  <div class="mt-8">
54
- <%= render Avo::PaginatorComponent.new pagy: @pagy, turbo_frame: @turbo_frame || 'none', index_params: @index_params, resource: @resource, parent_resource: @parent_resource %>
54
+ <%= render Avo::PaginatorComponent.new pagy: @pagy, turbo_frame: @turbo_frame || 'none', index_params: @index_params, resource: @resource, parent_model: @parent_model %>
55
55
  </div>
56
56
  <% end %>
57
57
  <% end %>
@@ -59,7 +59,7 @@
59
59
  <% if view_type.to_sym == :grid %>
60
60
  <%= render Avo::Index::ResourceGridComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_model: @parent_model) %>
61
61
  <div class="mt-14">
62
- <%= render Avo::PaginatorComponent.new pagy: @pagy, turbo_frame: @turbo_frame || 'none', index_params: @index_params, resource: @resource, parent_resource: @parent_resource %>
62
+ <%= render Avo::PaginatorComponent.new pagy: @pagy, turbo_frame: @turbo_frame || 'none', index_params: @index_params, resource: @resource, parent_model: @parent_model %>
63
63
  </div>
64
64
  <% end %>
65
65
  <% end %>
@@ -14,7 +14,8 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
14
14
  actions: [],
15
15
  reflection: nil,
16
16
  turbo_frame: "",
17
- parent_model: nil
17
+ parent_model: nil,
18
+ applied_filters: []
18
19
  )
19
20
  @resource = resource
20
21
  @resources = resources
@@ -26,6 +27,7 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
26
27
  @reflection = reflection
27
28
  @turbo_frame = turbo_frame
28
29
  @parent_model = parent_model
30
+ @applied_filters = applied_filters
29
31
  end
30
32
 
31
33
  def title
@@ -90,7 +92,7 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
90
92
  if @reflection.present?
91
93
  args = {
92
94
  via_relation_class: reflection_model_class,
93
- via_resource_id: @parent_model.id,
95
+ via_resource_id: @parent_model.id
94
96
  }
95
97
 
96
98
  if @reflection.is_a? ActiveRecord::Reflection::ThroughReflection
@@ -126,7 +128,12 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
126
128
  end
127
129
 
128
130
  def description
129
- return if @reflection.present?
131
+ # If this is a has many association, the user can pass a description to be shown just for this association.
132
+ if @reflection.present?
133
+ return field.description if field.present? && field.description
134
+
135
+ return
136
+ end
130
137
 
131
138
  @resource.resource_description
132
139
  end
@@ -21,7 +21,7 @@ module Avo
21
21
  @association_field = @parent_resource.get_field params[:related_name]
22
22
 
23
23
  if @association_field.present? && @association_field.scope.present?
24
- @query = @query.instance_exec(&@association_field.scope)
24
+ @query = Avo::Hosts::AssociationScopeHost.new(block: @association_field.scope, query: @query, parent: @parent_model).handle
25
25
  end
26
26
 
27
27
  super
@@ -45,6 +45,11 @@ module Avo
45
45
  if @field.present? && !@field.searchable
46
46
  query = @authorization.apply_policy @attachment_class
47
47
 
48
+ # Add the association scope to the query scope
49
+ if @field.scope.present?
50
+ query = Avo::Hosts::AssociationScopeHost.new(block: @field.scope, query: query, parent: @model).handle
51
+ end
52
+
48
53
  @options = query.all.map do |model|
49
54
  [model.send(@attachment_resource.class.title), model.id]
50
55
  end
@@ -310,11 +310,34 @@ module Avo
310
310
  end
311
311
 
312
312
  def set_applied_filters
313
- @applied_filters = JSON.parse(Base64.decode64(params[:filters]))
313
+ @applied_filters = Avo::Filters::BaseFilter.decode_filters(params[Avo::Filters::BaseFilter::PARAM_KEY])
314
+
315
+ # Some filters react to others and will have to be merged into this
316
+ @applied_filters = @applied_filters.merge reactive_filters
314
317
  rescue
315
318
  @applied_filters = {}
316
319
  end
317
320
 
321
+ def reactive_filters
322
+ filter_reactions = {}
323
+
324
+ # Go through all filters
325
+ @resource.get_filters
326
+ .select do |filter_class|
327
+ filter_class.instance_methods(false).include? :react
328
+ end
329
+ .each do |filter_class|
330
+ # Run the react method if it's present
331
+ reaction = filter_class.new.react
332
+
333
+ next if reaction.nil?
334
+
335
+ filter_reactions[filter_class.to_s] = filter_class.new.react
336
+ end
337
+
338
+ filter_reactions
339
+ end
340
+
318
341
  # Get the default state of the filters and override with the user applied filters
319
342
  def filters_to_be_applied
320
343
  filter_defaults = {}
@@ -44,7 +44,23 @@ module Avo
44
44
  end
45
45
 
46
46
  def search_resource(resource)
47
- results = apply_search_metadata(resource.search_query.call(params: params).limit(8), resource)
47
+ query = resource.search_query.call(params: params).limit(8)
48
+
49
+ # Figure oute if this is a belongs_to search
50
+ if params[:via_reflection_class].present? && params[:via_reflection_id].present?
51
+ # Fetch the field
52
+ field = belongs_to_field
53
+
54
+ if field.scope.present?
55
+ # Fetch the parent
56
+ parent = params[:via_reflection_class].safe_constantize.find params[:via_reflection_id]
57
+
58
+ # Add to the query
59
+ query = Avo::Hosts::AssociationScopeHost.new(block: belongs_to_field.scope, query: query, parent: parent).handle
60
+ end
61
+ end
62
+
63
+ results = apply_search_metadata(query, resource)
48
64
 
49
65
  result_object = {
50
66
  header: resource.name.pluralize,
@@ -75,5 +91,10 @@ module Avo
75
91
  result
76
92
  end
77
93
  end
94
+
95
+ def belongs_to_field
96
+ fields = ::Avo::App.get_resource_by_model_name(params[:via_reflection_class]).get_field_definitions
97
+ fields.find { |f| f.id.to_s == params[:via_association_id] }
98
+ end
78
99
  end
79
100
  end
@@ -59,11 +59,11 @@ module Avo
59
59
 
60
60
  def related_resources_path(
61
61
  parent_model,
62
- model,
62
+ record,
63
63
  keep_query_params: false,
64
64
  **args
65
65
  )
66
- return if model.nil?
66
+ return if record.nil?
67
67
 
68
68
  existing_params = {}
69
69
 
@@ -75,7 +75,7 @@ module Avo
75
75
  rescue
76
76
  end
77
77
 
78
- avo.resources_associations_index_path(@parent_resource.model_class.model_name.route_key, @parent_resource.model.id, **existing_params, **args )
78
+ avo.resources_associations_index_path(parent_model.model_name.route_key, record.id, **existing_params, **args)
79
79
  end
80
80
 
81
81
  def order_up_resource_path(model:, resource:, **args)
@@ -4,6 +4,10 @@ import URI from 'urijs'
4
4
  export default class extends Controller {
5
5
  static targets = ['urlRedirect']
6
6
 
7
+ static values = {
8
+ keepFiltersPanelOpen: Boolean,
9
+ }
10
+
7
11
  // eslint-disable-next-line class-methods-use-this
8
12
  uriParams() {
9
13
  return URI(window.location.toString()).query(true)
@@ -78,6 +82,11 @@ export default class extends Controller {
78
82
  ...url.query(true),
79
83
  }
80
84
 
85
+ if (this.keepFiltersPanelOpenValue) {
86
+ // eslint-disable-next-line camelcase
87
+ query.keep_filters_panel_open = this.keepFiltersPanelOpenValue
88
+ }
89
+
81
90
  if (encodedFilters) {
82
91
  query.filters = encodedFilters
83
92
  } else {
@@ -68,8 +68,15 @@ export default class extends Controller {
68
68
  }
69
69
 
70
70
  if (this.isBelongsToSearch) {
71
- // eslint-disable-next-line camelcase
72
- params = { ...params, via_association: this.dataset.viaAssociation }
71
+ params = {
72
+ ...params,
73
+ // eslint-disable-next-line camelcase
74
+ via_association_id: this.dataset.viaAssociationId,
75
+ // eslint-disable-next-line camelcase
76
+ via_reflection_class: this.dataset.viaReflectionClass,
77
+ // eslint-disable-next-line camelcase
78
+ via_reflection_id: this.dataset.viaReflectionId,
79
+ }
73
80
  }
74
81
 
75
82
  return url.segment(segments).search(params).toString()
@@ -1,20 +1,28 @@
1
- <div data-controller="boolean-filter" data-filter-name="<%= filter.name %>">
1
+ <div
2
+ data-controller="boolean-filter"
3
+ data-filter-name="<%= filter.name %>"
4
+ data-boolean-filter-keep-filters-panel-open-value="<%= @resource.keep_filters_panel_open %>"
5
+ >
2
6
  <%= filter_wrapper name: filter.name do %>
3
7
  <div class="flex items-center">
4
- <div class="space-y-2">
5
- <% filter.options.each do |value, label| %>
6
- <label class="flex items-center text-gray-700 text-sm">
7
- <%= check_box_tag filter.id, value, filter.selected_value(value.to_s, @applied_filters),
8
- class: 'mr-2 text-lg h-4 w-4',
9
- id: "avo_filters_#{filter.id.parameterize.underscore}",
10
- 'data-filter-class': filter.class,
11
- 'data-boolean-filter-target': 'option',
12
- 'data-action': 'input->boolean-filter#changeFilter'
13
- %>
14
- <%= label %>
15
- </label>
16
- <% end %>
17
- </div>
8
+ <% if filter.options.empty? %>
9
+ <div class="text-sm text-gray-600"><%= filter.class.empty_message %></div>
10
+ <% else %>
11
+ <div class="space-y-2">
12
+ <% filter.options.each do |value, label| %>
13
+ <label class="flex items-center text-gray-700 text-sm">
14
+ <%= check_box_tag filter.id, value, filter.selected_value(value.to_s, @applied_filters),
15
+ class: 'mr-2 text-lg h-4 w-4',
16
+ id: "avo_filters_#{filter.id.parameterize.underscore}",
17
+ 'data-filter-class': filter.class,
18
+ 'data-boolean-filter-target': 'option',
19
+ 'data-action': 'input->boolean-filter#changeFilter'
20
+ %>
21
+ <%= label %>
22
+ </label>
23
+ <% end %>
24
+ </div>
25
+ <% end %>
18
26
  <%= link_to 'url_redirect', request.url, data: { 'boolean-filter-target': 'urlRedirect', 'turbo-frame': params[:turbo_frame] }, style: 'hidden', class: 'hidden' %>
19
27
  </div>
20
28
  <% end %>
@@ -1,4 +1,8 @@
1
- <div data-controller="multiple-select-filter" data-filter-name="<%= filter.name %>">
1
+ <div
2
+ data-controller="multiple-select-filter"
3
+ data-filter-name="<%= filter.name %>"
4
+ data-multiple-select-filter-keep-filters-panel-open-value="<%= @resource.keep_filters_panel_open %>"
5
+ >
2
6
  <%= filter_wrapper name: filter.name do %>
3
7
  <%= select_tag filter.id, options_for_select(filter.options.invert, filter.selected_value(@applied_filters)),
4
8
  class: input_classes('w-full mb-0'),
@@ -1,4 +1,8 @@
1
- <div data-controller="select-filter" data-filter-name="<%= filter.name %>">
1
+ <div
2
+ data-controller="select-filter"
3
+ data-filter-name="<%= filter.name %>"
4
+ data-select-filter-keep-filters-panel-open-value="<%= @resource.keep_filters_panel_open %>"
5
+ >
2
6
  <%= filter_wrapper name: filter.name do %>
3
7
  <%= select_tag filter.id, options_for_select(filter.options.invert, filter.applied_or_default_value(@applied_filters)),
4
8
  class: input_classes('w-full mb-0'),
@@ -1,4 +1,8 @@
1
- <div data-controller="text-filter" data-filter-name="<%= filter.name %>">
1
+ <div
2
+ data-controller="text-filter"
3
+ data-filter-name="<%= filter.name %>"
4
+ data-text-filter-keep-filters-panel-open-value="<%= @resource.keep_filters_panel_open %>"
5
+ >
2
6
  <%= filter_wrapper name: filter.name do %>
3
7
  <%= text_field_tag filter.id, filter.applied_or_default_value(@applied_filters),
4
8
  class: input_classes('w-full mb-0'),
@@ -10,6 +10,7 @@
10
10
  reflection: @reflection,
11
11
  turbo_frame: params[:turbo_frame],
12
12
  parent_model: @parent_model,
13
+ applied_filters: @applied_filters,
13
14
  )
14
15
  %>
15
16
  <% end %>
@@ -0,0 +1,2 @@
1
+ <%# Example link item below %>
2
+ <%#= render Avo::Sidebar::LinkComponent.new label: 'Label', path: '/path' %>
data/db/factories.rb CHANGED
@@ -25,7 +25,7 @@ FactoryBot.define do
25
25
  Time.now - rand(10...365).days
26
26
  end
27
27
  end
28
- status { 0 }
28
+ status { ::Post.statuses.keys.sample }
29
29
  end
30
30
 
31
31
  factory :project do
@@ -42,11 +42,11 @@ FactoryBot.define do
42
42
  end
43
43
 
44
44
  factory :comment do
45
- body { Faker::Lorem.paragraphs(number: rand(4...10)).join(' ') }
45
+ body { Faker::Lorem.paragraphs(number: rand(4...10)).join(" ") }
46
46
  end
47
47
 
48
48
  factory :review do
49
- body { Faker::Lorem.paragraphs(number: rand(4...10)).join(' ') }
49
+ body { Faker::Lorem.paragraphs(number: rand(4...10)).join(" ") }
50
50
  end
51
51
 
52
52
  factory :person do
@@ -64,6 +64,8 @@ FactoryBot.define do
64
64
 
65
65
  factory :course do
66
66
  name { Faker::Educator.unique.course_name }
67
+ country { Course.countries.sample }
68
+ city { Course.cities.stringify_keys[country].sample }
67
69
  end
68
70
 
69
71
  factory :course_link, class: "Course::Link" do
@@ -44,6 +44,7 @@ module Avo
44
44
  class_attribute :after_update_path, default: :show
45
45
  class_attribute :invalid_fields
46
46
  class_attribute :record_selector, default: true
47
+ class_attribute :keep_filters_panel_open, default: false
47
48
 
48
49
  class << self
49
50
  delegate :t, to: ::I18n
@@ -62,6 +62,8 @@ module Avo
62
62
  attr_reader :relation_method
63
63
  attr_reader :types # for Polymorphic associations
64
64
  attr_reader :allow_via_detaching
65
+ attr_reader :scope
66
+ attr_reader :polymorphic_help
65
67
 
66
68
  def initialize(id, **args, &block)
67
69
  args[:placeholder] ||= I18n.t("avo.choose_an_option")
@@ -73,6 +75,8 @@ module Avo
73
75
  @types = args[:types]
74
76
  @relation_method = id.to_s.parameterize.underscore
75
77
  @allow_via_detaching = args[:allow_via_detaching] == true
78
+ @scope = args[:scope]
79
+ @polymorphic_help = args[:polymorphic_help]
76
80
  end
77
81
 
78
82
  def searchable
@@ -111,7 +115,13 @@ module Avo
111
115
  resource = target_resource
112
116
  resource = App.get_resource_by_model_name model if model.present?
113
117
 
114
- ::Avo::Services::AuthorizationService.apply_policy(user, resource.class.query_scope).all.map do |model|
118
+ query = Avo::Services::AuthorizationService.apply_policy(user, resource.class.query_scope)
119
+
120
+ if scope.present?
121
+ query = Avo::Hosts::AssociationScopeHost.new(block: scope, query: query, parent: get_model).handle
122
+ end
123
+
124
+ query.all.map do |model|
115
125
  [model.send(resource.class.title), model.id]
116
126
  end
117
127
  end