madmin 2.6.0 → 3.1.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.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +120 -4
  3. data/app/assets/stylesheets/madmin/actiontext.css +31 -0
  4. data/app/assets/stylesheets/madmin/base.css +22 -11
  5. data/app/assets/stylesheets/madmin/buttons.css +9 -9
  6. data/app/assets/stylesheets/madmin/forms.css +263 -7
  7. data/app/assets/stylesheets/madmin/pagination.css +10 -23
  8. data/app/assets/stylesheets/madmin/reset.css +1 -1
  9. data/app/assets/stylesheets/madmin/sidebar.css +6 -6
  10. data/app/assets/stylesheets/madmin/tables.css +1 -1
  11. data/app/controllers/madmin/base_controller.rb +1 -5
  12. data/app/controllers/madmin/resource_controller.rb +38 -17
  13. data/app/helpers/madmin/application_helper.rb +15 -2
  14. data/app/helpers/madmin/filters_helper.rb +26 -0
  15. data/app/helpers/madmin/sort_helper.rb +1 -1
  16. data/app/javascript/madmin/application.js +4 -0
  17. data/app/javascript/madmin/controllers/application.js +0 -3
  18. data/app/javascript/madmin/controllers/filters_controller.js +35 -0
  19. data/app/javascript/madmin/controllers/nested_form_controller.js +5 -0
  20. data/app/javascript/madmin/controllers/select_controller.js +269 -22
  21. data/app/views/layouts/madmin/application.html.erb +1 -0
  22. data/app/views/madmin/application/_active_filters.html.erb +15 -0
  23. data/app/views/madmin/application/_filter.html.erb +13 -0
  24. data/app/views/madmin/application/_filters.html.erb +32 -0
  25. data/app/views/madmin/application/_javascript.html.erb +0 -2
  26. data/app/views/madmin/application/_pagination.html.erb +32 -0
  27. data/app/views/madmin/application/edit.html.erb +1 -1
  28. data/app/views/madmin/application/index.html.erb +13 -12
  29. data/app/views/madmin/application/new.html.erb +1 -1
  30. data/app/views/madmin/application/show.html.erb +1 -1
  31. data/app/views/madmin/fields/array/_form.html.erb +1 -0
  32. data/app/views/madmin/fields/array/_index.html.erb +1 -0
  33. data/app/views/madmin/fields/array/_show.html.erb +1 -0
  34. data/app/views/madmin/fields/attachment/_form.html.erb +1 -5
  35. data/app/views/madmin/fields/attachment/_show.html.erb +3 -1
  36. data/app/views/madmin/fields/attachments/_form.html.erb +1 -1
  37. data/app/views/madmin/fields/attachments/_index.html.erb +1 -1
  38. data/app/views/madmin/fields/belongs_to/_form.html.erb +1 -1
  39. data/app/views/madmin/fields/belongs_to/_index.html.erb +5 -2
  40. data/app/views/madmin/fields/belongs_to/_show.html.erb +1 -1
  41. data/app/views/madmin/fields/enum/_form.html.erb +1 -1
  42. data/app/views/madmin/fields/file/_form.html.erb +1 -1
  43. data/app/views/madmin/fields/has_many/_form.html.erb +1 -1
  44. data/app/views/madmin/fields/has_many/_index.html.erb +1 -1
  45. data/app/views/madmin/fields/has_many/_show.html.erb +3 -11
  46. data/app/views/madmin/fields/has_one/_index.html.erb +5 -1
  47. data/app/views/madmin/fields/has_one/_show.html.erb +1 -1
  48. data/app/views/madmin/fields/json/_form.html.erb +1 -1
  49. data/app/views/madmin/fields/json/_show.html.erb +1 -1
  50. data/app/views/madmin/fields/nested_has_many/_index.html.erb +1 -1
  51. data/app/views/madmin/fields/nested_has_many/_show.html.erb +3 -11
  52. data/app/views/madmin/fields/nested_has_one/_form.html.erb +18 -0
  53. data/app/views/madmin/fields/polymorphic/_form.html.erb +1 -2
  54. data/app/views/madmin/fields/polymorphic/_index.html.erb +5 -1
  55. data/app/views/madmin/fields/polymorphic/_show.html.erb +1 -1
  56. data/app/views/madmin/fields/select/_form.html.erb +1 -1
  57. data/config/importmap.rb +0 -3
  58. data/config/locales/el.yml +44 -0
  59. data/config/locales/en.yml +44 -0
  60. data/config/locales/fr.yml +44 -0
  61. data/config/locales/ko.yml +44 -0
  62. data/lib/madmin/field.rb +27 -1
  63. data/lib/madmin/fields/array.rb +16 -0
  64. data/lib/madmin/fields/attachment.rb +7 -0
  65. data/lib/madmin/fields/belongs_to.rb +2 -1
  66. data/lib/madmin/fields/boolean.rb +1 -0
  67. data/lib/madmin/fields/currency.rb +7 -0
  68. data/lib/madmin/fields/date.rb +1 -0
  69. data/lib/madmin/fields/date_time.rb +1 -0
  70. data/lib/madmin/fields/decimal.rb +1 -0
  71. data/lib/madmin/fields/float.rb +1 -0
  72. data/lib/madmin/fields/has_many.rb +6 -23
  73. data/lib/madmin/fields/integer.rb +1 -0
  74. data/lib/madmin/fields/json.rb +21 -0
  75. data/lib/madmin/fields/nested_has_many.rb +2 -2
  76. data/lib/madmin/fields/nested_has_one.rb +48 -0
  77. data/lib/madmin/fields/polymorphic.rb +5 -0
  78. data/lib/madmin/fields/string.rb +2 -0
  79. data/lib/madmin/fields/text.rb +2 -0
  80. data/lib/madmin/filter.rb +96 -0
  81. data/lib/madmin/menu.rb +2 -1
  82. data/lib/madmin/page.rb +61 -0
  83. data/lib/madmin/pagination.rb +50 -0
  84. data/lib/madmin/resource.rb +85 -7
  85. data/lib/madmin/search.rb +5 -7
  86. data/lib/madmin/version.rb +1 -1
  87. data/lib/madmin.rb +45 -2
  88. metadata +17 -16
