agilibox 1.0.0 → 1.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +321 -2
  3. data/Rakefile +4 -0
  4. data/app/assets/javascripts/agilibox/autocomplete.coffee +2 -0
  5. data/app/assets/javascripts/agilibox/checkboxes_dropdown.coffee +9 -0
  6. data/app/assets/javascripts/agilibox/modals.coffee +81 -55
  7. data/app/assets/stylesheets/agilibox/actions.sass +2 -0
  8. data/app/assets/stylesheets/agilibox/all.sass +5 -0
  9. data/app/assets/stylesheets/agilibox/buttons.sass +11 -0
  10. data/app/assets/stylesheets/agilibox/filters.sass +13 -3
  11. data/app/assets/stylesheets/agilibox/forms.sass +49 -0
  12. data/app/assets/stylesheets/agilibox/icons.sass +5 -0
  13. data/app/assets/stylesheets/agilibox/modals.sass +15 -7
  14. data/app/assets/stylesheets/agilibox/pagination.sass +7 -0
  15. data/app/assets/stylesheets/agilibox/print.sass +10 -0
  16. data/app/assets/stylesheets/agilibox/tables.sass +26 -0
  17. data/app/controllers/agilibox/application_controller.rb +2 -4
  18. data/app/controllers/agilibox/small_data/filters_controller.rb +8 -6
  19. data/app/controllers/concerns/agilibox/api_controller_concern.rb +69 -0
  20. data/app/controllers/concerns/agilibox/back_url_concern.rb +13 -5
  21. data/app/emails/agilibox/email.rb +113 -0
  22. data/app/filters/agilibox/small_data/filter.rb +19 -1
  23. data/app/filters/agilibox/small_data/filter_strategy.rb +1 -1
  24. data/app/filters/agilibox/small_data/filter_strategy_by_date_begin.rb +3 -2
  25. data/app/filters/agilibox/small_data/filter_strategy_by_date_end.rb +3 -2
  26. data/app/filters/agilibox/small_data/filter_strategy_by_date_or_datetime_period.rb +56 -0
  27. data/app/filters/agilibox/small_data/filter_strategy_by_date_period.rb +5 -0
  28. data/app/filters/agilibox/small_data/filter_strategy_by_datetime_period.rb +5 -0
  29. data/app/filters/agilibox/small_data/filter_strategy_by_key_value.rb +14 -3
  30. data/app/filters/agilibox/small_data/filter_strategy_by_key_values.rb +13 -0
  31. data/app/filters/agilibox/small_data/filter_strategy_by_tags.rb +1 -1
  32. data/app/forms/agilibox/mini_form_object.rb +29 -0
  33. data/app/helpers/agilibox/all_helpers.rb +2 -0
  34. data/app/helpers/agilibox/bootstrap_helper.rb +62 -3
  35. data/app/helpers/agilibox/button_helper.rb +22 -13
  36. data/app/helpers/agilibox/filters_helper.rb +7 -8
  37. data/app/helpers/agilibox/font_awesome_helper.rb +67 -0
  38. data/app/helpers/agilibox/form_helper.rb +37 -14
  39. data/app/helpers/agilibox/i18n_helper.rb +5 -0
  40. data/app/helpers/agilibox/link_helper.rb +1 -1
  41. data/app/helpers/agilibox/pagination_helper.rb +18 -2
  42. data/app/helpers/agilibox/routes_helper.rb +6 -2
  43. data/app/helpers/agilibox/sorting_helper.rb +9 -4
  44. data/app/helpers/agilibox/text_helper.rb +51 -26
  45. data/app/helpers/h.rb +3 -0
  46. data/app/jobs/concerns/agilibox/setup_job_concern.rb +35 -0
  47. data/app/libs/agilibox/collection_update.rb +32 -0
  48. data/app/libs/agilibox/fcm/notifier.rb +28 -0
  49. data/app/libs/agilibox/fcm/request.rb +48 -0
  50. data/app/libs/agilibox/initialize_with.rb +20 -0
  51. data/app/libs/agilibox/mini_model_serializer/serialize.rb +30 -0
  52. data/app/libs/agilibox/mini_model_serializer/serializer.rb +23 -0
  53. data/app/libs/agilibox/monkey.rb +39 -0
  54. data/app/libs/agilibox/phone_number_sanitizer.rb +15 -0
  55. data/app/libs/agilibox/sortable_uuid_generator.rb +1 -1
  56. data/app/libs/agilibox/token_generator.rb +37 -0
  57. data/app/mailers/agilibox/application_mailer.rb +2 -0
  58. data/app/mailers/agilibox/generic_mailer.rb +9 -0
  59. data/app/models/concerns/agilibox/active_record_uuid_concern.rb +4 -2
  60. data/app/models/concerns/agilibox/default_values_concern.rb +4 -3
  61. data/app/models/concerns/agilibox/model_i18n.rb +37 -6
  62. data/app/models/concerns/agilibox/pluck_distinct.rb +13 -0
  63. data/app/models/concerns/agilibox/pluck_to_hash.rb +9 -0
  64. data/app/models/concerns/agilibox/polymorphic_id.rb +5 -2
  65. data/app/models/concerns/agilibox/search.rb +11 -13
  66. data/app/models/concerns/agilibox/timestamp_helpers.rb +17 -0
  67. data/app/serializers/agilibox/serializers/base.rb +47 -1
  68. data/app/serializers/agilibox/serializers/xlsx.rb +25 -23
  69. data/app/services/agilibox/service.rb +17 -0
  70. data/app/sms/agilibox/sms/application_sms.rb +42 -0
  71. data/app/sms/agilibox/sms/message.rb +25 -0
  72. data/app/sms/agilibox/sms/strategies/amazon_sns.rb +48 -0
  73. data/app/sms/agilibox/sms/strategies/base.rb +15 -0
  74. data/app/sms/agilibox/sms/strategies/test.rb +10 -0
  75. data/app/sms/agilibox/sms.rb +35 -0
  76. data/app/sorters/agilibox/sorter.rb +26 -0
  77. data/app/views/agilibox/_flash.html.slim +9 -0
  78. data/app/views/agilibox/forms/_checkboxes_dropdown.html.slim +14 -0
  79. data/app/views/agilibox/search/_form.html.slim +1 -6
  80. data/app/views/agilibox/search/_form_bs3.html.slim +13 -0
  81. data/app/views/agilibox/search/_form_bs4.html.slim +9 -0
  82. data/app/views/agilibox/search/_form_bs5.html.slim +8 -0
  83. data/app/views/kaminari/bootstrap4/_first_page.html.slim +2 -0
  84. data/app/views/kaminari/bootstrap4/_gap.html.slim +2 -0
  85. data/app/views/kaminari/bootstrap4/_last_page.html.slim +2 -0
  86. data/app/views/kaminari/bootstrap4/_next_page.html.slim +2 -0
  87. data/app/views/kaminari/bootstrap4/_page.html.slim +6 -0
  88. data/app/views/kaminari/bootstrap4/_paginator.html.slim +12 -0
  89. data/app/views/kaminari/bootstrap4/_prev_page.html.slim +2 -0
  90. data/config/cucumber.yml +9 -0
  91. data/config/locales/actions.en.yml +76 -0
  92. data/config/locales/actions.fr.yml +76 -0
  93. data/config/locales/attributes.en.yml +108 -0
  94. data/config/locales/attributes.fr.yml +108 -0
  95. data/config/locales/common.en.yml +0 -160
  96. data/config/locales/common.fr.yml +0 -171
  97. data/config/locales/dates.fr.yml +8 -0
  98. data/config/locales/errors.en.yml +5 -0
  99. data/config/locales/errors.fr.yml +5 -0
  100. data/db/migrate/20000101000000_enable_agilibox_extensions.rb +7 -0
  101. data/lib/agilibox/active_model_custom_error_messages.rb +23 -0
  102. data/lib/agilibox/active_model_type_cast.rb +52 -0
  103. data/lib/agilibox/config.rb +19 -0
  104. data/lib/agilibox/core_and_rails_ext.rb +2 -1
  105. data/lib/agilibox/cucumber_config.rb +53 -0
  106. data/lib/agilibox/cucumber_helpers/agilibox.rb +2 -0
  107. data/lib/agilibox/cucumber_helpers/ajax.rb +18 -0
  108. data/lib/agilibox/cucumber_helpers/apparition.rb +18 -0
  109. data/lib/agilibox/cucumber_helpers/capybara.rb +12 -0
  110. data/lib/agilibox/cucumber_helpers/capybara_selectors.rb +7 -0
  111. data/lib/agilibox/cucumber_helpers/chrome_headless.rb +16 -0
  112. data/lib/agilibox/cucumber_helpers/common_steps.rb +139 -0
  113. data/lib/agilibox/cucumber_helpers/cuprite.rb +20 -0
  114. data/lib/agilibox/cucumber_helpers/database_cleaner.rb +8 -0
  115. data/lib/agilibox/cucumber_helpers/factory_bot.rb +1 -0
  116. data/lib/agilibox/cucumber_helpers/rails.rb +1 -0
  117. data/lib/agilibox/cucumber_helpers/rspec.rb +6 -0
  118. data/lib/agilibox/cucumber_helpers/screenshots.rb +3 -0
  119. data/lib/agilibox/cucumber_helpers/select2.rb +23 -0
  120. data/lib/agilibox/cucumber_helpers/sign_in.rb +17 -0
  121. data/lib/agilibox/cucumber_helpers/simplecov.rb +1 -0
  122. data/lib/agilibox/cucumber_helpers/timecop.rb +3 -0
  123. data/lib/agilibox/cucumber_helpers/turbolinks.rb +34 -0
  124. data/lib/agilibox/cucumber_helpers/zonebie.rb +4 -0
  125. data/lib/agilibox/engine.rb +15 -0
  126. data/lib/agilibox/engine_file.rb +10 -0
  127. data/lib/agilibox/errors_middleware.rb +50 -0
  128. data/lib/agilibox/form_back_url.rb +4 -4
  129. data/lib/agilibox/rspec.rb +29 -0
  130. data/lib/agilibox/test_helpers.rb +13 -0
  131. data/lib/agilibox/version.rb +1 -1
  132. data/lib/tasks/cucumber.rake +76 -0
  133. metadata +165 -9
  134. data/app/filters/agilibox/small_data/filter_strategy_by_time_period.rb +0 -37
  135. data/lib/agilibox/active_record_comma_type_cast.rb +0 -12
