mensa 0.4.0 → 0.6.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/.zed/tasks.json +1 -1
  3. data/Gemfile +1 -0
  4. data/Gemfile.lock +12 -8
  5. data/README.md +30 -15
  6. data/app/components/mensa/add_filter/component.css +5 -3
  7. data/app/components/mensa/add_filter/component.html.erb +3 -3
  8. data/app/components/mensa/add_filter/component_controller.js +1 -0
  9. data/app/components/mensa/column_customizer/component.css +4 -4
  10. data/app/components/mensa/column_customizer/component.html.erb +4 -4
  11. data/app/components/mensa/column_customizer/component_controller.js +1 -1
  12. data/app/components/mensa/control_bar/component.css +23 -3
  13. data/app/components/mensa/control_bar/component.html.erb +13 -12
  14. data/app/components/mensa/empty_state/component.html.erb +1 -1
  15. data/app/components/mensa/empty_state/component.rb +1 -1
  16. data/app/components/mensa/filter_pill/component.html.erb +2 -2
  17. data/app/components/mensa/filter_pill_list/component.html.erb +2 -2
  18. data/app/components/mensa/header/component.html.erb +1 -1
  19. data/app/components/mensa/table/component.html.erb +8 -4
  20. data/app/components/mensa/table/component.rb +3 -3
  21. data/app/components/mensa/table/component_controller.js +104 -30
  22. data/app/components/mensa/view/component.html.erb +1 -1
  23. data/app/components/mensa/view/component.rb +1 -1
  24. data/app/components/mensa/views/component.html.erb +8 -8
  25. data/app/controllers/mensa/tables/exports_controller.rb +25 -5
  26. data/app/controllers/mensa/tables_controller.rb +8 -10
  27. data/app/helpers/mensa/application_helper.rb +2 -2
  28. data/app/jobs/mensa/export_job.rb +4 -0
  29. data/app/jobs/mensa/recurring_exports_job.rb +17 -0
  30. data/app/models/mensa/export.rb +54 -2
  31. data/app/tables/mensa/base.rb +55 -13
  32. data/app/tables/mensa/cell.rb +8 -4
  33. data/app/tables/mensa/column.rb +5 -0
  34. data/app/tables/mensa/config/column_dsl.rb +1 -0
  35. data/app/tables/mensa/config/dsl_logic.rb +19 -1
  36. data/app/tables/mensa/config/format_dsl.rb +7 -0
  37. data/app/tables/mensa/config/table_dsl.rb +2 -5
  38. data/app/tables/mensa/filter.rb +24 -1
  39. data/app/tables/mensa/format.rb +23 -0
  40. data/app/tables/mensa/scope.rb +5 -3
  41. data/app/views/mensa/exports/_dialog.html.erb +25 -2
  42. data/app/views/mensa/exports/_list.html.erb +15 -2
  43. data/app/views/mensa/tables/filters/show.turbo_stream.erb +4 -1
  44. data/app/views/mensa/tables/views/destroy.turbo_stream.erb +5 -1
  45. data/config/locales/en.yml +25 -0
  46. data/config/locales/nl.yml +24 -0
  47. data/config/routes.rb +1 -1
  48. data/db/migrate/20260612110000_add_repeat_to_mensa_exports.rb +8 -0
  49. data/lib/generators/mensa/templates/config/initializers/mensa.rb +29 -8
  50. data/lib/mensa/configuration.rb +64 -17
  51. data/lib/mensa/version.rb +1 -1
  52. data/lib/tasks/mensa_tasks.rake +7 -0
  53. metadata +6 -2
@@ -8,26 +8,24 @@ module Mensa
8
8
 
9
9
  attr_writer :original_view_context
10
10
  attr_accessor :component, :name, :table_view, :request
11
- attr_reader :params
12
11
 
12
+ config_reader :params
13
13
  config_reader :model
14
14
  config_reader :scope
15
15
  config_reader :link, call: false
16
- config_reader :supports_views?
17
16
  config_reader :supports_custom_views?
18
- config_reader :supports_filters?
19
17
  config_reader :view_columns_ordering?
20
18
  config_reader :show_header?
21
19
  config_reader :exportable?
22
20
  config_reader :export_with_password?
