pg_rails 7.2.3 → 7.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/pg_associable/app/helpers/pg_associable/form_builder_methods.rb +9 -2
  3. data/pg_associable/app/helpers/pg_associable/helpers.rb +4 -5
  4. data/pg_associable/app/javascript/modal_controller.js +80 -6
  5. data/pg_associable/spec/system/associable_spec.rb +30 -7
  6. data/pg_engine/app/assets/stylesheets/pg_rails_b5.scss +5 -0
  7. data/pg_engine/app/components/actions_component.rb +14 -0
  8. data/pg_engine/app/components/asociable_modal_component.html.slim +6 -0
  9. data/pg_engine/app/components/asociable_modal_component.rb +7 -0
  10. data/pg_engine/app/components/date_selector_component.rb +2 -2
  11. data/pg_engine/app/components/modal_component.html.slim +5 -0
  12. data/pg_engine/app/components/modal_component.rb +11 -0
  13. data/pg_engine/app/components/modal_content_component.rb +29 -0
  14. data/pg_engine/app/components/search_bar_component.html.slim +1 -1
  15. data/pg_engine/app/controllers/admin/accounts_controller.rb +1 -1
  16. data/pg_engine/app/controllers/admin/email_logs_controller.rb +1 -1
  17. data/pg_engine/app/controllers/admin/emails_controller.rb +4 -21
  18. data/pg_engine/app/controllers/admin/user_accounts_controller.rb +1 -1
  19. data/pg_engine/app/controllers/admin/users_controller.rb +1 -1
  20. data/pg_engine/app/controllers/concerns/pg_engine/resource.rb +288 -133
  21. data/pg_engine/app/controllers/pg_engine/base_controller.rb +5 -0
  22. data/pg_engine/app/controllers/public/mensaje_contactos_controller.rb +1 -1
  23. data/pg_engine/app/decorators/pg_engine/base_record_decorator.rb +36 -10
  24. data/pg_engine/app/helpers/pg_engine/flash_helper.rb +6 -2
  25. data/pg_engine/app/helpers/pg_engine/form_helper.rb +15 -2
  26. data/pg_engine/app/helpers/pg_engine/frame_helper.rb +52 -0
  27. data/pg_engine/app/lib/pg_engine/bootstrap5_breadcrumbs_builder.rb +22 -0
  28. data/pg_engine/app/lib/pg_engine/filtros_builder.rb +3 -2
  29. data/pg_engine/app/models/current.rb +1 -1
  30. data/pg_engine/app/models/email.rb +2 -0
  31. data/pg_engine/app/models/pg_engine/base_record.rb +13 -0
  32. data/pg_engine/app/views/admin/accounts/_form.html.slim +1 -4
  33. data/pg_engine/app/views/admin/email_logs/_form.html.slim +2 -5
  34. data/pg_engine/app/views/admin/emails/_form.html.slim +13 -13
  35. data/pg_engine/app/views/admin/emails/_send.html.slim +0 -1
  36. data/pg_engine/app/views/admin/emails/show.html.slim +19 -20
  37. data/pg_engine/app/views/admin/eventos/new.html.slim +0 -2
  38. data/pg_engine/app/views/admin/user_accounts/_form.html.slim +1 -4
  39. data/pg_engine/app/views/admin/users/_form.html.slim +1 -4
  40. data/pg_engine/app/views/pg_engine/base/edit.html.slim +1 -2
  41. data/pg_engine/app/views/pg_engine/base/index.html.slim +6 -3
  42. data/pg_engine/app/views/pg_engine/base/new.html.slim +1 -2
  43. data/pg_engine/app/views/public/mensaje_contactos/new.html.slim +2 -5
  44. data/pg_engine/config/initializers/kaminari.rb +3 -0
  45. data/pg_engine/config/initializers/ransack_memory.rb +13 -2
  46. data/pg_engine/config/locales/es.yml +1 -1
  47. data/pg_engine/lib/pg_engine/utils/pg_logger.rb +10 -10
  48. data/pg_engine/spec/controllers/admin/accounts_controller_spec.rb +2 -2
  49. data/pg_engine/spec/controllers/admin/email_logs_controller_spec.rb +2 -2
  50. data/pg_engine/spec/controllers/admin/emails_controller_spec.rb +2 -2
  51. data/pg_engine/spec/controllers/admin/user_accounts_controller_spec.rb +2 -2
  52. data/pg_engine/spec/controllers/admin/users_controller_spec.rb +2 -2
  53. data/pg_engine/spec/controllers/concerns/pg_engine/resource_helper_spec.rb +0 -2
  54. data/pg_engine/spec/lib/pg_engine/form_helper_spec.rb +51 -0
  55. data/pg_engine/spec/system/breadcrumbs_spec.rb +61 -0
  56. data/pg_engine/spec/system/destroy_spec.rb +1 -1
  57. data/pg_engine/spec/system/login_spec.rb +1 -1
  58. data/pg_engine/spec/system/modal_windows_spec.rb +89 -0
  59. data/pg_engine/spec/system/send_mail_spec.rb +1 -1
  60. data/pg_layout/app/javascript/application.js +38 -0
  61. data/pg_layout/app/javascript/config/turbo_rails/index.js +4 -1
  62. data/pg_layout/app/javascript/controllers/embedded_frame_controller.js +10 -0
  63. data/pg_layout/app/javascript/controllers/index.js +2 -0
  64. data/pg_layout/app/javascript/controllers/popover_toggler_controller.js +3 -2
  65. data/pg_layout/app/javascript/controllers/tooltip_controller.js +8 -0
  66. data/pg_layout/app/javascript/elements/index.js +1 -0
  67. data/pg_layout/app/javascript/elements/pg_event.js +13 -0
  68. data/pg_layout/app/views/devise/confirmations/new.html.erb +0 -1
  69. data/pg_layout/app/views/devise/passwords/edit.html.erb +0 -2
  70. data/pg_layout/app/views/devise/passwords/new.html.erb +0 -2
  71. data/pg_layout/app/views/devise/registrations/edit.html.erb +0 -2
  72. data/pg_layout/app/views/devise/registrations/new.html.erb +0 -2
  73. data/pg_layout/app/views/devise/unlocks/new.html.erb +0 -1
  74. data/pg_layout/app/views/layouts/pg_layout/base.html.slim +45 -17
  75. data/pg_layout/app/views/layouts/pg_layout/containerized.html.slim +1 -1
  76. data/pg_rails/lib/version.rb +1 -1
  77. data/pg_rails/scss/bootstrap_overrides.scss +2 -1
  78. data/pg_rails/scss/pg_rails.scss +8 -1
  79. data/pg_scaffold/lib/generators/pg_rspec/scaffold/templates/controller_spec.rb +2 -2
  80. data/pg_scaffold/lib/generators/pg_scaffold/templates/controller.rb +2 -4
  81. data/pg_scaffold/lib/generators/pg_slim/templates/_form.html.slim +2 -5
  82. data/pg_scaffold/lib/generators/pg_slim/templates/show.html.slim +1 -1
  83. metadata +17 -4
  84. data/pg_associable/app/views/pg_engine/base/_pg_associable_modal.html.slim +0 -14
  85. data/pg_layout/app/views/pg_layout/_modal_show.html.slim +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c2505003e224d063bd2c51742b431b315cef9e83b08c867adb0687f9184ad559
