headmin 0.4.2 → 0.5.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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -1
  3. data/Gemfile.lock +1 -1
  4. data/app/assets/javascripts/headmin/controllers/date_range_controller.js +12 -6
  5. data/app/assets/javascripts/headmin/controllers/filter_controller.js +61 -11
  6. data/app/assets/javascripts/headmin/controllers/filter_row_controller.js +50 -0
  7. data/app/assets/javascripts/headmin/controllers/flatpickr_controller.js +2 -6
  8. data/app/assets/javascripts/headmin/controllers/popup_controller.js +3 -1
  9. data/app/assets/javascripts/headmin/controllers/table_actions_controller.js +16 -21
  10. data/app/assets/javascripts/headmin/index.js +2 -0
  11. data/app/assets/javascripts/headmin.js +119 -38
  12. data/app/assets/stylesheets/headmin/filter.scss +74 -0
  13. data/app/assets/stylesheets/headmin/general.scss +0 -1
  14. data/app/assets/stylesheets/headmin/layout/body.scss +5 -0
  15. data/app/assets/stylesheets/headmin/popup.scss +0 -1
  16. data/app/assets/stylesheets/headmin.css +67 -2
  17. data/app/controllers/concerns/headmin/filterable.rb +27 -0
  18. data/app/models/headmin/filter/base.rb +238 -0
  19. data/app/models/headmin/filter/base_view.rb +64 -0
  20. data/app/models/headmin/filter/boolean.rb +15 -0
  21. data/app/models/headmin/filter/boolean_view.rb +61 -0
  22. data/app/models/headmin/filter/button_view.rb +25 -0
  23. data/app/models/headmin/filter/conditional_view.rb +16 -0
  24. data/app/models/headmin/filter/date.rb +19 -0
  25. data/app/models/headmin/filter/date_view.rb +52 -0
  26. data/app/models/headmin/filter/flatpickr_view.rb +54 -0
  27. data/app/models/headmin/filter/menu_item_view.rb +6 -0
  28. data/app/models/headmin/filter/money.rb +13 -0
  29. data/app/models/headmin/filter/number.rb +27 -0
  30. data/app/models/headmin/filter/number_view.rb +54 -0
  31. data/app/models/headmin/filter/operator_view.rb +30 -0
  32. data/app/models/headmin/filter/options_view.rb +61 -0
  33. data/app/models/headmin/filter/row_view.rb +13 -0
  34. data/app/models/headmin/filter/search.rb +18 -0
  35. data/app/models/headmin/filter/search_view.rb +31 -0
  36. data/app/models/headmin/filter/text.rb +25 -0
  37. data/app/models/headmin/filter/text_view.rb +53 -0
  38. data/app/models/headmin/filters.rb +29 -0
  39. data/app/models/headmin/form/datetime_range_view.rb +25 -0
  40. data/app/models/headmin/form/datetime_view.rb +45 -0
  41. data/app/models/headmin/form/flatpickr_range_view.rb +4 -15
  42. data/app/models/headmin/form/flatpickr_view.rb +3 -12
  43. data/app/models/view_model.rb +1 -1
  44. data/app/views/examples/admin.html.erb +13 -13
  45. data/app/views/examples/auth.html.erb +1 -1
  46. data/app/views/headmin/_filters.html.erb +6 -6
  47. data/app/views/headmin/_form.html.erb +2 -2
  48. data/app/views/headmin/_index.html.erb +1 -1
  49. data/app/views/headmin/_pagination.html.erb +1 -1
  50. data/app/views/headmin/_popup.html.erb +2 -2
  51. data/app/views/headmin/_table.html.erb +1 -1
  52. data/app/views/headmin/dropdown/_devise.html.erb +8 -8
  53. data/app/views/headmin/dropdown/_locale.html.erb +4 -4
  54. data/app/views/headmin/filters/_base.html.erb +95 -0
  55. data/app/views/headmin/filters/_boolean.html.erb +23 -0
  56. data/app/views/headmin/filters/_date.html.erb +14 -38
  57. data/app/views/headmin/filters/_flatpickr.html.erb +15 -48
  58. data/app/views/headmin/filters/_number.html.erb +23 -0
  59. data/app/views/headmin/filters/_options.html.erb +24 -0
  60. data/app/views/headmin/filters/_search.html.erb +14 -12
  61. data/app/views/headmin/filters/_text.html.erb +23 -0
  62. data/app/views/headmin/filters/filter/_button.html.erb +9 -10
  63. data/app/views/headmin/filters/filter/_conditional.html.erb +18 -0
  64. data/app/views/headmin/filters/filter/_menu_item.html.erb +5 -2
  65. data/app/views/headmin/filters/filter/_null_select.html.erb +8 -0
  66. data/app/views/headmin/filters/filter/_operator.html.erb +16 -0
  67. data/app/views/headmin/filters/filter/_row.html.erb +11 -0
  68. data/app/views/headmin/forms/_blocks.html.erb +1 -1
  69. data/app/views/headmin/forms/_date_range.html.erb +3 -3
  70. data/app/views/headmin/forms/_datetime.html.erb +41 -0
  71. data/app/views/headmin/forms/_datetime_range.html.erb +40 -0
  72. data/app/views/headmin/forms/_file.html.erb +3 -3
  73. data/app/views/headmin/forms/_flatpickr.html.erb +1 -1
  74. data/app/views/headmin/forms/_flatpickr_range.html.erb +3 -4
  75. data/app/views/headmin/forms/_label.html.erb +1 -1
  76. data/app/views/headmin/forms/_repeater.html.erb +12 -12
  77. data/app/views/headmin/forms/fields/_base.html.erb +1 -1
  78. data/app/views/headmin/forms/fields/_file.html.erb +2 -2
  79. data/app/views/headmin/forms/fields/_files.html.erb +2 -2
  80. data/app/views/headmin/forms/fields/_group.html.erb +5 -5
  81. data/app/views/headmin/forms/fields/_list.html.erb +4 -4
  82. data/app/views/headmin/forms/fields/_text.html.erb +2 -2
  83. data/app/views/headmin/layout/_footer.html.erb +1 -1
  84. data/app/views/headmin/layout/_main.html.erb +1 -1
  85. data/app/views/headmin/nav/_dropdown.html.erb +3 -3
  86. data/app/views/headmin/nav/_item.html.erb +2 -2
  87. data/app/views/headmin/nav/item/_devise.html.erb +8 -8
  88. data/app/views/headmin/nav/item/_locale.html.erb +4 -4
  89. data/app/views/headmin/table/_actions.html.erb +3 -6
  90. data/app/views/headmin/table/actions/_export.html.erb +1 -1
  91. data/app/views/headmin/table/body/_row.html.erb +3 -3
  92. data/app/views/headmin/table/foot/_id.html.erb +1 -1
  93. data/app/views/headmin/views/devise/confirmations/_new.html.erb +1 -1
  94. data/app/views/headmin/views/devise/passwords/_edit.html.erb +2 -2
  95. data/app/views/headmin/views/devise/passwords/_new.html.erb +1 -1
  96. data/app/views/headmin/views/devise/registrations/_edit.html.erb +4 -4
  97. data/app/views/headmin/views/devise/registrations/_new.html.erb +3 -3
  98. data/app/views/headmin/views/devise/sessions/_new.html.erb +3 -3
  99. data/app/views/headmin/views/devise/unlocks/_new.html.erb +1 -1
  100. data/config/locales/en.yml +4 -0
  101. data/config/locales/headmin/dropdown/en.yml +6 -0
  102. data/config/locales/headmin/dropdown/nl.yml +6 -0
  103. data/config/locales/headmin/filters/en.yml +26 -1
  104. data/config/locales/headmin/filters/nl.yml +26 -1
  105. data/config/locales/headmin/layout/en.yml +0 -9
  106. data/config/locales/headmin/layout/nl.yml +0 -9
  107. data/config/locales/headmin/nav/en.yml +7 -0
  108. data/config/locales/headmin/nav/nl.yml +7 -0
  109. data/config/locales/nl.yml +4 -0
  110. data/lib/generators/templates/views/layouts/auth.html.erb +1 -1
  111. data/lib/headmin/version.rb +1 -1
  112. data/package.json +1 -1
  113. metadata +42 -6
  114. data/app/controllers/concerns/headmin/filter.rb +0 -5
  115. data/app/controllers/concerns/headmin/searchable.rb +0 -15
  116. data/app/views/headmin/filters/_select.html.erb +0 -45
  117. data/app/views/headmin/filters/filter/_template.html.erb +0 -13
