avo 2.5.2.pre.7 → 2.6.1.pre.2

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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +5 -1
  4. data/app/assets/builds/action_cable.js +2 -0
  5. data/app/assets/builds/action_cable.js.map +7 -0
  6. data/app/assets/builds/avo.css +175 -122
  7. data/app/assets/builds/avo.js +63 -63
  8. data/app/assets/builds/avo.js.map +3 -3
  9. data/app/assets/stylesheets/avo.css +33 -1
  10. data/app/assets/stylesheets/css/search.css +1 -1
  11. data/app/assets/svgs/heroicons/solid/user-remove.svg +1 -1
  12. data/app/components/avo/actions_component.html.erb +3 -2
  13. data/app/components/avo/alert_component.html.erb +1 -1
  14. data/app/components/avo/alert_component.rb +24 -5
  15. data/app/components/avo/button_component.rb +50 -17
  16. data/app/components/avo/card_component.rb +12 -0
  17. data/app/components/avo/fields/belongs_to_field/autocomplete_component.html.erb +3 -0
  18. data/app/components/avo/fields/concerns/item_labels.rb +40 -0
  19. data/app/components/avo/fields/tags_field/edit_component.html.erb +1 -1
  20. data/app/components/avo/fields/tags_field/index_component.html.erb +1 -5
  21. data/app/components/avo/fields/tags_field/index_component.rb +2 -0
  22. data/app/components/avo/fields/tags_field/show_component.rb +3 -7
  23. data/app/components/avo/fields/tags_field/tag_component.html.erb +1 -1
  24. data/app/components/avo/filters_component.html.erb +1 -1
  25. data/app/components/avo/index/field_wrapper_component.html.erb +1 -1
  26. data/app/components/avo/index/grid_cover_empty_state_component.html.erb +1 -1
  27. data/app/components/avo/index/table_row_component.html.erb +1 -1
  28. data/app/components/avo/paginator_component.html.erb +2 -2
  29. data/app/components/avo/panel_component.html.erb +3 -3
  30. data/app/components/avo/panel_component.rb +1 -1
  31. data/app/components/avo/resource_component.rb +50 -0
  32. data/app/components/avo/sidebar/group_component.html.erb +4 -2
  33. data/app/components/avo/sidebar/heading_component.html.erb +1 -1
  34. data/app/components/avo/sidebar/link_component.html.erb +1 -1
  35. data/app/components/avo/sidebar/link_component.rb +1 -1
  36. data/app/components/avo/sidebar_component.html.erb +5 -13
  37. data/app/components/avo/sidebar_profile_component.html.erb +1 -1
  38. data/app/components/avo/views/resource_edit_component.html.erb +28 -3
  39. data/app/components/avo/views/resource_edit_component.rb +4 -6
  40. data/app/components/avo/views/resource_index_component.html.erb +17 -9
  41. data/app/components/avo/views/resource_new_component.html.erb +8 -2
  42. data/app/components/avo/views/resource_show_component.html.erb +16 -6
  43. data/app/components/avo/views/resource_show_component.rb +0 -45
  44. data/app/controllers/avo/actions_controller.rb +23 -8
  45. data/app/controllers/avo/associations_controller.rb +3 -3
  46. data/app/controllers/avo/base_controller.rb +11 -21
  47. data/app/controllers/avo/private_controller.rb +1 -0
  48. data/app/controllers/avo/search_controller.rb +33 -13
  49. data/app/helpers/avo/application_helper.rb +1 -1
  50. data/app/javascript/js/controllers/fields/key_value_controller.js +1 -1
  51. data/app/javascript/js/controllers/fields/tags_field_controller.js +3 -3
  52. data/app/javascript/js/controllers/filter_controller.js +4 -1
  53. data/app/javascript/js/controllers/search_controller.js +9 -1
  54. data/app/javascript/js/controllers.js +0 -2
  55. data/app/views/avo/actions/show.html.erb +5 -2
  56. data/app/views/avo/dashboards/_chartkick_card.html.erb +1 -1
  57. data/app/views/avo/dashboards/_metric_card.html.erb +1 -1
  58. data/app/views/avo/partials/_global_search.html.erb +1 -1
  59. data/app/views/avo/partials/_logo.html.erb +1 -1
  60. data/app/views/avo/partials/_navbar.html.erb +9 -6
  61. data/app/views/avo/partials/_resource_search.html.erb +1 -1
  62. data/app/views/avo/partials/_table_header.html.erb +3 -2
  63. data/app/views/avo/private/_links_and_buttons.html.erb +2 -2
  64. data/app/views/layouts/avo/application.html.erb +50 -53
  65. data/lib/avo/base_action.rb +24 -6
  66. data/lib/avo/engine.rb +1 -1
  67. data/lib/avo/fields/belongs_to_field.rb +4 -4
  68. data/lib/avo/fields/has_and_belongs_to_many_field.rb +2 -2
  69. data/lib/avo/fields/has_base_field.rb +2 -0
  70. data/lib/avo/fields/has_many_field.rb +2 -2
  71. data/lib/avo/fields/has_one_field.rb +2 -2
  72. data/lib/avo/fields/tags_field.rb +5 -5
  73. data/lib/avo/hosts/base_host.rb +2 -0
  74. data/lib/avo/version.rb +1 -1
  75. data/lib/generators/avo/templates/cards/chartkick_card.tt +1 -1
  76. data/lib/generators/avo/templates/cards/chartkick_card_sample.tt +1 -1
  77. data/lib/generators/avo/templates/cards/metric_card.tt +1 -1
  78. data/lib/generators/avo/templates/cards/metric_card_sample.tt +1 -1
  79. data/lib/tasks/avo_tasks.rake +7 -3
  80. data/public/avo-assets/avo.css +225 -172
  81. data/public/avo-assets/avo.js +63 -63
  82. data/public/avo-assets/avo.js.map +3 -3
  83. metadata +5 -4
  84. data/app/assets/stylesheets/css/alerts.css +0 -35
  85. data/app/javascript/js/controllers/alerts_controller.js +0 -26