4
- data.tar.gz: 0d3e3c74ef4c00f0e369302a592fccc0749ddb53c7143abd0be4dc1cf7da6418
3
+ metadata.gz: ad67165e1aad7f3b3e31dd236706d4b923eb2139740a095018257899b43928a7
4
+ data.tar.gz: f3d907a47d97cb7abbfb0e148bb2c2e9466a5255284f14f06c0363966e35e46b
5
5
  SHA512:
6
- metadata.gz: 64ee127677a3f745bd0d29b8fac8150b03470d4cd03d6d6b37078168a3bfd43ac3332f085942bb9d0ea94b164fdd863f65df76c116f8e1ff708ebe012ac31348
7
- data.tar.gz: 6cb3a28799ddd2a7c2fb621a1875b246b825355cd0e7b3a66925030138f46b0d81e1d5c0fa22ce29c4c5c747984145076af5a89085891112230ed34ebcd61732
6
+ metadata.gz: 3d8482db6b3b8455c819d4db06db374e40785cec70a9737bfca652c2368bddad17ca7a2d3656e1632b48b0ebeb03c5142e83855d3d70cfd9d043de3e7d34609d
7
+ data.tar.gz: e6e2457ac2a84097209da469aa76dd2538e72c9da05ed383884c19fceddc071b5dc515a82c444c4f3487d7912ca79790bd6eb06f0eed0ae0c4caf48e8797884f
@@ -16,6 +16,14 @@ module PgAssociable
16
16
  end