23
21
 
24
22
  def initialize(config = {})
25
- @params = config.to_h.deep_symbolize_keys
26
- @config = self.class.definition.merge(@params || {})
23
+ normalized_config = config.to_h.deep_symbolize_keys
24
+ @config = self.class.definition.merge(normalized_config)
25
+ @params = (@config[:params].presence || {}).deep_symbolize_keys
26
+ @config[:params] = @params
27
27
 
28
- ensure_internal_columns_for_joined_associations
29
-
30
- params[:hidden_columns]&.each do |column_name|
28
+ current_hidden_columns&.each do |column_name|
31
29
  c = columns.find { |c| c.name == column_name.to_sym }
32
30
  c.config[:visible] = false
33
31
  end
@@ -48,12 +46,17 @@ module Mensa
48
46
 
49
47
  # Returns all columns
50
48
  def columns
49
+ ensure_internal_columns_for_joined_associations
51
50
  @columns ||= column_order.map { |column_name| Mensa::Column.new(column_name, config: config.dig(:columns, column_name), table: self) }
52
51
  end
53
52
 
54
53
  # Returns a column by name
55
54
  # @param [String] name
56
55
  def column(name)
56
+ found_column = columns.find { |c| c.name == name.to_sym }
57
+ return found_column if found_column || @internal_columns_ensured
58
+
59
+ @columns = nil
57
60
  columns.find { |c| c.name == name.to_sym }
58
61
  end
59
62
 
@@ -117,16 +120,19 @@ module Mensa
117
120
  end
118
121
 
119
122
  # Returns the current path with configuration
120
- def path(order: {}, turbo_frame_id: nil, table_view_id: params[:table_view_id], column_order: params[:column_order], hidden_columns: params[:hidden_columns])
123
+ def path(order: {}, turbo_frame_id: current_turbo_frame_id, table_view_id: current_table_view_id, column_order: current_column_order, hidden_columns: current_hidden_columns, user_params: nil)
121
124
  # FIXME: if someone doesn't use as: :mensa in the routes, it breaks
122
- original_view_context.mensa.table_path(
123
- name,
125
+ path = original_view_context.mensa.table_path(name)
126
+ query = {
127
+ params: user_params || params,
124
128
  order: order_hash(order),
125
129
  turbo_frame_id: turbo_frame_id,
126
130
  table_view_id: table_view_id,
127
131
  column_order: column_order,
128
132
  hidden_columns: hidden_columns
129
- )
133
+ }.compact.to_query
134
+
135
+ query.present? ? "#{path}?#{query}" : path
130
136
  end
131
137
 
132
138
  def all_views
@@ -135,6 +141,10 @@ module Mensa
135
141
  views
136
142
  end
137
143
 
144
+ def views?
145
+ all_views.reject{_1.id == :default}.present?
146
+ end
147
+
138
148
  # The user that owns custom views. Returns nil when the host application has
139
149
  # no current user, in which case views cannot be saved.
140
150
  def current_user
@@ -146,21 +156,53 @@ module Mensa
146
156
  def table_id
147
157
  return @table_id if @table_id
148
158
 
149
- @table_id = params[:turbo_frame_id] || "#{name.to_s.gsub("/", "__")}-#{SecureRandom.base36}"
159
+ @table_id = current_turbo_frame_id || "#{name.to_s.gsub("/", "__")}-#{SecureRandom.base36}"
150
160
  end
151
161
 
152
162
  def original_view_context
153
163
  @original_view_context || component.original_view_context
154
164
  end
155
165
 
166
+ def current_query
167
+ config[:query]
168
+ end
169
+
170
+ def current_order
171
+ config[:order]
172
+ end
173
+
174
+ def current_order_provided?
175
+ config.key?(:order)
176
+ end
177
+
178
+ def current_table_view_id
179
+ config[:table_view_id]
180
+ end
181
+
182
+ def current_column_order
183
+ config[:column_order]
184
+ end
185
+
186
+ def current_hidden_columns
187
+ config[:hidden_columns]
188
+ end
189
+
190
+ def current_turbo_frame_id
191
+ config[:turbo_frame_id]
192
+ end
193
+
156
194
  private
157
195
 
