pg_rails 7.0.8.pre.alpha → 7.0.8.pre.alpha.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -0
  3. data/pg_associable/app/assets/stylesheets/pg_associable.scss +39 -40
  4. data/pg_associable/app/helpers/pg_associable/form_builder_methods.rb +45 -1
  5. data/pg_associable/app/helpers/pg_associable/helpers.rb +6 -4
  6. data/pg_associable/app/javascript/asociable_controller.tsx +96 -20
  7. data/pg_associable/app/javascript/modal_controller.js +4 -95
  8. data/pg_associable/app/views/pg_associable/_resultados_inline.html.slim +6 -8
  9. data/pg_associable/app/views/pg_engine/base/_pg_associable_modal.html.slim +5 -32
  10. data/pg_engine/app/assets/stylesheets/pg_rails_b5.scss +49 -41
  11. data/pg_engine/app/controllers/pg_engine/resource_helper.rb +6 -2
  12. data/pg_engine/app/decorators/pg_engine/base_decorator.rb +2 -2
  13. data/pg_engine/app/helpers/pg_engine/flash_helper.rb +2 -2
  14. data/pg_engine/app/helpers/pg_engine/form_helper.rb +70 -0
  15. data/pg_engine/app/helpers/pg_engine/route_helper.rb +14 -6
  16. data/pg_engine/app/views/admin/accounts/_form.html.slim +1 -1
  17. data/pg_engine/app/views/admin/user_accounts/_form.html.slim +1 -1
  18. data/pg_engine/app/views/admin/users/_form.html.slim +1 -1
  19. data/pg_engine/app/views/pg_engine/base/index.html.slim +3 -3
  20. data/pg_engine/config/simple_form/simple_form_bootstrap.rb +17 -9
  21. data/pg_engine/db/migrate/20240222115722_create_active_storage_tables.active_storage.rb +57 -0
  22. data/pg_engine/lib/pg_engine/engine.rb +1 -1
  23. data/pg_engine/lib/pg_engine/route_helpers.rb +1 -0
  24. data/pg_engine/lib/pg_engine/utils/pdf_preview_generator.rb +50 -0
  25. data/pg_engine/lib/pg_engine.rb +3 -0
  26. data/pg_engine/spec/fixtures/test.pdf +0 -0
  27. data/pg_engine/spec/pg_engine/pdf_preview_generator_spec.rb +12 -0
  28. data/pg_layout/app/assets/stylesheets/sidebar.scss +10 -2
  29. data/pg_layout/app/javascript/nested_controller.js +48 -0
  30. data/pg_layout/app/javascript/pg_form_controller.js +13 -0
  31. data/pg_layout/app/javascript/utils.ts +34 -0
  32. data/pg_layout/app/views/layouts/pg_layout/devise.html.slim +1 -1
  33. data/pg_layout/app/views/layouts/pg_layout/layout.html.slim +14 -11
  34. data/pg_layout/app/views/pg_layout/_flash.html.slim +1 -1
  35. data/pg_layout/app/views/pg_layout/_navbar.html.erb +10 -0
  36. data/pg_layout/app/views/pg_layout/_sidebar.html.erb +3 -3
  37. data/pg_layout/index.js +4 -0
  38. data/pg_rails/lib/version.rb +1 -1
  39. data/pg_scaffold/lib/generators/pg_slim/templates/_form.html.slim +2 -2
  40. metadata +9 -3
  41. data/pg_associable/app/views/pg_associable/_resultados.html.slim +0 -11
@@ -6,48 +6,44 @@
6
6
  --bs-form-invalid-border-color: #b50000;
7
7
  }
8
8
 
9
+ // Toasts
10
+ .toast.show {
11
+ display: inline-block;
12
+ }
9
13
 
10
14
  // FORMS
11
- // .pg-form {
12
- // max-width: 300px;
15
+ input[disabled] {
16
+ background-color: #e9e9ed;
17
+ color: black;
18
+ }
13
19
 
