pg_rails 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +73 -0
  4. data/Rakefile +36 -0
  5. data/app/assets/javascripts/pg_rails/asociacion_creable.js +85 -0
  6. data/app/assets/javascripts/pg_rails/best_in_place_datepicker.js +58 -0
  7. data/app/assets/javascripts/pg_rails/librerias.js +13 -0
  8. data/app/assets/javascripts/pg_rails/librerias_b3.js +14 -0
  9. data/app/assets/javascripts/pg_rails/validaciones.js +44 -0
  10. data/app/assets/javascripts/pg_rails.js +318 -0
  11. data/app/assets/stylesheets/pg_rails/librerias.scss +5 -0
  12. data/app/assets/stylesheets/pg_rails/pg_chosen.scss +29 -0
  13. data/app/assets/stylesheets/pg_rails/pg_rails.scss +199 -0
  14. data/app/assets/stylesheets/pg_rails_b3.scss +10 -0
  15. data/app/assets/stylesheets/pg_rails_b4.scss +12 -0
  16. data/app/assets/stylesheets/pg_rails_b5.scss +1 -0
  17. data/app/controllers/pg_rails/application_controller.rb +316 -0
  18. data/app/controllers/pg_rails/editar_en_lugar_controller.rb +24 -0
  19. data/app/decorators/pg_rails/base_decorator.rb +120 -0
  20. data/app/helpers/pg_rails/editar_en_lugar_helper.rb +106 -0
  21. data/app/helpers/pg_rails/form_helper.rb +25 -0
  22. data/app/helpers/pg_rails/postgres_helper.rb +15 -0
  23. data/app/helpers/pg_rails/print_helper.rb +176 -0
  24. data/app/inputs/pg_rails/asociacion_creable_input.rb +72 -0
  25. data/app/inputs/pg_rails/fecha_input.rb +20 -0
  26. data/app/inputs/pg_rails/selects_dependientes_input.rb +9 -0
  27. data/app/lib/pg_form_builder.rb +31 -0
  28. data/app/lib/pg_rails/filtros_builder.rb +338 -0
  29. data/app/models/pg_rails/application_record.rb +51 -0
  30. data/app/policies/pg_rails/application_policy.rb +104 -0
  31. data/app/views/application/_abrir_modal.js.erb +14 -0
  32. data/app/views/application/_actualizar_smart_listing.html.slim +3 -0
  33. data/app/views/application/_cerrar_modal.js.erb +8 -0
  34. data/app/views/application/_modal_ajax_form.js.erb +7 -0
  35. data/config/brakeman.ignore +42 -0
  36. data/config/locales/es.yml +17 -0
  37. data/config/routes.rb +3 -0
  38. data/config/spring.rb +1 -0
  39. data/lib/pg_rails/configuracion.rb +24 -0
  40. data/lib/pg_rails/core_ext.rb +17 -0
  41. data/lib/pg_rails/engine.rb +42 -0
  42. data/lib/pg_rails/simple_form/initializer.rb +583 -0
  43. data/lib/pg_rails/utils/logueador.rb +39 -0
  44. data/lib/pg_rails/version.rb +5 -0
  45. data/lib/pg_rails.rb +23 -0
  46. data/lib/tasks/auto_anotar_modelos.rake +34 -0
  47. data/lib/tasks/pg_rails_tasks.rake +5 -0
  48. metadata +89 -0