@@ -19,9 +19,10 @@ body.modal-open
19
19
 
20
20
  #modal-body
21
21
  position: relative
22
- margin: 10vh auto
23
- padding: 2rem
22
+ margin: 50px auto
23
+ padding: 20px
24
24
  width: auto
25
+ box-sizing: border-box
25
26
  min-width: 50%
26
27
  max-width: 980px
27
28
  background: white
@@ -32,14 +33,21 @@ body.modal-open
32
33
  overflow: auto
33
34
 
34
35
  #modal-close
36
+ margin: 0
37
+ padding: 0
38
+ display: block
39
+ font-size: 50px
40
+ line-height: 50px
41
+ height: 50px
42
+ width: 50px
43
+ position: fixed
44
+ top: 0px
45
+ right: 0px
35
46
  color: white
36
- position: absolute
37
- font-size: 4rem
38
- line-height: 0.5rem
39
- top: 0.25rem
40
- right: 0.25rem
41
47
  border: none
48
+ outline: none
42
49
  background: none
50
+ text-align: center
43
51
 
44
52
  #modal:not(.closable) #modal-close
45
53
  display: none
@@ -4,3 +4,10 @@
4
4
 
5
5
  li
6
6
  display: inline-block
7
+
8
+ .pagination
9
+ li
10
+ @extend .page-item !optional
11
+
12
+ a
13
+ @extend .page-link !optional
@@ -17,6 +17,7 @@
17
17
  width: auto !important