17
17
 
18
18
  def pg_associable(atributo, options = {})
19
+ # Si es new y tiene el nested asignado, no permito que se modifique
20
+ # porque de todos modos se pisaría en el create
21
+ if !object.persisted? &&
22
+ template.nested_record.present? &&
23
+ object.send(template.nested_key) == (template.nested_record.id)
24
+ options[:disabled] = true
25
+ end
26
+
19
27
  return input(atributo, options) if options[:disabled]
20
28
 
21
29
  collection, puede_crear = collection_pc(atributo, options)
@@ -31,8 +39,7 @@ module PgAssociable
31
39
  def collection_pc(atributo, _options)
32
40
  klass = clase_asociacion(atributo)
33
41
  user = Current.user
34
- in_modal = options[:asociable].present?
35
- puede_crear = !in_modal && Pundit::PolicyFinder.new(klass).policy.new(user, klass).new?
42
+ puede_crear = !template.using_modal? && Pundit::PolicyFinder.new(klass).policy.new(user, klass).new?
36
43
  collection = Pundit::PolicyFinder.new(klass).scope.new(user, klass).resolve
37
44
  [collection, puede_crear]
38
45
  end
@@ -3,11 +3,10 @@ module PgAssociable
3
3
  MAX_RESULTS = 8
4
4
 
5
5
  def pg_respond_abrir_modal
6
- respond_to do |format|
7
- format.turbo_stream do
8
- render turbo_stream: turbo_stream.append_all('body', partial: 'pg_associable_modal')
9
- end
10
- end
6
+ src = @clase_modelo.new.decorate.new_object_url
7
+ content = ModalContentComponent.new(src:).render_in(view_context)
8
+ modal = AsociableModalComponent.new(modal_id: params[:id]).with_content(content)
9
+ render turbo_stream: turbo_stream.append_all('body', modal)
11
10
  end
12
11
 
13
12
  def pg_respond_buscar
@@ -3,9 +3,9 @@ import * as bootstrap from 'bootstrap'
3
3
 
4
4
  export default class extends Controller {
5
5
  static outlets = ['asociable']
6
- static targets = ['response']
7
6
 
8
7
  modalPuntero = null
8
+ history = []
9
9
 
10
10
  connect (e) {
11
11
  this.modalPuntero = new bootstrap.Modal(this.element)
@@ -15,23 +15,97 @@ export default class extends Controller {
15
15
  })
16
16
  }
17
17
  this.modalPuntero.show()
18
+
19
+ this.element.addEventListener('turbo:frame-render', (ev) => {
20
+ if (ev.detail.fetchResponse.response.ok && ev.target.id === 'modal_content') {
21
+ this.history.push(ev.target.src)
22
+ }
23
+ })
24
+
25
+ this.element.addEventListener('pg:record-created', (ev) => {
26
+ const el = ev.data
27
+ if (this.asociableOutlets.length > 0) {
28
+ const newObject = JSON.parse(el.dataset.response)
29
+ this.asociableOutlet.completarCampo(newObject)
30
+ ev.stopPropagation()
31
+ this.remove()
32
+ } else {
33
+ this.back(ev)
34
+ }
35
+ })
36
+
37
+ this.element.addEventListener('pg:record-updated', (ev) => {
38
+ this.back(ev)
39
+ })
40
+
41
+ this.element.addEventListener('pg:record-destroyed', (ev) => {
42
+ this.remove()
43
+ })
44
+
18
45
  document.addEventListener('turbo:before-cache', () => {
19
46
  this.element.remove()
20
47
  }, { once: true })
