mensa 0.1.0 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/gem-push.yml +11 -0
  3. data/.gitignore +12 -0
  4. data/Gemfile +10 -8
  5. data/Gemfile.lock +299 -0
  6. data/MIT-LICENSE +20 -0
  7. data/Procfile +2 -0
  8. data/README.md +76 -18
  9. data/Rakefile +5 -13
  10. data/app/assets/config/mensa_manifest.js +5 -0
  11. data/app/assets/images/mensa/.keep +0 -0
  12. data/app/assets/stylesheets/mensa/application.css +11 -0
  13. data/app/components/mensa/add_filter/component.css +13 -0
  14. data/app/components/mensa/add_filter/component.html.slim +16 -0
  15. data/app/components/mensa/add_filter/component.rb +13 -0
  16. data/app/components/mensa/add_filter/component_controller.js +84 -0
  17. data/app/components/mensa/application_component.rb +12 -0
  18. data/app/components/mensa/cell/component.css +0 -0
  19. data/app/components/mensa/cell/component.html.slim +1 -0
  20. data/app/components/mensa/cell/component.rb +15 -0
  21. data/app/components/mensa/control_bar/component.css +5 -0
  22. data/app/components/mensa/control_bar/component.html.slim +15 -0
  23. data/app/components/mensa/control_bar/component.rb +21 -0
  24. data/app/components/mensa/filters/component.css +14 -0
  25. data/app/components/mensa/filters/component.html.slim +14 -0
  26. data/app/components/mensa/filters/component.rb +13 -0
  27. data/app/components/mensa/filters/component_controller.js +14 -0
  28. data/app/components/mensa/header/component.css +54 -0
  29. data/app/components/mensa/header/component.html.slim +8 -0
  30. data/app/components/mensa/header/component.rb +19 -0
  31. data/app/components/mensa/row_action/component.css +0 -0
  32. data/app/components/mensa/row_action/component.html.slim +6 -0
  33. data/app/components/mensa/row_action/component.rb +19 -0
  34. data/app/components/mensa/search/component.css +13 -0
  35. data/app/components/mensa/search/component.html.slim +17 -0
  36. data/app/components/mensa/search/component.rb +13 -0
  37. data/app/components/mensa/search/component_controller.js +62 -0
  38. data/app/components/mensa/table/component.css +0 -0
  39. data/app/components/mensa/table/component.html.slim +7 -0
  40. data/app/components/mensa/table/component.rb +16 -0
  41. data/app/components/mensa/table/component_controller.js +72 -0
  42. data/app/components/mensa/table_row/component.css +0 -0
  43. data/app/components/mensa/table_row/component.html.slim +6 -0
  44. data/app/components/mensa/table_row/component.rb +19 -0
  45. data/app/components/mensa/view/component.css +82 -0
  46. data/app/components/mensa/view/component.html.slim +17 -0
  47. data/app/components/mensa/view/component.rb +16 -0
  48. data/app/components/mensa/views/component.css +13 -0
  49. data/app/components/mensa/views/component.html.slim +18 -0
  50. data/app/components/mensa/views/component.rb +14 -0
  51. data/app/controllers/concerns/.keep +0 -0
  52. data/app/controllers/mensa/application_controller.rb +4 -0
  53. data/app/controllers/mensa/tables/filters_controller.rb +29 -0
  54. data/app/controllers/mensa/tables_controller.rb +37 -0
  55. data/app/helpers/mensa/application_helper.rb +5 -0
  56. data/app/helpers/mensa/tables_helper.rb +5 -0
  57. data/app/javascript/mensa/application.js +3 -0
  58. data/app/javascript/mensa/controllers/alert_controller.js +7 -0
  59. data/app/javascript/mensa/controllers/application.js +8 -0
  60. data/app/javascript/mensa/controllers/application_controller.js +57 -0
  61. data/app/javascript/mensa/controllers/index.js +24 -0
  62. data/app/jobs/mensa/application_job.rb +7 -0
  63. data/app/jobs/mensa/export_job.rb +91 -0
  64. data/app/models/concerns/.keep +0 -0
  65. data/app/models/mensa/application_record.rb +5 -0
  66. data/app/models/mensa/table_view.rb +5 -0
  67. data/app/tables/mensa/action.rb +26 -0
  68. data/app/tables/mensa/base.rb +103 -0
  69. data/app/tables/mensa/cell.rb +50 -0
  70. data/app/tables/mensa/column.rb +85 -0
  71. data/app/tables/mensa/config/action_dsl.rb +12 -0
  72. data/app/tables/mensa/config/column_dsl.rb +23 -0
  73. data/app/tables/mensa/config/dsl_logic.rb +90 -0
  74. data/app/tables/mensa/config/filter_dsl.rb +8 -0
  75. data/app/tables/mensa/config/render_dsl.rb +8 -0
  76. data/app/tables/mensa/config/table_dsl.rb +71 -0
  77. data/app/tables/mensa/config_readers.rb +13 -0
  78. data/app/tables/mensa/filter.rb +31 -0
  79. data/app/tables/mensa/row.rb +36 -0
  80. data/app/tables/mensa/scope.rb +99 -0
  81. data/app/views/layouts/mensa/application.html.slim +11 -0
  82. data/app/views/mensa/tables/filters/show.turbo_stream.slim +8 -0
  83. data/app/views/mensa/tables/index.html.slim +93 -0
  84. data/app/views/mensa/tables/show.html.slim +5 -0
  85. data/app/views/mensa/tables/show.turbo_stream.slim +5 -0
  86. data/bin/importmap +4 -0
  87. data/bin/rails +14 -0
  88. data/config/importmap.rb +8 -0
  89. data/config/locales/en.yml +8 -0
  90. data/config/locales/nl.yml +11 -0
  91. data/config/routes.rb +7 -0
  92. data/db/migrate/20240201184752_create_mensa_table_views.rb +15 -0
  93. data/lib/generators/mensa/install_generator.rb +24 -0
  94. data/lib/generators/mensa/tailwind_config_generator.rb +24 -0
  95. data/lib/generators/mensa/templates/config/initializers/mensa.rb +22 -0
  96. data/lib/mensa/configuration.rb +90 -0
  97. data/lib/mensa/engine.rb +37 -0
  98. data/lib/mensa/version.rb +1 -3
  99. data/lib/mensa.rb +24 -5
  100. data/lib/tasks/mensa_tasks.rake +13 -0
  101. data/mensa.gemspec +42 -0
  102. data/package.json +10 -0
  103. data/vendor/javascript/@rails--request.js.js +2 -0
  104. data/yarn.lock +837 -0
  105. metadata +245 -18
  106. data/.rubocop.yml +0 -13
  107. data/CHANGELOG.md +0 -5
  108. data/CODE_OF_CONDUCT.md +0 -84
  109. data/LICENSE.txt +0 -21
  110. data/sig/mensa.rbs +0 -4