18
18
  height: auto !important
19
19
 
20
+ .card-title,
20
21
  .panel-title,
21
22
  .context-header,
22
23
  thead *,
@@ -41,6 +42,7 @@
41
42
  #flash,
42
43
  display: none !important
43
44
 
45
+ .card,
44
46
  .panel,
45
47
  .table,
46
48
  .list-group,
@@ -49,14 +51,21 @@
49
51
  p,
50
52
  margin: 1em 0 !important
51
53
 
54
+ .card p,
55
+ .card table,
52
56
  .panel table,
53
57
  .well p,
54
58
  td p,
55
59
  margin: 0 !important
56
60
 
57
61
  .well,
62
+ .card,
58
63
  padding: 0.5em !important
59
64
 
65
+ .card-header,
66
+ .card-title,
67
+ .card-body,
68
+ .card-footer,
60
69
  .panel-heading,
61
70
  .panel-title,
62
71
  .panel-body,
@@ -71,4 +80,5 @@
71
80
  margin: 0 !important
72
81
 
73
82
  .panel-heading .panel-title,
83
+ .card-header .card-title,
74
84
  padding: 0 !important
@@ -0,0 +1,26 @@
1
+ table.default
2
+ @extend .table
3
+ @extend .table-bordered
4
+ @extend .table-hover
5
+
6
+ th.actions,
7
+ td.actions,
8
+ width: 5em
9
+ white-space: nowrap
10
+ text-align: center
11
+
12
+ th
13
+ text-align: center
14
+ vertical-align: middle !important
15
+
16
+ td[class*="date"]
17
+ width: 8em
18
+ text-align: center
19
+
20
+ td[class*="count"],
21
+ width: 1em
22
+ text-align: center
23
+
24
+ td[class*="total"],
25
+ td[class*="amount"],
26
+ text-align: right
@@ -1,5 +1,3 @@
1
- module Agilibox
2
- class ApplicationController < ActionController::Base
3
- protect_from_forgery with: :exception
4
- end
1
+ class Agilibox::ApplicationController < Agilibox.parent_controller
2
+ protect_from_forgery with: :exception
5
3
  end