@@ -0,0 +1,32 @@
1
+ <% if resource.filterable_attributes.any? %>
2
+ <button type="button" id="filters-button" class="btn btn-secondary" popovertarget="filters">
3
+ <%= t("madmin.filters.button") %>
4
+ <%= tag.span filters.size, class: "filters-count" if filters.any? %>
5
+ </button>
6
+
7
+ <div id="filters" class="filters" popover data-controller="filters">
8
+ <%= form_with url: resource.index_path, method: :get, skip_enforcing_utf8: true do %>
9
+ <% request.query_parameters.slice("q", "scope", "sort", "direction").each do |name, value| %>
10
+ <%= hidden_field_tag name, value, id: nil %>
11
+ <% end %>
12
+
13
+ <p class="filters-heading"><%= t("madmin.filters.heading") %></p>
14
+
15
+ <div class="filter-rows" data-filters-target="rows">
16
+ <% (filters.presence || [Madmin::Filter.blank(resource)]).each do |filter| %>
17
+ <%= render "filter", filter: filter %>
18
+ <% end %>
19
+ </div>
20
+
21
+ <template data-filters-target="row"><%= render "filter", filter: Madmin::Filter.blank(resource) %></template>
22
+ <% Madmin::Filter::OPERATORS.each_key do |type| %>
23
+ <template data-filters-target="operators" data-filter-type="<%= type %>"><%= options_for_select(filter_operator_options(type)) %></template>
24
+ <% end %>
25
+
26
+ <div class="filters-footer">
27
+ <button type="button" class="btn btn-link" data-action="filters#add"><%= t("madmin.filters.add") %></button>
28
+ <button type="submit" class="btn btn-primary"><%= t("madmin.filters.apply") %></button>
29
+ </div>
30
+ <% end %>
31
+ </div>
32
+ <% end %>
@@ -4,5 +4,3 @@
4
4
  <%= tag.script 'import "lexxy"'.html_safe, type: "module" if defined?(::Lexxy) %>
5
5
 
6
6
  <%= stylesheet_link_tag *Madmin.stylesheets, "data-turbo-track": "reload" %>