@@ -0,0 +1,29 @@
1
+
2
+ body .chosen-container-active.chosen-with-drop .chosen-single {
3
+ background: none;
4
+ }
5
+ body .chosen-container-single .chosen-single {
6
+ padding-top: 2px;
7
+ border: 1px solid #e5e6e7;
8
+ border: 1px solid #ced4da;
9
+ background: none;
10
+ box-shadow: none;
11
+ }
12
+
13
+ .chosen-container.chosen-with-drop .chosen-drop {
14
+ // margin-top: 0px;
15
+ }
16
+ .chosen-container-single a.chosen-single {
17
+ height: 36px;
18
+ line-height: 30px;
19
+ }
20
+ .chosen-container-single .chosen-single div {
21
+ padding-top: 5px;
22
+ }
23
+ .chosen-container-single .chosen-drop {
24
+ min-width: 190px;
25
+ margin-top: -3px;
26
+ }
27
+ .chosen-container.chosen-container-single .chosen-single {
28
+ border-radius: 4px;
29
+ }
@@ -0,0 +1,199 @@
1
+ // @import 'pg_rails/pg_chosen';
2
+
3
+ // Google Maps Places API
4
+ // Para que funcione en los modales
5
+ .pac-container {
6
+ z-index: 1051 !important;
7
+ }
8
+
9
+ .simple_form {
10
+ .ocultar {
11
+ input, select, textarea {
12
+ display: none;
13
+ }
14
+ }
15
+ }
16
+ .form-group.asociacion_creable {
17
+ input.oculto {
18
+ visibility: hidden;
19
+ position: absolute;
20
+ }
21
+
22
+ &:not(.completado) {
23
+ .borrar_seleccion {
24
+ display: none;
25
+ }
26
+ }
27
+ }
28
+ // oculto la columna de actions
29
+ .modal-seleccionar-asociado .listado th:last-child,
30
+ .modal-seleccionar-asociado .listado td:last-child {
31
+ display: none;
32
+ }
33
+ .modal-seleccionar-asociado .listado {
34
+ td {
35
+ cursor: pointer;
36
+ }
37
+ }
38
+ .modal-seleccionar-asociado .listado tr:hover {
39
+ background-color: lightgrey;
40
+ }
41
+
42
+ .listado {
43
+ td, th {
44
+ padding: 0.25rem 1rem;
45
+ }
46
+ }
47
+ .listado .btn-xs {
48
+ margin-right: 4px;
49
+ }
50
+ .listado .btn-sm {
51
+ min-width: 25px;
52
+ height: 25px;
53
+ padding: 1px;
54
+ margin-right: 4px;
55
+ }
56
+
57
+ .form-group.producto {
58
+ .form-control.selectize-control {
59
+ height: 36px;
60
+ // width: 200px;
61
+ }
62
+ .chosen-container {
63
+ // max-width: 200px;
64
+ }
65
+ }
66
+ // hay que agregar la clase pg-form a los forms que queremos afectar
67
+ .pg-form {
68
+ max-width: 1000px;
69
+
70
+ .form-control {
71
+ font-size: 13px;
72
+ }
73
+
74
+ .form-control.selectize-control {
75
+ height: 36px;
76
+ }
77
+ .form-group {
78
+ /*transition: height .5s ease 0s;*/
79
+ transition: opacity .3s ease 0s;
80
+ opacity: 1;
81
+ max-width: 300px;
82
+ margin-bottom: 6px;
83
+
84
+ &.datetime {
85
+ max-width: 600px;
86
+ }
87
+
88
+ &.dependent_fields {
89
+ max-width: 600px;
90
+ }
91
+
92
+ &.ocultar {
93
+ /*height: 0px;*/
94
+ opacity: 0;
95
+ }
96
+ }
97
+
98
+ .form-control.error {
99
+ border: 1px solid red;
100
+ }
101
+
102
+ label.error {
103
+ color: red;
104
+ }
105
+ }
106
+
107
+ .filter {
108
+ display: inline-block;
109
+ // margin-right: 10px;
110
+ // margin-bottom: 10px;
111
+ vertical-align: top;
112
+ min-width: 200px;
113
+
114
+ .input-group {
115
+ width: 230px;
116
+ }
117
+ }
118
+
119
+ .dependent_fields select {
120
+ // width: calc(99% / 2);
121
+ // width: 500px;
122
+ display: block;
123
+ margin-bottom: 15px;
124
+ }
125
+
126
+ .dependent_fields label {
127
+ display: block;
128
+ text-align: left!important;
129
+ }
130
+
131
+ .dependent_fields {
132
+ // max-width: 400px;
133
+ }
134
+
135
+ .btn.exportar:hover {
136
+ cursor: pointer;
137
+ }
138
+
139
+
140
+ // Editar en lugar
141
+ .editar_en_lugar_v2 {
142
+ cursor: pointer;
143
+ text-decoration: underline dotted lightgrey;
144
+ }
145
+ .editar_en_lugar_v2::after {
146
+ padding-left: 13px;
147
+ content: '✎';
148
+ opacity: 0;
149
+ }
150
+ .editar_en_lugar_v2:hover::after {
151
+ opacity: 1;
152
+ }
153
+
154
+ .editar_en_lugar_v2 form {
155
+ display: inline-block!important;
156
+ }
157
+ .editar_en_lugar_v2 input {
158
+ display: inline-block;
159
+ }
160
+ .editar_en_lugar_v2 input[type=submit] {
161
+ margin-left: 10px;
162
+ }
163
+
164
+ .pg_ajax_link {
165
+ cursor: pointer;
166
+ color: #337ab7;
167
+ text-decoration: none;
168
+ }
169
+ .pg_ajax_link:hover {
170
+ color: #23527c;
171
+ text-decoration: underline;
172
+ }
173
+
174
+ .pg-max-width-xxs {
175
+ max-width: 350px;
176
+ }
177
+ .pg-max-width-xs {
178
+ max-width: 450px;
179
+ }
180
+ .pg-max-width-sm {
181
+ max-width: 600px;
182
+ }
183
+ .pg-max-width {
184
+ max-width: 800px;
185
+ }
186
+ .pg-max-width-lg {
187
+ max-width: 1000px;
188
+ }
189
+ .pg-max-width-xl {
190
+ max-width: 1200px;
191
+ }
192
+
193
+ .pg-revert-width {
194
+ width: revert;
195
+ }
196
+
197
+ .pg-form input.date {
198
+ max-width: 140px;
199
+ }
@@ -0,0 +1,10 @@
1
+ // estilos para Bootstrap 3
2
+
3
+ @import 'pg_rails/pg_rails';
4
+
5
+ .pg-form {
6
+ .dropdown-menu > li > a {
7
+ padding: 6px 20px;
8
+ font-size: 14px;
9
+ }
10
+ }
@@ -0,0 +1,12 @@
1
+ // estilos para Bootstrap 4
2
+
3
+ @import "bootstrap-4.4.1/bootstrap";
4
+ @import 'pg_rails/pg_rails';
5
+
6
+ .tooltip { pointer-events: none; }
7
+
8
+ .form-group.asociacion_creable {
9
+ button {
10
+ font-size: 11px;
11
+ }
12
+ }
@@ -0,0 +1 @@
1
+ @import 'pg_rails/pg_rails';
@@ -0,0 +1,316 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgRails
4
+ class ApplicationController < ActionController::Base
5
+ include ApplicationHelper
6
+ include Pundit::Authorization
7
+ # include SmartListing::Helper::ControllerExtensions
8
+ # helper SmartListing::Helper
9
+ include PrintHelper
10
+ include PostgresHelper
11
+
12
+ rescue_from PrintHelper::FechaInvalidaError, with: :fecha_invalida
13
+ rescue_from Pundit::NotAuthorizedError, with: :not_authorized
14
+
15
+ layout :setear_layout
16
+
17
+ def self.inherited(klass)
18
+ super
19
+ # incluyo los helpers de /app/helpers de la main_app
20
+ klass.helper :all
21
+ end
22
+
23
+ helper_method :mobile_device?
24
+
25
+ def mobile_device?
26
+ request.user_agent =~ /Mobile|webOS/
27
+ end
28
+
29
+ helper_method :any_filter?
30
+
31
+ def any_filter?
32
+ params.keys.reject { |a| a.in? ["controller", "action", "page", "page_size", "order_by", "order_direction"] }.any?
33
+ end
34
+
35
+
36
+ helper_method :current_page_size
37
+
38
+ def current_page_size
39
+ if params[:page_size].present?
40
+ session[:page_size] = params[:page_size]
41
+ params[:page_size].to_i
42
+ else
43
+ default_page_size
44
+ end
45
+ end
46
+
47
+ def default_page_size
48
+ session[:page_size].present? ? session[:page_size].to_i : 10
49
+ end
50
+
51
+
52
+ protected
53
+
54
+ def pg_respond_update(object: nil, extra_js: nil)
55
+ object ||= instancia_modelo
56
+ respond_to do |format|
57
+ if (@saved = object.save)
58
+ format.html do
59
+ redirect_to object.decorate.target_object
60
+ end
61
+ format.json { render json: object.decorate }
62
+ else
63
+ format.turbo_stream do
64
+ flash.now[:error] = object.errors.full_messages.join(', ')
65
+ render turbo_stream: render_turbo_stream_flash_messages
66
+ end
67
+ format.html { render :edit, status: :unprocessable_entity }
68
+ format.json { render json: object.errors, status: :unprocessable_entity }
69
+ end
70
+ format.js do
71
+ render '_cerrar_modal', locals: {
72
+ contenido: 'form', extra_js: ['modal_ajax_form', extra_js]
73
+ }
74
+ end
75
+ end
76
+ end
77
+
78
+ def pg_respond_create(object: nil, extra_js: nil)
79
+ object ||= instancia_modelo
80
+ respond_to do |format|
81
+ if (@saved = object.save)
82
+ format.html do
83
+ if params[:save_and_next] == 'true'
84
+ new_path = url_for(@clase_modelo) + "/new"
85
+ redirect_to new_path, notice: "#{@clase_modelo.nombre_singular} creado."
86
+ else
87
+ redirect_to object.decorate.target_object
88
+ end
89
+ end
90
+ format.json { render json: object.decorate }
91
+ else
92
+ format.turbo_stream do
93
+ flash.now[:error] = object.errors.full_messages.join(', ')
94
+ render turbo_stream: render_turbo_stream_flash_messages
95
+ end
96
+ format.html { render :new, status: :unprocessable_entity }
97
+ format.json { render json: object.errors.full_messages, status: :unprocessable_entity }
98
+ end
99
+ format.js do
100
+ render '_cerrar_modal', locals: {
101
+ contenido: 'form', extra_js: ['modal_ajax_form', extra_js]
102
+ }
103
+ end
104
+ end
105
+ end
106
+
107
+ def pg_respond_index(collection)
108
+ respond_to do |format|
109
+ format.json { render json: collection }
110
+ format.js { render_smart_listing }
111
+ format.html { render_smart_listing }
112
+ format.xlsx do
113
+ render xlsx: 'download',
114
+ filename: "#{@clase_modelo.nombre_plural.gsub(' ', '-').downcase}" \
115
+ "-#{Time.zone.now.strftime("%Y-%m-%d-%H.%M.%S")}.xlsx"
116
+ end
117
+ end
118
+ end
119
+
120
+ def pg_respond_show(object = nil)
121
+ respond_to do |format|
122
+ format.json { render json: object || instancia_modelo }
123
+ format.html
124
+ end
125
+ end
126
+
127
+ def pg_respond_destroy(model, redirect_url = nil)
128
+ if destroy_model(model)
129
+ respond_to do |format|
130
+ format.html do
131
+ if redirect_url.present?
132
+ redirect_to redirect_url, notice: 'Elemento borrado.', status: 303
133
+ else
134
+ redirect_back(fallback_location: root_path, notice: 'Elemento borrado.', status: 303)
135
+ end
136
+ end
137
+ format.json { head :no_content }
138
+ end
139
+ else
140
+ respond_to do |format|
141
+ format.html do
142
+ if model.respond_to?(:associated_elements) && model.associated_elements.present?
143
+ @model = model
144
+ render destroy_error_details_view
145
+ else
146
+ flash[:error] = @error_message
147
+ # if redirect_url.present?
148
+ # redirect_to redirect_url
149
+ # else
150
+ redirect_back(fallback_location: root_path, status: 303)
151
+ # end
152
+ end
153
+ end
154
+ format.json { render json: { error: @error_message }, status: :unprocessable_entity }
155
+ end
156
+ end
157
+ end
158
+
159
+ # destroy_and_respond DEPRECADO
160
+ alias_method :destroy_and_respond, :pg_respond_destroy
161
+
162
+ def destroy_error_details_view
163
+ 'destroy_error_details'
164
+ end
165
+
166
+ def destroy_model(model)
167
+ @error_message = 'No se pudo eliminar el registro'
168
+ begin
169
+ destroy_method = model.respond_to?(:discard) ? :discard : :destroy
170
+ return true if model.send(destroy_method)
171
+
172
+ @error_message = model.errors.full_messages.join(', ')
173
+ false
174
+ rescue ActiveRecord::InvalidForeignKey => e
175
+ # class_name = /from table \"(?<table_name>[\p{L}_]*)\"/.match(e.message)[:table_name].singularize.camelcase
176
+ # # pk_id = /from table \"(?<pk_id>[\p{L}_]*)\"/.match(e.message)[:pk_id].singularize.camelcase
177
+ # clazz = Object.const_get class_name
178
+ # objects = clazz.where(model.class.table_name.singularize => model)
179
+ model_name = t("activerecord.models.#{model.class.name.underscore}")
180
+ @error_message = "#{model_name} no se pudo borrar porque tiene elementos asociados."
181
+ logger.debug e.message
182
+ end
183
+ false
184
+ end
185
+
186
+ def render_smart_listing
187
+ raise 'implementar en subclase'
188
+ end
189
+
190
+ def buscar_instancia
191
+ if Object.const_defined?('FriendlyId') && @clase_modelo.is_a?(FriendlyId)
192
+ @clase_modelo.friendly.find(params[:id])
193
+ else
194
+ @clase_modelo.find(params[:id])
195
+ end
196
+ end
197
+
198
+ def set_instancia_modelo
199
+ if action_name.in? %w[new create]
200
+ self.instancia_modelo = @clase_modelo.new(modelo_params)
201
+ else
202
+ self.instancia_modelo = buscar_instancia
203
+
204
+ instancia_modelo.assign_attributes(modelo_params) if action_name.in? %w[update]
205
+ end
206
+
207
+ instancia_modelo.current_user = send(PgRails.configuracion.current_user_method)
208
+
209
+ authorize instancia_modelo
210
+
211
+ self.instancia_modelo = instancia_modelo.decorate if action_name.in? %w[show edit new]
212
+ end
213
+
214
+ def instancia_modelo=(val)
215
+ instance_variable_set("@#{nombre_modelo}".to_sym, val)
216
+ end
217
+
218
+ def instancia_modelo
219
+ instance_variable_get("@#{nombre_modelo}".to_sym)
220
+ end
221
+
222
+ def modelo_params
223
+ if action_name == 'new'
224
+ params.permit(atributos_permitidos)
225
+ else
226
+ params.require(nombre_modelo).permit(atributos_permitidos)
227
+ end
228
+ end
229
+
230
+ def nombre_modelo
231
+ @clase_modelo.name.underscore
232
+ end
233
+
234
+ def default_url_options(options = {})
235
+ if Rails.env.production?
236
+ options.merge(protocol: 'https')
237
+ else
238
+ options
239
+ end
240
+ end
241
+
242
+ def setear_layout
243
+ if params[:sin_layout] == 'true'
244
+ false
245
+ else
246
+ 'application'
247
+ end
248
+ end
249
+
250
+ def clase_modelo
251
+ # agarro la variable o intento con el nombre del controller
252
+ @clase_modelo ||= self.class.name.singularize.gsub('Controller', '').constantize
253
+ end
254
+
255
+ def filtros_y_policy(campos)
256
+ @filtros = PgRails::FiltrosBuilder.new(
257
+ self, clase_modelo, campos
258
+ )
259
+ scope = policy_scope(clase_modelo)
260
+
261
+ @filtros.filtrar(scope)
262
+ end
263
+
264
+ def do_sort(scope, field, direction)
265
+ unless scope.model.column_names.include? field.to_s
266
+ return scope
267
+ end
268
+ scope = scope.order(field => direction)
269
+ instance_variable_set(:"@field", field)
270
+ instance_variable_set(:"@direction", direction)
271
+ scope
272
+ rescue ArgumentError => e
273
+ Utils::Logueador.warning(e.to_s)
274
+ scope
275
+ end
276
+
277
+ def sort_collection(scope, options = {})
278
+ if params[:order_by].present?
279
+ field = params[:order_by]
280
+ direction = params[:order_direction]
281
+ do_sort(scope, field, direction)
282
+ elsif options[:default].present?
283
+ field = options[:default].first[0]
284
+ direction = options[:default].first[1]
285
+ do_sort(scope, field, direction)
286
+ else
287
+ scope
288
+ end
289
+ end
290
+
291
+ def fecha_invalida
292
+ respond_to do |format|
293
+ format.json do
294
+ render json: { error: 'Formato de fecha inválido' }, status: :unprocessable_entity
295
+ end
296
+ format.js { render inline: 'showToast("error", "Formato de fecha inválido")' }
297
+ format.html { go_back('Formato de fecha inválido') }
298
+ end
299
+ end
300
+
301
+ def not_authorized
302
+ respond_to do |format|
303
+ format.json do
304
+ render json: { error: 'Not authorized' }, status: :unprocessable_entity
305
+ end
306
+ # format.js { render inline: 'showToast("error", "Formato de fecha inválido")' }
307
+ format.html { go_back('Not authorized') }
308
+ end
309
+ end
310
+
311
+ def go_back(message)
312
+ flash[:error] = message
313
+ redirect_back fallback_location: root_path
314
+ end
315
+ end
316
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgRails
4
+ class EditarEnLugarController < ::ApplicationController
5
+ def actualizar
6
+ key_modelo = params.keys[1]
7
+ modelo = Kernel.const_get(key_modelo)
8
+ objeto = modelo.find params[:id]
9
+ authorize objeto, :editar_en_lugar?
10
+ object_params = request.parameters[key_modelo]
11
+ objeto.current_user = current_user
12
+ objeto.update(object_params)
13
+ respond_with_bip(objeto, param: key_modelo)
14
+ rescue Pundit::NotAuthorizedError
15
+ objeto.errors.add(:base, 'no autorizado')
16
+ respond_with_bip(objeto)
17
+ rescue Pundit::Error => e
18
+ Rollbar.error(e)
19
+ logger.error(e.message)
20
+ objeto.errors.add(:base, e.message)
21
+ respond_with_bip(objeto)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgRails
4
+ class BaseDecorator < Draper::Decorator
5
+ include ActionView::Helpers
6
+ include PrintHelper
7
+ include FormHelper
8
+ include Pundit::Authorization
9
+
10
+ attr_accessor :output_buffer
11
+
12
+ delegate_all
13
+
14
+ def as_json(_options = {})
15
+ object.as_json.tap { |o| o[:to_s] = to_s }
16
+ end
17
+
18
+ # def method_missing(method_name, *args, &block) # rubocop:disable Style/MissingRespondToMissing
19
+ # valor = object.attributes[method_name.to_s]
20
+ # return super unless valor.present?
21
+
22
+ # if valor.instance_of?(Date)
23
+ # dmy(valor)
24
+ # # elsif valor.class == ActiveSupport::TimeWithZone
25
+ # # dmy_time(valor)
26
+ # else
27
+ # super
28
+ # end
29
+ # end
30
+
31
+ def destroy_link(message = '¿Estás seguro?')
32
+ return unless Pundit.policy!(helpers.send(PgRails.configuracion.current_user_method), object).destroy?
33
+
34
+ helpers.content_tag :span, rel: :tooltip, title: 'Eliminar' do
35
+ helpers.link_to object_url, data: { 'turbo-confirm': message, 'turbo-method': :delete },
36
+ class: "btn #{_config.clase_botones_chicos} btn-#{_config.boton_destroy}" do
37
+ helpers.content_tag :span, nil, class: clase_icono(_config.icono_destroy).to_s
38
+ end
39
+ end
40
+ end
41
+
42
+ def edit_link(texto = '')
43
+ return unless Pundit.policy!(helpers.send(PgRails.configuracion.current_user_method), object).edit?
44
+
45
+ helpers.content_tag :span, rel: :tooltip, title: 'Editar' do
46
+ helpers.link_to edit_object_url, data: { turbo_frame: :main },
47
+ class: "btn #{_config.clase_botones_chicos} btn-#{_config.boton_edit}" do
48
+ helpers.content_tag(:span, nil, class: clase_icono(_config.icono_edit).to_s) + texto
49
+ end
50
+ end
51
+ end
52
+
53
+ def show_link(texto = '')
54
+ return unless Pundit.policy!(helpers.send(PgRails.configuracion.current_user_method), object).show?
55
+
56
+ helpers.content_tag :span, rel: :tooltip, title: 'Ver' do
57
+ helpers.link_to object_url, data: { turbo_frame: :main },
58
+ class: "btn #{_config.clase_botones_chicos} btn-#{_config.boton_show}" do
59
+ helpers.content_tag(:span, nil, class: clase_icono(_config.icono_show).to_s) + texto
60
+ end
61
+ end
62
+ end
63
+
64
+ def export_link(url, texto = '')
65
+ return unless Pundit.policy!(helpers.send(PgRails.configuracion.current_user_method), object).export?
66
+
67
+ helpers.content_tag :span, rel: :tooltip, title: 'Exportar' do
68
+ helpers.content_tag :a, target: '_blank',
69
+ class: "btn #{_config.clase_botones_chicos} btn-#{_config.boton_export}", href: url_change_format(url) do
70
+ "#{helpers.content_tag(:span, nil, class: clase_icono('file-earmark-excel-fill'))} #{texto}".html_safe
71
+ end
72
+ end
73
+ end
74
+
75
+ def new_link(options = {})
76
+ return unless Pundit.policy!(helpers.send(PgRails.configuracion.current_user_method), object).new?
77
+
78
+ helpers.content_tag :span, rel: :tooltip, title: 'Crear' do
79
+ helpers.link_to new_object_url, class: "btn #{_config.clase_botones_chicos} btn-primary", remote: options[:remote] do
80
+ helpers.content_tag(:span, nil,
81
+ class: clase_icono('plus').to_s) + "<span class='d-none d-sm-inline'> Crear #{object.class.nombre_singular.downcase}</span>".html_safe
82
+ end
83
+ end
84
+ end
85
+
86
+ def edit_object_url
87
+ helpers.url_for([:edit, target_object].flatten)
88
+ end
89
+
90
+ def new_object_url
91
+ "#{helpers.url_for(target_index)}/new"
92
+ end
93
+
94
+ def object_url
95
+ helpers.url_for(target_object)
96
+ end
97
+
98
+ def target_object
99
+ default_module.present? ? [default_module, object] : object
100
+ end
101
+
102
+ def target_index
103
+ default_module.present? ? [default_module, object.class] : object.class
104
+ end
105
+
106
+ def default_module
107
+ nil
108
+ end
109
+
110
+ private
111
+
112
+ def _config
113
+ PgRails.configuracion
114
+ end
115
+
116
+ def clase_icono(icono)
117
+ "#{_config.sistema_iconos} #{_config.sistema_iconos}-#{icono}"
118
+ end
119
+ end
120
+ end