21
48
  }
22
49
 
23
- responseTargetConnected (e) {
24
- const newObject = JSON.parse(e.dataset.response)
25
- this.asociableOutlet.completarCampo(newObject)
26
- this.element.remove()
50
+ maximize (ev) {
51
+ const dialog = this.element.querySelector('.modal-dialog')
52
+ dialog.classList.toggle('modal-fullscreen')
53
+ const button = ev.currentTarget
54
+ const icon = button.querySelector('i')
55
+ icon.classList.toggle('bi-fullscreen')
56
+ icon.classList.toggle('bi-fullscreen-exit')
57
+
58
+ const tooltip = this.application.getControllerForElementAndIdentifier(button, 'tooltip')
59
+ if (tooltip) {
60
+ tooltip.hide()
61
+ if (icon.classList.contains('bi-fullscreen')) {
62
+ tooltip.setContent('Maximizar')
63
+ } else {
64
+ tooltip.setContent('Restaurar')
65
+ }
66
+ }
67
+ }
68
+
69
+ reloadTop () {
70
+ // FIXME: rename to main?
71
+ const topFrame = document.querySelector('#top')
72
+ if (topFrame.attributes.src) {
73
+ topFrame.reload()
74
+ } else {
75
+ topFrame.setAttribute('src', window.location)
76
+ }
77
+ }
78
+
79
+ back (ev) {
80
+ this.history.pop()
81
+ if (this.history.length > 0) {
82
+ const url = this.history[this.history.length - 1]
83
+ const frame = this.element.querySelector('#modal_content')
84
+ frame.src = url
85
+ frame.innerHTML = '<div style="min-height: 30em">Cargando...</div>'
86
+ } else {
87
+ this.modalPuntero.hide()
88
+ }
89
+ ev.stopPropagation()
90
+ this.reloadTop()
27
91
  }
28
92
 
29
93
  openModal () {
30
94
  this.modalPuntero.show()
31
95
  }
32
96
 
97
+ remove () {
98
+ this.element.remove()
99
+ }
100
+
33
101
  disconnect (e) {
34
- this.modalPuntero.hide()
102
+ // Antes, en lugar de quitar el modal-backdrop:
103
+ // this.modalPuntero.hide()
104
+ // pero tiraba a veces error:
105
+ // TypeError: can't convert null to object, _isWithActiveTrigger
106
+ document.querySelectorAll('.modal-backdrop').forEach((el) => {
107
+ el.remove()
108
+ })
35
109
  document.dispatchEvent(new Event('hidden.bs.modal'))
36
110
  this.modalPuntero.dispose()
37
111
  }
@@ -3,22 +3,45 @@ require 'rails_helper'
3
3
  describe 'Associable' do
4
4
  let(:user) { create :user, :developer }
5
5
 
6
+ let(:path) { '/admin/cosas/new' }
7
+
6
8
  before do
7
9
  login_as user
8
- driven_by :selenium
9
- visit '/admin/cosas/new'
10
- fill_in 'cosa_nombre', with: 'La cosa'
11
- select 'Los', from: 'cosa_tipo'
12
- find('.cosa_categoria_de_cosa input[type=text]').click
10
+ visit path
13
11
  end
14
12
 
15
13
  it do
14
+ fill_in 'cosa_nombre', with: 'La cosa'
15
+ select 'Los', from: 'cosa_tipo'
16
+ find('.cosa_categoria_de_cosa input[type=text]').click
16
17
  expect(page).to have_text :all, 'Nuevo'
17
18
  find('.cosa_categoria_de_cosa .list-group-item').click
18
19
  fill_in 'categoria_de_cosa_nombre', with: 'la categoría'
19
20
  select 'Completar', from: 'categoria_de_cosa_tipo'
20
- click_on 'Crear Categoría de cosa'
21
- click_on 'Crear Coso'
21
+ click_on 'Agregar Categoría de cosa'
22
+ click_on 'Cargar Coso'
22
23
  expect(page).to have_text 'Creado por'
23
24
  end
25
+
26
+ context 'cuando crea desde el nested' do
27
+ let!(:categ) { create :categoria_de_cosa }
28
+ let(:path) { "/admin/categoria_de_cosas/#{categ.hashid}/cosas/new" }
29
+
30
+ it do
31
+ input = find_by_id('cosa_categoria_de_cosa')
32
+ expect(input).to be_disabled
33
+ end
34
+ end
35
+
36
+ context 'cuando edita desde el nested' do
37
+ let!(:categ) { create :categoria_de_cosa }
38
+ let!(:cosa) { create :cosa, categoria_de_cosa: categ }
39
+ let(:path) { "/admin/categoria_de_cosas/#{categ.hashid}/cosas/#{cosa.id}/edit" }
40
+
41
+ it do
42
+ ele = find_by_id('cosa_categoria_de_cosa_id', visible: :all)
43
+ input = ele.sibling('input[type=text]')
44
+ expect(input).not_to be_disabled
45
+ end
46
+ end
24
47
  end
@@ -9,6 +9,11 @@
9
9
  --bs-form-invalid-border-color: #b50000;
10
10
  }