7
- <%= stylesheet_link_tag "https://unpkg.com/flatpickr/dist/flatpickr.min.css", "data-turbo-track": "reload" %>
8
- <%= stylesheet_link_tag "https://unpkg.com/tom-select/dist/css/tom-select.min.css", "data-turbo-track": "reload" %>
@@ -0,0 +1,32 @@
1
+ <div class="pagination">
2
+ <% if page.last > 1 %>
3
+ <% page_url = ->(number) { "#{request.path}?#{request.query_parameters.merge(page.param => number).to_query}" } %>
4
+ <nav class="pages" aria-label="<%= t("madmin.pagination.label") %>">
5
+ <%= link_to_if page.prev, "<", page.prev && page_url.call(page.prev), aria: { label: t("madmin.pagination.previous") } do %>
6
+ <a role="link" aria-disabled="true" aria-label="<%= t("madmin.pagination.previous") %>">&lt;</a>
7
+ <% end %>
8
+
9
+ <% page.series.each do |number| %>
10
+ <% if number == :gap %>
11
+ <a role="separator" aria-disabled="true">&hellip;</a>
12
+ <% elsif number == page.page %>
13
+ <a role="link" aria-disabled="true" aria-current="page"><%= number %></a>
14
+ <% else %>
15
+ <%= link_to number, page_url.call(number) %>
16
+ <% end %>
17
+ <% end %>
18
+
19
+ <%= link_to_if page.next, ">", page.next && page_url.call(page.next), aria: { label: t("madmin.pagination.next") } do %>
20
+ <a role="link" aria-disabled="true" aria-label="<%= t("madmin.pagination.next") %>">&gt;</a>
21
+ <% end %>
22
+ </nav>
23
+ <% end %>
24
+
25
+ <span class="pagination-info">
26
+ <% if page.to.zero? %>
27
+ <%= t("madmin.pagination.empty") %>
28
+ <% else %>
29
+ <%= t("madmin.pagination.info", from: page.from, to: page.to, count: page.count) %>
30
+ <% end %>
31
+ </span>
32
+ </div>
@@ -2,7 +2,7 @@
2
2
 
3
3
  <header class="header">
4
4
  <h1>
5
- <%= link_to resource.friendly_name.pluralize, resource.index_path %>
5
+ <%= link_to resource.friendly_plural_name, resource.index_path %>
6
6
  /
7
7
  <strong><%= t("madmin.actions.edit") %> <%= link_to resource.display_name(@record), resource.show_path(@record) %></strong>
8
8
  </h1>
@@ -1,12 +1,17 @@
1
- <%= content_for :title, resource.friendly_name.pluralize %>
1
+ <%= content_for :title, resource.friendly_plural_name %>
2
2
 
3
3
  <header class="header">
4
- <h1><%= resource.friendly_name.pluralize %></h1>
4
+ <h1><%= resource.friendly_plural_name %></h1>
5
5
 
6
6
  <div class="actions">
7
7
  <form class="search">
8
8
  <%= hidden_field_tag :page, params[:page], value: 1 %>
9
9
  <%= search_field_tag :q, params[:q], placeholder: t("madmin.search.placeholder") %>
10
+ <% filters.map(&:to_h).each do |filter| %>
11
+ <% filter.each do |name, value| %>
12
+ <%= hidden_field_tag "filters[][#{name}]", value, id: nil %>
13
+ <% end %>
14
+ <% end %>
10
15
  </form>
11
16
 
12
17
  <%= link_to clear_search_params, class: "btn btn-secondary" do %>
@@ -15,6 +20,8 @@
15
20
  </svg>
16
21
  <% end if params[:q].present? %>
17
22
 
23
+ <%= render "filters" %>
24
+
18
25
  <% resource.collection_actions.each do |action| %>
19
26
  <%= instance_exec(&action) %>
20
27
  <% end %>
@@ -27,13 +34,15 @@
27
34
  </div>
28
35
  </header>
29
36
 
37
+ <%= render "active_filters" %>
38
+
30
39
  <nav class="scopes">
31
40
  <% if resource.scopes.any? %>
32
41
  <%= link_to t("madmin.actions.all"), resource.index_path, class: class_names("btn btn-secondary", {"active" => params[:scope].blank?}) %>
33
42
  <% end %>
34
43
 
35
44
  <% resource.scopes.each do |scope| %>
36
- <%= link_to scope.to_s.humanize, resource.index_path(scope: scope, q: params[:q], sort: params[:sort], direction: params[:direction]), class: class_names("btn btn-secondary", {"active" => params[:scope] == scope.to_s}) %>
45
+ <%= link_to resource.scope_label(scope), index_path_with(scope: scope), class: class_names("btn btn-secondary", {"active" => params[:scope] == scope.to_s}) %>
37
46
  <% end %>
38
47
  </nav>
39
48
 
@@ -74,12 +83,4 @@
74
83
  </table>