@@ -1,8 +1,10 @@
1
1
  class Agilibox::SmallData::FiltersController < ::Agilibox::ApplicationController
2
- def create
3
- skip_authorization if respond_to?(:skip_authorization)
4
- skip_policy_scope if respond_to?(:skip_policy_scope)
2
+ skip_before_action :verify_authenticity_token, raise: false
3
+ skip_before_action :authenticate_user!, raise: false
4
+ skip_after_action :verify_authorized, raise: false
5
+ skip_after_action :verify_policy_scoped, raise: false
5
6
 
7
+ def create
6
8
  filters = ::Agilibox::SmallData::Filter.new(cookies)
7
9
  new_filters = params.fetch(:filters, {}).permit!.to_h
8
10
  filters.merge new_filters
@@ -23,8 +25,9 @@ class Agilibox::SmallData::FiltersController < ::Agilibox::ApplicationController
23
25
  url = [
24
26
  params[:form_url],
25
27
  request.referer,
26
- (main_app.root_path rescue "/"),
27
- ].select(&:present?).first
28
+ main_app.try(:root_path),
29
+ "/",
30
+ ].find(&:present?)
28
31
 
29
32
  # Delete page param
30
33
  base, query_string = url.split("?")
@@ -32,5 +35,4 @@ class Agilibox::SmallData::FiltersController < ::Agilibox::ApplicationController
32
35
  query_string = "?#{query_string}" if query_string.present?
33
36
  base + query_string
34
37
  end
35
-
36
38
  end