158
196
  def ensure_internal_columns_for_joined_associations
197
+ return if @internal_columns_ensured
198
+
159
199
  config[:columns] ||= {}
160
200
 
161
201
  auto_internal_column_names.each do |column_name|
162
202
  config[:columns][column_name] ||= {internal: true, filter: false}
163
203
  end
204
+
205
+ @internal_columns_ensured = true
164
206
  end
165
207
 
166
208
  def auto_internal_column_names
@@ -38,10 +38,14 @@ module Mensa
38
38
  content_tag(:i, "", class: "fa-solid fa-check")
39
39
  when FalseClass
40
40
  content_tag(:i, "", class: "fa-solid fa-xmark")
41
+ when Array
42
+ value.to_fs(:db)
41
43
  when Date
42
- respond_to?(:dt) ? dt(value) : value.strftime("%d.%m.%Y")
44
+ I18n.l(value.in_time_zone(column.format.time_zone), format: column.format.format)
45
+ # value.in_time_zone(column.format.time_zone).to_fs(column.format.format)
43
46
  when Time, DateTime
44
- respond_to?(:ln) ? ln(value) : value.strftime("%d-%m-%Y %H:%M:%S")
47
+ I18n.l(value.in_time_zone(column.format.time_zone), format: column.format.format)
48
+ # value.in_time_zone(column.format.time_zone).to_fs(column.format.format)
45
49
  else
46
50
  column.sanitize? ? sanitize(value.to_s) : value.to_s.html_safe
47
51
  end
@@ -54,9 +58,9 @@ module Mensa
54
58
  when TrueClass, FalseClass
55
59
  value.to_s
56
60
  when Date
57
- respond_to?(:dt) ? dt(value) : value.strftime("%d.%m.%Y")
61
+ I18n.l(value.in_time_zone(column.format.time_zone), format: column.format.format)
58
62
  when Time, DateTime
59
- respond_to?(:ln) ? ln(value) : value.strftime("%d-%m-%Y %H:%M:%S")
63
+ value.in_time_zone(column.format.time_zone).to_fs(column.format.format)
60
64
  else
61
65
  strip_tags(value.to_s)
62
66
  end
@@ -19,6 +19,7 @@ module Mensa
19
19
  config_reader :visible?
20
20
  config_reader :internal?
21
21
  config_reader :method # When a method needs to be called on the model, slow!
22
+ config_reader :format
22
23
 
23
24
  def sort_direction
24
25
  value = table.config.dig(:order, name)
@@ -88,6 +89,10 @@ module Mensa
88
89
  @filter ||= Mensa::Filter.new(column: self, config: table.config.dig(:columns, name, :filter) || {}, table: table)
89
90
  end
90
91
 
92
+ def format
93
+ @format ||= Mensa::Format.new(config: config.dig(:format).presence || {}, column: self)
94
+ end
95
+
91
96
  def human_name
92
97
  if table.model < ActiveRecord::Base
93
98
  table.model.human_attribute_name name
@@ -16,6 +16,7 @@ module Mensa::Config
16
16
  option :internal, default: false
17
17
  option :method
18
18
  option :type
19
+ option :format, default: :long, dsl_single_hash: Mensa::Config::FormatDsl, name_attribute: :format
19
20
 
20
21
  option :visible, default: true
21
22
  option :render, dsl: Mensa::Config::RenderDsl
@@ -17,11 +17,13 @@ module Mensa::Config
17
17
  class_methods do
18
18
  attr_reader :default_config
19
19
 
20
- def option(name, default: nil, config_name: nil, dsl: nil, dsl_hash: nil, dsl_array: nil)
20
+ def option(name, default: nil, config_name: nil, name_attribute: nil, dsl: nil, dsl_hash: nil, dsl_array: nil, dsl_single_hash: nil)
21
21
  if dsl
22
22
  dsl_option(name, dsl)
23
23
  elsif dsl_hash
24
24
  dsl_hash(name, dsl_hash, config_name: config_name)
25
+ elsif dsl_single_hash
26
+ dsl_single_hash(name, dsl_single_hash, name_attribute:, default: default)
25
27
  elsif dsl_array
26
28
  dsl_array(name, dsl_array, config_name: config_name)