@@ -2,5 +2,10 @@
2
2
  background: $card-cap-bg;
3
3
  height: 100vh;
4
4
  overflow-y: scroll;
5
+ display: flex;
6
+ flex-direction: column;
5
7
  }
6
8
 
9
+ .content {
10
+ flex-grow: 1
11
+ }
@@ -2,7 +2,6 @@
2
2
  padding: 10px;
3
3
  background: $white;
4
4
  z-index: $zindex-popover;
5
- width: $popover-max-width;
6
5
  @include reset-text();
7
6
  @include font-size($popover-font-size);
8
7
  word-wrap: break-word;
@@ -13103,7 +13103,6 @@ input[type=search] {
13103
13103
  }
13104
13104
  html {
13105
13105
  height: 100%;
13106
- background: red;
13107
13106
  }
13108
13107
  .list-group-item.active .text-muted {
13109
13108
  color: #fff !important;
@@ -13117,6 +13116,11 @@ mark,
13117
13116
  background: rgba(0, 0, 0, 0.03);
13118
13117
  height: 100vh;
13119
13118
  overflow-y: scroll;
13119
+ display: flex;
13120
+ flex-direction: column;
13121
+ }
13122
+ .content {
13123
+ flex-grow: 1;
13120
13124
  }
13121
13125
  .sidebar {
13122
13126
  font-size: 0.99rem;
@@ -13310,6 +13314,54 @@ body.empty {
13310
13314
  .h-filter-popup.closed {
13311
13315
  display: none;
13312
13316
  }
13317
+ .h-filter-row {
13318
+ padding: 0.5rem 0 0.5rem 0;
13319
+ position: relative;
13320
+ flex-wrap: unset;
13321
+ display: flex;
13322
+ gap: 10px;
13323
+ }
13324
+ .h-filter-row:first-child {
13325
+ padding-top: 0;
13326
+ }
13327
+ .h-filter-row:hover .h-filter-add-input,
13328
+ .h-filter-row:hover .h-filter-remove-input {
13329
+ display: block;
13330
+ }
13331
+ .h-filter-row input,
13332
+ .h-filter-row select:not(.h-filter-operator) {
13333
+ min-width: 200px;
13334
+ }
13335
+ .h-filter-operator {
13336
+ width: 62px;
13337
+ }
13338
+ .h-filter-conditional {
13339
+ position: relative;
13340
+ display: flex;
13341
+ justify-content: center;
13342
+ align-items: center;
13343
+ }
13344
+ .h-filter-conditional select {
13345
+ width: 50px;
13346
+ }
13347
+ .h-filter-conditional::before {
13348
+ content: "";
13349
+ position: absolute;
13350
+ top: calc(50% - 1px);
13351
+ left: 0;
13352
+ width: calc(50% - 35px);
13353
+ height: 1px;
13354
+ background: rgba(0, 0, 0, 0.2);
13355
+ }
13356
+ .h-filter-conditional::after {
13357
+ content: "";
13358
+ position: absolute;
13359
+ top: calc(50% - 1px);
13360
+ left: calc(50% + 35px);
13361
+ width: calc(50% - 35px);
13362
+ height: 1px;
13363
+ background: rgba(0, 0, 0, 0.2);
13364
+ }
13313
13365
  .h-filter-remove {
13314
13366
  border-left: 1px solid #d1d5db;
13315
13367
  padding-left: 8px;
@@ -13318,11 +13370,24 @@ body.empty {
13318
13370
  .h-filter-remove i::before {
13319
13371
  font-size: 0.8em;
13320
13372
  }
13373
+ .h-filter-add-input {
13374
+ position: absolute !important;
13375
+ top: calc(100% - 12px);
13376
+ left: calc(50% - 17px);
13377
+ z-index: 9;
13378
+ display: none;
13379
+ }
13380
+ .h-filter-remove-input {
13381
+ position: absolute !important;
13382
+ top: calc(50% - 15px);
13383
+ left: calc(100% - 4px);
13384
+ z-index: 9;
13385
+ display: none;
13386
+ }
13321
13387
  .h-popup {
13322
13388
  padding: 10px;
13323
13389
  background: #ffffff;
13324
13390
  z-index: 1070;
13325
- width: 276px;
13326
13391
  font-family: var(--bs-font-sans-serif);
13327
13392
  font-style: normal;
13328
13393
  font-weight: 400;
@@ -0,0 +1,27 @@
1
+ module Headmin
2
+ module Filterable
3
+ # Will create a Headmin::Filters object with a default configuration for "id" and "search"
4
+ #
5
+ # Example:
6
+ #
7
+ # orders = Order
8
+ # orders = filter(orders, {
9
+ # status: :text,
10
+ # price: :number,
11
+ # in_stock: :boolean
12
+ # })
13
+ # @orders = orders.all
14
+
15
+ def filter(collection, filter_types = {})
16
+ type_hash = default_filter_types.merge(filter_types)
17
+ Headmin::Filters.new(params, type_hash).query(collection)
18
+ end
19
+
20
+ def default_filter_types
21
+ {
22
+ id: :number,
23
+ search: :search
24
+ }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,238 @@
1
+ module Headmin
2
+ module Filter
3
+ class Base
4
+ # Constants
5
+ OPERATORS = %w[eq not_eq gt gteq lt lteq between not_between in not_in matches does_not_match is_null is_not_null starts_with ends_with]
6
+
7
+ OPERATORS_CONVERT_TO = {
8
+ convert_to_range: %w[between not_between],
9
+ convert_to_array: %w[in not_in],
10
+ convert_to_value: %w[eq not_eq gt gteq lt lteq matches does_not_match is_null is_not_null starts_with ends_with]
11
+ }
12
+
13
+ QUERY_VALUE_CONVERT_TO = {
14
+ convert_value_to_match: %w[matches does_not_match],
15
+ convert_to_starts: %w[starts_with],
16
+ convert_to_ends: %w[ends_with]
17
+ }
18
+
19
+ QUERY_OPERATOR_CONVERT_TO = {
20
+ matches: %w[starts_with ends_with]
21
+ }
22
+
23
+ # Methods
24
+ def initialize(attribute, params)
25
+ @raw_value = params[attribute]
26
+ @attribute = attribute
27
+ @instructions = []
28
+
29
+ if params.key?(attribute)
30
+ parse(@raw_value)
31
+ end
32
+ end
33
+
34
+ attr_reader :raw_value
35
+ attr_reader :attribute
36
+
37
+ def values
38
+ @instructions.map { |instruction| instruction[:value] }.compact
39
+ end
40
+
41
+ def operators
42
+ @instructions.map { |instruction| instruction[:operator] }.compact
43
+ end
44
+
45
+ def conditionals
46
+ @instructions.map { |instruction| instruction[:conditional] }.compact
47
+ end
48
+
49
+ def string(display_values = [])
50
+ build_instructions_string(display_values)
51
+ end
52
+
53
+ attr_reader :instructions
54
+
55
+ def query(collection)
56
+ return collection unless @instructions.any?
57
+
58
+ query = nil
59
+
60
+ @instructions.each do |instruction|
61
+ query = build_query(query, collection, instruction)
62
+ end
63
+
64
+ collection.where(query)
65
+ end
66
+
67
+ def cast_value(value)
68
+ # This method has to be implemented by the child.
69
+
70
+ raise NotImplementedMethodError, "Method cast_value has to be implemented by its child."
71
+ end
72
+
73
+ def display_value(value)
74
+ value
75
+ end
76
+
77
+ private
78
+
79
+ def parse(string)
80
+ instructions = split_instructions(string)
81
+ parse_instructions(instructions)
82
+ end
83
+
84
+ def split_instructions(string)
85
+ # Regex: It divides the string into separate parts:
86
+ # - Parts starting with +AND+ OR +OR+ until the next occurrence of +
87
+ # - First part of the string till the first occurrence of +
88
+ string.scan(/\+AND\+[^+]* | \+OR\+[^+]* | ^[^+]*/x)
89
+ end
90
+
91
+ def parse_instructions(instructions)
92
+ instructions.map { |instruction| parse_instruction(instruction) }
93
+ end
94
+
95
+ def parse_instruction(string)
96
+ conditional_raw, operator_raw, value_raw = split_instruction(string)
97
+
98
+ conditional = parse_conditional(conditional_raw)
99
+ operator = parse_operator(operator_raw, value_raw)
100
+ value = parse_value(value_raw, operator)
101
+
102
+ @instructions << {conditional: conditional, operator: operator, value: value}
103
+ end
104
+
105
+ def split_instruction(string)
106
+ # Regex: takes out the +AND+ / +OR+, if not found returns nil
107
+ conditional_raw = string.scan(/\+AND\+|\+OR\+/)[0]
108
+
109
+ string = string.remove("+AND+").remove("+OR+")
110
+
111
+ # Regex: takes out the value before the : character
112
+ operator_raw = string.match(/^[^:]*/)[0] == string ? nil : string.match(/^[^:]*/)[0]
113
+ value_raw = string.remove("#{operator_raw}:")
114
+
115
+ [conditional_raw, operator_raw, value_raw]
116
+ end
117
+
118
+ def parse_conditional(string)
119
+ string.present? ? string.delete("+").downcase : nil
120
+ end
121
+
122
+ def parse_operator(string, value)
123
+ if self.class::OPERATORS.include?(string)
124
+ # Operator is recognized, return the operator
125
+ string
126
+ elsif string.nil?
127
+ # There was no operator present, default operator is eq
128
+ "eq"
129
+ else
130
+ # There was an operator passed but it was not recognized, raise exception
131
+ raise OperatorNotSupportedError, "This operator is not supported by this filter."
132
+ end
133
+ end
134
+
135
+ def parse_value(string, operator)
136
+ if operator == "is_null" || operator == "is_not_null"
137
+ return string.to_i
138
+ end
139
+ process_value(string, operator)
140
+ end
141
+
142
+ def build_query(query, collection, instruction)
143
+ query_operator = convert_to_query_operator(instruction[:operator])
144
+ query_value = convert_to_query_value(instruction[:value], instruction[:operator])
145
+
146
+ query_operator, query_value = process_null_operators(query_operator, query_value)
147
+
148
+ model = collection.is_a?(Class) ? collection : collection.model
149
+ new_query = model.arel_table[attribute].send(query_operator, query_value)
150
+
151
+ query ? query.send(instruction[:conditional], new_query) : new_query
152
+ end
153
+
154
+ def process_null_operators(operator, value)
155
+ # In case of null operators (is_null and is_not_null), we have to intercept the operator and value values
156
+ # and transform them to the correct operator (eq or not_eq) and value (nil)
157
+ if operator == "is_null" && value == 1
158
+ ["eq", nil]
159
+ elsif operator == "is_null" && value == 0
160
+ ["not_eq", nil]
161
+ elsif operator == "is_not_null" && value == 1
162
+ ["not_eq", nil]
163
+ elsif operator == "is_not_null" && value == 0
164
+ ["eq", nil]
165
+ else
166
+ [operator, value]
167
+ end
168
+ end
169
+
170
+ def convert_to_range(value)
171
+ array = value.split(",")
172
+ cast_value(array[0])..cast_value(array[1])
173
+ end
174
+
175
+ def convert_to_array(value)
176
+ array = value.split(",")
177
+ array.map { |e| cast_value(e) }
178
+ end
179
+
180
+ def convert_to_value(value)
181
+ cast_value(value)
182
+ end
183
+
184
+ def convert_to_query_value(value, operator)
185
+ value_converter = self.class::QUERY_VALUE_CONVERT_TO.find { |key, values| values.include?(operator) }&.first
186
+
187
+ if value_converter.present?
188
+ send(value_converter, value)
189
+ else
190
+ value
191
+ end
192
+ end
193
+
194
+ def convert_value_to_match(value)
195
+ "%#{value}%"
196
+ end
197
+
198
+ def convert_to_query_operator(operator)
199
+ self.class::QUERY_OPERATOR_CONVERT_TO.find { |key, values| values.include?(operator) }&.first || operator
200
+ end
201
+
202
+ def match_operator_with_convert(operator)
203
+ self.class::OPERATORS_CONVERT_TO.find { |key, values| values.include?(operator) }&.first
204
+ end
205
+
206
+ def process_value(value, operator)
207
+ send(match_operator_with_convert(operator), value)
208
+ end
209
+
210
+ def build_instructions_string(display_values)
211
+ strings = @instructions.map { |instruction| build_instruction_string(instruction, display_values) }
212
+ strings.join(" ")
213
+ end
214
+
215
+ def build_instruction_string(instruction, display_values)
216
+ conditional = instruction[:conditional].present? ? "#{I18n.t("headmin.filters.conditionals.#{instruction[:conditional]}")} " : nil
217
+ operator = I18n.t("headmin.filters.operators.#{instruction[:operator]}")
218
+ value = display_values[:display_values].find { |item| item.second == instruction[:value] }
219
+ value = value.present? ? value.first : instruction[:value]
220
+
221
+ value = if instruction[:operator] == "is_null" || instruction[:operator] == "is_not_null"
222
+ # In case of special operators (is_null & is_not_null), we intercept the value
223
+ value == 1 ? I18n.t("headmin.filters.values.yes") : I18n.t("headmin.filters.values.no")
224
+ else
225
+ display_value(value)
226
+ end
227
+
228
+ "#{conditional}#{operator} &ldquo;#{value}&rdquo;"
229
+ end
230
+ end
231
+
232
+ class OperatorNotSupportedError < StandardError
233
+ end
234
+
235
+ class NotImplementedMethodError < StandardError
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,64 @@
1
+ module Headmin
2
+ module Filter
3
+ class BaseView < ViewModel
4
+ def menu_item_options
5
+ keys = %i[name label]
6
+ options = to_h.slice(*keys)
7
+ default_menu_item_options.merge(options)
8
+ end
9
+
10
+ def filter_template_options
11
+ keys = %i[name label]
12
+ options = to_h.slice(*keys)
13
+ default_filter_template_options.merge(options)
14
+ end
15
+
16
+ def filter_operator_options
17
+ keys = %i[allowed_operators]
18
+ options = to_h.slice(*keys)
19
+ default_filter_operator_options.merge(options)
20
+ end
21
+
22
+ def filter_button_options
23
+ keys = %i[name label display_values filter]
24
+ options = to_h.slice(*keys)
25
+ default_filter_button_options.merge(options)
26
+ end
27
+
28
+ def label
29
+ @label || name.to_s.humanize
30
+ end
31
+
32
+ private
33
+
34
+ def default_menu_item_options
35
+ {
36
+ name: nil,
37
+ label: label
38
+ }
39
+ end
40
+
41
+ def default_filter_template_options
42
+ {
43
+ name: nil,
44
+ label: label
45
+ }
46
+ end
47
+
48
+ def default_filter_operator_options
49
+ {
50
+ allowed_operators: []
51
+ }
52
+ end
53
+
54
+ def default_filter_button_options
55
+ {
56
+ name: nil,
57
+ label: label,
58
+ display_values: [],
59
+ filter: nil
60
+ }
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,15 @@
1
+ module Headmin
2
+ module Filter
3
+ class Boolean < Headmin::Filter::Base
4
+ OPERATORS = %w[eq not_eq is_null is_not_null]
5
+
6
+ def cast_value(value)
7
+ ActiveModel::Type::Boolean.new.cast(value)
8
+ end
9
+
10
+ def to_s
11
+ string
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,61 @@
1
+ module Headmin
2
+ module Filter
3
+ class BooleanView < ViewModel
4
+ def base_options
5
+ keys = %i[name label form]
6
+ options = to_h.slice(*keys)
7
+ default_base_options.merge(options)
8
+ end
9
+
10
+ def input_options
11
+ keys = %i[form]
12
+ options = to_h.slice(*keys)
13
+ default_input_options.merge(options)
14
+ end
15
+
16
+ private
17
+
18
+ def value
19
+ # If the value is nil we want to display the first option (which is true)
20
+ @value.nil? ? @value = true : ""
21
+ end
22
+
23
+ def id
24
+ "#{name}_value"
25
+ end
26
+
27
+ def name
28
+ @name || attribute
29
+ end
30
+
31
+ def label
32
+ @label || I18n.t("attributes.#{attribute}", default: name.to_s)
33
+ end
34
+
35
+ def default_base_options
36
+ {
37
+ label: label,
38
+ name: attribute,
39
+ filter: Headmin::Filter::Boolean.new(name, @params),
40
+ display_values: [[I18n.t("headmin.filters.values.true"), true], [I18n.t("headmin.filters.values.false"), false]],
41
+ allowed_operators: Headmin::Filter::Boolean::OPERATORS
42
+ }
43
+ end
44
+
45
+ def default_input_options
46
+ {
47
+ label: false,
48
+ wrapper: false,
49
+ id: id,
50
+ collection: [[I18n.t("headmin.filters.values.true"), 1], [I18n.t("headmin.filters.values.false"), 0]],
51
+ selected: value ? 1 : 0,
52
+ data: {
53
+ action: "change->filter#updateHiddenValue",
54
+ filter_target: "value",
55
+ filter_row_target: "original"
56
+ }
57
+ }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,25 @@
1
+ module Headmin
2
+ module Filter
3
+ class ButtonView < ViewModel
4
+ def id
5
+ @id || SecureRandom.hex
6
+ end
7
+
8
+ def name
9
+ @name.to_sym
10
+ end
11
+
12
+ def label
13
+ @label || name.to_s.humanize
14
+ end
15
+
16
+ def display_values
17
+ @display_values || []
18
+ end
19
+
20
+ def filter
21
+ @filter || nil
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ module Headmin
2
+ module Filter
3
+ class ConditionalView < ViewModel
4
+ def selected
5
+ @selected ? "+#{@selected.to_s.upcase}+" : nil
6
+ end
7
+
8
+ def values
9
+ {
10
+ "+AND+": I18n.t("headmin.filters.filter.conditional.and"),
11
+ "+OR+": I18n.t("headmin.filters.filter.conditional.or")
12
+ }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ module Headmin
2
+ module Filter
3
+ class Date < Headmin::Filter::Base
4
+ OPERATORS = %w[eq not_eq gt gteq lt lteq between not_between in not_in is_null is_not_null]
5
+
6
+ def cast_value(value)
7
+ value.present? ? value.to_date : ::Date.current
8
+ rescue
9
+ raise TypeError, "The value that was passed to this filter is not a date"
10
+ end
11
+
12
+ def display_value(value)
13
+ # This uses the default date format of headmin.
14
+ # Can be overwritten by setting default date format of the application.
15
+ I18n.l(value)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,52 @@
1
+ module Headmin
2
+ module Filter
3
+ class DateView < ViewModel
4
+ def base_options
5
+ keys = %i[name label form]
6
+ options = to_h.slice(*keys)
7
+ default_base_options.merge(options)
8
+ end
9
+
10
+ def input_options
11
+ keys = %i[form]
12
+ options = to_h.slice(*keys)
13
+ default_input_options.merge(options)
14
+ end
15
+
16
+ private
17
+
18
+ def id
19
+ "#{name}_value"
20
+ end
21
+
22
+ def name
23
+ @name || attribute
24
+ end
25
+
26
+ def label
27
+ @label || I18n.t("attributes.#{attribute}", default: name.to_s)
28
+ end
29
+
30
+ def default_base_options
31
+ {
32
+ label: label,
33
+ name: attribute,
34
+ filter: Headmin::Filter::Date.new(name, @params),
35
+ allowed_operators: Headmin::Filter::Date::OPERATORS - %w[in not_in between not_between]
36
+ }
37
+ end
38
+
39
+ def default_input_options
40
+ {
41
+ label: false,
42
+ wrapper: false,
43
+ id: id,
44
+ data: {action: "change->filter#updateHiddenValue",
45
+ filter_target: "value",
46
+ filter_row_target: "original"},
47
+ class: "form-control"
48
+ }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,54 @@
1
+ module Headmin
2
+ module Filter
3
+ class FlatpickrView < ViewModel
4
+ def base_options
5
+ keys = %i[name label form]
6
+ options = to_h.slice(*keys)
7
+ default_base_options.merge(options)
8
+ end
9
+
10
+ def input_options
11
+ keys = %i[form]
12
+ options = to_h.slice(*keys)
13
+ default_input_options.merge(options)
14
+ end
15
+
16
+ private
17
+
18
+ def id
19
+ "#{name}_value"
20
+ end
21
+
22
+ def name
23
+ @name || attribute
24
+ end
25
+
26
+ def label
27
+ @label || I18n.t("attributes.#{attribute}", default: name.to_s)
28
+ end
29
+
30
+ def default_base_options
31
+ {
32
+ label: label,
33
+ name: attribute,
34
+ filter: Headmin::Filter::Date.new(name, @params),
35
+ allowed_operators: Headmin::Filter::Date::OPERATORS - %w[in not_in between not_between]
36
+ }
37
+ end
38
+
39
+ def default_input_options
40
+ {
41
+ label: false,
42
+ wrapper: false,
43
+ id: id,
44
+ data: {
45
+ action: "change->filter#updateHiddenValue",
46
+ filter_target: "value",
47
+ filter_row_target: "original"
48
+ },
49
+ class: "form-control"
50
+ }
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,6 @@
1
+ module Headmin
2
+ module Filter
3
+ class MenuItemView < ViewModel
4
+ end
5
+ end
6
+ end