@@ -0,0 +1,69 @@
1
+ module Agilibox::ApiControllerConcern
2
+ extend ActiveSupport::Concern
3
+
4
+ private
5
+
6
+ def render_json(json = {}, options = {})
7
+ json.reverse_merge!(current_user: current_user)
8
+ options.reverse_merge!(current_user: current_user)
9
+
10
+ json = Agilibox::MiniModelSerializer::Serialize.call(json, options)
11
+
12
+ render options.merge(json: json)
13
+ end
14
+
15
+ def render_json_error(any_object, options = {})
16
+ if any_object.is_a?(ActiveModel::Validations)
17
+ json = {
18
+ :error => json_error_string_for_model(any_object),
19
+ :model_errors => json_errors_hash_for_model(any_object),
20
+ }
21
+ elsif any_object.is_a?(String)
22
+ json = {error: any_object}
23
+ else
24
+ json = any_object
25
+ end
26
+
27
+ options[:status] ||= :unprocessable_entity
28
+
29
+ render_json(json, options)
30
+ end
31
+
32
+ def json_errors_hash_for_model(object)
33
+ object.errors
34
+ .to_hash
35
+ .map { |a, m| [a, message: m.first, full_message: object.errors.full_message(a, m.first)] }
36
+ .uniq(&:first)
37
+ .to_h
38
+ end
39
+
40
+ def json_error_string_for_model(object)
41
+ json_errors_hash_for_model(object).values.pluck(:full_message).join(", ")
42
+ end
43
+
44
+ def render_not_found
45
+ render_json_error t("errors.not_found"), status: :not_found
46
+ end
47
+
48
+ def render_forbidden
49
+ render_json_error t("errors.forbidden"), status: :forbidden
50
+ end
51
+
52
+ def render_unauthorized
53
+ render_json_error t("errors.unauthorized"), status: :unauthorized
54
+ end
55
+
56
+ def render_forbidden_or_unauthorized
57
+ current_user ? render_forbidden : render_unauthorized
58
+ end
59
+
60
+ included do |controller|
61
+ if controller < ActionController::Rescue
62
+ if defined?(Pundit::NotAuthorizedError)
63
+ rescue_from Pundit::NotAuthorizedError, with: :render_forbidden_or_unauthorized
64
+ end
65
+
66
+ rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
67
+ end
68
+ end
69
+ end
@@ -3,16 +3,24 @@ module Agilibox::BackUrlConcern
3
3
 
4
4
  private
5
5
 
6
- def default_back_url; end
6
+ def default_back_url
7
+ end
7
8
 
8
9
  def back_url
9
- [
10
+ url = [
10
11
  params[:back_url],
11
12
  request.referer,
12
13
  default_back_url,
13
- main_app.root_path,
14
+ main_app.try(:root_path),
14
15
  "/",
15
- ].select(&:present?).first
16
- end
16
+ ].find(&:present?)
17
17
 
18
+ uri = URI.parse(url)
19
+ uri.host = nil
20
+ uri.port = nil
21
+ uri.scheme = nil
22
+ uri.user = nil
23
+ uri.password = nil
24
+ uri.to_s
25
+ end
18
26
  end
@@ -0,0 +1,113 @@
1
+ class Agilibox::Email
2
+ include ActiveModel::Model
3
+ include Agilibox::ModelToS
4
+ include Agilibox::ModelI18n
5
+
6
+ validates :to, presence: true
7
+ validates :subject, presence: true
8
+ validates :body, presence: true
9
+
10
+ validate :validate_addr_formats
11
+
12
+ attr_accessor(
13
+ :current_user,
14
+ :from,
15
+ :reply_to,
16
+ :to,
17
+ :cc,
18
+ :subject,
19
+ :body,
20
+ :attachments,
21
+ )
22
+
23
+ def initialize(*)
24
+ super
25
+ assign_default_values
26
+ end
27
+
28
+ def attachment_names
29
+ attachments.keys.join(", ")
30
+ end
31
+
32
+ def validate_and_deliver
33
+ ok = valid?
34
+ deliver if ok
35
+ ok
36
+ end
37
+
38
+ def data
39
+ {
40
+ :from => from,
41
+ :reply_to => reply_to,
42
+ :to => to,
43
+ :cc => cc,
44
+ :subject => subject,
45
+ :body => body,
46
+ :attachments => attachments,
47
+ }
48
+ end
49
+
50
+ def deliver
51
+ deliver_now
52
+ end
53
+
54
+ def deliver_now
55
+ Agilibox::GenericMailer.generic_email(data).deliver_now
56
+ end
57
+
58
+ def deliver_later
59
+ Agilibox::GenericMailer.generic_email(data).deliver_later
60
+ end
61
+
62
+ private
63
+
64
+ def assign_default_values
65
+ self.from ||= default_from
66
+ self.reply_to ||= default_reply_to
67
+ self.to ||= default_to
68
+ self.cc ||= default_cc
69
+ self.subject ||= default_subject
70
+ self.body ||= default_body
71
+ self.attachments ||= default_attachments
72
+ end
73
+
74
+ def default_from
75
+ end
76
+
77
+ def default_reply_to
78
+ "#{current_user} <#{current_user.email}>" if current_user
79
+ end
80
+
81
+ def default_to
82
+ end
83
+
84
+ def default_cc
85
+ end
86
+
87
+ def default_subject
88
+ end
89
+
90
+ def default_body
91
+ end
92
+
93
+ def default_attachments
94
+ {}
95
+ end
96
+
97
+ def validate_addr_formats
98
+ validate_addr_formats_for(:from)
99
+ validate_addr_formats_for(:reply_to)
100
+ validate_addr_formats_for(:to)
101
+ validate_addr_formats_for(:cc)
102
+ end
103
+
104
+ def validate_addr_formats_for(attr)
105
+ require "mail"
106
+
107
+ string = public_send(attr).to_s
108
+ return if string.blank?
109
+ addrs = Mail.new(to: string).to_addrs
110
+ return true if addrs.any? && addrs.all? { |addr| URI::MailTo::EMAIL_REGEXP.match?(addr) }
111
+ errors.add(attr, :invalid)
112
+ end
113
+ end
@@ -1,4 +1,6 @@
1
1
  class Agilibox::SmallData::Filter