27
29
  else
@@ -80,6 +82,22 @@ module Mensa::Config
80
82
  end
81
83
  end
82
84
 
85
+ ###
86
+ # Define a DSL that builds a single hash
87
+ # by calling option :action, dsl_single_hash: Mensa::ActionDsl
88
+ #
89
+ def dsl_single_hash(option_name, klass, name_attribute: :name, default: nil)
90
+ config_name ||= option_name.to_s.to_sym
91
+
92
+ @default_config ||= {}
93
+ @default_config[option_name.to_sym] = default.nil? ? {} : {name_attribute => default}
94
+
95
+ define_method(option_name) do |name = nil, &block|
96
+ config[config_name] = {name_attribute => name}
97
+ config[config_name].merge!(klass.new(nil, &block).config.compact)
98
+ end
99
+ end
100
+
83
101
  ###
84
102
  # Define a DSL that builds an array
85
103
  # by calling option :action, dsl_array: Mensa::ActionDsl
@@ -0,0 +1,7 @@
1
+ module Mensa::Config
2
+ class FormatDsl
3
+ include DslLogic
4
+
5
+ option :time_zone, default: -> { Time.zone }
6
+ end
7
+ end
@@ -30,8 +30,6 @@
30
30
  # order last_name: :asc
31
31
  # link { |user| edit_user_path(user) }
32
32
  #
33
- # supports_views true
34
- # supports_filters true
35
33
  #
36
34
  # action :activate do
37
35
  # link { |user| edit_user_path(user) }
@@ -48,6 +46,7 @@ module Mensa::Config
48
46
  class TableDsl
49
47
  include DslLogic
50
48
 