14
- input[disabled] {
15
- background-color: #e9e9ed;
16
- color: black;
17
- }
20
+ .form-control,
21
+ .form-select {
22
+ border: 1px solid #a7b7bb;
18
23
 
19
- .form-control,
20
- .form-select {
21
- width: 100%;
22
- height: 35px;
23
- border: 1px solid #a7b7bb;
24
- border-radius: 3px;
24
+ &:focus, &:focus-visible {
25
+ outline: 1px solid color.adjust(#a7b7bb, $saturation: +30%);
26
+ }
27
+ }
28
+ select[multiple] {
29
+ height: inherit;
30
+ }
25
31
 
26
- &.form-control-sm,
27
- &.form-select-sm {
28
- height: 29px;
29
- }
32
+ input[type=date] {
33
+ max-width: 12em;
34
+ }
30
35
 
31
- &.is-invalid {
32
- background-image: none!important; /* override bootstrap forms */
33
- padding-right: 0; /* override bootstrap forms */
34
- }
36
+ input[type=datetime-local], input[type=datetime] {
37
+ max-width: 15em;
38
+ }
35
39
 
36
- &:focus, &:focus-visible {
37
- outline: 1px solid color.adjust(#a7b7bb, $saturation: +30%);
38
- }
39
- }
40
- select[multiple] {
41
- height: inherit;
42
- }
43
- .form-control:focus {
44
- box-shadow: none!important; /* override bootstrap forms */
45
- }
40
+ .form-select:not(:has(option)) {
41
+ background-color: #d5d5d5;
42
+ }
46
43
 
47
- input[type=date] {
48
- max-width: 170px;
49
- }
50
- // }
44
+ .form-control.is-invalid, .form-select.is-invalid {
45
+ background-color: #fff3f3;
46
+ }
51
47
 
52
48
  // LISTADOS
53
49
  .listado {
@@ -56,9 +52,7 @@
56
52
  }
57
53
  }
58
54
  .listado .btn-sm {
59
- min-width: 25px;
60
- height: 25px;
61
- padding: 1px;
55
+ padding: 0em 0.3em;
62
56
  margin-right: 4px;
63
57
  }
64
58
 
@@ -66,9 +60,23 @@
66
60
  .filter {
67
61
  display: inline-block;
68
62
  vertical-align: top;
69
- min-width: 200px;
63
+ max-width: 17em;
64
+ }
70
65
 
71
- .input-group {
72
- width: 230px;
73
- }
66
+ // Modal
67
+ .modal-content {
68
+ box-shadow: 15px 15px 9px 0px rgba(0, 0, 0, 0.6);
69
+ }
70
+
71
+ // Nested
72
+ .link-to-add:hover {
73
+ background-color: #f4f4f4;
74
+ }
75
+
76
+ .link-to-add {
77
+ display: inline-block;
78
+ width: 100%;
79
+ text-align: center;
80
+ border-radius: var(--bs-border-radius);
81
+ padding: 0.6em 0.4em;
74
82
  }
@@ -93,10 +93,12 @@ module PgEngine
93
93
  if params[:asociable]
94
94
  format.turbo_stream do
95
95
  render turbo_stream:
96
- turbo_stream.update('pg-associable-form', <<~HTML
96
+ # rubocop:disable Rails/SkipsModelValidations
97
+ turbo_stream.update_all('.modal.show .pg-associable-form', <<~HTML
97
98
  <div data-modal-target="response" data-response='#{object.decorate.to_json}'></div>
98
99
  HTML
99
100
  )
101
+ # rubocop:enable Rails/SkipsModelValidations
100
102
  end
101
103
  end
102
104
  format.html do
@@ -113,8 +115,10 @@ module PgEngine
113
115
  # self.instancia_modelo = instancia_modelo.decorate
114
116
  if params[:asociable]
115
117
  format.turbo_stream do
118
+ # rubocop:disable Rails/SkipsModelValidations
116
119
  render turbo_stream:
117
- turbo_stream.update('pg-associable-form', partial: 'form', locals: { asociable: true })
120
+ turbo_stream.update_all('.modal.show .pg-associable-form', partial: 'form', locals: { asociable: true })
121
+ # rubocop:enable Rails/SkipsModelValidations
118
122
  end
119
123
  end
120
124
  format.html { render :new, status: :unprocessable_entity }
@@ -12,8 +12,8 @@ module PgEngine
12
12
 
13
13
  delegate_all
14
14
 
15
- def as_json(_options = {})
16
- object.as_json.tap { |o| o[:to_s] = to_s }
15
+ def as_json(options = {})
16
+ object.as_json(options).tap { |o| o[:to_s] = to_s }
17
17
  end
18
18
 
19
19
  # rubocop:disable Style/MissingRespondToMissing
@@ -14,9 +14,9 @@ module PgEngine
14
14
  case flash_type
15
15
  when 'notice'
16
16
  'info'
17
- when 'error'
18
- 'danger'
19
17
  when 'alert'
18
+ 'danger'
19
+ when 'warning'
20
20
  'warning'
21
21
  when 'success'
22
22
  'success'
@@ -29,5 +29,75 @@ module PgEngine
29
29
  uri.path = "#{uri.path}.#{formato}"
30
30
  uri.to_s
31
31
  end
32
+
33
+ # This method creates a link with `data-id` `data-fields` attributes.
34
+ # These attributes are used to create new instances of the nested fields through Javascript.
35
+ def link_to_add_fields(name, form, association, required: false)
36
+ # Takes an object (@person) and creates a new instance of its associated model (:addresses)
37
+ # To better understand, run the following in your terminal:
38
+ # rails c --sandbox
39
+ # @person = Person.new
40
+ # new_object = @person.send(:addresses).klass.new
41
+ new_object = form.object.send(association).klass.new
42
+
43
+ # Saves the unique ID of the object into a variable.
44
+ # This is needed to ensure the key of the associated array is unique.
45
+ # This is makes parsing the content in the `data-fields` attribute easier through Javascript.
46
+ # We could use another method to achive this.
47
+ id = new_object.object_id
48
+
49
+ # https://api.rubyonrails.org/ fields_for(record_name, record_object = nil, fields_options = {}, &block)
50
+ # record_name = :addresses
51
+ # record_object = new_object
52
+ # fields_options = { child_index: id }
53
+ # child_index` is used to ensure the key of the associated array is unique,
54
+ # and that it matched the value in the `data-id` attribute.
55
+ # `person[addresses_attributes][child_index_value][_destroy]`
56
+ fields =
57
+ form.fields_for(association, new_object, child_index: id) do |builder|
58
+ # `association.to_s.singularize + "_fields"` ends up evaluating to `address_fields`
59
+ # The render function will then look for `views/people/_address_fields.html.erb`
60
+ # The render function also needs to be passed the value of 'builder', because
61
+ # `views/people/_address_fields.html.erb` needs this to render the form tags.
62
+ render("#{association.to_s.singularize}_fields", f: builder)
63
+ end
64
+
65
+ # This renders a simple link, but passes information into `data` attributes.
66
+ # This info can be named anything we want, but in this case we chose `data-id:` and `data-fields:`.
67
+ # The `id:` is from `new_object.object_id`.
68
+ # The `fields:` are rendered from the `fields` blocks.
69
+ # We use `gsub("\n", "")` to remove anywhite space from the rendered partial.
70
+ # The `id:` value needs to match the value used in `child_index: id`.
71
+ link_to(
72
+ 'javascript:void(0)',
73
+ class: 'link-to-add',
74
+ data: {
75
+ controller: 'nested',
76
+ action: 'nested#addItem',
77
+ id:,
78
+ required:,
79
+ fields: fields.gsub("\n", '')
80
+ }
81
+ ) do
82
+ # rubocop:disable Rails/OutputSafety
83
+ "<i class=\"bi bi-plus-lg\"></i> #{name}".html_safe
84
+ # rubocop:enable Rails/OutputSafety
85
+ end
86
+ end
87
+
88
+ def link_to_remove(text = nil, &)
89
+ if block_given?
90
+ link_to('javascript:void(0)', class: 'link-to-remove text-danger-emphasis', title: 'Quitar',
91
+ data: { controller: 'nested', action: 'nested#quitar' }, &)
92
+ elsif text.present?
93
+ link_to text, 'javascript:void(0)', class: 'link-to-remove text-danger-emphasis', title: 'Quitar',
94
+ data: { controller: 'nested', action: 'nested#quitar' }
95
+ else
96
+ link_to 'javascript:void(0)', class: 'link-to-remove text-danger-emphasis', title: 'Quitar',
97
+ data: { controller: 'nested', action: 'nested#quitar' } do
98
+ '<i class="bi bi-x-lg"></i>'.html_safe
99
+ end
100
+ end
101
+ end
32
102
  end
33
103
  end
@@ -18,7 +18,7 @@ module PgEngine
18
18
 
19
19
  def self.namespace(context)
20
20
  req = request(context)
21
- route = Rails.application.routes.recognize_path(req.path)
21
+ route = Rails.application.routes.recognize_path(req.path, method: req.env['REQUEST_METHOD'])
22
22
  parts = route[:controller].split('/')
23
23
  return unless parts.length > 1
24
24
 
@@ -32,12 +32,20 @@ module PgEngine
32
32
  NamespaceDeductor.namespace(self)
33
33
  end
34
34
 
35
- def namespaced_path(object, prefix: nil, suffix: nil)
35
+ def namespaced_path(object, options = {})
36
36
  target = [pg_namespace, object]
37
- target.prepend prefix if prefix
38
- target.append suffix if suffix
39
- target = target.flatten.compact
40
- polymorphic_url(target, only_path: true)
37
+
38
+ if options[:prefix]
39
+ target.prepend options[:prefix]
40
+ options.delete(:prefix)
41
+ end
42
+
43
+ if options[:suffix]
44
+ target.append options[:suffix]
45
+ options.delete(:suffix)
46
+ end
47
+
48
+ polymorphic_url(target.flatten.compact, options.merge(only_path: true))
41
49
  end
42
50
  end
43
51
  end
@@ -1,6 +1,6 @@
1
1
  / # locals: (object: nil, asociable: false)
2
2
 
3
- div style="max-width: 300px"
3
+ div style="max-width: 22em"
4
4
  = pg_form_for(@account || object) do |f|
5
5
  = f.mensajes_de_error
6
6
 
@@ -1,6 +1,6 @@
1
1
  / # locals: (object: nil, asociable: false)
2
2
 
3
- div style="max-width: 300px"
3
+ div style="max-width: 22em"
4
4
  = pg_form_for(@user_account || object) do |f|
5
5
  = f.mensajes_de_error
6
6
 
@@ -1,6 +1,6 @@
1
1
  / # locals: (object: nil, asociable: false)
2
2
 
3
- div style="max-width: 300px"
3
+ div style="max-width: 22em"
4
4
  = pg_form_for(@user || object) do |f|
5
5
  = f.mensajes_de_error
6
6
 
@@ -9,8 +9,8 @@
9
9
  = @clase_modelo.new.decorate.new_link
10
10
  .ms-1
11
11
  = @clase_modelo.new.decorate.export_link(request.url)
12
- .collapse.p-2.border-bottom#filtros class="#{ 'show' if any_filter? }"
13
- .d-flex.align-items-center
12
+ .collapse.border-bottom#filtros class="#{ 'show' if any_filter? }"
13
+ .d-flex.align-items-center.p-2
14
14
  .px-2.d-none.d-sm-inline-block
15
15
  span.bi.bi-funnel-fill
16
16
  = form_tag nil, class: '', method: :get do
@@ -20,7 +20,7 @@
20
20
  = button_tag class: 'btn btn-sm btn-primary col-auto' do
21
21
  span.bi.bi-search
22
22
  .col-auto
23
- = link_to namespaced_path(@clase_modelo),
23
+ = link_to namespaced_path(@clase_modelo, clean: true),
24
24
  class: 'btn btn-sm btn-secondary col-auto' do
25
25
  | Limpiar
26
26
 
@@ -46,7 +46,7 @@ SimpleForm.setup do |config|
46
46
  # vertical forms
47
47
  #
48
48
  # vertical default_wrapper
49
- config.wrappers :vertical_form, class: 'mb-3' do |b|
49
+ control_wrapper = lambda do |b|
50
50
  b.use :html5
51
51
  b.use :placeholder
52
52
  b.optional :maxlength
@@ -60,6 +60,21 @@ SimpleForm.setup do |config|
60
60
  b.use :hint, wrap_with: { class: 'form-text' }
61
61
  end
62
62
 
63
+ select_wrapper = lambda do |b|
64
+ b.use :html5
65
+ b.optional :readonly
66
+ b.use :label, class: 'form-label'
67
+ b.use :input, class: 'form-select', error_class: 'is-invalid'
68
+ b.use :full_error, wrap_with: { class: 'invalid-feedback' }
69
+ b.use :hint, wrap_with: { class: 'form-text' }
70
+ end
71
+
72
+ config.wrappers :vertical_no_margin_control, &control_wrapper
73
+
74
+ config.wrappers :vertical_no_margin_select, &select_wrapper
75
+
76
+ config.wrappers :vertical_form, class: 'mb-3', &control_wrapper
77
+
63
78
  # vertical input for boolean
64
79
  config.wrappers :vertical_boolean, tag: 'fieldset', class: 'mb-3' do |b|
65
80
  b.use :html5
@@ -112,14 +127,7 @@ SimpleForm.setup do |config|
112
127
  end
113
128
 
114
129
  # vertical select input
115
- config.wrappers :vertical_select, class: 'mb-3' do |b|
116
- b.use :html5
117
- b.optional :readonly
118
- b.use :label, class: 'form-label'
119
- b.use :input, class: 'form-select', error_class: 'is-invalid'
120
- b.use :full_error, wrap_with: { class: 'invalid-feedback' }
121
- b.use :hint, wrap_with: { class: 'form-text' }
122
- end
130
+ config.wrappers :vertical_select, class: 'mb-3', &select_wrapper
123
131
 
124
132
  # vertical multi select
125
133
  config.wrappers :vertical_multi_select, class: 'mb-3' do |b|
@@ -0,0 +1,57 @@
1
+ # This migration comes from active_storage (originally 20170806125915)
2
+ class CreateActiveStorageTables < ActiveRecord::Migration[7.0]
3
+ def change
4
+ # Use Active Record's configured type for primary and foreign keys
5
+ primary_key_type, foreign_key_type = primary_and_foreign_key_types
6
+
7
+ create_table :active_storage_blobs, id: primary_key_type do |t|
8
+ t.string :key, null: false
9
+ t.string :filename, null: false
10
+ t.string :content_type
11
+ t.text :metadata
12
+ t.string :service_name, null: false
13
+ t.bigint :byte_size, null: false
14
+ t.string :checksum
15
+
16
+ if connection.supports_datetime_with_precision?
17
+ t.datetime :created_at, precision: 6, null: false
18
+ else
19
+ t.datetime :created_at, null: false
20
+ end
21
+
22
+ t.index [ :key ], unique: true
23
+ end
24
+
25
+ create_table :active_storage_attachments, id: primary_key_type do |t|
26
+ t.string :name, null: false
27
+ t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
28
+ t.references :blob, null: false, type: foreign_key_type
29
+
30
+ if connection.supports_datetime_with_precision?
31
+ t.datetime :created_at, precision: 6, null: false
32
+ else
33
+ t.datetime :created_at, null: false
34
+ end
35
+
36
+ t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true
37
+ t.foreign_key :active_storage_blobs, column: :blob_id
38
+ end
39
+
40
+ create_table :active_storage_variant_records, id: primary_key_type do |t|
41
+ t.belongs_to :blob, null: false, index: false, type: foreign_key_type
42
+ t.string :variation_digest, null: false
43
+
44
+ t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true
45
+ t.foreign_key :active_storage_blobs, column: :blob_id
46
+ end
47
+ end
48
+
49
+ private
50
+ def primary_and_foreign_key_types
51
+ config = Rails.configuration.generators
52
+ setting = config.options[config.orm][:primary_key_type]
53
+ primary_key_type = setting || :primary_key
54
+ foreign_key_type = setting || :bigint
55
+ [primary_key_type, foreign_key_type]
56
+ end
57
+ end
@@ -14,7 +14,7 @@ module PgEngine
14
14
  end
15
15
 
16
16
  if Rails.env.local?
17
- initializer 'configurar_factories', after: 'factory_bot.set_factory_paths' do
17
+ initializer 'pg_engine.set_factory_paths', after: 'factory_bot.set_factory_paths' do
18
18
  # Para que tome las factories de pg_engine/spec/factories
19
19
  # además de las de dummy/spec/factories
20
20
  FactoryBot.definition_file_paths << "#{root}/spec/factories"
@@ -6,6 +6,7 @@ module PgEngine
6
6
  get :abrir_modal
7
7
  post :buscar
8
8
  end
9
+ yield if block_given?
9
10
  end
10
11
  end
11
12
  end
@@ -0,0 +1,50 @@
1
+ require 'English'
2
+
3
+ module PgEngine
4
+ class PdfPreviewGenerator
5
+ def open_tempfile
6
+ tempfile = Tempfile.open('PgEnginePdfPreview-', 'tmp')
7
+
8
+ begin
9
+ yield tempfile
10
+ ensure
11
+ tempfile.close
12
+ tempfile.unlink
13
+ end
14
+ end
15
+
16
+ def capture(*argv, to:)
17
+ to.binmode
18
+
19
+ open_tempfile do |err|
20
+ IO.popen(argv, err:) { |out| IO.copy_stream(out, to) }
21
+ err.rewind
22
+
23
+ unless $CHILD_STATUS.success?
24
+ raise "#{argv.first} failed (status #{$CHILD_STATUS.exitstatus}): #{err.read.to_s.chomp}"
25
+ end
26
+ end
27
+
28
+ to.rewind
29
+ end
30
+
31
+ def draw(*argv)
32
+ open_tempfile do |file|
33
+ capture(*argv, to: file)
34
+
35
+ yield file
36
+ end
37
+ end
38
+
39
+ def run(pdf_string)
40
+ open_tempfile do |tmp_pdf_file|
41
+ tmp_pdf_file.binmode
42
+ tmp_pdf_file.write pdf_string
43
+ tmp_pdf_file.close
44
+ draw 'pdftoppm', '-singlefile', '-cropbox', '-r', '72', '-png', tmp_pdf_file.path do |file|
45
+ return file.read
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -5,6 +5,9 @@ require_relative 'pg_engine/core_ext'
5
5
  require_relative 'pg_engine/configuracion'
6
6
  require_relative 'pg_engine/route_helpers'
7
7
  require_relative 'pg_engine/utils/pg_logger'
8
+ require_relative 'pg_engine/utils/pdf_preview_generator'
9
+
10
+ require_relative '../app/helpers/pg_engine/print_helper'
8
11
 
9
12
  module PgEngine
10
13
  class << self
Binary file
@@ -0,0 +1,12 @@
1
+ require 'rails_helper'
2
+
3
+ describe PgEngine::PdfPreviewGenerator do
4
+ let(:pdf_string) { File.read("#{PgEngine::Engine.root}/spec/fixtures/test.pdf") }
5
+ let(:instancia) { described_class.new }
6
+
7
+ describe '#run' do
8
+ subject { instancia.run(pdf_string) }
9
+
10
+ it { is_expected.to be_a String }
11
+ end
12
+ end
@@ -54,6 +54,7 @@ $chevron-color: 200,200,200,.5;
54
54
  font-weight: bold;
55
55
  --bs-link-color-rgb: white!important;
56
56
  }
57
+ // Los small-items están deprecados
57
58
  #sidebar {
58
59
  &.opened {
59
60
  .sidebar--small-items {
@@ -68,7 +69,7 @@ $chevron-color: 200,200,200,.5;
68
69
  display: block;
69
70
  }
70
71
  .sidebar--large-items {
71
- display: none;
72
+ // display: none;
72
73
  }
73
74
  }
74
75
  }