2
+ include ActiveModel::Model
3
+
2
4
  STRATEGIES = {}
3
5
 
4
6
  attr_reader :jar
@@ -8,7 +10,7 @@ class Agilibox::SmallData::Filter
8
10
  end
9
11
 
10
12
  def strategies
11
- self.class::STRATEGIES
13
+ self.class::STRATEGIES.with_indifferent_access
12
14
  end
13
15
 
14
16
  def apply(query)
@@ -42,6 +44,10 @@ class Agilibox::SmallData::Filter
42
44
  end
43
45
  end
44
46
 
47
+ def respond_to_missing?(method, *)
48
+ super || strategies.key?(method.to_s) || strategies.key?(method.to_s.chomp("="))
49
+ end
50
+
45
51
  def read
46
52
  JSON.parse jar["filters"].to_s
47
53
  rescue JSON::ParserError
@@ -56,6 +62,18 @@ class Agilibox::SmallData::Filter
56
62
  write read.merge(new_filters)
57
63
  end
58
64
 
65
+ def actives_count
66
+ read.count { |k, v| strategies.key?(k.to_s) && v.present? }
67
+ end
68
+
69
+ def any?
70
+ actives_count.positive?
71
+ end
72
+
73
+ def empty?
74
+ !any?
75
+ end
76
+
59
77
  private
60
78
 
61
79
  def get(key)
@@ -1,5 +1,5 @@
1
1
  class Agilibox::SmallData::FilterStrategy
2
- def apply(query, value)
2
+ def apply(_query, _value)
3
3
  raise NotImplementedError
4
4
  end
5
5
  end
@@ -1,6 +1,7 @@
1
1
  class Agilibox::SmallData::FilterStrategyByDateBegin < ::Agilibox::SmallData::FilterStrategyByKeyValue
2
2
  def apply(query, value)
3
- value = Time.parse(value).beginning_of_day
4
- query.where("#{key} >= ?", value)
3
+ value = Date.parse(value)
4
+ column = column_for(query)
5
+ query.where("#{column} >= ?", value)
5
6
  end
6
7
  end
@@ -1,6 +1,7 @@
1
1
  class Agilibox::SmallData::FilterStrategyByDateEnd < ::Agilibox::SmallData::FilterStrategyByKeyValue
2
2
  def apply(query, value)
3
- value = Time.parse(value).end_of_day
4
- query.where("#{key} <= ?", value)
3
+ value = Date.parse(value)
4
+ column = column_for(query)
5
+ query.where("#{column} <= ?", value)
5
6
  end
6
7
  end