49
+ option :params, default: {}
51
50
  option :model, default: -> {
52
51
  begin
53
52
  self.class.name.demodulize.to_s.classify.gsub("Table", "").singularize.constantize
@@ -75,9 +74,7 @@ module Mensa::Config
75
74
 
76
75
  option :render, dsl: Mensa::Config::RenderDsl
77
76
 
78
- option :supports_views, default: false
79
- option :supports_custom_views, default: false
80
- option :supports_filters, default: true
77
+ option :supports_custom_views, default: true
81
78
  option :show_header, default: true
82
79
  # Whether the table allows to change column ordering
83
80
  option :view_columns_ordering, default: true
@@ -60,11 +60,20 @@ module Mensa
60
60
 
61
61
  def to_s
62
62
  parts = [column.human_name, operator_label]
63
- formatted_value = value.is_a?(Array) ? value.join(", ") : value
64
63
  parts << formatted_value if formatted_value.present? && operator_with_value?
65
64
  parts.join(" ")
66
65
  end
67
66
 
67
+ def formatted_value
68
+ options = collection_options
69
+
70
+ if value.is_a?(Array)
71
+ value.map { |entry| label_for_value(entry, options) }.join(", ")
72
+ else
73
+ label_for_value(value, options)
74
+ end
75
+ end
76
+
68
77
  def filter_scope(record_scope)
69
78
  if scope
70
79
  record_scope.instance_exec(normalize(value), &scope)
@@ -140,6 +149,20 @@ module Mensa
140
149
 
141
150
  private
142
151
 
152
+ def label_for_value(selected_value, options)
153
+ option = options.find { |_label, value| value.to_s == selected_value.to_s }
154
+ option ? option.first : selected_value
155
+ end
156
+
157
+ def collection_options
158
+ collection = column.filter&.collection
159
+ return [] if collection.blank?
160
+
161
+ collection.map do |item|
162
+ item.is_a?(Array) ? [item.first.to_s, item.last.to_s] : [item.to_s, item.to_s]
163
+ end
164
+ end
165
+
143
166
  # Unused at the moment
144
167
  def normalize(query)
145
168
  query # .to_s.gsub(/\s(?![&!|])/, '\\\\ ')
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mensa
4
+ # Represents a batch action that can be applied to multiple selected records.
5
+ #
6
+ # batch :archive do
7
+ # title "Archive"
8
+ # process { |records| records.update_all(archived: true) }
9
+ # end
10
+ class Format
11
+ include ConfigReaders
12
+
13
+ defined_by Mensa::Config::FormatDsl
14
+
15
+ config_reader :format
16
+ config_reader :time_zone
17
+
18
+ def initialize(config:, column:)
19
+ @column = column
20
+ @config = self.class.definition.merge(config || {})
21
+ end
22
+ end
23
+ end
@@ -14,7 +14,7 @@ module Mensa
14
14
  return @filtered_scope if @filtered_scope
15
15
 
16
16
  @filtered_scope = scope
17
- @filtered_scope = search(@filtered_scope, params[:query]) if params[:query].present?
17
+ @filtered_scope = search(@filtered_scope, current_query) if current_query.present?
18
18
 
19
19
  # Use inject
20
20
  active_filters.each do |filter|
@@ -44,6 +44,8 @@ module Mensa
44
44
  def selected_scope
45
45
  return @selected_scope if @selected_scope
46
46
 
47
+ ensure_internal_columns_for_joined_associations
48
+
47
49
  @selected_scope = ordered_scope
48
50
  @selected_scope = @selected_scope.select([:id] + columns.filter_map(&:attribute))
49
51
 
@@ -72,7 +74,7 @@ module Mensa
72
74
  # (even with blank values), use only those — blank means "explicitly no sort".
73
75
  # Falls back to the view/config default only when no order params were sent.
74
76
  def effective_order
75
- result = params.key?(:order) ? (params[:order] || {}) : (config[:order] || {})
77
+ result = current_order_provided? ? (current_order || {}) : (config[:order] || {})
76
78
  result = result.symbolize_keys.compact_blank.transform_values(&:to_sym)
77
79
  result.transform_keys { |column_name| column(column_name).attribute_for_condition }
78
80
  end
@@ -81,7 +83,7 @@ module Mensa
81
83
  # nil values become "" so they appear in the URL as order[col]= (which tells
82
84
  # the server the user explicitly cleared that column's sort direction).
83
85
  def order_hash(new_params = {})
84
- base = params[:order]&.symbolize_keys || config[:order]&.symbolize_keys || {}
86
+ base = current_order&.symbolize_keys || config[:order]&.symbolize_keys || {}
85
87
  merged = base.merge(new_params.symbolize_keys)
86
88
  merged.transform_values { |v| v.nil? ? "" : v.to_sym }
87
89
  end
@@ -5,7 +5,7 @@
5
5
  <header class="mensa-table__export-dialog__header">
6
6
  <h2 class="mensa-table__export-dialog__title"><%= t("mensa.exports.title", default: "Export %{table}", table: table.name.to_s.humanize) %></h2>
7
7
  <button class="mensa-table__export-dialog__close" type="button" data-action="mensa-table#cancelExport" aria-label="<%= t("mensa.exports.close", default: "Close") %>">
8
- <i class="fa-solid fa-xmark"></i>
8
+ <i class="<%= Mensa.config.icons[:exports_dialog_close] %>"></i>
9
9
  </button>
10
10
  </header>
11
11
 
@@ -37,12 +37,35 @@
37
37
  </label>
38
38
  </fieldset>
39
39
 
40
+ <fieldset class="mensa-table__export-dialog__fieldset">
41
+ <legend class="mensa-table__export-dialog__section-title"><%= t("mensa.exports.repeat_section", default: "Repeat") %></legend>
42
+ <label class="mensa-table__export-dialog__option">
43
+ <input type="radio" name="repeat_mode" value="once" checked data-action="change->mensa-table#toggleExportRepeat">
44
+ <span><%= t("mensa.exports.repeat_once", default: "Only once") %></span>
45
+ </label>
46
+ <label class="mensa-table__export-dialog__option">
47
+ <input type="radio" name="repeat_mode" value="repeating" data-action="change->mensa-table#toggleExportRepeat">
48
+ <span><%= t("mensa.exports.repeat_repeating", default: "Repeating") %></span>
49
+ </label>
50
+
51
+ <div class="mensa-table__export-dialog__repeat-options" hidden data-mensa-table-repeat-options>
52
+ <select name="repeat" class="mensa-table__export-dialog__select">
53
+ <option value="daily"><%= t("mensa.exports.repeat_intervals.daily", default: "daily") %></option>
54
+ <option value="weekly"><%= t("mensa.exports.repeat_intervals.weekly", default: "weekly") %></option>
55
+ <option value="monthly"><%= t("mensa.exports.repeat_intervals.monthly", default: "monthly") %></option>
56
+ <option value="quarterly"><%= t("mensa.exports.repeat_intervals.quarterly", default: "quarterly") %></option>
57
+ <option value="bi-yearly"><%= t("mensa.exports.repeat_intervals.bi-yearly", default: "bi-yearly") %></option>
58
+ <option value="yearly"><%= t("mensa.exports.repeat_intervals.yearly", default: "yearly") %></option>
59
+ </select>
60
+ </div>
61
+ </fieldset>
62
+
40
63
  <div class="mensa-table__export-dialog__actions">
41
64
  <button class="mensa-table__export-dialog__button mensa-table__export-dialog__button--secondary" type="button" data-action="mensa-table#cancelExport">
42
65
  <%= t("mensa.exports.cancel", default: "Cancel") %>
43
66
  </button>
44
67
  <button class="mensa-table__export-dialog__button mensa-table__export-dialog__button--primary" type="submit">
45
- <i class="fa-solid fa-file-export"></i>
68
+ <i class="<%= Mensa.config.icons[:exports_dialog_submit] %>"></i>
46
69
  <%= t("mensa.exports.submit", default: "Export") %>
47
70
  </button>
48
71
  </div>
@@ -6,21 +6,34 @@
6
6
  <ul class="mensa-table__export-dialog__list">
7
7
  <% exports.each do |export| %>
8
8
  <li class="mensa-table__export-dialog__item">
9
+ <%= button_to mensa.table_export_path(export.table_name, export),
10
+ method: :delete,
11
+ class: "mensa-table__export-dialog__delete",
12
+ form_class: "mensa-table__export-dialog__delete-form",
13
+ aria: {label: t("mensa.exports.delete", default: "Delete export") } do %>
14
+ <i class="<%= Mensa.config.icons[:exports_list_delete] %>"></i>
15
+ <% end %>
16
+
9
17
  <div class="mensa-table__export-dialog__item-info">
10
18
  <span class="mensa-table__export-dialog__item-name"><%= export.filename.presence || t("mensa.exports.item_name", default: "%{table} export", table: export.table_name.to_s.humanize) %></span>
19
+ <% if export.repeating? %>
20
+ <span class="mensa-table__export-dialog__item-repeat"><%= export.repeat_label %></span>
21
+ <% end %>
11
22
  <span class="mensa-table__export-dialog__item-meta"><%= export.created_at.strftime("%Y-%m-%d %H:%M") %></span>
12
23
  </div>
13
24
  <div class="mensa-table__export-dialog__item-action">
14
25
  <% if export.downloadable? %>
15
26
  <%= link_to mensa.download_table_export_path(export.table_name, export), class: "mensa-table__export-dialog__download", data: {turbo: false} do %>
16
- <i class="fa-solid fa-download"></i>
27
+ <i class="<%= Mensa.config.icons[:exports_list_download] %>"></i>
17
28
  <%= t("mensa.exports.download", default: "Download") %>
18
29
  <% end %>
19
30
  <% elsif export.failed? %>
20
31
  <span class="mensa-table__export-dialog__status mensa-table__export-dialog__status--failed"><%= t("mensa.exports.failed", default: "Failed") %></span>
32
+ <% elsif export.completed? %>
33
+ <span class="mensa-table__export-dialog__status mensa-table__export-dialog__status--pending"><%= t("mensa.exports.waiting", default: "Waiting") %></span>
21
34
  <% else %>
22
35
  <span class="mensa-table__export-dialog__status mensa-table__export-dialog__status--pending">
23
- <i class="fa-solid fa-spinner fa-spin"></i>
36
+ <i class="<%= Mensa.config.icons[:exports_list_processing] %>"></i>
24
37
  <%= t("mensa.exports.processing", default: "Preparing…") %>
25
38
  </span>
26
39
  <% end %>
@@ -54,5 +54,8 @@
54
54
  <% end %>
55
55
  </ul>
56
56
  <hr class="mensa-table__add_filter__popover_container__separator">
57
- <a class="mensa-table__add_filter__popover_container__clear" href="#" data-action="click->mensa-add-filter#reset">Clear</a>
57
+ <a class="mensa-table__add_filter__popover_container__clear" href="#" data-action="click->mensa-add-filter#reset">
58
+ <span class="flex-1"><%= t('mensa.tables.filters.show.clear') %></span>
59
+ <span class="mensa-table__add_filter__enter-hint">⎋ Esc</span>
60
+ </a>
58
61
  <% end %>
@@ -1,5 +1,9 @@
1
1
  <%= turbo_stream.replace "mensa-views-#{@table.table_id}" do %>
2
- <%= render Mensa::Views::Component.new(table: @table) %>
2
+ <% if @table.views? %>
3
+ <%= render Mensa::Views::Component.new(table: @table) %>
4
+ <% else %>
5
+ <div id="mensa-views-<%= @table.table_id %>"></div>
6
+ <% end %>
3
7
  <% end %>
4
8
 
5
9
  <%= turbo_stream.update "filters-#{@table.table_id}" do %>
@@ -1,5 +1,14 @@
1
1
  en:
2
2
  mensa:
3
+ tables:
4
+ filters:
5
+ show:
6
+ clear: Cancel
7
+ actions: Actions
8
+ save: Save
9
+ update_view: Update view
10
+ save_view: Save as new view
11
+ save_as_new_view: Save as new view
3
12
  operators:
4
13
  is: is
5
14
  isnt: isn't
@@ -12,6 +21,7 @@ en:
12
21
  lteq: less than or equal to
13
22
  is_empty: is empty
14
23
  add_filter:
24
+ add: Add filter
15
25
  component:
16
26
  add_filter: Add filter
17
27
  filter_pill_list:
@@ -20,6 +30,7 @@ en:
20
30
  search_only: Search
21
31
  search:
22
32
  component:
33
+ search: Search and filter
23
34
  search_in: Search in %{view}
24
35
  cancel: Cancel
25
36
  save: Save
@@ -35,8 +46,22 @@ en:
35
46
  available_downloads: Available downloads
36
47
  empty: You have no downloads yet. Create one below.
37
48
  item_name: "%{table} export"
49
+ repeats_with_interval: "Repeats %{interval}"
50
+ repeat_section: Repeat
51
+ repeat: Repeat
52
+ repeat_once: "Only once"
53
+ repeat_repeating: Repeating
54
+ repeat_intervals:
55
+ daily: daily
56
+ weekly: weekly
57
+ monthly: monthly
58
+ quarterly: quarterly
59
+ bi-yearly: bi-yearly
60
+ yearly: yearly
38
61
  download: Download
62
+ delete: Delete export
39
63
  processing: Preparing…
64
+ waiting: Waiting
40
65
  failed: Failed
41
66
  new_export: New export
42
67
  scope_all: All records (matching current filters)
@@ -1,5 +1,14 @@
1
1
  nl:
2
2
  mensa:
3
+ tables:
4
+ filters:
5
+ show:
6
+ clear: Annuleren
7
+ actions: Acties
8
+ save: Bewaar
9
+ update_view: Bewaar weergave
10
+ save_view: Bewaar als nieuwe weergave
11
+ save_as_new_view: Bewaar als nieuwe weergave
3
12
  operators:
4
13
  is: is
5
14
  isnt: is niet
@@ -12,6 +21,7 @@ nl:
12
21
  lteq: kleiner dan of gelijk aan
13
22
  is_empty: is leeg
14
23
  add_filter:
24
+ add: Filter toevoegen
15
25
  component:
16
26
  add_filter: Filter toevoegen
17
27
  filter_pill_list:
@@ -36,8 +46,22 @@ nl:
36
46
  available_downloads: Beschikbare downloads
37
47
  empty: Je hebt nog geen downloads. Maak er hieronder een aan.
38
48
  item_name: "%{table} export"
49
+ repeats_with_interval: "Herhaalt %{interval}"
50
+ repeat_section: Herhalen
51
+ repeat: Herhalen
52
+ repeat_once: "Eenmalig"
53
+ repeat_repeating: Herhalend
54
+ repeat_intervals:
55
+ daily: dagelijks
56
+ weekly: wekelijks
57
+ monthly: maandelijks
58
+ quarterly: per kwartaal
59
+ bi-yearly: halfjaarlijks
60
+ yearly: jaarlijks
39
61
  download: Downloaden
62
+ delete: Export verwijderen
40
63
  processing: Bezig…
64
+ waiting: Wachtend
41
65
  failed: Mislukt
42
66
  new_export: Nieuwe export
43
67
  scope_all: Alle records (volgens huidige filters)
data/config/routes.rb CHANGED
@@ -4,7 +4,7 @@ Mensa::Engine.routes.draw do
4
4
  resources :filters
5
5
  resources :views, only: [:create, :update, :destroy]
6
6
  resources :batch_actions, only: [:create]
7
- resources :exports, only: [:index, :create] do
7
+ resources :exports, only: [:index, :create, :destroy] do
8
8
  member do
9
9
  get :download
10
10
  end
@@ -0,0 +1,8 @@
1
+ class AddRepeatToMensaExports < ActiveRecord::Migration[7.1]
2
+ def change
3
+ add_column :mensa_exports, :repeat, :string, null: false, default: ""
4
+ add_column :mensa_exports, :last_repeat_run_at, :datetime
5
+ add_index :mensa_exports, :repeat
6
+ add_index :mensa_exports, :last_repeat_run_at
7
+ end
8
+ end
@@ -9,14 +9,35 @@ Mensa.setup do |config|
9
9
 
10
10
  # Override icons in use
11
11
  config.icons = {
12
- order_indicator_asc: "fa-solid fa-arrow-up",
13
- order_indicator_desc: "fa-solid fa-arrow-down",
14
- control_bar_search: "fa-solid fa-magnifying-glass",
15
- control_bar_filter: "fa-solid fa-filter",
16
- control_bar_edit: "fa-solid fa-table-columns",
17
- control_bar_compress: "fa-solid fa-compress",
12
+ add_filter_trigger: "fa-solid fa-circle-plus",
13
+ add_filter_selected: "fal fa-check",
14
+ column_customizer_toggle: "fa-solid fa-table-columns",
15
+ column_customizer_handle: "fa-solid fa-grip-vertical",
16
+ column_customizer_visibility_on: "fa-solid fa-eye",
17
+ column_customizer_visibility_off: "fa-solid fa-eye-slash",
18
+ control_bar_reset: "fa-solid fa-rotate-left",
19
+ control_bar_save_dropdown: "fa-solid fa-chevron-down",
20
+ control_bar_view_filters_show: "fa-solid fa-eye",
21
+ control_bar_view_filters_hide: "fa-solid fa-eye-slash",
18
22
  control_bar_export: "fa-solid fa-file-export",
19
- search: "fa-solid fa-magnifying-glass",
20
- filters_add_filter: "fa-solid fa-plus"
23
+ empty_state_icon: "fa-solid fa-magnifying-glass",
24
+ filter_pill_remove: "fa-solid fa-xmark",
25
+ filter_pill_list_search: "fa-solid fa-magnifying-glass",
26
+ filter_pill_list_clear: "fa-solid fa-xmark",
27
+ header_order_indicator_asc: "fa-solid fa-arrow-up",
28
+ header_order_indicator_desc: "fa-solid fa-arrow-down",
29
+ views_trigger: "fa-solid fa-sort",
30
+ views_option_selected: "fa-solid fa-check",
31
+ views_option_menu: "fa-solid fa-ellipsis",
32
+ views_option_system: "fa-solid fa-ban",
33
+ views_rename: "fa-solid fa-pencil",
34
+ views_duplicate: "fa-solid fa-copy",
35
+ views_delete: "fa-solid fa-trash",
36
+ exports_dialog_close: "fa-solid fa-xmark",
37
+ exports_dialog_submit: "fa-solid fa-file-export",
38
+ exports_list_delete: "fa-solid fa-trash",
39
+ exports_list_download: "fa-solid fa-download",
40
+ exports_list_processing: "fa-solid fa-spinner fa-spin",
41
+ tables_standard_error: "fa-solid fa-circle-exclamation"
21
42
  }
22
43
  end