@@ -1,4 +1,6 @@
1
- <div data-model-id="<%= @resource.model.id %>">
1
+ <div data-model-id="<%= @resource.model.id %>"
2
+ class="space-y-8"
3
+ >
2
4
  <% @resource.panels.each do |resource_panel| %>
3
5
  <%= form_with model: @resource.model,
4
6
  scope: @resource.form_scope,
@@ -9,11 +11,17 @@
9
11
  <%= render Avo::ReferrerParamsComponent.new back_path: back_path %>
10
12
  <%= render Avo::PanelComponent.new(title: resource_panel[:name], description: @resource.resource_description, display_breadcrumbs: true) do |c| %>
11
13
  <% c.tools do %>
12
- <%= a_link back_path, icon: 'arrow-left' do %>
14
+ <%= a_link back_path,
15
+ style: :text,
16
+ icon: 'arrow-left' do %>
13
17
  <%= t('avo.cancel').capitalize %>
14
18
  <% end %>
15
19
  <% if can_see_the_save_button? %>
16
- <%= a_button color: :green, loading: true, type: :submit, icon: 'save' do %>
20
+ <%= a_button color: :primary,
21
+ style: :primary,
22
+ loading: true,
23
+ type: :submit,
24
+ icon: 'save' do %>
17
25
  <%= t('avo.save').capitalize %>
18
26
  <% end %>
19
27
  <% end %>
@@ -40,4 +48,21 @@
40
48
  <% end %>
41
49
  <% end %>
42
50
  <% end %>
51
+ <% if @reflection.blank? %>
52
+ <% if has_one_panels.present? %>
53
+ <% has_one_panels.each_with_index do |field, index| %>
54
+ <%= render field.component_for_view(:show).new(field: field, resource: @resource, index: index) %>
55
+ <% end %>
56
+ <% end %>
57
+ <% if has_many_panels.present? %>
58
+ <% has_many_panels.each_with_index do |field, index| %>
59
+ <%= render field.component_for_view(:show).new(field: field, resource: @resource, index: index) %>
60
+ <% end %>
61
+ <% end %>
62
+ <% if has_as_belongs_to_many_panels.present? %>
63
+ <% has_as_belongs_to_many_panels.each_with_index do |field, index| %>
64
+ <%= render field.component_for_view(:show).new(field: field, resource: @resource, index: index) %>
65
+ <% end %>
66
+ <% end %>
67
+ <% end %>
43
68
  </div>
@@ -4,8 +4,12 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
4
4
  include Avo::ResourcesHelper
5
5
  include Avo::ApplicationHelper
6
6
 
7
+ attr_reader :fields_by_panel, :has_one_panels, :has_many_panels, :has_as_belongs_to_many_panels
8
+
7
9
  def initialize(resource: nil)