11
11
 
12
+ // Navs
13
+ nav[aria-label=breadcrumb] a {
14
+ font-weight: bold;
15
+ }
16
+
12
17
  // Toasts
13
18
  .toast.show {
14
19
  display: inline-block;
@@ -0,0 +1,14 @@
1
+ class ActionsComponent < ViewComponent::Base
2
+ def initialize(record)
3
+ @record = record.decorate
4
+
5
+ super
6
+ end
7
+
8
+ erb_template <<~ERB
9
+ <div>
10
+ <%= @record.edit_link %>
11
+ <%= @record.destroy_link_redirect %>
12
+ </div>
13
+ ERB
14
+ end
@@ -0,0 +1,6 @@
1
+ .modal[class="#{@klass} modal-#{@modal_id}" tabindex="-1" data-controller="modal"
2
+ data-modal-asociable-outlet=".asociable-#{@modal_id}"
3
+ data-turbo-temporary="true"]
4
+ .modal-dialog
5
+ .modal-content
6
+ = content
@@ -0,0 +1,7 @@
1
+ class AsociableModalComponent < ModalComponent
2
+ def initialize(modal_id: nil, **)
3
+ @modal_id = modal_id
4
+
5
+ super(**)
6
+ end
7
+ end
@@ -3,9 +3,9 @@ class DateSelectorComponent < ViewComponent::Base
3
3
  @field_id = field_id
4
4
 
5
5
  @types = [
6
- ['Días corridos (L a D)', 'calendar_days'],
7
- ['Días hábiles (L a V)', 'business_days'],
8
6
  ['Días hábiles no feriados', 'business_days_excluding_holidays'],
7
+ ['Días hábiles (L a V)', 'business_days'],
8
+ ['Días corridos (L a D)', 'calendar_days'],
9
9
  ['Semanas', 'weeks'],
10
10
  ['Meses', 'months']
11
11
  ]
@@ -0,0 +1,5 @@
1
+ .modal[class="#{@klass}" tabindex="-1" data-controller="modal"
2
+ data-remove-on-hide="true" data-turbo-temporary="true"]
3
+ .modal-dialog
4
+ .modal-content
5
+ = content
@@ -0,0 +1,11 @@
1
+ class ModalComponent < ViewComponent::Base
2
+ def initialize(klass: 'modal-xl')
3
+ @klass = klass
4
+
5
+ super
6
+ end
7
+
8
+ def before_render
9
+ controller.instance_variable_set(:@using_modal, true)
10
+ end
11
+ end
@@ -0,0 +1,29 @@
1
+ class ModalContentComponent < ViewComponent::Base
2
+ def initialize(src: nil)
3
+ @src = src
4
+ with_content(loading_html) if @src.present?
5
+
6
+ super
7
+ end
8
+
9
+ def loading_html
10
+ <<~HTML.html_safe
11
+ <div class="text-center text-body-secondary fs-3" style="min-height: 15em">
12
+ Cargando...
13
+ </div>
14
+ HTML
15
+ end
16
+
17
+ erb_template <<~ERB
18
+ <div class="modal-body">
19
+ <div class="d-flex justify-content-around sticky-top">
20
+ <div class="flash position-relative w-100 d-flex justify-content-center">
21
+ </div>
22
+ </div>
23
+ <%= helpers.turbo_frame_tag :modal_content,
24
+ **{ src: @src, refresh: :morph }.compact do %>
25
+ <%= content %>
26
+ <% end %>
27
+ </div>
28
+ ERB
29
+ end
@@ -3,7 +3,7 @@
3
3
  .d-flex.align-items-center.px-3.py-2
4
4
  .px-2.d-none.d-sm-inline-block
5
5
  span.bi.bi-funnel-fill
6
- = helpers.search_form_for @q, url: url_for, html: { 'data-turbo-action': :advance } do |f|
6
+ = helpers.search_form_for @q, url: url_for do |f|
7
7
  .row.g-1
8
8
  .col
9
9
  .row.g-1
@@ -6,7 +6,7 @@ module Admin
6
6
  class AccountsController < AdminController
7
7
  include PgEngine::Resource
8
8
 
9
- before_action { @clase_modelo = Account }
9
+ self.clase_modelo = Account
10
10
 
11
11
  before_action(only: :index) { authorize Account }
12
12
 
@@ -6,7 +6,7 @@ module Admin
6
6
  class EmailLogsController < AdminController
7
7
  include PgEngine::Resource
8
8
 
9
- before_action { @clase_modelo = EmailLog }
9
+ self.clase_modelo = EmailLog
10
10
 
11
11
  before_action(only: :index) { authorize EmailLog }
12
12
 
@@ -6,7 +6,7 @@ module Admin
6
6
  class EmailsController < AdminController
7
7
  include PgEngine::Resource
8
8
 
9
- before_action { @clase_modelo = Email }
9
+ self.clase_modelo = Email
10
10
 
11
11
  before_action(only: :index) { authorize Email }
12
12
 
@@ -14,26 +14,9 @@ module Admin
14
14
 
15
15
  add_breadcrumb Email.nombre_plural, :admin_emails_path
16
16
 
17
- def new
18
- render template: 'admin/emails/_send', locals: { email: @email },
19
- layout: 'pg_layout/containerized'
20
- end
21
-
22
- def create
23
- saved = false
24
- ActiveRecord::Base.transaction do
25
- # TODO: acá la transaction jode porque el ActiveJob no puede deserializar el Email
26
- # Con rails 7.2 esto se debería arreglar
27
- if (saved = @email.save)
28
- PgEngine::AdminMailer.with(email_object: @email).admin_mail.deliver_later
29
- end
30
- end
31
- if saved
32
- redirect_to @email.decorate.target_object
33
- else
34
- render template: 'admin/emails/_send',
35
- layout: 'pg_layout/containerized', status: :unprocessable_entity,
36
- locals: { email: @email }
17
+ after_action only: :create do
18
+ if @saved
19
+ PgEngine::AdminMailer.with(email_object: @email).admin_mail.deliver_later
37
20
  end
38
21
  end
39
22
 
@@ -6,7 +6,7 @@ module Admin
6
6
  class UserAccountsController < AdminController
7
7
  include PgEngine::Resource
8
8
 
9
- before_action { @clase_modelo = UserAccount }
9
+ self.clase_modelo = UserAccount
10
10
 
11
11
  before_action(only: :index) { authorize UserAccount }
12
12
 
@@ -6,7 +6,7 @@ module Admin
6
6
  class UsersController < AdminController
7
7
  include PgEngine::Resource
8
8
 
9
- before_action { @clase_modelo = User }
9
+ self.clase_modelo = User
10
10
 
11
11
  before_action(only: :index) { authorize User }
12
12