75
84
  </div>
76
85
 
77
- <div class="pagination">
78
- <% if @pagy.respond_to?(:series_nav) %>
79
- <%== @pagy.series_nav if @pagy.last > 1 %>
80
- <%== @pagy.info_tag %>
81
- <% else %>
82
- <%== pagy_nav @pagy if @pagy.last > 1 %>
83
- <%== pagy_info @pagy %>
84
- <% end %>
85
- </div>
86
+ <%= render "pagination", page: @page %>
@@ -2,7 +2,7 @@
2
2
 
3
3
  <header class="header">
4
4
  <h1>
5
- <%= link_to resource.friendly_name.pluralize, resource.index_path %>
5
+ <%= link_to resource.friendly_plural_name, resource.index_path %>
6
6
  /
7
7
  <strong><%= t("madmin.titles.new", name: resource.friendly_name) %></strong>
8
8
  </h1>
@@ -2,7 +2,7 @@
2
2
 
3
3
  <header class="header">
4
4
  <h1>
5
- <%= link_to resource.friendly_name.pluralize, resource.index_path %>
5
+ <%= link_to resource.friendly_plural_name, resource.index_path %>
6
6
  /
7
7
  <%= resource.display_name(@record) %>
8
8
  </h1>
@@ -0,0 +1 @@
1
+ <%= form.text_field field.attribute_name, value: field.text(record), class: "form-input" %>
@@ -0,0 +1 @@
1
+ <%= field.text(record) %>
@@ -0,0 +1 @@
1
+ <%= field.text(record) %>
@@ -1,11 +1,7 @@
1
- <%= form.file_field field.to_param, class: "form-input"%>
1
+ <%= form.file_field field.to_param, direct_upload: field.options.direct_upload, class: "form-input" %>
2
2
 
3
3
  <% if form.object.persisted? %>
4
4
  <div class="mt-2">
5
5
  <%= render partial: field.to_partial_path("show"), locals: {field: field, record: record} %>
6
-
7
- <% if (value = field.value(record) && value&.attached?) %>
8
- <%= link_to t("madmin.actions.remove"), Madmin.resource_for(value).show_path(value), data: { turbo_method: :delete, turbo_confirm: t("madmin.confirmations.remove_with_changes")} %>
9
- <% end %>
10
6
  </div>
11
7
  <% end %>
@@ -5,5 +5,7 @@
5
5
  <%= link_to attachment.filename, main_app.url_for(attachment), target: :_blank, class: "text-blue-500 underline" %>
6
6
  <% end %>
7
7
 
8
- <%= link_to t("madmin.actions.remove"), Madmin.resource_for(attachment).show_path(attachment), data: { turbo_method: :delete, turbo_confirm: t("madmin.confirmations.remove_with_changes")} %>
8
+ <% if (remove_path = field.remove_path(attachment)) %>
9
+ <%= link_to t("madmin.actions.remove"), remove_path, data: { turbo_method: :delete, turbo_confirm: t("madmin.confirmations.remove_with_changes")} %>
10
+ <% end %>
9
11
  <% end %>
@@ -1 +1 @@
1
- <%= form.file_field field.attribute_name, multiple: true, class: "form-input" %>
1
+ <%= form.file_field field.attribute_name, multiple: true, direct_upload: field.options.direct_upload, class: "form-input" %>
@@ -1 +1 @@
1
- <%= t("madmin.fields.attachment", count: field.value(record).count) %>
1
+ <%= t("madmin.fields.attachment", count: field.value(record).size) %>
@@ -1 +1 @@
1
- <%= form.select field.to_param, field.options_for_select(record), { prompt: true }, { data: { controller: "select", select_url_value: field.index_path } } %>
1
+ <%= form.select field.to_param, field.options_for_select(record), { prompt: true }, { class: "form-input", data: { controller: "select", select_url_value: field.index_path } } %>
@@ -1,4 +1,7 @@
1
1
  <% if (object = field.value(record)) %>
2
- <% object_resource = Madmin.resource_for(object) %>
3
- <%= link_to object_resource.display_name(object), object_resource.show_path(object) %>
2
+ <% if (associated_resource = field.associated_resource_for(object)) %>
3
+ <%= link_to_record associated_resource, object %>
4
+ <% else %>
5
+ <%= render partial: "missing_resource", locals: { resource_name: Madmin.resource_name_for(object) } %>
6
+ <% end %>
4
7
  <% end %>