8
10
  @resource = resource
11
+
12
+ split_panel_fields
9
13
  end
10
14
 
11
15
  def back_path
@@ -21,10 +25,4 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
21
25
  def can_see_the_save_button?
22
26
  @resource.authorization.authorize_action :edit, raise_exception: false
23
27
  end
24
-
25
- private
26
-
27
- def via_resource?
28
- params[:via_resource_class].present? && params[:via_resource_id].present?
29
- end
30
28
  end
@@ -1,19 +1,28 @@
1
1
  <div>
2
2
  <%= render Avo::PanelComponent.new(title: title, description: description, data: { component: 'resources-index' }, display_breadcrumbs: @reflection.blank?) do |c| %>
3
3
  <% c.tools do %>
4
+ <% if can_attach? %>
5
+ <%= a_link attach_path,
6
+ icon: 'heroicons/outline/link',
7
+ color: :primary,
8
+ style: :text,
9
+ 'data-turbo-frame': 'attach_modal',
10
+ 'data-target': 'attach' do %>
11
+ <%= t('avo.attach_item', item: singular_resource_name).capitalize %>
12
+ <% end %>
13
+ <% end %>
4
14
  <% if can_see_the_actions_button? %>
5
15
  <%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
6
16
  <% end %>
7
17
  <% if can_see_the_create_button? %>
8
- <%= a_link create_path, icon: 'heroicons/outline/plus', 'data-target': 'create', color: :primary do %>
18
+ <%= a_link create_path,
19
+ icon: 'heroicons/outline/plus',
20
+ 'data-target': 'create',
21
+ style: :primary,
22
+ color: :primary do %>
9
23
  <%= t('avo.create_new_item', item: singular_resource_name.downcase ) %>
10
24
  <% end %>
11
25
  <% end %>
12
- <% if can_attach? %>
13
- <%= a_link attach_path, icon: 'heroicons/outline/link', color: :primary, 'data-turbo-frame': 'attach_modal', 'data-target': 'attach' do %>
14
- <%= t('avo.attach_item', item: singular_resource_name).capitalize %>
15
- <% end %>
16
- <% end %>
17
26
  <% end %>
18
27
  <% c.body do %>
19
28
  <div class="flex flex-col xs:flex-row xs:justify-between space-y-2 xs:space-y-0 py-4 <%= 'hidden' if @resource.search_query.nil? && @filters.empty? && available_view_types.count <= 1 %>"
@@ -50,15 +59,14 @@
50
59
  <% c.bare_content do %>
51
60
  <% if view_type.to_sym == :table %>
52
61
  <% if @models.present? %>
53
- <div class="mt-8">
62
+ <div class="mt-4">
54
63
  <%= render Avo::PaginatorComponent.new pagy: @pagy, turbo_frame: @turbo_frame || 'none', index_params: @index_params, resource: @resource, parent_model: @parent_model %>
55
64
  </div>
56
65
  <% end %>
57
66
  <% end %>
58
-
59
67
  <% if view_type.to_sym == :grid %>
60
68
  <%= render Avo::Index::ResourceGridComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_model: @parent_model) %>
61
- <div class="mt-14">
69
+ <div class="mt-6">
62
70
  <%= render Avo::PaginatorComponent.new pagy: @pagy, turbo_frame: @turbo_frame || 'none', index_params: @index_params, resource: @resource, parent_model: @parent_model %>
63
71
  </div>
64
72
  <% end %>
@@ -14,11 +14,17 @@
14
14
  <%= render Avo::PanelComponent.new(title: resource_panel[:name], description: @resource.resource_description, display_breadcrumbs: true) do |c| %>
15
15
  <% c.tools do %>
16
16
  <div class="flex justify-end space-x-2">
17
- <%= a_link back_path, icon: 'arrow-left' do %>
17
+ <%= a_link back_path,
18
+ style: :text,
19
+ icon: 'arrow-left' do %>
18
20
  <%= t('avo.cancel').capitalize %>
19
21
  <% end %>
20
22
  <% if can_see_the_save_button? %>
21
- <%= a_button color: 'green', loading: true, type: :submit, icon: 'save' do %>
23
+ <%= a_button color: :primary,
24
+ style: :primary,
25
+ loading: true,
26
+ type: :submit,
27
+ icon: 'save' do %>
22
28
  <%= t('avo.save').capitalize %>
23
29
  <% end %>