@@ -0,0 +1,56 @@
1
+ class Agilibox::SmallData::FilterStrategyByDateOrDatetimePeriod < ::Agilibox::SmallData::FilterStrategyByKeyValue
2
+ def initialize(*)
3
+ if instance_of?(Agilibox::SmallData::FilterStrategyByDateOrDatetimePeriod)
4
+ raise "please use FilterStrategyByDatePeriod or FilterStrategyByDatetimePeriod"
5
+ end
6
+
7
+ super
8
+ end
9
+
10
+ def apply(query, value) # rubocop:disable Metrics/MethodLength
11
+ value = value.to_s
12
+
13
+ if value == "today"
14
+ a = now
15
+ b = now
16
+ elsif value == "yesterday"
17
+ a = (now - 1.day)
18
+ b = (now - 1.day)
19
+ elsif value == "this_week"
20
+ a = now.beginning_of_week
21
+ b = now.end_of_week
22
+ elsif value == "this_month"
23
+ a = now.beginning_of_month
24
+ b = now.end_of_month
25
+ elsif value == "this_year"
26
+ a = now.beginning_of_year
27
+ b = now.end_of_year
28
+ elsif value == "last_week"
29
+ a = (now - 1.week).beginning_of_week
30
+ b = (now - 1.week).end_of_week
31
+ elsif value == "last_month"
32
+ a = (now - 1.month).beginning_of_month
33
+ b = (now - 1.month).end_of_month
34
+ elsif value == "last_year"
35
+ a = (now - 1.year).beginning_of_year
36
+ b = (now - 1.year).end_of_year
37
+ elsif (m = value.match(/last\.([0-9]+)\.(days?|weeks?|months?|years?)/))
38
+ a = now - m[1].to_i.public_send(m[2])
39
+ b = now
40
+ else
41
+ return query
42
+ end
43
+
44
+ if now.is_a?(Time)
45
+ a = a.beginning_of_day if a
46
+ b = b.end_of_day if b
47
+ end
48
+
49
+ column = column_for(query)
50
+
51
+ query = query.where("#{column} >= ?", a) if a
52
+ query = query.where("#{column} <= ?", b) if b
53
+
54
+ query
55
+ end
56
+ end
@@ -0,0 +1,5 @@
1
+ class Agilibox::SmallData::FilterStrategyByDatePeriod < ::Agilibox::SmallData::FilterStrategyByDateOrDatetimePeriod
2
+ def now
3
+ Date.current
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Agilibox::SmallData::FilterStrategyByDatetimePeriod < ::Agilibox::SmallData::FilterStrategyByDateOrDatetimePeriod
2
+ def now
3
+ Time.zone.now
4
+ end
5
+ end
@@ -2,15 +2,26 @@ class Agilibox::SmallData::FilterStrategyByKeyValue < ::Agilibox::SmallData::Fil
2
2
  attr_reader :key
3
3
 
4
4
  def initialize(key = nil)
5
+ super()
5
6
  @key = key
6
7
  end
7
8
 
8
9
  def apply(query, value)
9
10
  value = true if value == "true"
10
11
  value = false if value == "false"
11
- value = nil if value == "nil"
12
- value = nil if value == "null"
13
12
 
14
- query.where("#{key} = ?", value)
13
+ column = column_for(query)
14
+
15
+ if value.to_s.in?(%w(nil null))
16
+ query.where("#{column} IS NULL")
17
+ elsif value.to_s.in?(%w(not_nil not_null))
18
+ query.where("#{column} IS NOT NULL")
19
+ else
20
+ query.where("#{column} = ?", value)
21
+ end
22
+ end
23
+
24
+ def column_for(query)
25
+ key.is_a?(Symbol) ? "#{query.model.table_name}.#{key}" : key.to_s
15
26
  end
16
27
  end
@@ -0,0 +1,13 @@
1
+ class Agilibox::SmallData::FilterStrategyByKeyValues < ::Agilibox::SmallData::FilterStrategyByKeyValue
2
+ def apply(query, value)
3
+ value = value.split if value.is_a?(String)
4
+ value = value.select(&:present?)
5
+ column = column_for(query)
6
+
7
+ if value.any?
8
+ query.where("#{column} IN (?)", value)
9
+ else
10
+ query
11
+ end
12
+ end
13
+ end
@@ -1,6 +1,6 @@
1
1
  class Agilibox::SmallData::FilterStrategyByTags < ::Agilibox::SmallData::FilterStrategy