@@ -1,6 +1,6 @@
1
1
  <% if (object = field.value(record)) %>
2
2
  <% if (associated_resource = field.associated_resource_for(object)) %>
3
- <%= link_to associated_resource.display_name(object), associated_resource.show_path(object) %>
3
+ <%= link_to_record associated_resource, object %>
4
4
  <% else %>
5
5
  <%= render partial: "missing_resource", locals: { resource_name: Madmin.resource_name_for(object) } %>
6
6
  <% end %>
@@ -1 +1 @@
1
- <%= form.select field.attribute_name, field.options_for_select(record), { prompt: true }, { data: { controller: "select" } } %>
1
+ <%= form.select field.attribute_name, field.options_for_select(record), { prompt: true }, { class: "form-input" } %>
@@ -1 +1 @@
1
- <%= form.file_field field.attribute_name, class: "form-input" %>
1
+ <%= form.file_field field.attribute_name, direct_upload: field.options.direct_upload, class: "form-input" %>
@@ -1 +1 @@
1
- <%= form.select "#{field.attribute_name.to_s.singularize}_ids", field.options_for_select(record), { prompt: true }, { multiple: true, data: { controller: "select", select_url_value: field.index_path } } %>
1
+ <%= form.select "#{field.attribute_name.to_s.singularize}_ids", field.options_for_select(record), { prompt: true }, { multiple: true, class: "form-input", data: { controller: "select", select_url_value: field.index_path } } %>
@@ -1 +1 @@
1
- <%= pluralize field.value(record).count, field.attribute_name.to_s %>
1
+ <%= pluralize field.value(record).size, field.attribute_name.to_s %>
@@ -1,18 +1,10 @@
1
- <% pagy, records = field.paginated_value(record, params) %>
1
+ <% page, records = field.paginated_value(record, params) %>
2
2
  <% records.each do |object| %>
3
3
  <% if (associated_resource = field.associated_resource_for(object)) %>
4
- <div><%= link_to associated_resource.display_name(object), associated_resource.show_path(object) %></div>
4
+ <div><%= link_to_record associated_resource, object %></div>
5
5
  <% else %>
6
6
  <%= render partial: "missing_resource", locals: { resource_name: Madmin.resource_name_for(object) } %>
7
7
  <% end %>
8
8
  <% end %>
9
9
 
10
- <div class="pagination">
11
- <% if pagy.respond_to?(:series_nav) %>
12
- <%== pagy.series_nav if pagy.last > 1 %>
13
- <%== pagy.info_tag %>
14
- <% else %>
15
- <%== pagy_nav pagy if pagy.last > 1 %>
16
- <%== pagy_info pagy %>
17
- <% end %>
18
- </div>
10
+ <%= render "pagination", page: page %>
@@ -1,3 +1,7 @@
1
1
  <% if (object = field.value(record)) %>
2
- <%= link_to Madmin.resource_for(object).display_name(object), Madmin.resource_for(object).show_path(object) %>
2
+ <% if (associated_resource = field.associated_resource_for(object)) %>
3
+ <%= link_to_record associated_resource, object %>
4
+ <% else %>
5
+ <%= render partial: "missing_resource", locals: { resource_name: Madmin.resource_name_for(object) } %>
6
+ <% end %>
3
7
  <% end %>
@@ -1,6 +1,6 @@
1
1
  <% if (object = field.value(record)) %>
2
2
  <% if (associated_resource = field.associated_resource_for(object)) %>
3
- <%= link_to associated_resource.display_name(object), associated_resource.show_path(object) %>
3
+ <%= link_to_record associated_resource, object %>
4
4
  <% else %>
5
5
  <%= render partial: "missing_resource", locals: { resource_name: Madmin.resource_name_for(object) } %>
6
6
  <% end %>
@@ -1 +1 @@
1
- <%= form.text_area field.attribute_name, class: "form-input" %>
1
+ <%= form.text_area field.attribute_name, value: field.json(record), class: "form-input" %>
@@ -1 +1 @@
1
- <%= field.value(record) %>
1
+ <pre><%= field.json(record) %></pre>
@@ -1 +1 @@
1
- <%= pluralize field.value(record).count, field.attribute_name.to_s %>
1
+ <%= pluralize field.value(record).size, field.attribute_name.to_s %>
@@ -1,18 +1,10 @@
1
- <% pagy, records = field.paginated_value(record, params) %>
1
+ <% page, records = field.paginated_value(record, params) %>
2
2
  <% records.each do |object| %>