24
30
  <% end %>
@@ -1,39 +1,45 @@
1
1
  <div data-model-id="<%= @resource.model.id %>"
2
2
  data-selected-resources-name="<%= @resource.model_key %>"
3
3
  data-selected-resources='["<%= @resource.model.id %>"]'
4
- class="space-y-8"
4
+ class="space-y-12"
5
5
  >
6
6
  <% @resource.panels.each_with_index do |resource_panel, index| %>
7
7
  <%= render Avo::PanelComponent.new(title: title, description: @resource.resource_description, display_breadcrumbs: @reflection.blank?, index: index) do |c| %>
8
8
  <% c.tools do %>
9
9
  <% if resource_panel[:name] == @resource.default_panel_name %>
10
10
  <% if @reflection.present? && @resource.model.present? %>
11
- <%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
12
11
  <% if can_detach? %>
13
12
  <%= a_button url: detach_path,
14
13
  icon: 'detach',
15
14
  method: :delete,
16
15
  form_class: 'flex flex-col sm:flex-row sm:inline-flex',
16
+ style: :text,
17
17
  data: {
18
18
  confirm: "Are you sure you want to detach this #{@reflection.name.to_s}."
19
19
  } do %>
20
20
  <%= t('avo.detach_item', item: @reflection.name.to_s).capitalize %>
21
21
  <% end %>
22
+ <%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
22
23
  <% end %>
23
24
  <% if can_see_the_edit_button? %>
24
- <%= a_link edit_path, color: :primary, icon: 'edit' do %>
25
+ <%= a_link edit_path,
26
+ color: :primary,
27
+ style: :primary,
28
+ icon: 'edit' do %>
25
29
  <%= t('avo.edit').capitalize %>
26
30
  <% end %>
27
31
  <% end %>
28
32
  <% else %>
29
- <%= a_link back_path, icon: 'arrow-left' do %>
33
+ <%= a_link back_path,
34
+ style: :text,
35
+ icon: 'arrow-left' do %>
30
36
  <%= t('avo.go_back') %>
31
37
  <% end %>
32
- <%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
33
38
  <% if can_see_the_destroy_button? %>
34
39
  <%= a_button url: helpers.resource_path(model: @resource.model, resource: @resource),
35
40
  method: :delete,
36
41
  local: true,
42
+ style: :text,
37
43
  title: t('avo.delete_item', item: @resource.model.model_name.name.downcase).capitalize,
38
44
  loading: true,
39
45
  confirm: t('avo.are_you_sure', item: @resource.model.model_name.name.downcase),
@@ -48,8 +54,12 @@
48
54
  <%= t('avo.delete').capitalize %>
49
55
  <% end %>
50
56
  <% end %>
57
+ <%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
51
58
  <% if @resource.authorization.authorize_action(:edit, raise_exception: false) %>
52
- <%= a_link edit_path, color: :primary, icon: 'edit' do %>
59
+ <%= a_link edit_path,
60
+ color: :primary,
61
+ style: :primary,
62
+ icon: 'edit' do %>
53
63
  <%= t('avo.edit').capitalize %>
54
64
  <% end %>
55
65
  <% end %>
@@ -44,53 +44,8 @@ class Avo::Views::ResourceShowComponent < Avo::ResourceComponent
44
44
  helpers.edit_resource_path(model: @resource.model, resource: @resource, **args)
45
45
  end
46
46
 
47
- def detach_path
48
- helpers.resource_detach_path(params[:resource_name], params[:id], @reflection.name.to_s, @resource.model.id)
49
- end
50
-
51
- def destroy_path
52
- helpers.resource_path(model: @resource.model, resource: @resource)
53
- end
54
-
55
- def can_detach?
56
- authorize_association_for("detach")
57
- end
58
-
59
- def can_see_the_edit_button?
60
- @resource.authorization.authorize_action(:edit, raise_exception: false)
61
- end
62
-
63
- def can_see_the_destroy_button?
64
- @resource.authorization.authorize_action(:destroy, raise_exception: false)
65
- end
66
-
67
47
  private
68
48
 