2
2
  def apply(query, value)
3
- value = [*value].flatten.select{ |v| v.present? }
3
+ value = [*value].flatten.select(&:present?)
4
4
 
5
5
  if value.any?
6
6
  query.tagged_with(value)
@@ -0,0 +1,29 @@
1
+ class Agilibox::MiniFormObject < SimpleDelegator
2
+ include ActiveModel::Validations
3
+ extend Forwardable
4
+
5
+ def valid?
6
+ __getobj__.valid?
7
+ run_validations!
8
+ end
9
+
10
+ def invalid?
11
+ !valid?
12
+ end
13
+
14
+ alias validate valid?
15
+
16
+ def validate!
17
+ valid? || raise_validation_error
18
+ end
19
+
20
+ def_delegators :__getobj__, :errors
21
+
22
+ def save
23
+ valid? && __getobj__.save
24
+ end
25
+
26
+ def save!
27
+ validate! && __getobj__.save!
28
+ end
29
+ end
@@ -2,7 +2,9 @@ module Agilibox::AllHelpers
2
2
  include Agilibox::BootstrapHelper
3
3
  include Agilibox::ButtonHelper
4
4
  include Agilibox::FiltersHelper
5
+ include Agilibox::FontAwesomeHelper
5
6
  include Agilibox::FormHelper
7
+ include Agilibox::I18nHelper
6
8
  include Agilibox::LinkHelper
7
9
  include Agilibox::PaginationHelper
8
10
  include Agilibox::RoutesHelper
@@ -1,6 +1,65 @@
1
1
  module Agilibox::BootstrapHelper
2
- def icon(id)
3
- id = id.to_s.gsub("_", "-")
4
- content_tag(:span, class: "icon fa fa-#{id}"){}
2
+ class << self
3
+ attr_writer :card_classes
4
+
5
+ def card_classes
6
+ @card_classes ||= {
7
+ :card => "card",
8
+ :header => "card-header",
9
+ :body => "card-body",
10
+ :footer => "card-footer",
11
+ }
12
+ end
13
+ end
14
+
15
+ def bs_progress_bar(percentage)
16
+ tag.div(class: "progress") do
17
+ tag.div(class: "progress-bar", style: "width:#{percentage}%") do
18
+ "#{percentage}%"
19
+ end
20
+ end
21
+ end
22
+
23
+ def bs_card( # rubocop:disable Metrics/ParameterLists
24
+ header: nil,
25
+ body: true,
26
+ footer: nil,
27
+ card_tag: :div,
28
+ header_tag: :div,
29
+ body_tag: :div,
30
+ footer_tag: :div,
31
+ card_class: nil,
32
+ header_class: nil,
33
+ body_class: nil,
34
+ footer_class: nil,
35
+ &block
36
+ )
37
+ global_classes = Agilibox::BootstrapHelper.card_classes
38
+ card_classes = ([global_classes[:card]] + card_class.to_s.split).compact.sort
39
+ header_classes = ([global_classes[:header]] + header_class.to_s.split).compact.sort
40
+ body_classes = ([global_classes[:body]] + body_class.to_s.split).compact.sort
41
+ footer_classes = ([global_classes[:footer]] + footer_class.to_s.split).compact.sort
42
+
43
+ if header
44
+ header_html = content_tag(header_tag, class: header_classes) { header }
45
+ else
46
+ header_html = "".html_safe
47
+ end
48
+
49
+ if body
50
+ body_html = content_tag(body_tag, class: body_classes) { capture(&block) }
51
+ else
52
+ body_html = capture(&block)
53
+ end
54
+
55
+ if footer
56
+ footer_html = content_tag(footer_tag, class: footer_classes) { footer }
57
+ else
58
+ footer_html = "".html_safe
59
+ end
60
+
61
+ content_tag(card_tag, class: card_classes) do
62
+ header_html + body_html + footer_html
63
+ end
5
64
  end
6
65
  end