3
3
  <% if (associated_resource = field.associated_resource_for(object)) %>
4
- <div><%= link_to associated_resource.display_name(object), associated_resource.show_path(object) %></div>
4
+ <div><%= link_to_record associated_resource, object %></div>
5
5
  <% else %>
6
6
  <%= render partial: "missing_resource", locals: { resource_name: Madmin.resource_name_for(object) } %>
7
7
  <% end %>
8
8
  <% end %>
9
9
 
10
- <div class="pagination">
11
- <% if pagy.respond_to?(:series_nav) %>
12
- <%== pagy.series_nav if pagy.last > 1 %>
13
- <%== pagy.info_tag %>
14
- <% else %>
15
- <%== pagy_nav pagy if pagy.last > 1 %>
16
- <%== pagy_info pagy %>
17
- <% end %>
18
- </div>
10
+ <%= render "pagination", page: page %>
@@ -0,0 +1,18 @@
1
+ <div data-controller="nested-form" data-nested-form-single="true">
2
+ <template data-nested-form-target="template">
3
+ <%= form.fields_for field.attribute_name, field.to_model.new do |nested_form| %>
4
+ <%= render partial: field.to_partial_path("fields"), locals: { f: nested_form, field: field } %>
5
+ <% end %>
6
+ </template>
7
+
8
+ <% if (object = field.value(record)) %>
9
+ <%= form.fields_for field.attribute_name, object do |nested_form| %>
10
+ <%= render partial: field.to_partial_path("fields"), locals: { f: nested_form, field: field } %>
11
+ <% end %>
12
+ <% end %>
13
+
14
+ <%# Nothing is created until "Add" is clicked, since the template isn't submitted %>
15
+ <%= tag.div data: { nested_form_target: "links" }, hidden: object.present? do %>
16
+ <%= link_to t("madmin.nested_form.add_new"), "#", data: { action: "click->nested-form#add_association" } %>
17
+ <% end %>
18
+ </div>
@@ -1,4 +1,3 @@
1
1
  <%= form.fields_for field.attribute_name do |pf| %>
2
- <%= pf.select :value, field.options_for_select(record).map(&:to_global_id), { selected: field.value(record)&.to_global_id, prompt: true }, { data: { controller: "select" } } %>
3
- <%= pf.hidden_field :type, value: "polymorphic" %>
2
+ <%= pf.select :value, field.options_for_select(record).map(&:to_global_id), { selected: field.value(record)&.to_global_id, prompt: true }, { class: "form-input" } %>
4
3
  <% end %>
@@ -1,3 +1,7 @@
1
1
  <% if (object = field.value(record)) %>
2
- <%= link_to Madmin.resource_for(object).display_name(object), Madmin.resource_for(object).show_path(object) %>
2
+ <% if (associated_resource = field.associated_resource_for(object)) %>
3
+ <%= link_to_record associated_resource, object %>
4
+ <% else %>
5
+ <%= render partial: "missing_resource", locals: { resource_name: Madmin.resource_name_for(object) } %>
6
+ <% end %>
3
7
  <% end %>
@@ -1,6 +1,6 @@
1
1
  <% if (object = field.value(record)) %>
2
2
  <% if (associated_resource = field.associated_resource_for(object)) %>
3
- <%= link_to associated_resource.display_name(object), associated_resource.show_path(object), class: "text-blue-500 underline" %>
3
+ <%= link_to_record associated_resource, object, class: "text-blue-500 underline" %>
4
4
  <% else %>
5
5
  <%= render partial: "missing_resource", locals: { resource_name: Madmin.resource_name_for(object) } %>
6
6
  <% end %>
@@ -1 +1 @@
1
- <%= form.select field.attribute_name, field.options_for_select(record), { prompt: true }, { data: { controller: "select" } } %>
1
+ <%= form.select field.attribute_name, field.options_for_select(record), { prompt: true }, { class: "form-input" } %>
data/config/importmap.rb CHANGED
@@ -10,6 +10,3 @@ pin "lexxy", to: "lexxy.js" if defined?(::Lexxy)
10
10
 
11
11
  pin_all_from Madmin::Engine.root.join("app/javascript/madmin/controllers"), under: "controllers", to: "madmin/controllers"
12
12
  pin_all_from Rails.root.join("app/javascript/madmin/controllers"), under: "controllers", to: "madmin/controllers"