69
- def via_resource?
70
- params[:via_resource_class].present? && params[:via_resource_id].present?
71
- end
72
-
73
- def split_panel_fields
74
- @fields_by_panel = {}
75
- @has_one_panels = []
76
- @has_many_panels = []
77
- @has_as_belongs_to_many_panels = []
78
-
79
- @resource.get_fields.each do |field|
80
- case field.class.to_s
81
- when "Avo::Fields::HasOneField"
82
- @has_one_panels << field
83
- when "Avo::Fields::HasManyField"
84
- @has_many_panels << field
85
- when "Avo::Fields::HasAndBelongsToManyField"
86
- @has_as_belongs_to_many_panels << field
87
- else
88
- @fields_by_panel[field.panel_name] ||= []
89
- @fields_by_panel[field.panel_name] << field
90
- end
91
- end
92
- end
93
-
94
49
  # In development and test environments we shoudl show the invalid field errors
95
50
  def should_display_invalid_fields_errors?
96
51
  (Rails.env.development? || Rails.env.test?) && @resource.invalid_fields.present?
@@ -14,14 +14,12 @@ module Avo
14
14
  resource_ids = action_params[:fields][:resource_ids].split(",")
15
15
  models = @resource.class.find_scope.find resource_ids
16
16
 
17
- fields = action_params[:fields].select do |key, value|
18
- key != "resource_ids"
19
- end
17
+ fields = action_params[:fields].except("resource_ids")
20
18
 
21
19
  args = {
22
20
  fields: fields,
23
21
  current_user: _current_user,
24
- resource: resource,
22
+ resource: resource
25
23
  }
26
24
 
27
25
  args[:models] = models unless @action.standalone
@@ -49,8 +47,7 @@ module Avo
49
47
 
50
48
  def respond(response)
51
49
  response[:type] ||= :reload
52
- response[:message_type] ||= :notice
53
- response[:message] ||= I18n.t("avo.action_ran_successfully")
50
+ messages = get_messages response
54
51
 
55
52
  if response[:type] == :download
56
53
  return send_data response[:path], filename: response[:filename]
@@ -58,6 +55,11 @@ module Avo
58
55
 
59
56
  respond_to do |format|
60
57
  format.html do
58
+ # Flash the messages collected from the action
59
+ messages.each do |message|
60
+ flash[message[:type]] = message[:body]
61
+ end
62
+
61
63
  if response[:type] == :redirect
62
64
  path = response[:path]
63
65
 
@@ -65,12 +67,25 @@ module Avo
65
67
  path = instance_eval(&path)
66
68
  end
67
69
 
68
- redirect_to path, "#{response[:message_type]}": response[:message]
70
+ redirect_to path
69
71
  elsif response[:type] == :reload
70
- redirect_back fallback_location: resources_path(resource: @resource), "#{response[:message_type]}": response[:message]
72
+ redirect_back fallback_location: resources_path(resource: @resource)
71
73
  end
72
74
  end
73
75
  end
74
76
  end
77
+
78
+ private
79
+
80
+ def get_messages(response)
81
+ default_message = {
82
+ type: :info,
83
+ body: I18n.t("avo.action_ran_successfully")
84
+ }
85
+
86
+ return [default_message] if response[:messages].blank?
87
+
88
+ response[:messages]
89
+ end
75
90
  end
76
91
  end
@@ -46,8 +46,8 @@ module Avo
46
46
  query = @authorization.apply_policy @attachment_class
47
47
 
48
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
49
+ if @field.attach_scope.present?
50
+ query = Avo::Hosts::AssociationScopeHost.new(block: @field.attach_scope, query: query, parent: @model).handle
51
51
  end
52
52
 
53
53
  @options = query.all.map do |model|
@@ -65,7 +65,7 @@ module Avo
65
65
 
66
66
  respond_to do |format|
67
67
  if @model.save
68
- format.html { redirect_to resource_path(model: @model, resource: @resource), notice: t("avo.attachment_class_attached", attachment_class: @related_resource.name) }
68
+ format.html { redirect_back fallback_location: resource_path(model: @model, resource: @resource), notice: t("avo.attachment_class_attached", attachment_class: @related_resource.name) }
69
69
  else
70
70
  format.html { render :new }
71
71
  end
@@ -11,8 +11,6 @@ module Avo
11
11
  before_action :set_edit_title_and_breadcrumbs, only: [:edit, :update]
12
12
  before_action :fill_model, only: [:create, :update]
13
13
  before_action :authorize_action
14
- before_action :reset_pagination_if_filters_changed, only: :index
15
- before_action :cache_applied_filters, only: :index
16
14
 
17
15
  def index
18
16
  @page_title = @resource.plural_name.humanize
