custom_table 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +210 -0
- data/Rakefile +8 -0
- data/app/assets/config/custom_table_manifest.js +1 -0
- data/app/assets/stylesheets/custom_table/application.scss +1 -0
- data/app/assets/stylesheets/custom_table/table.scss +89 -0
- data/app/controllers/concerns/custom_table_concern.rb +118 -0
- data/app/controllers/custom_table/application_controller.rb +4 -0
- data/app/controllers/custom_table/settings_controller.rb +86 -0
- data/app/helpers/custom_table/application_helper.rb +483 -0
- data/app/helpers/custom_table/fieldset_helper.rb +90 -0
- data/app/helpers/custom_table/icons_helper.rb +42 -0
- data/app/inputs/date_picker_input.rb +52 -0
- data/app/javascript/controllers/batch_actions_controller.js +53 -0
- data/app/javascript/controllers/table_controller.js +109 -0
- data/app/jobs/custom_table/application_job.rb +4 -0
- data/app/mailers/custom_table/application_mailer.rb +6 -0
- data/app/models/concerns/custom_table_settings.rb +42 -0
- data/app/models/custom_table/application_record.rb +5 -0
- data/app/views/custom_table/_download.haml +19 -0
- data/app/views/custom_table/_field.haml +8 -0
- data/app/views/custom_table/_field_plain.haml +1 -0
- data/app/views/custom_table/_fieldset.haml +2 -0
- data/app/views/custom_table/_filter.html.haml +104 -0
- data/app/views/custom_table/_settings.html.haml +57 -0
- data/app/views/custom_table/_table.html.haml +261 -0
- data/app/views/custom_table/_table.xlsx.axlsx +76 -0
- data/app/views/custom_table/_table_fe.xlsx.fast_excel +141 -0
- data/app/views/custom_table/_table_row.html.haml +72 -0
- data/app/views/custom_table/_table_row_data.html.haml +26 -0
- data/app/views/custom_table/settings/destroy.html.haml +4 -0
- data/app/views/custom_table/settings/edit.html.haml +2 -0
- data/app/views/custom_table/settings/update.html.haml +4 -0
- data/app/views/layouts/custom_table/application.html.erb +15 -0
- data/config/initializers/simple_form_bootstrap.rb +468 -0
- data/config/locales/en.yml +18 -0
- data/config/locales/ru.yml +18 -0
- data/config/routes.rb +5 -0
- data/lib/custom_table/configuration.rb +10 -0
- data/lib/custom_table/engine.rb +12 -0
- data/lib/custom_table/version.rb +3 -0
- data/lib/custom_table.rb +14 -0
- data/lib/generators/custom_table/USAGE +8 -0
- data/lib/generators/custom_table/custom_table_generator.rb +16 -0
- data/lib/generators/custom_table/templates/initializer.rb +3 -0
- data/lib/generators/custom_table/templates/migration.rb +5 -0
- data/lib/tasks/custom_table_tasks.rake +4 -0
- metadata +300 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
|
5
|
+
static targets = [ "checkbox", "form" ]
|
6
|
+
|
7
|
+
connect() {
|
8
|
+
if (this.hasFormTarget && this.hasCheckboxTarget) {
|
9
|
+
console.log("Batch actions form", this.formTarget)
|
10
|
+
this.refresh();
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
14
|
+
submit(event) {
|
15
|
+
|
16
|
+
if (!this.hasCheckboxTarget) {
|
17
|
+
console.log("No checkbox targets at all")
|
18
|
+
return
|
19
|
+
}
|
20
|
+
|
21
|
+
// Clearing any matching hidden fields from form
|
22
|
+
this.formTarget.querySelectorAll('input[name="'+this.checkboxTargets[0].getAttribute("name")+'"]').forEach((cb) => {
|
23
|
+
cb.parentNode.removeChild(cb)
|
24
|
+
});
|
25
|
+
|
26
|
+
// Adding selected fields to form as hiddens
|
27
|
+
this.checkboxTargets.forEach((cb) => {
|
28
|
+
|
29
|
+
if (!cb.checked) return;
|
30
|
+
|
31
|
+
let input = document.createElement('input');
|
32
|
+
input.setAttribute('name', cb.getAttribute("name"));
|
33
|
+
input.setAttribute('value', cb.getAttribute("value"));
|
34
|
+
input.setAttribute('type', "hidden")
|
35
|
+
|
36
|
+
this.formTarget.appendChild(input);//append the input to the form
|
37
|
+
|
38
|
+
})
|
39
|
+
|
40
|
+
// event.preventDefault()
|
41
|
+
console.log("Batch actions submit form")
|
42
|
+
|
43
|
+
}
|
44
|
+
|
45
|
+
refresh(){
|
46
|
+
let v = false
|
47
|
+
this.checkboxTargets.forEach((cb) => {
|
48
|
+
if (cb.checked) v = true;
|
49
|
+
});
|
50
|
+
this.formTarget.querySelector('input[type="submit"]').disabled = !v
|
51
|
+
}
|
52
|
+
|
53
|
+
}
|
@@ -0,0 +1,109 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
|
5
|
+
static targets = [ "move", "hide", "sum", "sumval" ]
|
6
|
+
|
7
|
+
connect() {
|
8
|
+
if (this.hasMoveTarget) this.moveup();
|
9
|
+
if (this.hasHideTarget) this.hideEmpty();
|
10
|
+
|
11
|
+
if (!this.hasSumTarget) {
|
12
|
+
let d = document.createElement("caption")
|
13
|
+
d.classList.add("d-none", "table-sum")
|
14
|
+
d.dataset.tableTarget = "sum"
|
15
|
+
d.innerHTML = '<i class="fa fa-times-circle mt-1 float-start" data-action="click->table#clearSum"></i><span data-table-target="sumval"></span>'
|
16
|
+
this.element.appendChild(d)
|
17
|
+
}
|
18
|
+
|
19
|
+
this.element.querySelector("tbody").addEventListener('click', (e) => {
|
20
|
+
const cell = e.target.closest('td.amount');
|
21
|
+
if (!cell) {return;} // Quit, not clicked on a cell
|
22
|
+
if (!isNaN(this.cellValue(cell))) {
|
23
|
+
const row = cell.parentElement;
|
24
|
+
cell.classList.toggle("selected")
|
25
|
+
this.showSum();
|
26
|
+
}
|
27
|
+
});
|
28
|
+
|
29
|
+
}
|
30
|
+
|
31
|
+
moveup() {
|
32
|
+
console.log("Moving row")
|
33
|
+
let elm = this.moveTarget
|
34
|
+
let tbl = elm.parentElement
|
35
|
+
this.moveTarget.remove()
|
36
|
+
tbl.prepend(elm)
|
37
|
+
}
|
38
|
+
|
39
|
+
showSum(){
|
40
|
+
let elements = this.element.querySelectorAll("td.selected");
|
41
|
+
if (elements.length == 0) {
|
42
|
+
this.sumTarget.classList.add("d-none")
|
43
|
+
return
|
44
|
+
}
|
45
|
+
this.sumTarget.classList.remove("d-none")
|
46
|
+
let sum = 0
|
47
|
+
elements.forEach((e) => {
|
48
|
+
sum += this.cellValue(e)
|
49
|
+
})
|
50
|
+
if (this.hasSumTarget) {
|
51
|
+
this.sumvalTarget.innerHTML = "SUM="+Math.round(sum * 100) / 100
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
cellValue(e){
|
56
|
+
if (e.querySelector("span[data-raw]")) {
|
57
|
+
return Number(e.querySelector("span[data-raw]").dataset.raw);
|
58
|
+
}
|
59
|
+
else {
|
60
|
+
return Number(e.innerText.replace(/\s,/g, ''));
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
clearSum(){
|
65
|
+
this.element.querySelectorAll("td.selected").forEach((e) => {
|
66
|
+
e.classList.remove("selected")
|
67
|
+
})
|
68
|
+
this.showSum();
|
69
|
+
}
|
70
|
+
|
71
|
+
hideEmpty(){
|
72
|
+
|
73
|
+
console.log("Hiding empty!!!")
|
74
|
+
|
75
|
+
this.element.querySelectorAll("tr th").forEach((e, i) => {
|
76
|
+
|
77
|
+
if (!e.classList.contains("hide-empty")) return;
|
78
|
+
|
79
|
+
let tds = this.element.querySelectorAll("tr td:nth-child(".concat(i+1, ")"));
|
80
|
+
if (Array.prototype.slice.call(tds).every(td => { return (td.parentElement.classList.contains("totals") || td.textContent == "0" || !td.textContent || Number(td.textContent) == 0); })) {
|
81
|
+
e.hidden = true;
|
82
|
+
tds.forEach(e => {e.hidden = true})
|
83
|
+
}
|
84
|
+
|
85
|
+
});
|
86
|
+
|
87
|
+
}
|
88
|
+
|
89
|
+
toggle(event) {
|
90
|
+
let params = event.params
|
91
|
+
console.log("Table Toggle", event.currentTarget.innerHTML)
|
92
|
+
this.element.querySelectorAll(params.css).forEach((e, i) => {
|
93
|
+
e.classList.toggle("d-none")
|
94
|
+
});
|
95
|
+
event.currentTarget.classList.toggle("opened")
|
96
|
+
}
|
97
|
+
|
98
|
+
search(event) {
|
99
|
+
console.log("Table Search", event.currentTarget.value)
|
100
|
+
this.element.querySelectorAll("tbody tr").forEach((tr) => {
|
101
|
+
if (tr.innerText.toLowerCase().includes(event.currentTarget.value.toLowerCase())) {
|
102
|
+
tr.classList.remove("d-none")
|
103
|
+
} else {
|
104
|
+
tr.classList.add("d-none")
|
105
|
+
}
|
106
|
+
})
|
107
|
+
}
|
108
|
+
|
109
|
+
}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module CustomTableSettings
|
2
|
+
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
serialize :custom_table
|
7
|
+
end
|
8
|
+
|
9
|
+
def save_custom_table_settings model_class, variant = nil, fields: nil, sorts: nil, per_page: nil
|
10
|
+
|
11
|
+
model = model_class.model_name.to_s
|
12
|
+
key = model
|
13
|
+
key = "#{model}-#{variant}" if !variant.nil?
|
14
|
+
self.custom_table = {} if self.custom_table.nil?
|
15
|
+
self.custom_table[key] = {} if self.custom_table[key].nil?
|
16
|
+
self.custom_table[key][:model] = model
|
17
|
+
self.custom_table[key][:fields] = fields.symbolize_keys if !fields.nil?
|
18
|
+
self.custom_table[key][:sorts] = sorts if !sorts.nil?
|
19
|
+
self.custom_table[key][:per_page] = per_page.to_i if !per_page.nil? && [25, 50, 100].include?(per_page.to_i)
|
20
|
+
|
21
|
+
return save!
|
22
|
+
# write_attribute :custom_table, (custom_table||{}).merge(ss)
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def destroy_custom_table_settings model_class, variant = nil
|
27
|
+
|
28
|
+
return true if self.custom_table.nil?
|
29
|
+
|
30
|
+
model = model_class.model_name.to_s
|
31
|
+
key = model
|
32
|
+
key = "#{model}-#{variant}" if !variant.nil?
|
33
|
+
|
34
|
+
return true if self.custom_table[key].nil?
|
35
|
+
|
36
|
+
self.custom_table.delete(key)
|
37
|
+
|
38
|
+
return save
|
39
|
+
# write_attribute :custom_table, (custom_table||{}).merge(ss)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
- model_class = local_assigns[:collection].nil? ? local_assigns[:class] : local_assigns[:collection].model
|
2
|
+
- local_assigns[:with_selected_fields] = true if local_assigns[:with_selected_fields].nil?
|
3
|
+
- if (model_class.present? && can?(:download, model_class))
|
4
|
+
.btn-group
|
5
|
+
= link_to params.permit!.merge({:format => :xlsx, all_fields: true}), :class => "btn btn-outline-primary" do
|
6
|
+
= t("download")
|
7
|
+
= custom_table_download_icon
|
8
|
+
|
9
|
+
- if (local_assigns[:downloads] && downloads.length > 0) || (local_assigns[:with_selected_fields] == true)
|
10
|
+
|
11
|
+
%button.btn.btn-outline-primary.dropdown-toggle{type: "button", data: {"bs-toggle": "dropdown"}}
|
12
|
+
%ul.dropdown-menu
|
13
|
+
- if local_assigns[:with_selected_fields] == true
|
14
|
+
%li
|
15
|
+
= link_to params.permit!.merge({:format => :xlsx}), :class => "dropdown-item" do
|
16
|
+
= t("custom_table.download_only_selected_fields")
|
17
|
+
- downloads.each do |d|
|
18
|
+
%li
|
19
|
+
= link_to d[:title], d[:href], class: "dropdown-item"
|
@@ -0,0 +1,8 @@
|
|
1
|
+
%div.d-flex.justify-content-between.custom-table-field
|
2
|
+
%b
|
3
|
+
- if local_assigns[:hint]
|
4
|
+
%abbr{title: hint}= label
|
5
|
+
- else
|
6
|
+
= label
|
7
|
+
%span.text-end.d-inline{id: "#{column}_#{object.model_name.singular}_#{object.id}"}= yield
|
8
|
+
-# %dt{class: adaptive ? "col-lg-2 col-md-5" : "col-5"}= label
|
@@ -0,0 +1 @@
|
|
1
|
+
%span{id: "#{column}_#{object.model_name.singular}_#{object.id}"}= yield
|
@@ -0,0 +1,104 @@
|
|
1
|
+
.card
|
2
|
+
|
3
|
+
.card-body.bg-light
|
4
|
+
|
5
|
+
- url = local_assigns[:url].presence
|
6
|
+
- variant = local_assigns[:variant].presence
|
7
|
+
|
8
|
+
- a = {}
|
9
|
+
|
10
|
+
= custom_table_form_for @q, url: url do |f|
|
11
|
+
|
12
|
+
.d-none
|
13
|
+
= f.input :s, as: :hidden, input_html: {value: params[:q].try(:[], :s)}
|
14
|
+
|
15
|
+
- fields = custom_table_fields_for(search_model, variant: variant, current_search: params[:q], predefined_fields: local_assigns[:fields]) if local_assigns[:fields].nil?
|
16
|
+
|
17
|
+
- fields.each do |key, defs|
|
18
|
+
|
19
|
+
- next if defs[:search].nil?
|
20
|
+
|
21
|
+
- a[key] = []
|
22
|
+
|
23
|
+
- defs[:search] = [defs[:search]] if !defs[:search].kind_of?(Array)
|
24
|
+
- defs[:label] = defs[:search][0][:label] if defs[:label].nil?
|
25
|
+
- defs[:label] = search_model.human_attribute_name(key) if defs[:label].nil?
|
26
|
+
|
27
|
+
- defs[:search].each do |field|
|
28
|
+
|
29
|
+
- a[key].push field[:q]
|
30
|
+
- label = field[:label] || defs[:label]
|
31
|
+
|
32
|
+
- if field[:type] == :text
|
33
|
+
|
34
|
+
- ih = {class: "form-control-sm"}
|
35
|
+
- ih = ih.merge(defs[:input_html]) if !defs[:input_html].nil?
|
36
|
+
|
37
|
+
= f.input field[:q], input_html: ih, required: false, label: false, placeholder: label
|
38
|
+
|
39
|
+
- if field[:type] == :boolean
|
40
|
+
|
41
|
+
= f.input field[:q], as: :boolean, input_html: {}, required: false, label: label, unchecked_value: ""
|
42
|
+
|
43
|
+
- if field[:type] == :switch
|
44
|
+
|
45
|
+
= f.input field[:q], as: :select, input_html: {:class => "form-select form-select-sm"}, required: false, label: false, :collection => [[t("yes")+" | #{label}", "true"], [t("no")+" | #{label}", "false"]], include_blank: label
|
46
|
+
|
47
|
+
- if field[:type] == :select
|
48
|
+
|
49
|
+
- collection = field[:collection]
|
50
|
+
- label_method = nil
|
51
|
+
|
52
|
+
- if collection.class.to_s =~ /ActiveRecord_Relation/i
|
53
|
+
- collection = collection.accessible_by(current_ability)
|
54
|
+
- label_method = :to_s
|
55
|
+
|
56
|
+
= f.input field[:q], :as => :select, :input_html => {:class => "form-select form-select-sm"}, :required => false, :label => false, :collection => collection, label_method: label_method, include_blank: label, value_method: field[:value_method]
|
57
|
+
|
58
|
+
- if field[:type] == :grouped_select
|
59
|
+
|
60
|
+
- collection = field[:collection]
|
61
|
+
|
62
|
+
- collection = collection.accessible_by(current_ability) if collection.class.to_s =~ /ActiveRecord_Relation/i
|
63
|
+
|
64
|
+
= f.input field[:q], :as => :grouped_select, :input_html => {:class => "form-select form-select-sm"}, :required => false, :label => false, group_method: field[:group_method], :collection => collection, include_blank: label
|
65
|
+
|
66
|
+
- if field[:type] == :autocomplete
|
67
|
+
|
68
|
+
- collection = field[:collection]
|
69
|
+
|
70
|
+
- collection = collection.accessible_by(current_ability) if collection.class.to_s =~ /ActiveRecord_Relation/i
|
71
|
+
|
72
|
+
= f.input field[:q], :as => :autocomplete, :input_html => {:class => "input-sm"}, :required => false, :label => false, :collection => collection, prompt: label
|
73
|
+
|
74
|
+
- if field[:type] == :enum
|
75
|
+
|
76
|
+
- collection = field[:collection]
|
77
|
+
- collection = [search_model, key.to_s.pluralize] if collection.nil?
|
78
|
+
- coll = collection[0].send(collection[1]).keys.map { |w| [(collection[0].method_defined?(:human_enum_name) ? collection[0].human_enum_name(collection[1].to_s.singularize, w) : w), w] }
|
79
|
+
|
80
|
+
= f.input field[:q], :as => :select, :input_html => {:class => "form-select form-select-sm"}, :required => false, :label => false, :collection => coll, include_blank: label
|
81
|
+
|
82
|
+
- if field[:type] == :dates
|
83
|
+
.col-12
|
84
|
+
.input-group.input-group-sm{"data-controller": "dates"}
|
85
|
+
= f.input_field field[:q][0], :as => :date_picker, :label => false, :class => "form-control", data: {"dates-target": "dateFrom"}, :placeholder => label+" ("+t("custom_table.date_from")+")"
|
86
|
+
= f.input_field field[:q][1], :as => :date_picker, :label => false, :class => "form-control", data: {"dates-target": "dateTo"}, :placeholder => label+" ("+t("custom_table.date_to")+")"
|
87
|
+
%button.btn.btn-outline-secondary.dropdown-toggle(type="button" data-bs-toggle="dropdown" aria-expanded="false")
|
88
|
+
%ul.dropdown-menu.dropdown-menu-end
|
89
|
+
%button.dropdown-item{type: "button", "data-action": "dates#prevWeek"}= t("analytics.previos_week")
|
90
|
+
%button.dropdown-item{type: "button", "data-action": "dates#prevMonth"}= t("analytics.previos_month")
|
91
|
+
%button.dropdown-item{type: "button", "data-action": "dates#prevYear"}= t("analytics.previos_year")
|
92
|
+
%hr.dropdown-divider
|
93
|
+
%button.dropdown-item{type: "button", "data-action": "dates#clear"}= t("clear")
|
94
|
+
|
95
|
+
.btn-group
|
96
|
+
= f.button :submit, t("search"), name: "", :class => "btn-sm btn-primary"
|
97
|
+
- if user_signed_in? && current_user_has_customizable_fields_for?(search_model, variant) && !local_assigns[:hide_customization] && !local_assigns[:fields]# && !(profile_path rescue nil).nil?
|
98
|
+
= custom_table_settings_button search_model, variant
|
99
|
+
- if params[:q].present? && !params[:q].except(:s).empty?
|
100
|
+
= link_to url_for(params.permit!.except(:q)), class: "btn btn-sm btn-outline-danger", title: t("custom_table.clear_search") do
|
101
|
+
= custom_table_cancel_icon
|
102
|
+
|
103
|
+
= yield f
|
104
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
- variant = nil if !defined?("variant")
|
2
|
+
- field_key = search_model.model_name.to_s
|
3
|
+
- field_key += "-#{variant}" if !variant.nil?
|
4
|
+
- cls = local_assigns[:large].nil? ? "btn-sm" : ""
|
5
|
+
- items = custom_table_fields_settings_for(search_model, variant: variant)
|
6
|
+
|
7
|
+
- columns = 1; size = "modal-md"
|
8
|
+
- (columns = 2; size = "modal-lg") if items.length > 10
|
9
|
+
- (columns = 3; size = "modal-xl") if items.length > 30
|
10
|
+
|
11
|
+
.modal.fade.show{id: "customize-items-#{field_key}", tabindex: "-1", role: "dialog", "aria-labelledby": "loginLabel", "aria-hidden": "true", data: {controller: "remote-modal",
|
12
|
+
action: "turbo:before-render@document->remote-modal#hideBeforeRender"}}
|
13
|
+
.modal-dialog{class: size}
|
14
|
+
.modal-content
|
15
|
+
.modal-header
|
16
|
+
%h4#loginLabel.modal-title
|
17
|
+
= t("custom_table.customize_table")+":"
|
18
|
+
= search_model.model_name.human
|
19
|
+
- if !variant.nil?
|
20
|
+
%small= "(#{variant})"
|
21
|
+
|
22
|
+
%button.btn-close(type="button" data-bs-dismiss="modal" aria-label="close")
|
23
|
+
.modal-body
|
24
|
+
|
25
|
+
= turbo_frame_tag "custom-table-settings" do
|
26
|
+
|
27
|
+
%p= t("custom_table.customize_table_description")
|
28
|
+
|
29
|
+
= simple_form_for current_user, url: custom_table.setting_path(id: search_model.model_name) do |f|
|
30
|
+
|
31
|
+
= f.hidden_field "model", value: search_model.model_name
|
32
|
+
= f.hidden_field "variant", value: variant
|
33
|
+
|
34
|
+
= f.simple_fields_for :fields do |sfi|
|
35
|
+
%div{data: {controller: "sortable", "data-sortable-handle-value": ".handle"}, style: "columns: #{columns}"}
|
36
|
+
|
37
|
+
- items.each do |field, item|
|
38
|
+
-# = abort item.inspect
|
39
|
+
%div
|
40
|
+
%i.handle{style: "cursor: move", class: custom_table_move_icon_class}
|
41
|
+
.form-check.form-switch.d-inline-block
|
42
|
+
-# - if item[:appear] == :always
|
43
|
+
= sfi.hidden_field field, value: 1 if item[:appear] == :always
|
44
|
+
= sfi.check_box field, {checked: item[:selected], class: "form-check-input", disabled: (item[:appear] == :always)}, 1, 0#, as: :boolean, wrapper: :custom_boolean_switch, label: item[0], checked: true
|
45
|
+
%label.form-check-label= item[:label]
|
46
|
+
-# - if items.length < 20
|
47
|
+
-# = sf.input "fields", :as => :check_boxes, wrapper: :custom_boolean_switch, wrapper_html: {data: "kek"}, :collection => items, :label_text => false, label: false, checked: checkeds
|
48
|
+
-# - else
|
49
|
+
-# .row
|
50
|
+
-# .col-md-6
|
51
|
+
-# = sf.input "fields", :as => :check_boxes, wrapper: :custom_boolean_switch, :collection => items[0..(items.length/2)], :label_text => false, label: false, checked: checkeds
|
52
|
+
-# .col-md-6
|
53
|
+
-# = sf.input "fields", :as => :check_boxes, wrapper: :custom_boolean_switch, :collection => items[(items.length/2+1)..], :label_text => false, label: false, checked: checkeds
|
54
|
+
|
55
|
+
.mt-2
|
56
|
+
= f.button :submit, class: "btn-success", value: t("custom_table.save_settings")
|
57
|
+
= link_to t("custom_table.reset_settings"), custom_table.setting_path(id: search_model.model_name, variant: variant), data: {"turbo-method": "delete", "turbo-confirm": t("custom_table.are_you_sure")}, class: "btn btn-outline-secondary"
|