13
-
14
- pin "tom-select", to: "https://ga.jspm.io/npm:tom-select@2.4.1/dist/js/tom-select.complete.js"
15
- pin "tailwindcss-stimulus-components", to: "https://ga.jspm.io/npm:tailwindcss-stimulus-components@6.1.2/dist/tailwindcss-stimulus-components.module.js"
@@ -40,3 +40,47 @@ el:
40
40
  missing_resource:
41
41
  message: "Λείπει: %{resource}"
42
42
  create_hint: "Δημιουργήστε το εκτελώντας:"
43
+ pagination:
44
+ label: Σελιδοποίηση
45
+ previous: Προηγούμενη
46
+ next: Επόμενη
47
+ info: "Εμφάνιση %{from}-%{to} από %{count}"
48
+ empty: Δεν βρέθηκαν εγγραφές
49
+ filters:
50
+ button: "Φίλτρα"
51
+ heading: "Εμφάνιση εγγραφών όπου"
52
+ add: "+ Προσθήκη φίλτρου"
53
+ apply: "Εφαρμογή"
54
+ clear_all: "Εκκαθάριση φίλτρων"
55
+ remove: "Αφαίρεση φίλτρου"
56
+ column: "Στήλη"
57
+ operator: "Συνθήκη"
58
+ value: "Τιμή"
59
+ operators:
60
+ string:
61
+ contains: "περιέχει"
62
+ eq: "είναι"
63
+ starts_with: "ξεκινά με"
64
+ blank: "είναι κενό"
65
+ present: "δεν είναι κενό"
66
+ number:
67
+ eq: "="
68
+ gt: ">"
69
+ gte: "≥"
70
+ lt: "<"
71
+ lte: "≤"
72
+ blank: "είναι κενό"
73
+ date:
74
+ eq: "στις"
75
+ gte: "στις ή μετά"
76
+ lte: "στις ή πριν"
77
+ blank: "είναι κενό"
78
+ datetime:
79
+ gte: "μετά"
80
+ lte: "πριν"
81
+ blank: "είναι κενό"
82
+ boolean:
83
+ "true": "είναι αληθές"
84
+ "false": "είναι ψευδές"
85
+ array:
86
+ includes: "περιέχει"
@@ -40,3 +40,47 @@ en:
40
40
  missing_resource:
41
41
  message: "%{resource} is missing."
42
42
  create_hint: "Create it by running:"
43
+ pagination:
44
+ label: Pagination
45
+ previous: Previous
46
+ next: Next
47
+ info: "Displaying %{from}-%{to} of %{count}"
48
+ empty: No records found
49
+ filters:
50
+ button: "Filters"
51
+ heading: "Show records where"
52
+ add: "+ Add filter"
53
+ apply: "Apply"
54
+ clear_all: "Clear filters"
55
+ remove: "Remove filter"
56
+ column: "Column"
57
+ operator: "Condition"
58
+ value: "Value"
59
+ operators:
60
+ string:
61
+ contains: "contains"
62
+ eq: "is"
63
+ starts_with: "starts with"
64
+ blank: "is empty"
65
+ present: "is not empty"
66
+ number:
67
+ eq: "="
68
+ gt: ">"
69
+ gte: "≥"
70
+ lt: "<"
71
+ lte: "≤"
72
+ blank: "is empty"
73
+ date:
74
+ eq: "on"
75
+ gte: "on or after"
76
+ lte: "on or before"
77
+ blank: "is empty"
78
+ datetime:
79
+ gte: "after"
80
+ lte: "before"
81
+ blank: "is empty"
82
+ boolean:
83
+ "true": "is true"
84
+ "false": "is false"
85
+ array:
86
+ includes: "includes"
@@ -40,3 +40,47 @@ fr:
40
40
  missing_resource:
41
41
  message: "%{resource} est introuvable."
42
42
  create_hint: "Pour le créer, exécutez :"