@@ -49,10 +47,10 @@ module Avo
49
47
  # Check if the sortable field option is actually a proc and we need to do a custom sort
50
48
  field_id = @index_params[:sort_by].to_sym
51
49
  field = @resource.get_field_definitions.find { |field| field.id == field_id }
52
- if field&.sortable.is_a?(Proc)
53
- @query = field.sortable.call(@query, @index_params[:sort_direction])
50
+ @query = if field&.sortable.is_a?(Proc)
51
+ field.sortable.call(@query, @index_params[:sort_direction])
54
52
  else
55
- @query = @query.order("#{@resource.model_class.table_name}.#{@index_params[:sort_by]} #{@index_params[:sort_direction]}")
53
+ @query.order("#{@resource.model_class.table_name}.#{@index_params[:sort_by]} #{@index_params[:sort_direction]}")
56
54
  end
57
55
  end
58
56
 
@@ -61,7 +59,14 @@ module Avo
61
59
  @query = filter_class.safe_constantize.new.apply_query request, @query, filter_value
62
60
  end
63
61
 
64
- @pagy, @models = pagy(@query, items: @index_params[:per_page], link_extra: "data-turbo-frame=\"#{params[:turbo_frame]}\"", size: [1, 2, 2, 1])
62
+ extra_pagy_params = {}
63
+
64
+ # Reset open filters when a user navigates to a new page
65
+ extra_pagy_params[:keep_filters_panel_open] = if params[:keep_filters_panel_open] == "1"
66
+ "0"
67
+ end
68
+
69
+ @pagy, @models = pagy(@query, items: @index_params[:per_page], link_extra: "data-turbo-frame=\"#{params[:turbo_frame]}\"", size: [1, 2, 2, 1], params: extra_pagy_params)
65
70
 
66
71
  # Create resources for each model
67
72
  @resources = @models.map do |model|
@@ -353,21 +358,6 @@ module Avo
353
358
  filter_defaults.merge(@applied_filters)
354
359
  end
355
360
 
356
- # Caching these so we know when the filters have changed so we reset the pagination
357
- def cache_applied_filters
358
- ::Avo::App.cache_store.delete applied_filters_cache_key if params[:filters].nil?
359
-
360
- ::Avo::App.cache_store.write(applied_filters_cache_key, params[:filters], expires_in: 1.day)
361
- end
362
-
363
- def reset_pagination_if_filters_changed
364
- params[:page] = 1 if params[:filters] != ::Avo::App.cache_store.read(applied_filters_cache_key)
365
- end
366
-
367
- def applied_filters_cache_key
368
- "avo.base_controller.#{@resource.model_key}.applied_filters"
369
- end
370
-
371
361
  def set_edit_title_and_breadcrumbs
372
362
  @resource = @resource.hydrate(model: @model, view: :edit, user: _current_user)
373
363
  @page_title = @resource.default_panel_name.to_s
@@ -3,6 +3,7 @@ require_dependency "avo/application_controller"
3
3
  module Avo
4
4
  class PrivateController < ApplicationController
5
5
  def design
6
+ @page_title = "Design [Private]"
6
7
  end
7
8
  end
8
9
  end
@@ -46,19 +46,8 @@ module Avo
46
46
  def search_resource(resource)
47
47
  query = resource.search_query.call(params: params).limit(8)
48
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
49
+ # Figure out if this is a belongs_to search
50
+ query = apply_attach_scope query
62
51
 
63
52
  results = apply_search_metadata(query, resource)
64
53
 
@@ -72,6 +61,37 @@ module Avo
72
61
  [resource.name.pluralize.downcase, result_object]
73
62
  end
74
63
 
64
+ # Figure out if it's a belongs to search and if it has the attach_scope block present.
65
+ # If so, set the parent for those edit view and the parent with the grandparent for the new view.
66
+ def apply_attach_scope(query)
67
+ return query if params[:via_reflection_class].blank?
68
+
69
+ # Fetch the field
70
+ field = belongs_to_field
71
+
72
+ # No need to modify the query if there's no attach_scope present.
73
+ return query if field.attach_scope.blank?
74
+
75
+ # Try to fetch the parent.
76
+ if params[:via_reflection_id].present?
77
+ parent = params[:via_reflection_class].safe_constantize.find params[:via_reflection_id]
78
+ end
79
+
80
+ # If the parent is nil it probably means that someone's creating the record so it's not attached yet.
81
+ # In these scenarios, try to find the grandparent for the new views where the parent is nil
82
+ # and initialize the parent record with the grandparent attached so the user has the required information
83
+ # to scope the query.
84
+ if parent.blank? && params[:via_parent_resource_id].present? && params[:via_parent_resource_class].present? && params[:via_relation].present?
85
+ grandparent = params[:via_parent_resource_class].safe_constantize.find params[:via_parent_resource_id]
86
+ parent = params[:via_reflection_class].safe_constantize.new(
87
+ params[:via_relation] => grandparent
88
+ )
89
+ end
90
+
91
+ # Add to the query
92
+ Avo::Hosts::AssociationScopeHost.new(block: belongs_to_field.attach_scope, query: query, parent: parent).handle
93
+ end
94
+
75
95
  def apply_search_metadata(models, avo_resource)
