avo 1.21.1.pre.1 → 1.22.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +61 -61
- data/README.md +1 -1
- data/app/assets/builds/avo.css +4 -4
- data/app/assets/builds/avo.js +40 -18
- data/app/assets/builds/avo.js.map +2 -2
- data/app/components/avo/fields/belongs_to_field/autocomplete_component.html.erb +14 -7
- data/app/components/avo/fields/belongs_to_field/autocomplete_component.rb +54 -10
- data/app/components/avo/fields/belongs_to_field/edit_component.html.erb +31 -8
- data/app/components/avo/fields/belongs_to_field/edit_component.rb +37 -0
- data/app/components/avo/fields/select_field/edit_component.html.erb +3 -3
- data/app/components/avo/navigation_link_component.html.erb +1 -1
- data/app/components/avo/navigation_link_component.rb +2 -1
- data/app/controllers/avo/application_controller.rb +16 -14
- data/app/controllers/avo/base_controller.rb +2 -4
- data/app/controllers/avo/relations_controller.rb +1 -4
- data/app/javascript/avo.js +1 -0
- data/app/javascript/js/controllers/fields/belongs_to_field_controller.js +23 -15
- data/app/javascript/js/controllers/fields/date_field_controller.js +10 -2
- data/app/javascript/js/controllers/item_selector_controller.js +29 -19
- data/app/javascript/js/controllers/search_controller.js +8 -3
- data/app/views/avo/partials/_logo.html.erb +3 -1
- data/app/views/avo/partials/_turbo_frame_wrap.html.erb +7 -1
- data/app/views/avo/relations/new.html.erb +1 -1
- data/app/views/avo/sidebar/_sidebar.html.erb +1 -3
- data/db/factories.rb +9 -5
- data/lib/avo/base_resource.rb +16 -14
- data/lib/avo/fields/base_field.rb +7 -4
- data/lib/avo/fields/belongs_to_field.rb +92 -11
- data/lib/avo/fields/key_value_field.rb +28 -8
- data/lib/avo/version.rb +1 -1
- data/lib/generators/avo/templates/locales/avo.en.yml +1 -0
- data/lib/generators/avo/templates/locales/avo.nb-NO.yml +1 -0
- data/lib/generators/avo/templates/locales/avo.pt-BR.yml +1 -0
- data/lib/generators/avo/templates/locales/avo.ro.yml +1 -0
- data/public/avo-assets/avo.css +4 -4
- data/public/avo-assets/avo.js +40 -18
- data/public/avo-assets/avo.js.map +2 -2
- metadata +2 -2
@@ -8,15 +8,22 @@
|
|
8
8
|
<div class="relative w-full" autocomplete="off">
|
9
9
|
<%= @form.text_field @field.foreign_key,
|
10
10
|
value: field_label,
|
11
|
-
class: helpers.input_classes('w-full', has_error: @field.model_errors.include?(@field.id)),
|
11
|
+
class: helpers.input_classes('w-full', has_error: @field.model_errors.include?(@field.id)),
|
12
12
|
placeholder: @field.placeholder,
|
13
|
+
'data-search-target': 'button clearValue',
|
14
|
+
# This instructs the search_controller if it should enable/disabled this field when the user switches polymorphic associations
|
15
|
+
# It should not enable the field if the record is being created through an association
|
16
|
+
'data-should-be-disabled': @disabled,
|
13
17
|
disabled: true %>
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
<% unless @disabled %>
|
19
|
+
<div class="absolute top-1/2 left-auto right-3 mr-px -mt-2 cursor-pointer hidden text-gray-500"
|
20
|
+
data-tippy="tooltip"
|
21
|
+
data-search-target="clearButton"
|
22
|
+
title="<%= I18n.translate 'avo.clear_value' %>"
|
23
|
+
data-action="click->search#clearValue"
|
24
|
+
><%= helpers.svg 'x', class: 'h-4' %>
|
25
|
+
</div>
|
26
|
+
<% end %>
|
20
27
|
</div>
|
21
28
|
<%= @form.hidden_field @foreign_key, value: field_value, 'data-search-target': 'hiddenId clearValue' %>
|
22
29
|
</div>
|
@@ -1,33 +1,77 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Avo::Fields::BelongsToField::AutocompleteComponent < ViewComponent::Base
|
4
|
-
def initialize(form:, field:, type: nil,
|
4
|
+
def initialize(form:, field:, model_key:, foreign_key:, disabled:, type: nil, resource: nil, polymorphic_record: nil)
|
5
5
|
@form = form
|
6
6
|
@field = field
|
7
7
|
@type = type
|
8
8
|
@model_key = model_key
|
9
9
|
@foreign_key = foreign_key
|
10
|
+
@resource = resource
|
11
|
+
@disabled = disabled
|
12
|
+
@polymorphic_record = polymorphic_record
|
10
13
|
end
|
11
14
|
|
12
15
|
def field_label
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
16
|
+
result = @field.field_label
|
17
|
+
|
18
|
+
# New records won't have the value (instantiated model) present but the polymorphic_type and polymorphic_id prefilled
|
19
|
+
if should_prefill?
|
20
|
+
result = @field.value&.class == @type ? @field.field_label : nil
|
17
21
|
end
|
22
|
+
|
23
|
+
result
|
18
24
|
end
|
19
25
|
|
20
26
|
def field_value
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
27
|
+
result = @field.field_value
|
28
|
+
|
29
|
+
# New records won't have the value (instantiated model) present but the polymorphic_type and polymorphic_id prefilled
|
30
|
+
if should_prefill?
|
31
|
+
result = @field.value&.class == @type ? @field.field_value : nil
|
25
32
|
end
|
33
|
+
|
34
|
+
result
|
26
35
|
end
|
27
36
|
|
28
37
|
private
|
29
38
|
|
39
|
+
def should_prefill?
|
40
|
+
@field.is_polymorphic? && searchable? && !(new_record? && has_polymorphic_association?)
|
41
|
+
end
|
42
|
+
|
30
43
|
def searchable?
|
31
|
-
@
|
44
|
+
@field.searchable
|
45
|
+
end
|
46
|
+
|
47
|
+
def new_record?
|
48
|
+
@resource.model.new_record?
|
49
|
+
end
|
50
|
+
|
51
|
+
def has_polymorphic_association?
|
52
|
+
polymorphic_class.present? && polymorphic_id.present?
|
53
|
+
end
|
54
|
+
|
55
|
+
# Get the polymorphic class
|
56
|
+
def polymorphic_class
|
57
|
+
@resource.model["#{@field.foreign_key}_type"]
|
58
|
+
end
|
59
|
+
|
60
|
+
# Get the polymorphic id
|
61
|
+
def polymorphic_id
|
62
|
+
@resource.model["#{@field.foreign_key}_id"]
|
63
|
+
end
|
64
|
+
|
65
|
+
# Get the resource for that polymorphic class
|
66
|
+
def polymorphic_resource
|
67
|
+
::Avo::App.get_resource_by_model_name polymorphic_class
|
68
|
+
end
|
69
|
+
|
70
|
+
# Extract the needed fields to identify the record for polymorphic associations
|
71
|
+
def polymorphic_fields
|
72
|
+
{
|
73
|
+
id: polymorphic_resource.id,
|
74
|
+
label: polymorphic_resource.title
|
75
|
+
}
|
32
76
|
end
|
33
77
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<%
|
2
|
-
if
|
2
|
+
if is_polymorphic?
|
3
3
|
|
4
4
|
# Set the model keys so we can pass them over
|
5
5
|
model_keys = @field.types.map do |type|
|
@@ -13,7 +13,7 @@
|
|
13
13
|
data-association-class="<%= @field&.target_resource&.model_class || nil %>"
|
14
14
|
>
|
15
15
|
<%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
|
16
|
-
<%= @form.select
|
16
|
+
<%= @form.select @field.type_input_foreign_key, @field.types.map { |type| [type.to_s.underscore.humanize, type.to_s] },
|
17
17
|
{
|
18
18
|
value: @field.value,
|
19
19
|
include_blank: @field.placeholder,
|
@@ -29,7 +29,7 @@
|
|
29
29
|
# If the select field is disabled, no value will be sent. It's how HTML works.
|
30
30
|
# Thus the extra hidden field to actually send the related id to the server.
|
31
31
|
if disabled %>
|
32
|
-
<%= @form.hidden_field
|
32
|
+
<%= @form.hidden_field @field.type_input_foreign_key %>
|
33
33
|
<% end %>
|
34
34
|
<% end %>
|
35
35
|
<% @field.types.each do |type| %>
|
@@ -39,10 +39,20 @@
|
|
39
39
|
>
|
40
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
41
|
<% if @field.searchable %>
|
42
|
-
<%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
|
42
|
+
<%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
|
43
|
+
field: @field,
|
44
|
+
type: type,
|
45
|
+
model_key: model_keys[type.to_s],
|
46
|
+
foreign_key: @field.id_input_foreign_key,
|
47
|
+
resource: @resource,
|
48
|
+
disabled: disabled,
|
49
|
+
polymorphic_record: polymorphic_record
|
50
|
+
%>
|
43
51
|
<% else %>
|
44
|
-
<%= @form.select
|
52
|
+
<%= @form.select @field.id_input_foreign_key,
|
53
|
+
options_for_select(@field.values_for_type(type), @resource.present? && @resource.model.present? ? @resource.model[@field.id_input_foreign_key] : nil),
|
45
54
|
{
|
55
|
+
value: @resource.model[@field.id_input_foreign_key].to_s,
|
46
56
|
include_blank: @field.placeholder,
|
47
57
|
},
|
48
58
|
{
|
@@ -50,6 +60,12 @@
|
|
50
60
|
disabled: disabled
|
51
61
|
}
|
52
62
|
%>
|
63
|
+
<%
|
64
|
+
# If the select field is disabled, no value will be sent. It's how HTML works.
|
65
|
+
# Thus the extra hidden field to actually send the related id to the server.
|
66
|
+
if disabled %>
|
67
|
+
<%= @form.hidden_field @field.id_input_foreign_key %>
|
68
|
+
<% end %>
|
53
69
|
<% end %>
|
54
70
|
<% end %>
|
55
71
|
</div>
|
@@ -58,11 +74,18 @@
|
|
58
74
|
<% else %>
|
59
75
|
<%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
|
60
76
|
<% if @field.searchable %>
|
61
|
-
<%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
|
77
|
+
<%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
|
78
|
+
field: @field,
|
79
|
+
model_key: @field.target_resource&.model_key,
|
80
|
+
foreign_key: @field.id_input_foreign_key,
|
81
|
+
resource: @resource,
|
82
|
+
disabled: disabled
|
83
|
+
%>
|
62
84
|
<% else %>
|
63
|
-
<%= @form.select @field.
|
85
|
+
<%= @form.select @field.id_input_foreign_key, @field.options,
|
64
86
|
{
|
65
87
|
include_blank: @field.placeholder,
|
88
|
+
value: @field.value
|
66
89
|
},
|
67
90
|
{
|
68
91
|
class: helpers.input_classes('w-full', has_error: @field.model_errors.include?(@field.id)),
|
@@ -73,7 +96,7 @@
|
|
73
96
|
# If the select field is disabled, no value will be sent. It's how HTML works.
|
74
97
|
# Thus the extra hidden field to actually send the related id to the server.
|
75
98
|
if disabled %>
|
76
|
-
<%= @form.hidden_field @field.
|
99
|
+
<%= @form.hidden_field @field.id_input_foreign_key %>
|
77
100
|
<% end %>
|
78
101
|
<% end %>
|
79
102
|
<% end %>
|
@@ -1,6 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Avo::Fields::BelongsToField::EditComponent < Avo::Fields::EditComponent
|
4
|
+
def initialize(field: nil, resource: nil, index: 0, form: nil, displayed_in_modal: false)
|
5
|
+
super field: field, resource: resource, index: index, form: form, displayed_in_modal: displayed_in_modal
|
6
|
+
|
7
|
+
@polymorphic_record = nil
|
8
|
+
end
|
9
|
+
|
4
10
|
def disabled
|
5
11
|
return true if @field.readonly
|
6
12
|
return true if @field.target_resource.present? && @field.target_resource.model_class.name == params[:via_resource_class]
|
@@ -8,4 +14,35 @@ class Avo::Fields::BelongsToField::EditComponent < Avo::Fields::EditComponent
|
|
8
14
|
|
9
15
|
false
|
10
16
|
end
|
17
|
+
|
18
|
+
def is_polymorphic?
|
19
|
+
@field.types.present?
|
20
|
+
end
|
21
|
+
|
22
|
+
def has_polymorphic_association?
|
23
|
+
polymorphic_class.present? && polymorphic_id.present?
|
24
|
+
end
|
25
|
+
|
26
|
+
# Get the polymorphic class
|
27
|
+
def polymorphic_class
|
28
|
+
@resource.model["#{@field.foreign_key}_type"]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Get the polymorphic id
|
32
|
+
def polymorphic_id
|
33
|
+
@resource.model["#{@field.foreign_key}_id"]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Get the actual resource
|
37
|
+
def polymorphic_record
|
38
|
+
return unless has_polymorphic_association?
|
39
|
+
|
40
|
+
return unless is_polymorphic?
|
41
|
+
|
42
|
+
return @polymorphic_record if @polymorphic_record.present?
|
43
|
+
|
44
|
+
@polymorphic_record = polymorphic_class.safe_constantize.find polymorphic_id
|
45
|
+
|
46
|
+
@polymorphic_record
|
47
|
+
end
|
11
48
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
|
2
2
|
<%= @form.select @field.id, @field.options_for_select, { selected: @field.value, prompt: @field.placeholder }, {
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
class: helpers.input_classes(' w-full', has_error: @field.model_errors.include?(@field.id)),
|
4
|
+
disabled: @field.readonly,
|
5
|
+
value: @field.model.present? ? @field.model[@field.id] : @field.value } %>
|
6
6
|
<% end %>
|
@@ -1,3 +1,3 @@
|
|
1
|
-
<%= active_link_to @path, class: 'text-gray-800 py-2 px-4 block font-normal hover:bg-gray-100 rounded-md mb-1 mx-3 text-sm leading-none', active: @active do %>
|
1
|
+
<%= active_link_to @path, class: 'text-gray-800 py-2 px-4 block font-normal hover:bg-gray-100 rounded-md mb-1 mx-3 text-sm leading-none', active: @active, target: @target do %>
|
2
2
|
<div class="w-4"></div> <%= @label %>
|
3
3
|
<% end %>
|
@@ -1,10 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Avo::NavigationLinkComponent < ViewComponent::Base
|
4
|
-
def initialize(label: nil, path: nil, active: :inclusive, size: :md)
|
4
|
+
def initialize(label: nil, path: nil, active: :inclusive, size: :md, target: "_self")
|
5
5
|
@label = label
|
6
6
|
@path = path
|
7
7
|
@active = active
|
8
8
|
@size = size
|
9
|
+
@target = target
|
9
10
|
end
|
10
11
|
end
|
@@ -1,6 +1,11 @@
|
|
1
1
|
module Avo
|
2
2
|
class ApplicationController < ::ActionController::Base
|
3
|
-
|
3
|
+
if defined?(Pundit::Authorization)
|
4
|
+
include Pundit::Authorization
|
5
|
+
else
|
6
|
+
include Pundit
|
7
|
+
end
|
8
|
+
|
4
9
|
include Pagy::Backend
|
5
10
|
include Avo::ApplicationHelper
|
6
11
|
include Avo::UrlHelpers
|
@@ -111,7 +116,12 @@ module Avo
|
|
111
116
|
end
|
112
117
|
|
113
118
|
def fill_model
|
114
|
-
|
119
|
+
# We have to skip filling the the model if this is an attach action
|
120
|
+
is_attach_action = params[model_param_key].blank? && params[:related_name].present? && params[:fields].present?
|
121
|
+
|
122
|
+
unless is_attach_action
|
123
|
+
@model = @resource.fill_model(@model_to_fill, cast_nullable(model_params))
|
124
|
+
end
|
115
125
|
end
|
116
126
|
|
117
127
|
def hydrate_resource
|
@@ -187,18 +197,6 @@ module Avo
|
|
187
197
|
query
|
188
198
|
end
|
189
199
|
|
190
|
-
# def authorize_user
|
191
|
-
# return if params[:controller] == 'avo/search'
|
192
|
-
|
193
|
-
# model = record = resource.model
|
194
|
-
|
195
|
-
# if ['show', 'edit', 'update'].include?(params[:action]) && params[:controller] == 'avo/resources'
|
196
|
-
# record = resource
|
197
|
-
# end
|
198
|
-
|
199
|
-
# # AuthorizationService::authorize_action _current_user, record, params[:action] return render_unauthorized unless
|
200
|
-
# end
|
201
|
-
|
202
200
|
def _authenticate!
|
203
201
|
instance_eval(&Avo.configuration.authenticate)
|
204
202
|
end
|
@@ -243,5 +241,9 @@ module Avo
|
|
243
241
|
def on_api_path
|
244
242
|
request.original_url.match?(/.*#{Avo::App.root_path}\/avo_api\/.*/)
|
245
243
|
end
|
244
|
+
|
245
|
+
def model_param_key
|
246
|
+
@resource.form_scope
|
247
|
+
end
|
246
248
|
end
|
247
249
|
end
|
@@ -7,6 +7,7 @@ module Avo
|
|
7
7
|
before_action :hydrate_resource
|
8
8
|
before_action :set_model, only: [:show, :edit, :destroy, :update]
|
9
9
|
before_action :set_model_to_fill
|
10
|
+
before_action :fill_model, only: [:create, :update]
|
10
11
|
before_action :authorize_action
|
11
12
|
before_action :reset_pagination_if_filters_changed, only: :index
|
12
13
|
before_action :cache_applied_filters, only: :index
|
@@ -83,6 +84,7 @@ module Avo
|
|
83
84
|
def new
|
84
85
|
@model = @resource.model_class.new
|
85
86
|
@resource = @resource.hydrate(model: @model, view: :new, user: _current_user)
|
87
|
+
# abort @model.course.inspect
|
86
88
|
|
87
89
|
@page_title = @resource.default_panel_name
|
88
90
|
add_breadcrumb resource_name.humanize, resources_path(resource: @resource)
|
@@ -112,7 +114,6 @@ module Avo
|
|
112
114
|
|
113
115
|
def create
|
114
116
|
# model gets instantiated and filled in the fill_model method
|
115
|
-
fill_model
|
116
117
|
saved = @model.save
|
117
118
|
@resource.hydrate(model: @model, view: :new, user: _current_user)
|
118
119
|
|
@@ -166,7 +167,6 @@ module Avo
|
|
166
167
|
|
167
168
|
def update
|
168
169
|
# model gets instantiated and filled in the fill_model method
|
169
|
-
fill_model
|
170
170
|
saved = @model.save
|
171
171
|
@resource = @resource.hydrate(model: @model, view: :edit, user: _current_user)
|
172
172
|
|
@@ -194,8 +194,6 @@ module Avo
|
|
194
194
|
private
|
195
195
|
|
196
196
|
def model_params
|
197
|
-
model_param_key = @resource.form_scope
|
198
|
-
|
199
197
|
request_params = params.require(model_param_key).permit(permitted_params)
|
200
198
|
|
201
199
|
if @resource.devise_password_optional && request_params[:password].blank? && request_params[:password_confirmation].blank?
|
@@ -37,10 +37,7 @@ module Avo
|
|
37
37
|
query = @authorization.apply_policy @attachment_class
|
38
38
|
|
39
39
|
@options = query.all.map do |model|
|
40
|
-
|
41
|
-
value: model.id,
|
42
|
-
label: model.send(@attachment_resource.class.title)
|
43
|
-
}
|
40
|
+
[model.send(@attachment_resource.class.title), model.id]
|
44
41
|
end
|
45
42
|
end
|
46
43
|
|
data/app/javascript/avo.js
CHANGED
@@ -59,6 +59,7 @@ document.addEventListener('turbo:before-fetch-response', (e) => {
|
|
59
59
|
})
|
60
60
|
document.addEventListener('turbo:visit', () => document.body.classList.add('turbo-loading'))
|
61
61
|
document.addEventListener('turbo:submit-start', () => document.body.classList.add('turbo-loading'))
|
62
|
+
document.addEventListener('turbo:submit-end', () => document.body.classList.remove('turbo-loading'))
|
62
63
|
document.addEventListener('turbo:before-cache', () => {
|
63
64
|
document.querySelectorAll('[data-turbo-remove-before-cache]').forEach((element) => element.remove())
|
64
65
|
})
|
@@ -58,22 +58,16 @@ export default class extends Controller {
|
|
58
58
|
|
59
59
|
if (this.isSearchable) {
|
60
60
|
const textInput = target.querySelector('input[type="text"]')
|
61
|
-
if (textInput)
|
62
|
-
textInput.setAttribute('valid-name', textInput.getAttribute('name'))
|
63
|
-
}
|
61
|
+
if (textInput) this.nameToValidName(textInput)
|
64
62
|
|
65
63
|
const hiddenInput = target.querySelector('input[type="hidden"]')
|
66
|
-
if (hiddenInput)
|
67
|
-
hiddenInput.setAttribute(
|
68
|
-
'valid-name',
|
69
|
-
hiddenInput.getAttribute('name'),
|
70
|
-
)
|
71
|
-
}
|
64
|
+
if (hiddenInput) this.nameToValidName(hiddenInput)
|
72
65
|
} else {
|
73
66
|
const select = target.querySelector('select')
|
74
|
-
if (select)
|
75
|
-
|
76
|
-
|
67
|
+
if (select) this.nameToValidName(select)
|
68
|
+
|
69
|
+
const hiddenInput = target.querySelector('input[type="hidden"]')
|
70
|
+
if (hiddenInput) this.nameToValidName(hiddenInput)
|
77
71
|
|
78
72
|
if (this.selectedType !== type) {
|
79
73
|
select.selectedIndex = 0
|
@@ -101,11 +95,16 @@ export default class extends Controller {
|
|
101
95
|
const textInput = target.querySelector('input[type="text"]')
|
102
96
|
const hiddenInput = target.querySelector('input[type="hidden"]')
|
103
97
|
|
104
|
-
|
105
|
-
|
98
|
+
this.validNameToName(textInput)
|
99
|
+
this.validNameToName(hiddenInput)
|
106
100
|
} else {
|
107
101
|
const select = target.querySelector('select')
|
108
|
-
|
102
|
+
const hiddenInput = target.querySelector('input[type="hidden"]')
|
103
|
+
this.validNameToName(select)
|
104
|
+
|
105
|
+
if (hiddenInput) {
|
106
|
+
this.validNameToName(hiddenInput)
|
107
|
+
}
|
109
108
|
}
|
110
109
|
}
|
111
110
|
|
@@ -122,7 +121,16 @@ export default class extends Controller {
|
|
122
121
|
} else if (target) {
|
123
122
|
try {
|
124
123
|
target.querySelector('select').setAttribute('name', '')
|
124
|
+
target.querySelector('input[type="hidden"]').setAttribute('name', '')
|
125
125
|
} catch (error) {}
|
126
126
|
}
|
127
127
|
}
|
128
|
+
|
129
|
+
validNameToName(target) {
|
130
|
+
target.setAttribute('name', target.getAttribute('valid-name'))
|
131
|
+
}
|
132
|
+
|
133
|
+
nameToValidName(target) {
|
134
|
+
target.setAttribute('valid-name', target.getAttribute('name'))
|
135
|
+
}
|
128
136
|
}
|
@@ -1,8 +1,14 @@
|
|
1
1
|
import { Controller } from 'stimulus'
|
2
2
|
import { DateTime } from 'luxon'
|
3
|
-
import { castBoolean } from '../../helpers/cast_boolean'
|
4
3
|
import flatpickr from 'flatpickr'
|
5
4
|
|
5
|
+
import { castBoolean } from '../../helpers/cast_boolean'
|
6
|
+
|
7
|
+
// Get the DateTime with the TZ offset applied.
|
8
|
+
function universalTimestamp(timestampStr) {
|
9
|
+
return new Date(new Date(timestampStr).getTime() + (new Date(timestampStr).getTimezoneOffset() * 60 * 1000))
|
10
|
+
}
|
11
|
+
|
6
12
|
export default class extends Controller {
|
7
13
|
static targets = ['input']
|
8
14
|
|
@@ -43,7 +49,9 @@ export default class extends Controller {
|
|
43
49
|
// this.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
44
50
|
options.appTimezone = this.inputTarget.dataset.timezone
|
45
51
|
} else {
|
46
|
-
|
52
|
+
// Because the browser treats the date like a timestamp and updates it ot 00:00 hour, when on a western timezone the date will be converted with one day offset.
|
53
|
+
// Ex: 2022-01-30 will render as 2022-01-29 on an American timezone
|
54
|
+
currentValue = universalTimestamp(this.inputTarget.value)
|
47
55
|
}
|
48
56
|
|
49
57
|
options.defaultDate = currentValue
|
@@ -1,9 +1,9 @@
|
|
1
1
|
import { Controller } from 'stimulus'
|
2
2
|
|
3
3
|
export default class extends Controller {
|
4
|
-
static targets = ['panel']
|
4
|
+
static targets = ['panel'];
|
5
5
|
|
6
|
-
checkbox = {}
|
6
|
+
checkbox = {};
|
7
7
|
|
8
8
|
get actionsPanelPresent() {
|
9
9
|
return this.actionsButtonElement !== null
|
@@ -17,6 +17,12 @@ export default class extends Controller {
|
|
17
17
|
}
|
18
18
|
}
|
19
19
|
|
20
|
+
get actionLinks() {
|
21
|
+
return document.querySelectorAll(
|
22
|
+
'.js-actions-dropdown a[data-actions-picker-target="resourceAction"]',
|
23
|
+
)
|
24
|
+
}
|
25
|
+
|
20
26
|
set currentIds(value) {
|
21
27
|
this.stateHolderElement.dataset.selectedResources = JSON.stringify(value)
|
22
28
|
|
@@ -32,8 +38,12 @@ export default class extends Controller {
|
|
32
38
|
connect() {
|
33
39
|
this.resourceName = this.element.dataset.resourceName
|
34
40
|
this.resourceId = this.element.dataset.resourceId
|
35
|
-
this.actionsButtonElement = document.querySelector(
|
36
|
-
|
41
|
+
this.actionsButtonElement = document.querySelector(
|
42
|
+
`[data-actions-dropdown-button="${this.resourceName}"]`,
|
43
|
+
)
|
44
|
+
this.stateHolderElement = document.querySelector(
|
45
|
+
`[data-selected-resources-name="${this.resourceName}"]`,
|
46
|
+
)
|
37
47
|
}
|
38
48
|
|
39
49
|
addToSelected() {
|
@@ -45,7 +55,9 @@ export default class extends Controller {
|
|
45
55
|
}
|
46
56
|
|
47
57
|
removeFromSelected() {
|
48
|
-
this.currentIds = this.currentIds.filter(
|
58
|
+
this.currentIds = this.currentIds.filter(
|
59
|
+
(item) => item.toString() !== this.resourceId,
|
60
|
+
)
|
49
61
|
}
|
50
62
|
|
51
63
|
toggle(event) {
|
@@ -59,22 +71,20 @@ export default class extends Controller {
|
|
59
71
|
}
|
60
72
|
|
61
73
|
enableResourceActions() {
|
62
|
-
|
63
|
-
.
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
})
|
74
|
+
this.actionLinks.forEach((link) => {
|
75
|
+
link.classList.add('text-gray-700')
|
76
|
+
link.classList.remove('text-gray-500')
|
77
|
+
link.setAttribute('data-href', link.getAttribute('href'))
|
78
|
+
link.dataset.disabled = false
|
79
|
+
})
|
69
80
|
}
|
70
81
|
|
71
82
|
disableResourceActions() {
|
72
|
-
|
73
|
-
.
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
})
|
83
|
+
this.actionLinks.forEach((link) => {
|
84
|
+
link.classList.remove('text-gray-700')
|
85
|
+
link.classList.add('text-gray-500')
|
86
|
+
link.setAttribute('href', link.getAttribute('data-href'))
|
87
|
+
link.dataset.disabled = true
|
88
|
+
})
|
79
89
|
}
|
80
90
|
}
|
@@ -82,7 +82,9 @@ export default class extends Controller {
|
|
82
82
|
|
83
83
|
document.querySelector('.aa-DetachedOverlay').remove()
|
84
84
|
|
85
|
-
this.
|
85
|
+
if (this.hasClearButtonTarget) {
|
86
|
+
this.clearButtonTarget.classList.remove('hidden')
|
87
|
+
}
|
86
88
|
} else {
|
87
89
|
Turbo.visit(item._url, { action: 'advance' })
|
88
90
|
}
|
@@ -157,7 +159,7 @@ export default class extends Controller {
|
|
157
159
|
this.buttonTarget.onclick = () => this.showSearchPanel()
|
158
160
|
|
159
161
|
this.clearValueTargets.forEach((target) => {
|
160
|
-
if (target.getAttribute('value')) {
|
162
|
+
if (target.getAttribute('value') && this.hasClearButtonTarget) {
|
161
163
|
this.clearButtonTarget.classList.remove('hidden')
|
162
164
|
}
|
163
165
|
})
|
@@ -184,6 +186,9 @@ export default class extends Controller {
|
|
184
186
|
},
|
185
187
|
})
|
186
188
|
|
187
|
-
|
189
|
+
// When using search for belongs-to
|
190
|
+
if (this.buttonTarget.dataset.shouldBeDisabled !== 'true') {
|
191
|
+
this.buttonTarget.removeAttribute('disabled')
|
192
|
+
}
|
188
193
|
}
|
189
194
|
}
|
@@ -1,3 +1,9 @@
|
|
1
1
|
<% if name.present? %><turbo-frame id="<%= name %>"><% end %>
|
2
|
-
|
2
|
+
<%
|
3
|
+
# When rendering the frames the flashed content gets lost.
|
4
|
+
# By including the alerts partial, the stimulus will pick them up and display them to the user.
|
5
|
+
%>
|
6
|
+
<%= render partial: 'avo/partials/alerts' if flash.present? && name.present? %>
|
7
|
+
|
8
|
+
<%= yield %>
|
3
9
|
<% if name.present? %></turbo-frame><% end %>
|
@@ -11,7 +11,7 @@
|
|
11
11
|
|
12
12
|
<div class="flex-1 flex items-center justify-center px-8 text-lg mt-8 mb-12">
|
13
13
|
<div class="flex-1 flex flex-col items-center justify-center px-24 text-base">
|
14
|
-
<%= form.select :related_id, options_for_select(@options
|
14
|
+
<%= form.select :related_id, options_for_select(@options, nil),
|
15
15
|
{
|
16
16
|
include_blank: t('avo.choose_an_option'),
|
17
17
|
},
|
@@ -1,8 +1,6 @@
|
|
1
1
|
<div class="application-sidebar flex h-full bg-white text-white w-56 z-50 border-r border-gray-300">
|
2
2
|
<div class="flex flex-col w-full">
|
3
|
-
<%=
|
4
|
-
<%= render partial: "avo/partials/logo" %>
|
5
|
-
<% end %>
|
3
|
+
<%= render partial: "avo/partials/logo" %>
|
6
4
|
|
7
5
|
<div class="flex-1 flex flex-col justify-between">
|
8
6
|
<div class="tools py-4">
|