@@ -0,0 +1,4 @@
1
+ module Mensa
2
+ class ApplicationController < ActionController::Base#::Mensa.config.base_controller.constantize
3
+ end
4
+ end
@@ -0,0 +1,29 @@
1
+ module Mensa
2
+ module Tables
3
+ class FiltersController < ::ApplicationController
4
+ layout false
5
+
6
+ class Filter
7
+ include ActiveModel::Model
8
+ attr_accessor :value
9
+ end
10
+
11
+ def index
12
+ config = {}.merge(params.permit(:id, :page, :table_id, :target, :table_view_id, :turbo_frame_id, order: {}, filters: {}).to_h)
13
+ @table = Mensa.for_name(params[:table_id], config)
14
+ end
15
+
16
+ # Returns the filter information on the column-name
17
+ def show
18
+ config = {}.merge(params.permit(:id, :page, :table_id, :target, :table_view_id, :turbo_frame_id, order: {}, filters: {}).to_h)
19
+ @table = Mensa.for_name(params[:table_id], config)
20
+ @table.original_view_context = helpers
21
+ @column = @table.column(params[:id])
22
+ respond_to do |format|
23
+ format.turbo_stream
24
+ format.html
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,37 @@
1
+ module Mensa
2
+ class TablesController < ::ApplicationController
3
+ layout :decide_layout
4
+
5
+ def index
6
+ render layout: 'mensa/application'
7
+ end
8
+
9
+ def show
10
+ config = if params[:table_view_id]
11
+ @view = Mensa::TableView.find_by(table_name: params[:id], id: params[:table_view_id])
12
+ @view&.data || {}
13
+ else
14
+ {}
15
+ end
16
+
17
+ config = config.merge(params.permit(:format, :query, :id, :page, :table_view_id, :turbo_frame_id, order: {}, filters: {}).to_h)
18
+
19
+ @table = Mensa.for_name(params[:id], config)
20
+ @table.table_view = @view
21
+ @table.original_view_context = helpers
22
+
23
+ respond_to do |format|
24
+ format.turbo_stream
25
+ format.html
26
+ format.xlsx do
27
+ Mensa::ExportJob.perform_async(current_user.id, params[:id])
28
+ head 200
29
+ end
30
+ end
31
+ end
32
+
33
+ def decide_layout
34
+ return false if params[:turbo_frame_id]
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,5 @@
1
+ module Mensa
2
+ module ApplicationHelper
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Mensa
2
+ module TablesHelper
3
+ include Pagy::Frontend
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ import "@fortawesome/fontawesome-free"
2
+ import "@hotwired/turbo-rails"
3
+ import "mensa/controllers"
@@ -0,0 +1,7 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ alert() {
5
+ alert("alert")
6
+ }
7
+ }
@@ -0,0 +1,8 @@
1
+ import { Application } from "@hotwired/stimulus"
2
+
3
+ const application = Application.start()
4
+
5
+ application.debug = false
6
+ window.Stimulus = application
7
+
8
+ export { application }
@@ -0,0 +1,57 @@
1
+ // application_controller.js
2
+ import { Controller } from '@hotwired/stimulus'
3
+
4
+ export default class ApplicationController extends Controller {
5
+ connect () {
6
+ this.element[this.identifier] = this
7
+ }
8
+
9
+ getController (element, identifier) {
10
+ return this.application.getControllerForElementAndIdentifier(element, identifier)
11
+ }
12
+
13
+ triggerEvent (el, name, data) {
14
+ let event
15
+ if (typeof window.CustomEvent === 'function') {
16
+ event = new CustomEvent(name, { detail: data, cancelable: true, bubbles: true })
17
+ } else {
18
+ event = document.createEvent('CustomEvent')
19
+ event.initCustomEvent(name, true, true, data)
20
+ }
21
+ el.dispatchEvent(event)
22
+ }
23
+
24
+ elementScrolled (element) {
25
+ if (element.scrollHeight - Math.round(element.scrollTop) === element.clientHeight) {
26
+ return true
27
+ }
28
+ return false
29
+ }
30
+
31
+ debouncedHover (element, timeout, handler) {
32
+ var timeoutId = null
33
+ element.addEventListener(marker, 'mouseover', function () {
34
+ timeoutId = setTimeout(handler, timeout)
35
+ })
36
+
37
+ element.addEventListener(marker, 'mouseout', function () {
38
+ clearTimeout(timeoutId)
39
+ })
40
+ }
41
+
42
+ get ourUrl () {
43
+ // This requires the mensaTableOutlet
44
+ let turboFrame = this.mensaTableOutlet?.turboFrameTarget
45
+ if(!turboFrame) {
46
+ turboFrame = this.turboFrameTarget
47
+ }
48
+ let url
49
+
50
+ if (turboFrame && turboFrame.getAttribute('src')) {
51
+ url = new URL(turboFrame.getAttribute('src'))
52
+ } else {
53
+ url = new URL(window.location.href)
54
+ }
55
+ return url
56
+ }
57
+ }
@@ -0,0 +1,24 @@
1
+ import { application } from "mensa/controllers/application"
2
+
3
+ // import AddFilterComponentController from 'components/add_filter/component_controller';
4
+ // application.register('mensa-add-filter', AddFilterComponentController)
5
+
6
+ import AddFilterComponentController from "mensa/components/add_filter/component_controller";
7
+ application.register("mensa-add-filter", AddFilterComponentController);
8
+
9
+ import FiltersComponentController from "mensa/components/filters/component_controller";
10
+ application.register("mensa-filters", FiltersComponentController);
11
+
12
+ import SearchComponentController from "mensa/components/search/component_controller";
13
+ application.register("mensa-search", SearchComponentController);
14
+
15
+ import TableComponentController from 'mensa/components/table/component_controller'
16
+ application.register("mensa-table", TableComponentController);
17
+
18
+ // Eager load all controllers defined in the import map under controllers/**/*_controller
19
+ // import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
20
+ // eagerLoadControllersFrom("controllers", application)
21
+
22
+ // Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
23
+ // import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
24
+ // lazyLoadControllersFrom("controllers", application)
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mensa
4
+ class ApplicationJob
5
+ include Sidekiq::Job
6
+ end
7
+ end
@@ -0,0 +1,91 @@
1
+ module Mensa
2
+ class ExportJob < ApplicationJob
3
+ queue_as :default
4
+
5
+ # export_started: lambda do |user_id, table_name|
6
+ # end,
7
+ # export_complete: lambda do |user_id, table_name, attachment|
8
+ # end
9
+
10
+ def perform(user_id, table_name)
11
+ table = Mensa.for_name(table_name)
12
+ context = Mensa.config.callbacks[:export_started].call(user_id, table_name)
13
+
14
+ styles = []
15
+ default_style = { b: true, bg_color: '3B82F6', fg_color: 'FFFFFF', border: { style: :thin, color: '000000' }, sz: 8,
16
+ alignment: { vertical: :bottom, horizontal: :left } }
17
+
18
+ p = Axlsx::Package.new
19
+ p.use_shared_strings = true
20
+
21
+ wb = p.workbook
22
+
23
+ wrap_text = wb.styles.add_style({ format_code: "@", alignment: { horizontal: :left, vertical: :top, wrap_text: true } })
24
+ nowrap_text = wb.styles.add_style({ format_code: "@", alignment: { horizontal: :left, vertical: :top, wrap_text: false } })
25
+ number_format = wb.styles.add_style format_code: '#'
26
+ datetime_format = wb.styles.add_style format_code: "dddd, d mmmm yyyy hh:mm:ss"
27
+ date_format = wb.styles.add_style format_code: "dddd, d mmmm yyyy"
28
+
29
+ # custom_styles = ActionTable.config.format_config.each_with_object({}) { |(key, value), hash| hash[key] = value[:xlsx_style] if value.key?(:xlsx_style) }
30
+ custom_style = {}
31
+
32
+ wb.add_worksheet(name: table_name.first(31)) do |sheet|
33
+ column_widths = []
34
+ # TODO: Separate display columns for export?
35
+ table.display_columns.map.with_index do |column, index|
36
+ styles[index] = sheet.styles.add_style(default_style) #.merge(column.export_style || {}))
37
+ # width = column.export_style.delete(:width)
38
+ # column_widths[index] = width if width
39
+ end
40
+
41
+ first_row = sheet.add_row(table.display_columns.map { |c| c.human_name }, style: styles, height: 28)
42
+
43
+ first_cell = first_row.first
44
+ last_row = first_row
45
+
46
+ table.export_rows.each do |row|
47
+ # next if row.reject { |column, value| value.nil? || value == '' }.blank?
48
+ #
49
+ # row_types = :string #row.map(&:second)
50
+ # row_values = row.map(&:third)
51
+ # options = row.map(&:last)
52
+ #
53
+ # row_styles = options.map.with_index { |option, i|
54
+ # type = row_types[i]
55
+ # value = row_values[i]
56
+ # export_style = option.dig(:export, :xlsx_style)
57
+ #
58
+ # if export_style.present?
59
+ # wb.styles.add_style(export_style)
60
+ # elsif custom_styles[type].present?
61
+ # wb.styles.add_style(custom_styles[type])
62
+ # elsif [:integer, :float, :decimal, :number].include?(type)
63
+ # number_format
64
+ # elsif [:datetime, :time, :timestamp].include?(type)
65
+ # datetime_format
66
+ # elsif type == :date
67
+ # date_format
68
+ # elsif value.is_a?(Axlsx::RichText) || type == :text
69
+ # wrap_text
70
+ # else
71
+ # nowrap_text
72
+ # end
73
+ # }
74
+ values = table.display_columns.map { |column| row.value(column) }
75
+ row_styles = table.display_columns.map { |column| nowrap_text }
76
+ last_row = sheet.add_row(values, style: row_styles)
77
+ end
78
+
79
+ last_cell = last_row.last
80
+
81
+ sheet.column_widths(*column_widths)
82
+ sheet.auto_filter = Axlsx.cell_range([first_cell, last_cell], false)
83
+ end
84
+
85
+ attachment = { io: p.to_stream,
86
+ content_type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', filename: "#{table_name}_export_#{Time.current.strftime("%Y-%m-%d-%H:%M:%S")}.xlsx" }
87
+
88
+ Mensa.config.callbacks[:export_completed].call(user_id, table_name, context, attachment)
89
+ end
90
+ end
91
+ end
File without changes
@@ -0,0 +1,5 @@
1
+ module Mensa
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Mensa
2
+ class TableView < ApplicationRecord
3
+ belongs_to :user, optional: true
4
+ end
5
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mensa
4
+ # Provide additional links at the end of the row, with an icon, link and a name
5
+ #
6
+ # action :delete do
7
+ # link { |contact| delete_contact_path(contact) }
8
+ # link_attributes "data-turbo-method" => "delete"
9
+ # icon "fa-xmark"
10
+ # end
11
+ class Action
12
+ include ConfigReaders
13
+ attr_reader :name, :table, :config
14
+
15
+ def initialize(name, config:, table:)
16
+ @name = name
17
+ @table = table
18
+ @config = config
19
+ end
20
+
21
+ config_reader :link
22
+ config_reader :link_attributes
23
+ config_reader :icon
24
+ config_reader :show
25
+ end
26
+ end
@@ -0,0 +1,103 @@
1
+ module Mensa
2
+ class Base
3
+ include Pagy::Backend
4
+ include ConfigReaders
5
+ include Scope
6
+
7
+ attr_writer :original_view_context
8
+ attr_accessor :component, :name, :table_view
9
+ attr_reader :config, :params
10
+
11
+ config_reader :model
12
+ config_reader :link
13
+ config_reader :supports_views?
14
+ config_reader :view_condensed?
15
+ config_reader :view_condensed_toggle?
16
+ config_reader :view_columns_sorting?
17
+ config_reader :show_header?
18
+
19
+ def initialize(config = {})
20
+ @params = config.deep_symbolize_keys
21
+ @config = self.class.definition.merge(@params || {})
22
+ end
23
+
24
+ def column_order
25
+ config[:column_order] || config[:columns]&.keys
26
+ end
27
+
28
+ # Returns all columns
29
+ def columns
30
+ return @columns if @columns
31
+
32
+ @columns ||= column_order.map { |column_name| Mensa::Column.new(column_name, config: config.dig(:columns, column_name), table: self) }
33
+ end
34
+
35
+ # Returns a column by name
36
+ # @param [String] name
37
+ def column(name)
38
+ columns.find { |c| c.name == name.to_sym }
39
+ end
40
+
41
+ # Returns the columns to be displayed
42
+ def display_columns
43
+ @display_columns ||= columns.select(&:visible?).reject(&:internal?)
44
+ end
45
+
46
+ # Returns the rows to be displayed
47
+ def rows
48
+ paged_scope.map { |row| Mensa::Row.new(self, row) }
49
+ end
50
+
51
+ def export_rows
52
+ ordered_scope.map { |row| Mensa::Row.new(self, row) }
53
+ end
54
+
55
+ def actions?
56
+ config[:actions].present?
57
+ end
58
+
59
+ def actions
60
+ return @actions if @actions
61
+
62
+ @actions ||= config[:actions].keys.map { |action_name| Mensa::Action.new(action_name, config: config.dig(:actions, action_name), table: self) }
63
+ end
64
+
65
+ # Returns the current path with configuration
66
+ def path(order: {}, turbo_frame_id: nil, table_view_id: nil)
67
+ # FIXME: if someone doesn't use as: :mensa in the routes, it breaks
68
+ original_view_context.mensa.table_path(name, order: order_hash(order), turbo_frame_id: turbo_frame_id, table_view_id: table_view_id)
69
+ end
70
+
71
+ def menu
72
+ Satis::Menus::Builder.build([:table, :view_menu]) do |m|
73
+ m.item :export, icon: "fal fa-file-export", link: nil
74
+ end
75
+ end
76
+
77
+ def all_views
78
+ [Mensa::TableView.new(name: I18n.t('.mensa.views.all'))] + TableView.where(table_name: name).where(user: [nil, Current.user])
79
+ end
80
+
81
+ def active_filters
82
+ (config[:filters] || {}).map { |column_name, value| Mensa::Filter.new(value, column: column(column_name), config: config.dig(:filters, column_name), table: self) }
83
+ end
84
+
85
+ def table_id
86
+ return @table_id if @table_id
87
+
88
+ @table_id = params[:turbo_frame_id] || "#{name.to_s.gsub("/", "__")}-#{SecureRandom.base36}"
89
+ end
90
+
91
+ def original_view_context
92
+ @original_view_context || component.original_view_context
93
+ end
94
+
95
+ private
96
+
97
+ class << self
98
+ def definition(&)
99
+ @definition ||= Mensa::Config::TableDsl.new(self.name, &).config
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mensa
4
+ class Cell
5
+ include ActionView::Helpers::SanitizeHelper
6
+ include ::ApplicationHelper
7
+ include ActionView::Helpers::TagHelper
8
+ include ActionView::Helpers::UrlHelper
9
+ include Rails.application.routes.url_helpers
10
+
11
+ attr_reader :column, :row
12
+
13
+ def initialize(row:, column:)
14
+ @row = row
15
+ @column = column
16
+ end
17
+
18
+ def value
19
+ @value ||= row.value(column)
20
+ end
21
+
22
+ def render(format)
23
+ proc = column.config.dig(:render, format.to_sym)
24
+ if proc
25
+ row.table.original_view_context.instance_exec(row.record, &proc)
26
+ else
27
+ send("to_#{format}".to_sym)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def to_html
34
+ case value
35
+ when NilClass
36
+ ''
37
+ when TrueClass
38
+ content_tag(:i, '', class: 'fa fa-check')
39
+ when FalseClass
40
+ content_tag(:i, '', class: 'fa fa-xmark')
41
+ when Date
42
+ respond_to?(:dt) ? dt(value) : value.strftime('%d.%m.%Y')
43
+ when Time, DateTime
44
+ respond_to?(:ln) ? ln(value) : value.strftime('%d-%m-%Y %H:%M:%S')
45
+ else
46
+ column.sanitize? ? sanitize(value.to_s) : value.to_s.html_safe
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mensa
4
+ class Column
5
+ include ConfigReaders
6
+ attr_reader :name, :table, :config
7
+
8
+ def initialize(name, config:, table:)
9
+ @name = name
10
+ @table = table
11
+ @config = config
12
+ end
13
+
14
+ config_reader :sortable?
15
+ config_reader :sanitize?
16
+ config_reader :visible?
17
+ config_reader :internal?
18
+ config_reader :method # When a method needs to be called on the model, slow!
19
+
20
+ def sort_direction
21
+ table.config.dig(:order, name)&.to_sym
22
+ end
23
+
24
+ def next_sort_direction
25
+ if sort_direction == :asc
26
+ :desc
27
+ elsif sort_direction == :desc
28
+ nil
29
+ else
30
+ :asc
31
+ end
32
+ end
33
+
34
+ def attribute
35
+ return @attribute if @attribute
36
+
37
+ @attribute = if config[:attribute].present?
38
+ "#{config[:attribute]} AS #{name}"
39
+ elsif table.model.column_names.include? name.to_s
40
+ name.to_s
41
+ else
42
+ nil
43
+ end
44
+ end
45
+
46
+ def attribute_for_condition
47
+ return @attribute_for_condition if @attribute_for_condition
48
+
49
+ @attribute_for_condition = if config[:attribute].present?
50
+ config[:attribute]
51
+ elsif table.model.column_names.include? name.to_s
52
+ name.to_s
53
+ else
54
+ nil
55
+ end
56
+ end
57
+
58
+ def filter?
59
+ config.key?(:filter)
60
+ end
61
+
62
+ def filter
63
+ return unless filter?
64
+
65
+ @filter ||= Mensa::Filter.new(nil, column: self, config: config[:filter] || {}, table: table)
66
+ end
67
+
68
+ def human_name
69
+ if table.model < ActiveRecord::Base
70
+ table.model.human_attribute_name name
71
+ else
72
+ name.to_s.humanize
73
+ end
74
+ end
75
+
76
+ def menu
77
+ Satis::Menus::Builder.build(:filter_menu, event: 'click') do |m|
78
+ if sortable?
79
+ m.item :sort_ascending, icon: 'fa-solid fa-arrow-up-short-wide'.freeze, link: table.path(order: { name => :asc }), link_attributes: { "data-turbo-frame": "_self" }
80
+ m.item :sort_descending, icon: 'fa-solid fa-arrow-down-wide-short'.freeze, link: table.path(order: { name => :asc }), link_attributes: { "data-turbo-frame": "_self" }
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mensa::Config
4
+ class ActionDsl
5
+ include DslLogic
6
+
7
+ option :icon
8
+ option :show, default: ->(record) { true }
9
+ option :link
10
+ option :link_attributes, default: {}
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mensa::Config
4
+ class ColumnDsl
5
+ include DslLogic
6
+
7
+ option :sortable, default: true
8
+ option :sanitize, default: true
9
+ # Allows for sql-parts too
10
+ #
11
+ # attribute 'EXTRACT(YEAR FROM AGE(born_on))::int as age'
12
+ #
13
+ option :attribute
14
+ # Internal columns will never be shown, but are there to be selected, to be used in methods
15
+ # Mensa doesn't select the whole records, to only select what we need
16
+ option :internal
17
+ option :method
18
+
19
+ option :visible, default: :true
20
+ dsl_option :render, Mensa::Config::RenderDsl
21
+ dsl_option :filter, Mensa::Config::FilterDsl
22
+ end
23
+ end