76
96
  models.map do |model|
77
97
  resource = avo_resource.dup.hydrate(model: model).hydrate_fields(model: model)
@@ -118,7 +118,7 @@ module Avo
118
118
  end
119
119
 
120
120
  def input_classes(extra_classes = "", has_error: false)
121
- classes = "appearance-none inline-flex bg-gray-100 disabled:cursor-not-allowed text-gray-600 disabled:opacity-50 rounded py-2 px-3 leading-tight border focus:border-gray-600 focus-visible:ring-0 focus:text-gray-700"
121
+ classes = "appearance-none inline-flex bg-gray-25 disabled:cursor-not-allowed text-gray-600 disabled:opacity-50 rounded py-2 px-3 leading-tight border focus:border-gray-600 focus-visible:ring-0 focus:text-gray-700"
122
122
 
123
123
  classes += if has_error
124
124
  " border-red-600"
@@ -110,7 +110,7 @@ export default class extends Controller {
110
110
 
111
111
  inputField(id = 'key', index, key, value) {
112
112
  return `<input
113
- class="${this.options.inputClasses} !rounded-none border-gray-600 border-r border-l-0 border-b-0 border-t-0 focus:border-gray-300 w-1/2 focus:outline-none outline-none key-value-input-${id}"
113
+ class="${this.options.inputClasses} focus:bg-gray-100 !rounded-none border-gray-600 border-r border-l-0 border-b-0 border-t-0 focus:border-gray-300 w-1/2 focus:outline-none outline-none key-value-input-${id}"
114
114
  data-action="input->key-value#${id}FieldUpdated"
115
115
  placeholder="${this.options[`${id}_label`]}"
116
116
  data-index="${index}"
@@ -14,8 +14,8 @@ export default class extends BaseController {
14
14
  return this.getJsonAttribute(this.inputTarget, 'data-whitelist-items', [])
15
15
  }
16
16
 
17
- get blacklistItems() {
18
- return this.getJsonAttribute(this.inputTarget, 'data-blacklist-items', [])
17
+ get disallowedItems() {
18
+ return this.getJsonAttribute(this.inputTarget, 'data-disallowed-items', [])
19
19
  }
20
20
 
21
21
  get enforceSuggestions() {
@@ -37,7 +37,7 @@ export default class extends BaseController {
37
37
  get tagifyOptions() {
38
38
  let options = {
39
39
  whitelist: this.whitelistItems,
40
- blacklist: this.blacklistItems,
40
+ blacklist: this.disallowedItems,
41
41
  enforceWhitelist: this.enforceSuggestions,
42
42
  delimiters: this.delimiters.join('|'),
43
43
  maxTags: 10,
@@ -84,9 +84,12 @@ export default class extends Controller {
84
84
 
85
85
  if (this.keepFiltersPanelOpenValue) {
86
86
  // eslint-disable-next-line camelcase
87
- query.keep_filters_panel_open = this.keepFiltersPanelOpenValue
87
+ query.keep_filters_panel_open = this.keepFiltersPanelOpenValue ? 1 : null
88
88
  }
89
89
 
90
+ // force to go to the first page if the filters changed
91
+ query.page = 1
92
+
90
93
  if (encodedFilters) {
91
94
  query.filters = encodedFilters
92
95
  } else {
@@ -76,6 +76,12 @@ export default class extends Controller {
76
76
  via_reflection_class: this.dataset.viaReflectionClass,
77
77
  // eslint-disable-next-line camelcase
78
78
  via_reflection_id: this.dataset.viaReflectionId,
79
+ // eslint-disable-next-line camelcase
80
+ via_parent_resource_id: this.dataset.viaParentResourceId,
81
+ // eslint-disable-next-line camelcase
82
+ via_parent_resource_class: this.dataset.viaParentResourceClass,
83
+ // eslint-disable-next-line camelcase
84
+ via_relation: this.dataset.viaRelation,
79
85
  }
80
86
  }
81
87
 
@@ -193,7 +199,7 @@ export default class extends Controller {
193
199
  Mousetrap.bind(['command+k', 'ctrl+k'], () => this.showSearchPanel())
194
200
  }
195
201
 
196
- autocomplete({
202
+ const { destroy } = autocomplete({
197
203
  container: this.autocompleteTarget,
198
204
  placeholder: this.translationKeys.placeholder,
199
205
  translations: {
@@ -216,6 +222,8 @@ export default class extends Controller {
216
222
  },
217
223
  })
218
224
 
225
+ document.addEventListener('turbo:before-render', destroy)
226
+
219
227
  // When using search for belongs-to
220
228
  if (this.buttonTarget.dataset.shouldBeDisabled !== 'true') {
221
229
  this.buttonTarget.removeAttribute('disabled')
@@ -2,7 +2,6 @@ import { application } from './application'
2
2
 
3
3
  import ActionController from './controllers/action_controller'
4
4
  import ActionsPickerController from './controllers/actions_picker_controller'
5
- import AlertsController from './controllers/alerts_controller'
6
5
  import AttachmentsController from './controllers/attachments_controller'
7
6
  import BelongsToFieldController from './controllers/fields/belongs_to_field_controller'
8
7
  import BooleanFilterController from './controllers/boolean_filter_controller'
@@ -33,7 +32,6 @@ import TrixFieldController from './controllers/fields/trix_field_controller'
33
32
 
34
33
  application.register('action', ActionController)
35
34
  application.register('actions-picker', ActionsPickerController)
36
- application.register('alerts', AlertsController)
37
35
  application.register('attachments', AttachmentsController)
38
36
  application.register('boolean-filter', BooleanFilterController)
39
37
  application.register('copy-to-clipboard', CopyToClipboardController)
@@ -29,11 +29,14 @@
29
29
  </div>
30
30
  <% end %>
31
31
  <% c.controls do %>
32
- <%= a_button data: { action: 'click->modal#close' }, size: :sm do %>
32
+ <%= a_button data: { action: 'click->modal#close' },
33
+ size: :sm,
34
+ color: :primary do %>
33
35
  <%= @action.cancel_button_label %>
34
36
  <% end %>
35
37
  <%= a_button type: :submit,
36
- color: :green,
38
+ color: :primary,
39
+ style: :primary,
37
40
  size: :sm,
38
41
  data: @action.class.submit_button_data_attributes do %>
39
42
  <%= @action.confirm_button_label %>
@@ -1,3 +1,3 @@
1
1
  <div class="relative flex flex-col <%= @card.chartkick_classes %>">
2
- <%= send(@card.chart_type, @card.compute_result.result_data, **@card.chartkick_options) %>
2
+ <%= send(@card.chart_type, @card.result_data, **@card.chartkick_options) %>
3
3
  </div>
@@ -1,5 +1,5 @@
1
1
  <div class="flex mt-4 items-end">
2
2
  <span class="text-3xl"><%= @card.prefix %></span>
3
- <span class="text-5xl"><%= @card.compute_result.result_data %></span>
3
+ <span class="text-5xl"><%= @card.result_data %></span>
4
4
  <span class="text-3xl"><%= @card.suffix %></span>
5
5
  </div>
@@ -1,4 +1,4 @@
1
- <div data-controller="search" class="relative flex global-search rounded border border-gray-200 sm:min-w-[16rem]" data-turbo-remove-before-cache>
1
+ <div data-controller="search" class="relative flex global-search rounded border border-gray-200 sm:min-w-[16rem]">
2
2
  <div class="flex-1 text-gray-500"
3
3
  data-search-target="autocomplete"
4
4
  data-search-resource="global"
@@ -1,3 +1,3 @@
1
- <%= link_to root_path, class: 'logo-placeholder h-20 bg-white p-4 flex justify-start' do %>
1
+ <%= link_to root_path, class: 'logo-placeholder h-10 bg-white flex justify-start' do %>
2
2
  <%= image_tag '/avo-assets/logo.png', class: 'h-full', title: 'Avo' %>
3
3
  <% end %>