zutils 3.0.7 → 4.0.0
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.
- checksums.yaml +4 -4
- data/README.md +397 -20
- data/app/assets/stylesheets/zutils.scss +110 -2
- data/app/views/shared/_action_links.html.erb +43 -44
- data/app/views/shared/_btn_action_links.html.erb +130 -75
- data/app/views/shared/_card_list.html.erb +21 -54
- data/app/views/shared/_dropdown_menu.html.erb +95 -0
- data/app/views/shared/_empty_state.html.erb +15 -0
- data/app/views/shared/_empty_state_card.html.erb +29 -0
- data/app/views/shared/_fields.html.erb +48 -47
- data/app/views/shared/_file_chooser.html.erb +23 -0
- data/app/views/shared/_flash_only_toastr.html.erb +3 -0
- data/app/views/shared/_form.html.erb +28 -17
- data/app/views/shared/_index.html.erb +60 -68
- data/app/views/shared/_list.html.erb +164 -144
- data/app/views/shared/_modal.html.erb +26 -24
- data/app/views/shared/_pagination.html.erb +90 -0
- data/app/views/shared/_search_form.html.erb +84 -39
- data/app/views/shared/_show.html.erb +75 -182
- data/app/views/shared/_turbo_modal.html.erb +21 -0
- data/lib/zutils/engine.rb +3 -1
- data/lib/zutils/helpers.rb +173 -0
- data/lib/zutils/version.rb +1 -1
- data/lib/zutils.rb +2 -2
- metadata +27 -22
- data/.gitignore +0 -8
- data/CODE_OF_CONDUCT.md +0 -74
- data/Gemfile +0 -6
- data/Rakefile +0 -2
- data/app/assets/javascripts/zutils.js +0 -86
- data/zutils.gemspec +0 -39
|
@@ -1,52 +1,97 @@
|
|
|
1
|
-
<% card_color ||= '
|
|
2
|
-
<% search_object ||= @q%>
|
|
3
|
-
<% default_field = {name: :id_eq, attrs: {label: false, required: false, placeholder: 'ID', wrapper: false}} %>
|
|
4
|
-
<% received_field = defined?(main_field) ? main_field : {} %>
|
|
5
|
-
<% main_field = default_field.deep_merge(received_field) %>
|
|
1
|
+
<% card_color ||= 'primary' %>
|
|
2
|
+
<% search_object ||= @q %>
|
|
6
3
|
<% opened ||= false %>
|
|
4
|
+
|
|
7
5
|
<%= search_form_for search_object, url: url do |f| %>
|
|
8
|
-
<div
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
6
|
+
<div
|
|
7
|
+
class="card shadow-sm border rounded-2 mb-3 bg-body"
|
|
8
|
+
data-controller="filtering"
|
|
9
|
+
data-filtering-opened-value="<%= opened %>"
|
|
10
|
+
>
|
|
11
|
+
<div class="card-header bg-transparent border-0 pt-3 pb-0 px-3">
|
|
12
|
+
<h6 class="mb-0 fw-bold text-secondary">
|
|
13
|
+
<i class="fa fa-search me-2 opacity-50"></i>
|
|
14
|
+
Pesquisar
|
|
15
|
+
</h6>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div class="card-body p-3">
|
|
19
|
+
<div class="d-flex align-items-center w-100">
|
|
20
|
+
<button
|
|
21
|
+
type="button"
|
|
22
|
+
class="btn bg-secondary-subtle px-0 rounded-circle me-2 flex-shrink-0"
|
|
23
|
+
style="width: 42px; height: 42px;"
|
|
24
|
+
data-action="click->filtering#toggle"
|
|
25
|
+
title="Filtros Avançados"
|
|
26
|
+
>
|
|
27
|
+
<i
|
|
28
|
+
class="fa <%= opened ? 'fa-angle-down' : 'fa-sliders-h' %>"
|
|
29
|
+
data-filtering-target="icon"
|
|
30
|
+
></i>
|
|
31
|
+
</button>
|
|
32
|
+
|
|
33
|
+
<div class="flex-grow-1 position-relative d-flex align-items-center">
|
|
34
|
+
<% main_field_name = defined?(main_field) ? main_field[:name] : :id_eq
|
|
35
|
+
main_attrs = {
|
|
36
|
+
label: false,
|
|
37
|
+
required: false,
|
|
38
|
+
placeholder: 'ID',
|
|
39
|
+
wrapper: false,
|
|
40
|
+
input_html: { class: 'form-control pe-5' }
|
|
41
|
+
}.deep_merge(defined?(main_field) ? (main_field[:attrs] || {}) : {}) %>
|
|
42
|
+
|
|
43
|
+
<%= f.input main_field_name, main_attrs %>
|
|
44
|
+
|
|
45
|
+
<button
|
|
46
|
+
type="submit"
|
|
47
|
+
class="
|
|
48
|
+
btn border-0 position-absolute end-0 me-2 p-2 text-secondary
|
|
49
|
+
d-flex align-items-center justify-content-center
|
|
50
|
+
"
|
|
51
|
+
style="background: transparent; z-index: 5;"
|
|
52
|
+
>
|
|
53
|
+
<i class="fa fa-search fs-5"></i>
|
|
21
54
|
</button>
|
|
22
55
|
</div>
|
|
23
|
-
<%= f.input (main_field&.dig(:name) || :id_eq), main_field&.dig(:attrs) %>
|
|
24
|
-
<div class="input-group-append">
|
|
25
|
-
<%= button_tag type: "submit", class: "btn btn-#{card_color}" do %>
|
|
26
|
-
<i class="fa fa-search"></i>
|
|
27
|
-
<span class="d-none d-sm-inline-block">
|
|
28
|
-
Buscar
|
|
29
|
-
</span>
|
|
30
|
-
<% end %>
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
33
|
-
<div class="row">
|
|
34
|
-
<div class="col-md-12 active-filters" data-filtering-target='filters'></div>
|
|
35
56
|
</div>
|
|
57
|
+
|
|
58
|
+
<div
|
|
59
|
+
class="active-filters d-flex gap-1 flex-wrap pt-2 ps-5"
|
|
60
|
+
data-filtering-target="filters"
|
|
61
|
+
style="margin-top: -5px;"
|
|
62
|
+
></div>
|
|
36
63
|
</div>
|
|
37
|
-
|
|
64
|
+
|
|
65
|
+
<div
|
|
66
|
+
class="card-body pt-0 collapse <%= 'show' if opened %>"
|
|
67
|
+
data-filtering-target="body"
|
|
68
|
+
>
|
|
69
|
+
<div class="d-flex align-items-center">
|
|
70
|
+
<h6 class="mb-0 fw-bold">
|
|
71
|
+
<i class="fa fa-filter me-2 opacity-50"></i>
|
|
72
|
+
Filtros Detalhados
|
|
73
|
+
</h6>
|
|
74
|
+
|
|
75
|
+
<hr class="flex-grow-1 ms-3 opacity-10 border-black">
|
|
76
|
+
</div>
|
|
77
|
+
|
|
38
78
|
<div class="row">
|
|
39
79
|
<% search_fields ||= [] %>
|
|
80
|
+
|
|
40
81
|
<% search_fields.each do |sf| %>
|
|
41
|
-
<%
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
82
|
+
<% field_attrs = { required: false, wrapper_html: {class: "col-md-3"} }.deep_merge(sf[:attrs] || {}) %>
|
|
83
|
+
|
|
84
|
+
<%= f.input sf[:name], field_attrs %>
|
|
85
|
+
<% end %>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div class="d-flex justify-content-end align-items-center gap-2">
|
|
89
|
+
<%= link_to "Limpar", url, class: "btn btn-link btn-sm text-decoration-none text-muted" %>
|
|
90
|
+
|
|
91
|
+
<%= button_tag type: "submit", class: "btn btn-sm btn-#{card_color} px-4" do %>
|
|
92
|
+
Aplicar
|
|
45
93
|
<% end %>
|
|
46
94
|
</div>
|
|
47
|
-
<%= button_tag type: "submit", class: "btn btn-#{card_color}" do %>
|
|
48
|
-
<i class="fa fa-search"></i> Buscar
|
|
49
|
-
<% end %>
|
|
50
95
|
</div>
|
|
51
96
|
</div>
|
|
52
|
-
<% end %>
|
|
97
|
+
<% end %>
|
|
@@ -5,213 +5,106 @@
|
|
|
5
5
|
<% nested_resource ||= false %>
|
|
6
6
|
<% relationships ||= {} %>
|
|
7
7
|
|
|
8
|
-
<%
|
|
9
|
-
<% hide ||= %w[show
|
|
10
|
-
<% size ||= "
|
|
8
|
+
<% color ||= 'primary' %>
|
|
9
|
+
<% hide ||= %w[new show] %>
|
|
10
|
+
<% size ||= "sm" %>
|
|
11
|
+
<% buttons_size ||= "md" %>
|
|
11
12
|
|
|
12
|
-
<%
|
|
13
|
-
<%
|
|
14
|
-
<% delete_color ||= 'danger' %>
|
|
13
|
+
<% edit_color ||= 'btn-warning' %>
|
|
14
|
+
<% delete_color ||= 'btn-outline-danger' %>
|
|
15
15
|
|
|
16
16
|
<% if show_columns.empty? %>
|
|
17
17
|
<% form_fields ||= (object.class.respond_to?('form_fields') ? object.class.form_fields : {}) %>
|
|
18
18
|
<% form_ordered_fields ||= (form_fields.empty? ? object.class.column_names : form_fields.keys) %>
|
|
19
|
-
<% show_columns = form_ordered_fields - ['id',
|
|
19
|
+
<% show_columns = form_ordered_fields - ['id','created_at','updated_at','deleted_at'] - excluded_columns + additional_columns %>
|
|
20
20
|
<% end %>
|
|
21
21
|
|
|
22
22
|
<% unless hide.include? "top_bar" %>
|
|
23
|
-
<div class="card
|
|
24
|
-
<div class="card-header">
|
|
25
|
-
<div class="
|
|
26
|
-
<
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Visualizar <%=t object.class.model_name.human %>
|
|
31
|
-
</strong>
|
|
32
|
-
</h3>
|
|
33
|
-
</div>
|
|
34
|
-
<div class="col-12 col-md mr-auto">
|
|
35
|
-
<% if can? :manage, object %>
|
|
36
|
-
<%= render "shared/btn_action_links", size: "xs", object: object, labels: true, hide: hide, new_color: new_color, edit_color: edit_color, delete_color: delete_color %>
|
|
37
|
-
<% end %>
|
|
38
|
-
</div>
|
|
23
|
+
<div class="card border-0 border-top border-4 border-<%= color %> shadow-sm mb-0 rounded-bottom-0">
|
|
24
|
+
<div class="card-header bg-body-tertiary border-bottom-0">
|
|
25
|
+
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2">
|
|
26
|
+
<h5 class="mb-0">
|
|
27
|
+
<i class="<%= object.class.icon %>"></i>
|
|
28
|
+
<%= t object.class.model_name.human %>
|
|
29
|
+
</h5>
|
|
39
30
|
</div>
|
|
40
31
|
</div>
|
|
41
32
|
</div>
|
|
42
33
|
<% end %>
|
|
34
|
+
|
|
43
35
|
<% if lookup_context.find_all("#{object.class.model_name.plural}/_#{object.class.model_name.singular}").any? %>
|
|
44
|
-
|
|
36
|
+
<div class="mb-3"><%= render object %></div>
|
|
45
37
|
<% else %>
|
|
46
|
-
<div class="card
|
|
47
|
-
<div class="card-body p-
|
|
48
|
-
<table class="table table-
|
|
38
|
+
<div class="card shadow-sm border-0 rounded-0 rounded-bottom mb-3">
|
|
39
|
+
<div class="card-body p-1" data-controller="bootstrap-table">
|
|
40
|
+
<table class="table table-striped table-hover align-middle mb-0">
|
|
49
41
|
<thead>
|
|
50
42
|
<tr>
|
|
51
|
-
<th width=
|
|
43
|
+
<th data-width="20" data-width-unit="%">Campo</th>
|
|
52
44
|
<th>Valor</th>
|
|
53
45
|
</tr>
|
|
54
46
|
</thead>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
<%
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
47
|
+
|
|
48
|
+
<tbody>
|
|
49
|
+
<% show_columns.each do |column| %>
|
|
50
|
+
<% value = object.send(column) %>
|
|
51
|
+
|
|
52
|
+
<% unless /_currency$/ =~ column %>
|
|
53
|
+
<tr>
|
|
54
|
+
<th class="fw-semibold">
|
|
55
|
+
<% if /_id$/ =~ column && object.respond_to?(column.split('_id')[0]) %>
|
|
56
|
+
<%= object.class.human_attribute_name(column.split("_id")[0]) %>
|
|
57
|
+
<% elsif /_cents$/ =~ column %>
|
|
58
|
+
<%= object.class.human_attribute_name(column.split("_cents")[0]) %>
|
|
59
|
+
<% else %>
|
|
60
|
+
<%= object.class.human_attribute_name(column) %>
|
|
61
|
+
<% end %>
|
|
62
|
+
</th>
|
|
63
|
+
|
|
64
|
+
<td>
|
|
65
|
+
<% if column.to_s.include?('.') %>
|
|
66
|
+
<% o = object %>
|
|
67
|
+
<% column.split('.').each{|m| o = o.send(m) if o } %>
|
|
68
|
+
<%= o %>
|
|
69
|
+
<% elsif value.is_a?(Date) ||
|
|
70
|
+
value.is_a?(Time) ||
|
|
71
|
+
value.is_a?(DateTime) ||
|
|
72
|
+
value.is_a?(ActiveSupport::TimeWithZone) %>
|
|
73
|
+
<%= l value %>
|
|
74
|
+
<% elsif value.respond_to? 'attached?' %>
|
|
75
|
+
<% if value.attached? %>
|
|
76
|
+
<%= link_to rails_blob_path(value),
|
|
77
|
+
class: "btn btn-outline-#{color} btn-sm",
|
|
78
|
+
target: '_blank' do %>
|
|
79
|
+
<i class="fa fa-file"></i>
|
|
80
|
+
<span class="d-none d-md-inline">Arquivo</span>
|
|
82
81
|
<% end %>
|
|
83
82
|
<% end %>
|
|
83
|
+
<% elsif /_id$/ =~ column && object.respond_to?(column.split('_id')[0]) %>
|
|
84
|
+
<%= object.send(column.split("_id")[0])&.name %>
|
|
85
|
+
<% elsif /_cents$/ =~ column %>
|
|
86
|
+
<%= number_to_currency object.send(column.split("_cents")[0]) %>
|
|
87
|
+
<% else %>
|
|
88
|
+
<%= value %>
|
|
84
89
|
<% end %>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
<%= number_to_currency object.send(column.split("_cents")[0]) %>
|
|
89
|
-
<% else %>
|
|
90
|
-
<%= object.send(column) %>
|
|
91
|
-
<% end %>
|
|
92
|
-
</td>
|
|
93
|
-
</tr>
|
|
90
|
+
</td>
|
|
91
|
+
</tr>
|
|
92
|
+
<% end %>
|
|
94
93
|
<% end %>
|
|
95
|
-
|
|
94
|
+
</tbody>
|
|
96
95
|
</table>
|
|
97
96
|
</div>
|
|
98
97
|
</div>
|
|
98
|
+
<% end %>
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
</div>
|
|
110
|
-
<div class="card-body p-0">
|
|
111
|
-
<table class="table table-bordered table-striped p-0">
|
|
112
|
-
<% unless hide_table_header %>
|
|
113
|
-
<thead>
|
|
114
|
-
<tr>
|
|
115
|
-
<th width='15%'><%= t('field') %></th>
|
|
116
|
-
<th><%= t('value') %></th>
|
|
117
|
-
</tr>
|
|
118
|
-
</thead>
|
|
119
|
-
<% end %>
|
|
120
|
-
<tbody>
|
|
121
|
-
<% value.each do |v| %>
|
|
122
|
-
<tr>
|
|
123
|
-
<td>
|
|
124
|
-
<strong>
|
|
125
|
-
<% if /_id$/ =~ v && Kernel.const_get(key.to_s.singularize.camelize).new.respond_to?(v.split('_id')[0]) %>
|
|
126
|
-
<%= Kernel.const_get(key.to_s.singularize.camelize).human_attribute_name(v.split('_id')[0]) %>
|
|
127
|
-
<% elsif /_cents$/ =~ v %>
|
|
128
|
-
<%= Kernel.const_get(key.to_s.singularize.camelize).human_attribute_name(v.split("_cents")[0]) %>
|
|
129
|
-
<% else %>
|
|
130
|
-
<%= Kernel.const_get(key.to_s.singularize.camelize).human_attribute_name(v) %>
|
|
131
|
-
<% end %>
|
|
132
|
-
</strong>
|
|
133
|
-
</td>
|
|
134
|
-
<td>
|
|
135
|
-
<% if object.send(key.to_s).send(v).class == Date or
|
|
136
|
-
object.send(key.to_s).send(v).class == DateTime or
|
|
137
|
-
object.send(key.to_s).send(v).class == ActiveSupport::TimeWithZone or
|
|
138
|
-
object.send(key.to_s).send(v).class == Time %>
|
|
139
|
-
<%=l object.send(key.to_s).send(v) %>
|
|
140
|
-
<% elsif /_id$/ =~ v && object.send(key.to_s).respond_to?(v.split('_id')[0]) %>
|
|
141
|
-
<%= object.send(key.to_s).send(v.split("_id")[0])&.name %>
|
|
142
|
-
<% elsif /_cents$/ =~ v %>
|
|
143
|
-
<%= number_to_currency object.send(key.to_s).send(v.split("_cents")[0]) %>
|
|
144
|
-
<% else %>
|
|
145
|
-
<%= object.send(key.to_s).send(v) %>
|
|
146
|
-
<% end %>
|
|
147
|
-
</td>
|
|
148
|
-
</tr>
|
|
149
|
-
<% end %>
|
|
150
|
-
</tbody>
|
|
151
|
-
</table>
|
|
152
|
-
</div>
|
|
153
|
-
</div>
|
|
154
|
-
<% else %>
|
|
155
|
-
<div class="box card-<%= card_color %>">
|
|
156
|
-
<div class="card-header with-border">
|
|
157
|
-
<h3 class="card-title">
|
|
158
|
-
<%= Kernel.const_get(key.to_s.singularize.camelize).model_name.human.pluralize %>
|
|
159
|
-
</h3>
|
|
160
|
-
</div>
|
|
161
|
-
<div class="card-body p-0">
|
|
162
|
-
<table class="table table-bordered table-striped p-0">
|
|
163
|
-
<thead>
|
|
164
|
-
<tr>
|
|
165
|
-
<% value.each do |v| %>
|
|
166
|
-
<th>
|
|
167
|
-
<% if /_id$/ =~ v && Kernel.const_get(key.to_s.singularize.camelize).new.respond_to?(v.split('_id')[0]) %>
|
|
168
|
-
<%= Kernel.const_get(key.to_s.singularize.camelize).human_attribute_name(v.split('_id')[0]) %>
|
|
169
|
-
<% else %>
|
|
170
|
-
<%= Kernel.const_get(key.to_s.singularize.camelize).human_attribute_name(v) %>
|
|
171
|
-
<% end %>
|
|
172
|
-
</th>
|
|
173
|
-
<% end %>
|
|
174
|
-
</tr>
|
|
175
|
-
</thead>
|
|
176
|
-
<tbody>
|
|
177
|
-
<% object.send(key.to_s).each do |r| %>
|
|
178
|
-
<tr>
|
|
179
|
-
<% value.each do |v| %>
|
|
180
|
-
<td>
|
|
181
|
-
<% if v.include?('.') %>
|
|
182
|
-
<% o = r %>
|
|
183
|
-
<% v.split('.').each{|m| o = o.send(m) if o } %>
|
|
184
|
-
<%= o %>
|
|
185
|
-
<% elsif r.send(v).class == Date or
|
|
186
|
-
r.send(v).class == DateTime or
|
|
187
|
-
r.send(v).class == ActiveSupport::TimeWithZone or
|
|
188
|
-
r.send(v).class == Time %>
|
|
189
|
-
<%=l r.send(v) %>
|
|
190
|
-
<% elsif r.send(v).respond_to? 'attached?' %>
|
|
191
|
-
<% if r.send(v).class == ActiveStorage::Attached::One %>
|
|
192
|
-
<% if r.send(v).attached? %>
|
|
193
|
-
<%= link_to rails_blob_path(r.send(v)), class: "btn btn-#{card_color} btn-xs", target: '_blank' do %>
|
|
194
|
-
<i class="fa fa-file-o"></i> Arquivo
|
|
195
|
-
<% end %>
|
|
196
|
-
<% end %>
|
|
197
|
-
<% end %>
|
|
198
|
-
<% elsif /_id$/ =~ v && r.respond_to?(v.split('_id')[0]) %>
|
|
199
|
-
<%= r.send(v.split("_id")[0])&.name %>
|
|
200
|
-
<% else %>
|
|
201
|
-
<%= r.send(v) %>
|
|
202
|
-
<% end %>
|
|
203
|
-
</td>
|
|
204
|
-
<% end %>
|
|
205
|
-
</tr>
|
|
206
|
-
<% end %>
|
|
207
|
-
</tbody>
|
|
208
|
-
</table>
|
|
209
|
-
</div>
|
|
210
|
-
</div>
|
|
211
|
-
<% end %>
|
|
212
|
-
<% end %>
|
|
100
|
+
<div class="card-footer">
|
|
101
|
+
<% unless hide.include? "bottom_bar" %>
|
|
102
|
+
<%= render "shared/btn_action_links",
|
|
103
|
+
object: object,
|
|
104
|
+
labels: true,
|
|
105
|
+
hide: hide,
|
|
106
|
+
edit_color: edit_color,
|
|
107
|
+
delete_color: delete_color,
|
|
108
|
+
size: buttons_size %>
|
|
213
109
|
<% end %>
|
|
214
|
-
|
|
215
|
-
<% unless hide.include? "bottom_bar" %>
|
|
216
|
-
<%= render "shared/btn_action_links", object: object, labels: true, hide: hide, new_color: new_color, edit_color: edit_color, delete_color: delete_color %>
|
|
217
|
-
<% end %>
|
|
110
|
+
</div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<% title ||= '' %>
|
|
2
|
+
<% size ||= 'lg' %>
|
|
3
|
+
<% hide_header ||= false %>
|
|
4
|
+
<% modal_frame_tag ||= "turbo_modal" %>
|
|
5
|
+
|
|
6
|
+
<%= turbo_frame_tag modal_frame_tag do %>
|
|
7
|
+
<% unless hide_header %>
|
|
8
|
+
<div class="modal-header">
|
|
9
|
+
<h5 class="modal-title"><%= title %></h5>
|
|
10
|
+
|
|
11
|
+
<button
|
|
12
|
+
type="button"
|
|
13
|
+
class="btn-close"
|
|
14
|
+
data-bs-dismiss="modal"
|
|
15
|
+
aria-label="Fechar"
|
|
16
|
+
></button>
|
|
17
|
+
</div>
|
|
18
|
+
<% end %>
|
|
19
|
+
|
|
20
|
+
<div class="modal-body p-0"><%= yield %></div>
|
|
21
|
+
<% end %>
|
data/lib/zutils/engine.rb
CHANGED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
module Zutils
|
|
2
|
+
module Helpers
|
|
3
|
+
def eval_with_rescue(code)
|
|
4
|
+
eval(code)
|
|
5
|
+
rescue Exception => e
|
|
6
|
+
"error"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# ── Flash ────────────────────────────────────────────────────────────
|
|
10
|
+
def bootstrap_flash(options = {})
|
|
11
|
+
flash_messages = []
|
|
12
|
+
|
|
13
|
+
begin
|
|
14
|
+
object_name = controller_name.singularize
|
|
15
|
+
obj = instance_variable_get("@#{object_name}")
|
|
16
|
+
|
|
17
|
+
if obj && obj.respond_to?(:errors) && obj.errors.size > 0
|
|
18
|
+
msg = if obj.errors[:base].present?
|
|
19
|
+
obj.errors[:base]
|
|
20
|
+
else
|
|
21
|
+
I18n.t("helpers.links.#{action_name}_error",
|
|
22
|
+
model: Kernel.const_get(object_name.camelize).model_name.human, default: "erro de validação")
|
|
23
|
+
end
|
|
24
|
+
flash[:error] = msg if msg.present?
|
|
25
|
+
end
|
|
26
|
+
rescue
|
|
27
|
+
# ignore
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
flash.each do |type, message|
|
|
31
|
+
message = Array(message).select(&:presence)
|
|
32
|
+
next if message.empty?
|
|
33
|
+
data = case type.to_s
|
|
34
|
+
when "success", "notice"
|
|
35
|
+
{ title: options[:success_title] || "Sucesso!", icon: options[:success_icon] || "fas fa-check", type: :success, kind: "success" }
|
|
36
|
+
when "error"
|
|
37
|
+
{ title: options[:error_title] || "Erro!", icon: options[:error_icon] || "fas fa-ban", type: :danger, kind: "error" }
|
|
38
|
+
when "alert"
|
|
39
|
+
{ title: options[:alert_title] || "Alerta!", icon: options[:alert_icon] || "fas fa-exclamation-triangle", type: :warning, kind: "warning" }
|
|
40
|
+
else
|
|
41
|
+
{ title: options[:info_title] || "Aviso", icon: options[:info_icon] || "fas fa-info", type: :info, kind: "info" }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
next unless data
|
|
45
|
+
|
|
46
|
+
tag_options = {
|
|
47
|
+
class: "alert alert-#{data[:type]} alert-dismissible d-flex align-items-center",
|
|
48
|
+
role: "alert",
|
|
49
|
+
data: { kind: data[:kind], text: message.first.to_s }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
close_button = content_tag(:button, "", type: "button", class: "btn-close",
|
|
53
|
+
"data-bs-dismiss": "alert", "aria-label": "Fechar")
|
|
54
|
+
|
|
55
|
+
icon = content_tag(:i, "", class: "#{data[:icon]} me-2")
|
|
56
|
+
title = content_tag(:strong, data[:title])
|
|
57
|
+
text = safe_join([title, " ", sanitize(Array(message).first.to_s.gsub("|", "<br>"), tags: %w[br], attributes: [])])
|
|
58
|
+
|
|
59
|
+
content = safe_join([close_button, icon, text])
|
|
60
|
+
flash_messages << content_tag(:div, content, tag_options)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
flash[:error] = nil
|
|
64
|
+
safe_join(flash_messages, "\n")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def render_turbo_stream_flash_messages(local: false)
|
|
68
|
+
partial = local ? "shared/flash" : "shared/flash"
|
|
69
|
+
turbo_stream.prepend(local ? "localflash" : "flash", partial: partial)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def render_turbo_stream_flash_messages_only_toastr
|
|
73
|
+
turbo_stream.prepend "flash", partial: "shared/flash_only_toastr"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# ── DOM helpers ──────────────────────────────────────────────────────
|
|
77
|
+
def nested_dom_id(*args)
|
|
78
|
+
args.map { |arg| arg.respond_to?(:to_key) ? dom_id(arg) : arg.to_s }.join("_")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# ── Menu ─────────────────────────────────────────────────────────────
|
|
82
|
+
def menu_activated?(menu_item)
|
|
83
|
+
check = ->(test) { eval_with_rescue(test) rescue false }
|
|
84
|
+
|
|
85
|
+
check.call(menu_item.dig(:active_test)) ||
|
|
86
|
+
menu_item.dig(:children).to_a.any? { |c| check.call(c[:active_test]) } ||
|
|
87
|
+
menu_item.dig(:children).to_a.flat_map { |c| c.dig(:children).to_a }.any? { |c| check.call(c[:active_test]) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# ── Display helpers (BS5) ────────────────────────────────────────────
|
|
91
|
+
def display(object, field, options = {})
|
|
92
|
+
icon = options[:icon] || "fa fa-stream"
|
|
93
|
+
title = options[:title] || object.class.human_attribute_name(field)
|
|
94
|
+
value = options[:value] || interactive_send(object, field)
|
|
95
|
+
hide_title = options[:hide_title] || false
|
|
96
|
+
|
|
97
|
+
parts = []
|
|
98
|
+
unless hide_title
|
|
99
|
+
parts << content_tag(:strong, tag.i("", class: icon) + " #{title}")
|
|
100
|
+
end
|
|
101
|
+
parts << content_tag(:p, value.presence || "-", class: "text-body-secondary")
|
|
102
|
+
safe_join(parts, "\n")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def display_boolean(object, field, options = {})
|
|
106
|
+
bool_value = options[:value] || interactive_send(object, field)
|
|
107
|
+
label = bool_value ? (options[:true_label] || "Sim") : (options[:false_label] || "Não")
|
|
108
|
+
css = bool_value ? "bg-success" : "bg-danger"
|
|
109
|
+
|
|
110
|
+
badge = content_tag(:span, label, class: "badge #{css}")
|
|
111
|
+
display(object, field, options.merge(value: badge))
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def display_boolean_icon(object, field, options = {})
|
|
115
|
+
bool_value = options[:value] || interactive_send(object, field)
|
|
116
|
+
title = options[:title] || object.class.human_attribute_name(field)
|
|
117
|
+
icon_true = options[:icon_true] || options[:icon] || "fa fa-thumbs-up"
|
|
118
|
+
icon_false = options[:icon_false] || options[:icon] || "fa fa-thumbs-down"
|
|
119
|
+
css = bool_value ? "bg-success" : "bg-danger"
|
|
120
|
+
|
|
121
|
+
icon = bool_value ? tag.i("", class: icon_true) : tag.i("", class: icon_false)
|
|
122
|
+
content = content_tag(:span, icon, class: "badge #{css}",
|
|
123
|
+
data: { "bs-toggle": "tooltip", "bs-title": "#{title}: #{bool_value ? "Sim" : "Não"}" })
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def interactive_send(obj, field)
|
|
127
|
+
field.to_s.split(".").each { |m| obj = obj.send(m) if obj }
|
|
128
|
+
|
|
129
|
+
case obj
|
|
130
|
+
when Date, Time, DateTime, ActiveSupport::TimeWithZone
|
|
131
|
+
I18n.l(obj)
|
|
132
|
+
else
|
|
133
|
+
obj
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# ── Section / Header ─────────────────────────────────────────────────
|
|
138
|
+
def section_header(title, icon: "fa-info-circle", color: "primary", border: true)
|
|
139
|
+
content_tag(:h5, class: "text-uppercase small text-#{color} fw-bold mb-4 #{"border-bottom" if border} pb-2") do
|
|
140
|
+
concat tag.i(class: "fa #{icon} me-2")
|
|
141
|
+
concat title
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def smart_date_tag(date, icon: nil, label: nil, text_class: "text-body")
|
|
146
|
+
return "-" if date.blank?
|
|
147
|
+
|
|
148
|
+
day_month = l(date, format: "%d %b")
|
|
149
|
+
if date.year > Date.today.year
|
|
150
|
+
year_tag = " <small class='opacity-75' style='font-size: 0.85em;'>#{date.year}</small>".html_safe
|
|
151
|
+
else
|
|
152
|
+
year_tag = ""
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
parts = []
|
|
156
|
+
parts << tag.i("", class: "bi bi-#{icon} me-1 text-muted") if icon.present?
|
|
157
|
+
parts << content_tag(:span, "#{label}:", class: "small text-muted me-1") if label.present?
|
|
158
|
+
parts << "#{day_month}#{year_tag}".html_safe
|
|
159
|
+
|
|
160
|
+
content_tag(:span, safe_join(parts), class: text_class)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def format_date(value)
|
|
164
|
+
return if value.blank?
|
|
165
|
+
|
|
166
|
+
Date.iso8601(value.to_s).strftime("%d/%m/%Y")
|
|
167
|
+
rescue
|
|
168
|
+
Date.parse(value.to_s).strftime("%d/%m/%Y")
|
|
169
|
+
rescue
|
|
170
|
+
value
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
data/lib/zutils/version.rb
CHANGED