43
+ pagination:
44
+ label: Pagination
45
+ previous: Précédent
46
+ next: Suivant
47
+ info: "Affichage de %{from} à %{to} sur %{count}"
48
+ empty: Aucun enregistrement trouvé
49
+ filters:
50
+ button: "Filtres"
51
+ heading: "Afficher les enregistrements où"
52
+ add: "+ Ajouter un filtre"
53
+ apply: "Appliquer"
54
+ clear_all: "Effacer les filtres"
55
+ remove: "Retirer le filtre"
56
+ column: "Colonne"
57
+ operator: "Condition"
58
+ value: "Valeur"
59
+ operators:
60
+ string:
61
+ contains: "contient"
62
+ eq: "est"
63
+ starts_with: "commence par"
64
+ blank: "est vide"
65
+ present: "n'est pas vide"
66
+ number:
67
+ eq: "="
68
+ gt: ">"
69
+ gte: "≥"
70
+ lt: "<"
71
+ lte: "≤"
72
+ blank: "est vide"
73
+ date:
74
+ eq: "le"
75
+ gte: "le ou après"
76
+ lte: "le ou avant"
77
+ blank: "est vide"
78
+ datetime:
79
+ gte: "après"
80
+ lte: "avant"
81
+ blank: "est vide"
82
+ boolean:
83
+ "true": "est vrai"
84
+ "false": "est faux"
85
+ array:
86
+ includes: "contient"
@@ -38,3 +38,47 @@ ko:
38
38
  missing_resource:
39
39
  message: "%{resource}이(가) 존재하지 않습니다."
40
40
  create_hint: "다음 명령어를 실행하여 생성하세요:"
41
+ pagination:
42
+ label: 페이지 탐색
43
+ previous: 이전
44
+ next: 다음
45
+ info: "%{count}개 중 %{from}-%{to} 표시"
46
+ empty: 레코드가 없습니다
47
+ filters:
48
+ button: "필터"
49
+ heading: "다음 조건의 레코드 표시"
50
+ add: "+ 필터 추가"
51
+ apply: "적용"
52
+ clear_all: "필터 지우기"
53
+ remove: "필터 제거"
54
+ column: "열"
55
+ operator: "조건"
56
+ value: "값"
57
+ operators:
58
+ string:
59
+ contains: "포함"
60
+ eq: "일치"
61
+ starts_with: "시작"
62
+ blank: "비어 있음"
63
+ present: "비어 있지 않음"
64
+ number:
65
+ eq: "="
66
+ gt: ">"
67
+ gte: "≥"
68
+ lt: "<"
69
+ lte: "≤"
70
+ blank: "비어 있음"
71
+ date:
72
+ eq: "당일"
73
+ gte: "이후(당일 포함)"
74
+ lte: "이전(당일 포함)"
75
+ blank: "비어 있음"
76
+ datetime:
77
+ gte: "이후"
78
+ lte: "이전"
79
+ blank: "비어 있음"
80
+ boolean:
81
+ "true": "참"
82
+ "false": "거짓"
83
+ array:
84
+ includes: "포함"
data/lib/madmin/field.rb CHANGED
@@ -6,6 +6,10 @@ module Madmin
6
6
  to_s.split("::").last.underscore
7
7
  end
8
8
 
9
+ # The kind of index filter a field offers (:string, :number, :date,
10
+ # :datetime or :boolean), or nil for none
11
+ def self.filter_type = nil
12
+
9
13
  def initialize(attribute_name:, model:, resource:, options:)
10
14
  @attribute_name = attribute_name.to_sym
11
15
  @model = model
@@ -29,8 +33,18 @@ module Madmin
29
33
  attribute_name
30
34
  end
31
35
 
36
+ # Converts the submitted form value into the value assigned to the record
37
+ def cast(value)
38
+ value
39
+ end
40
+
41
+ # Whether the value returned by `cast` can be saved. The form is shown again with an error when it can't.
42
+ def accepts?(value)
43
+ true
44
+ end
45
+
32
46
  def label
33
- options[:label].presence || attribute_name.to_s.titleize
47
+ options[:label].presence || model.human_attribute_name(attribute_name)
34
48
  end
35
49
 
36
50
  # Used for checking visibility of attribute on an view
@@ -62,6 +76,18 @@ module Madmin
62
76
  false
63
77
  end
64
78
 
79
+ def filter_type
80
+ self.class.filter_type if filterable?
81
+ end
82
+
83
+ private
84
+
85
+ # Only database columns can be filtered, and not encrypted ones since their
86
+ # stored values can't be compared. `filter: false` turns it off
87
+ def filterable?
88
+ options.fetch(:filter, true) && resource.model_column_names.include?(attribute_name.to_s) && !model.try(:encrypted_attributes)&.include?(attribute_name)
89
+ end
90
+
65
91
  ActiveSupport.run_load_hooks(:madmin_field, self)
66
92
  end
67
93
  end