@@ -84,11 +85,18 @@ $chevron-color: 200,200,200,.5;
84
85
  // display: none!important;
85
86
  flex-basis: 0px;
86
87
  transition: flex-basis 0.5s;
88
+
89
+ // Para que al cerrar y abrir no se vean los textos por fuera de la sidebar
90
+ clip-path: inset(0 0 0 0);
87
91
  // flex-grow: 1;
88
92
 
89
93
  &.opened {
90
94
  // display: block!important;
91
- flex-basis: 140px;
95
+ flex-basis: 9em;
96
+ }
97
+ & > * {
98
+ width: 9em;
99
+ position:fixed;
92
100
  }
93
101
  }
94
102
 
@@ -0,0 +1,48 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ connect () {
5
+ this.showRemove()
6
+ }
7
+
8
+ addItem () {
9
+ // Save a unique timestamp to ensure the key of the associated array is unique.
10
+ const time = new Date().getTime()
11
+ // Save the data id attribute into a variable. This corresponds to `new_object.object_id`.
12
+ const linkId = this.element.dataset.id
13
+ // Create a new regular expression needed to find any instance of the `new_object.object_id` used in the fields data attribute if there's a value in `linkId`.
14
+ const regexp = linkId ? new RegExp(linkId, 'g') : null
15
+ // Replace all instances of the `new_object.object_id` with `time`, and save markup into a variable if there's a value in `regexp`.
16
+ const newFields = regexp ? this.element.dataset.fields.replace(regexp, time) : null
17
+ // Add the new markup to the form if there are fields to add.
18
+ if (newFields) {
19
+ this.element.insertAdjacentHTML('beforebegin', newFields)
20
+ }
21
+ this.element.closest('form').dispatchEvent(new Event('nestedField:added'))
22
+ this.showRemove()
23
+ }
24
+
25
+ quitar () {
26
+ const parent = this.element.closest('.nested-fields')
27
+ parent.style.display = 'none'
28
+ parent.classList.add('removed')
29
+ parent.querySelector('[name*=destroy]').value = 'true'
30
+ this.element.closest('form').dispatchEvent(new Event('nestedField:removed'))
31
+ this.showRemove()
32
+ }
33
+
34
+ showRemove () {
35
+ const container = this.element.closest('.nested-container')
36
+ if (container.dataset.required === 'true') {
37
+ if (container.querySelectorAll('.nested-fields:not(.removed)').length === 1) {
38
+ container.querySelectorAll('.link-to-remove').forEach((e) => {
39
+ e.style.visibility = 'hidden'
40
+ })
41
+ } else {
42
+ container.querySelectorAll('.link-to-remove').forEach((e) => {
43
+ e.style.visibility = ''
44
+ })
45
+ }
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,13 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ connect () {
5
+ this.element.querySelectorAll('.form-select, .form-control').forEach((slct) => {
6
+ slct.addEventListener('change', (e) => {
7
+ if (e.target.value) {
8
+ slct.classList.remove('is-invalid')
9
+ }
10
+ })
11
+ })
12
+ }
13
+ }
@@ -0,0 +1,34 @@
1
+ export function round (value) {
2
+ return Math.round(value * 100) / 100
3
+ }
4
+
5
+ export function printCurrency (value, moneda) {
6
+ return monedaSimbolo(moneda) + numberWithDots(round(value).toFixed(2).replace('.', ','))
7
+ }
8
+
9
+ export function showPercentage (value) {
10
+ return '% ' + value
11
+ }
12
+
13
+ export function monedaSimbolo (moneda) {
14
+ switch (moneda) {
15
+ case 'pesos':
16
+ return '$ '
17
+ case 'dolares':
18
+ return 'U$S '
19
+ case 'euros':
20
+ return '€ '
21
+ case 'reales':
22
+ return 'R$ '
23
+ case 'pesos_chilenos':
24
+ return 'CLP '
25
+ case 'pesos_mexicanos':
26
+ return 'MXN '
27
+ default:
28
+ return '$ '
29
+ }
30
+ }
31
+
32
+ export function numberWithDots (x) {
33
+ return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.')
34
+ }
@@ -19,6 +19,6 @@ html
19
19
 
20
20
  css:
21
21
  .pg-form {
22
- max-width: 300px;
22
+ max-width: 22em;
23
23
  margin: auto;
24
24
  }