cm-admin 0.7.7 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +6 -5
  3. data/Gemfile.lock +49 -9
  4. data/app/assets/images/image_not_available.png +0 -0
  5. data/app/assets/stylesheets/cm_admin/base/table.scss +1 -1
  6. data/app/assets/stylesheets/cm_admin/cm_admin.css.scss +1 -0
  7. data/app/assets/stylesheets/cm_admin/pages/import_page.scss +125 -0
  8. data/app/controllers/cm_admin/resource_controller.rb +26 -4
  9. data/app/helpers/cm_admin/application_helper.rb +41 -0
  10. data/app/javascript/packs/cm_admin/exports.js +0 -1
  11. data/app/jobs/file_import_processor_job.rb +38 -0
  12. data/app/models/concerns/cm_admin/file_import.rb +40 -0
  13. data/app/models/file_import.rb +27 -0
  14. data/app/policies/cm_admin/file_import_policy.rb +11 -0
  15. data/app/views/cm_admin/main/_actions_dropdown.html.slim +3 -15
  16. data/app/views/cm_admin/main/_associated_table.html.slim +5 -0
  17. data/app/views/cm_admin/main/_member_custom_action_modal.html.slim +13 -0
  18. data/app/views/cm_admin/main/_table.html.slim +3 -1
  19. data/app/views/cm_admin/main/_tabs.html.slim +1 -1
  20. data/app/views/cm_admin/main/_top_navbar.html.slim +2 -0
  21. data/app/views/cm_admin/main/import_form.html.slim +35 -0
  22. data/cm_admin.gemspec +2 -1
  23. data/config/routes.rb +6 -0
  24. data/lib/cm_admin/model.rb +7 -1
  25. data/lib/cm_admin/models/column.rb +3 -1
  26. data/lib/cm_admin/models/field.rb +4 -2
  27. data/lib/cm_admin/models/form_field.rb +3 -2
  28. data/lib/cm_admin/models/importer.rb +17 -0
  29. data/lib/cm_admin/version.rb +1 -1
  30. data/lib/cm_admin/view_helpers/field_display_helper.rb +8 -0
  31. data/lib/cm_admin/view_helpers/form_field_helper.rb +8 -6
  32. data/lib/cm_admin/view_helpers.rb +18 -18
  33. data/lib/cm_admin.rb +2 -1
  34. data/lib/generators/cm_admin/add_graphql_generator.rb +25 -0
  35. data/lib/generators/cm_admin/install_generator.rb +2 -0
  36. data/lib/generators/cm_admin/templates/cm_admin_initializer.rb +1 -1
  37. data/lib/generators/cm_admin/templates/concerns/attachable.rb +63 -0
  38. data/lib/generators/cm_admin/templates/concerns/filtered_list.rb +22 -0
  39. data/lib/generators/cm_admin/templates/concerns/paginator.rb +12 -0
  40. data/lib/generators/cm_admin/templates/constants.rb +3 -0
  41. data/lib/generators/cm_admin/templates/exceptions/base_exception.rb +9 -0
  42. data/lib/generators/cm_admin/templates/graphql/enums/base/sort_column.rb +7 -0
  43. data/lib/generators/cm_admin/templates/graphql/enums/base/sort_direction.rb +8 -0
  44. data/lib/generators/cm_admin/templates/graphql/graphql_schema.rb +55 -0
  45. data/lib/generators/cm_admin/templates/graphql/inputs/base/attachment.rb +15 -0
  46. data/lib/generators/cm_admin/templates/graphql/inputs/base/filter.rb +15 -0
  47. data/lib/generators/cm_admin/templates/graphql/inputs/base/paging.rb +15 -0
  48. data/lib/generators/cm_admin/templates/graphql/inputs/base/sort.rb +15 -0
  49. data/lib/generators/cm_admin/templates/graphql/mutations/base_mutation.rb +8 -0
  50. data/lib/generators/cm_admin/templates/graphql/objects/base/attachment_type.rb +31 -0
  51. data/lib/generators/cm_admin/templates/graphql/objects/base/paging_type.rb +9 -0
  52. data/lib/generators/cm_admin/templates/graphql/queries/base_query.rb +4 -0
  53. data/package-lock.json +131 -35
  54. data/tmp/cache/webpacker/last-compilation-digest-development +1 -1
  55. data/yarn.lock +5145 -6202
  56. metadata +47 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc4dad533a8a02bcc62cc27dd83d829b07794c32a2849e2eebc21de1c1f6cc84
4
- data.tar.gz: 30b772fbd67cf68633111b3290685e8d3d9d03c269d93313ab915d490c458311
3
+ metadata.gz: 5fa47a489bd6b44db00d91cb7631c5491646fd00148ba756b87208f1218ee705
4
+ data.tar.gz: 5a61fd4d0b01637fbf16bd0a443b912929d12993863a936bd329682433ce54f6
5
5
  SHA512:
6
- metadata.gz: 31ec145cf1c7bb3c60a878b733fd251169d5403b6501f421180880ef56c7857f37e5d303ed4b93bd56a6ebfa4b7642f526db533864156a6e5a4e50af19b69d8a
7
- data.tar.gz: c45f05a62af30393e5d8bb458374afc51695c311310d6b3d497d6326913cbc45de634385d57a757e91748033de2765e321619c2ed64d1807ec612b763b98a8c1
6
+ metadata.gz: 708568975092346a45370f46c3b7119feb5a4c4bc43a33ec61a30a3966da7ed8ec13d1a9d91e3a2120f2d5a462f18cb5827a9deaed6fc84bc451b0740f80187d
7
+ data.tar.gz: fc2fba169f614e1d48a11cec08135a12db4e20eb0dcfb6eada41a810bbd7bbeb40b046512c472232542670da2e2c301db58b3263c37fe05de791c3e2b15d46a8
data/Gemfile CHANGED
@@ -1,11 +1,12 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
- gem 'pagy', '~> 4.11.0'
4
- gem 'slim'
5
- gem "rake", "~> 12.0"
6
- gem "rspec", "~> 3.0"
7
3
  gem 'cocoon'
4
+ gem 'pagy', '~> 4.11.0'
8
5
  gem 'pundit'
6
+ gem 'rake', '~> 12.0'
7
+ gem 'rspec', '~> 3.0'
8
+ gem 'rubocop'
9
+ gem 'slim'
9
10
 
10
11
  # Specify your gem's dependencies in cm_admin.gemspec
11
12
  gemspec
data/Gemfile.lock CHANGED
@@ -1,9 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cm-admin (0.7.7)
5
- axlsx_rails (~> 0.6.1)
4
+ cm-admin (0.8.0)
5
+ caxlsx_rails
6
6
  cocoon (~> 1.2.15)
7
+ csv-importer (~> 0.8.2)
7
8
  local_time (~> 2.1.0)
8
9
  pagy (~> 4.11.0)
9
10
  pundit (~> 2.2.0)
@@ -31,38 +32,55 @@ GEM
31
32
  i18n (>= 1.6, < 2)
32
33
  minitest (>= 5.1)
33
34
  tzinfo (~> 2.0)
34
- axlsx_rails (0.6.1)
35
- actionpack (>= 3.1)
36
- caxlsx (>= 3.0)
35
+ ast (2.4.2)
36
+ axiom-types (0.1.1)
37
+ descendants_tracker (~> 0.0.4)
38
+ ice_nine (~> 0.11.0)
39
+ thread_safe (~> 0.3, >= 0.3.1)
37
40
  builder (3.2.4)
38
41
  caxlsx (3.2.0)
39
42
  htmlentities (~> 4.3, >= 4.3.4)
40
43
  marcel (~> 1.0)
41
44
  nokogiri (~> 1.10, >= 1.10.4)
42
45
  rubyzip (>= 1.3.0, < 3)
46
+ caxlsx_rails (0.6.3)
47
+ actionpack (>= 3.1)
48
+ caxlsx (>= 3.0)
43
49
  cocoon (1.2.15)
50
+ coercible (1.0.0)
51
+ descendants_tracker (~> 0.0.1)
44
52
  concurrent-ruby (1.1.10)
45
53
  crass (1.0.6)
54
+ csv-importer (0.8.2)
55
+ virtus
56
+ descendants_tracker (0.0.4)
57
+ thread_safe (~> 0.3, >= 0.3.1)
46
58
  diff-lcs (1.4.4)
47
- erubi (1.10.0)
59
+ erubi (1.11.0)
48
60
  htmlentities (4.3.4)
49
61
  i18n (1.11.0)
50
62
  concurrent-ruby (~> 1.0)
63
+ ice_nine (0.11.2)
51
64
  local_time (2.1.0)
52
- loofah (2.18.0)
65
+ loofah (2.19.0)
53
66
  crass (~> 1.0.2)
54
67
  nokogiri (>= 1.5.9)
55
68
  marcel (1.0.2)
56
69
  method_source (1.0.0)
70
+ mini_portile2 (2.8.0)
57
71
  minitest (5.16.2)
58
- nokogiri (1.13.7-arm64-darwin)
72
+ nokogiri (1.13.8)
73
+ mini_portile2 (~> 2.8.0)
59
74
  racc (~> 1.4)
60
75
  pagy (4.11.0)
76
+ parallel (1.22.1)
77
+ parser (3.1.2.0)
78
+ ast (~> 2.4.1)
61
79
  pundit (2.2.0)
62
80
  activesupport (>= 3.0.0)
63
81
  racc (1.6.0)
64
82
  rack (2.2.4)
65
- rack-proxy (0.7.2)
83
+ rack-proxy (0.7.4)
66
84
  rack
67
85
  rack-test (2.0.2)
68
86
  rack (>= 1.3)
@@ -78,7 +96,10 @@ GEM
78
96
  rake (>= 12.2)
79
97
  thor (~> 1.0)
80
98
  zeitwerk (~> 2.5)
99
+ rainbow (3.1.1)
81
100
  rake (12.3.3)
101
+ regexp_parser (2.5.0)
102
+ rexml (3.2.5)
82
103
  rspec (3.10.0)
83
104
  rspec-core (~> 3.10.0)
84
105
  rspec-expectations (~> 3.10.0)
@@ -92,6 +113,18 @@ GEM
92
113
  diff-lcs (>= 1.2.0, < 2.0)
93
114
  rspec-support (~> 3.10.0)
94
115
  rspec-support (3.10.2)
116
+ rubocop (1.30.1)
117
+ parallel (~> 1.10)
118
+ parser (>= 3.1.0.0)
119
+ rainbow (>= 2.2.2, < 4.0)
120
+ regexp_parser (>= 1.8, < 3.0)
121
+ rexml (>= 3.2.5, < 4.0)
122
+ rubocop-ast (>= 1.18.0, < 2.0)
123
+ ruby-progressbar (~> 1.7)
124
+ unicode-display_width (>= 1.4.0, < 3.0)
125
+ rubocop-ast (1.18.0)
126
+ parser (>= 3.1.1.0)
127
+ ruby-progressbar (1.11.0)
95
128
  rubyzip (2.3.2)
96
129
  semantic_range (3.0.0)
97
130
  slim (4.1.0)
@@ -99,9 +132,15 @@ GEM
99
132
  tilt (>= 2.0.6, < 2.1)
100
133
  temple (0.8.2)
101
134
  thor (1.2.1)
135
+ thread_safe (0.3.6)
102
136
  tilt (2.0.10)
103
137
  tzinfo (2.0.4)
104
138
  concurrent-ruby (~> 1.0)
139
+ unicode-display_width (2.1.0)
140
+ virtus (2.0.0)
141
+ axiom-types (~> 0.1)
142
+ coercible (~> 1.0)
143
+ descendants_tracker (~> 0.0, >= 0.0.3)
105
144
  webpacker (5.4.3)
106
145
  activesupport (>= 5.2)
107
146
  rack-proxy (>= 0.6.1)
@@ -119,6 +158,7 @@ DEPENDENCIES
119
158
  pundit
120
159
  rake (~> 12.0)
121
160
  rspec (~> 3.0)
161
+ rubocop
122
162
  slim
123
163
 
124
164
  BUNDLED WITH
@@ -222,7 +222,7 @@
222
222
  width: calc(100% - 285px);
223
223
  left: 245px;
224
224
  background-color: #fff;
225
- z-index: 4;
225
+ z-index: 3;
226
226
  .table-sticky-top {
227
227
  position: sticky;
228
228
  top: 254px;
@@ -10,6 +10,7 @@
10
10
  * files in this directory. Styles in this file should be added after the last require_* statement.
11
11
  * It is generally better to create a new file per style scope.
12
12
  *
13
+ *= require 'cm_admin/pages/import_page'
13
14
  *= require 'cm_admin/base/table'
14
15
  *= require 'cm_admin/base/navbar'
15
16
  *= require 'cm_admin/base/sidebar'
@@ -0,0 +1,125 @@
1
+ @import "../helpers/index.scss";
2
+
3
+ @function size($size) {
4
+ @return map-get($font-size, $size);
5
+ }
6
+
7
+ @function weight($weight) {
8
+ @return map-get($font-weight, $weight);
9
+ }
10
+
11
+ .import-page {
12
+ &__header {
13
+ min-height: 110px;
14
+ padding: 24px;
15
+ background: $white;
16
+ .header-title {
17
+ @include font($size: size(24), $color: $primary-text-clr, $weight: bold);
18
+ font-family: $primary-font;
19
+ line-height: 32px;
20
+ margin-bottom: 8px;
21
+ }
22
+ .header-description {
23
+ @include font($size: size(14), $color: $primary-text-clr);
24
+ font-family: $primary-font;
25
+ line-height: 22px;
26
+ margin-bottom: 0;
27
+ }
28
+ }
29
+ &__body {
30
+ padding: 16px 24px;
31
+ .body-title {
32
+ @include font($size: size(16), $color: $primary-text-clr, $weight: bold);
33
+ font-family: $primary-font;
34
+ line-height: 24px;
35
+ margin-bottom: 16px;
36
+ }
37
+
38
+ .success-card {
39
+ max-width: 752px;
40
+ margin-top: 24px;
41
+ padding: 24px;
42
+ background: $green-lightest-clr;
43
+ border-radius: $radius-4;
44
+ .success-title {
45
+ @include font($size: size(12), $color: $green-regular-clr, $weight: bold);
46
+ font-family: $primary-font;
47
+ margin-bottom: 16px;
48
+ text-transform: uppercase;
49
+ }
50
+ .success-msg {
51
+ @include font($size: size(16), $weight: bold);
52
+ font-family: $primary-font;
53
+ line-height: 24px;
54
+ margin-bottom: 8px;
55
+ }
56
+ .success-msg-info {
57
+ @include font($size: size(14));
58
+ font-family: $primary-font;
59
+ line-height: 22px;
60
+ margin-bottom: 0;
61
+ }
62
+ }
63
+
64
+ .actions-wrapper {
65
+ margin-top: 32px;
66
+ button {
67
+ padding: 5px 10px;
68
+ }
69
+ }
70
+
71
+ //form UI
72
+ .csv-import-form {
73
+ .form-card {
74
+ max-width: 752px;
75
+ margin-bottom: 32px;
76
+ padding: 24px;
77
+ background: $white;
78
+ border: 1px solid $grey-light-clr;
79
+ border-radius: $radius-4;
80
+ }
81
+ .note-card {
82
+ width: 100%;
83
+ margin-top: 24px;
84
+ padding: 24px;
85
+ background: $blue-lightest-clr;
86
+ border-radius: $radius-4;
87
+ .note-title {
88
+ @include font($size: size(12), $color: $blue-regular-clr, $weight: bold);
89
+ font-family: $primary-font;
90
+ margin-bottom: 16px;
91
+ text-transform: uppercase;
92
+ }
93
+ .steps-wrapper {
94
+ .steps-title {
95
+ @include font($size: size(16), $weight: bold);
96
+ font-family: $primary-font;
97
+ line-height: 24px;
98
+ margin-bottom: 8px;
99
+ }
100
+ .steps-list {
101
+ margin-bottom: 0;
102
+ padding-inline-start: 16px;
103
+ li {
104
+ @include font($size: size(14));
105
+ font-family: $primary-font;
106
+ line-height: 22px;
107
+ margin-bottom: 8px;
108
+ &:nth-last-child(1) {
109
+ margin-bottom: 0;
110
+ }
111
+ a {
112
+ color: $brand-color;
113
+ text-decoration: underline !important;
114
+ }
115
+ }
116
+ }
117
+ }
118
+ }
119
+ .import-btn {
120
+ padding: 5px 10px;
121
+ }
122
+ }
123
+
124
+ }
125
+ }
@@ -71,13 +71,36 @@ module CmAdmin
71
71
  end
72
72
  end
73
73
 
74
+ def import
75
+ @model = Model.find_by({name: controller_name.titleize})
76
+ allowed_params = params.permit(file_import: [:associated_model_name, :import_file]).to_h
77
+ file_import = ::FileImport.new(allowed_params[:file_import])
78
+ file_import.added_by = Current.user
79
+ respond_to do |format|
80
+ if file_import.save!
81
+ format.html { redirect_back fallback_location: cm_admin.send("#{@model.name.underscore}_index_path"), notice: "Your import is successfully queued." }
82
+ end
83
+ end
84
+ end
85
+
86
+ def import_form
87
+ @model = Model.find_by({name: controller_name.titleize})
88
+ respond_to do |format|
89
+ format.html { render '/cm_admin/main/import_form' }
90
+ end
91
+ end
92
+
74
93
  def cm_custom_method(params)
75
94
  scoped_model = "CmAdmin::#{@model.name}Policy::Scope".constantize.new(Current.user, @model.name.constantize).resolve
76
95
  resource_identifier
77
96
  respond_to do |format|
78
97
  if @action.action_type == :custom
79
98
  if @action.child_records
80
- format.html { render @action.layout }
99
+ if request.xhr?
100
+ format.html { render partial: '/cm_admin/main/associated_table' }
101
+ else
102
+ format.html { render @action.layout }
103
+ end
81
104
  elsif @action.display_type == :page
82
105
  data = @action.parent == "index" ? @ar_object.data : @ar_object
83
106
  format.html { render @action.partial }
@@ -137,7 +160,7 @@ module CmAdmin
137
160
  child_records = @ar_object.send(@current_action.child_records)
138
161
  @associated_model = CmAdmin::Model.find_by(name: @model.ar_model.reflect_on_association(@current_action.child_records).klass.name)
139
162
  if child_records.is_a? ActiveRecord::Relation
140
- @associated_ar_object = filter_by(params, child_records)
163
+ @associated_ar_object = filter_by(params, child_records, @associated_model.filter_params(params))
141
164
  else
142
165
  @associated_ar_object = child_records
143
166
  end
@@ -155,8 +178,7 @@ module CmAdmin
155
178
 
156
179
  records = "CmAdmin::#{@model.name}Policy::Scope".constantize.new(Current.user, @model.name.constantize).resolve if records.nil?
157
180
  records = records.order("#{@current_action.sort_column} #{@current_action.sort_direction}")
158
-
159
- final_data = CmAdmin::Models::Filter.filtered_data(filter_params, records, @model.filters)
181
+ final_data = CmAdmin::Models::Filter.filtered_data(filter_params, records, @associated_model ? @associated_model.filters : @model.filters)
160
182
  pagy, records = pagy(final_data)
161
183
  filtered_result.data = records
162
184
  filtered_result.pagy = pagy
@@ -24,5 +24,46 @@ module CmAdmin
24
24
  return action_name.to_sym
25
25
  end
26
26
  end
27
+
28
+ def formatted_error_message(model_name, field_name)
29
+ invalid_rows = model_name.send(field_name)
30
+ if invalid_rows.present?
31
+ content_tag(:div) do
32
+ concat error_header
33
+ concat error_items(invalid_rows)
34
+ end
35
+ end
36
+ end
37
+
38
+ def error_header
39
+ content_tag :div, class: 'info-split' do
40
+ concat content_tag(:div, "Row number", class: 'info-split__lhs')
41
+ concat content_tag(:div, "Error")
42
+ end
43
+ end
44
+
45
+ def error_items(invalid_rows)
46
+ content_tag :div do
47
+ invalid_rows.each do |row_item|
48
+ concat format_error_item(row_item)
49
+ end
50
+ end
51
+ end
52
+
53
+ def format_error_item(row_item)
54
+ content_tag :div, class: 'info-split' do
55
+ concat content_tag(:div, row_item[0], class: 'info-split__lhs')
56
+ concat format_error(row_item[2])
57
+ end
58
+ end
59
+
60
+ def format_error(errors)
61
+ content_tag :div do
62
+ errors.each do |error|
63
+ message = error[1].instance_of?(Array) ? error[1].join(', ') : error[1]
64
+ concat content_tag(:div, error[0].titleize + '-' + message)
65
+ end
66
+ end
67
+ end
27
68
  end
28
69
  end
@@ -1,6 +1,5 @@
1
1
  $(document).on('click', '.export-to-file-btn', function(e) {
2
2
  e.preventDefault();
3
3
  query_param = window.location.href.split("?")[1]
4
- $('#export-to-file-form').get(0).setAttribute('action', '/cm_admin/export_to_file.js?' + query_param);
5
4
  $("#export-to-file-form").submit();
6
5
  });
@@ -0,0 +1,38 @@
1
+ class FileImportProcessorJob < ApplicationJob
2
+ queue_as :default
3
+
4
+ def perform(file_import)
5
+ model = CmAdmin::Model.find_by({name: file_import.associated_model_name})
6
+ path = ActiveStorage::Blob.service.path_for(file_import.import_file.key)
7
+ importer = model.importer.class_name.classify.constantize.new(path: path)
8
+ case model.importer.importer_type.to_s
9
+ when 'csv_importer'
10
+ run_csv_importer(importer, file_import)
11
+ when 'custom_importer'
12
+ run_custom_importer(importer, file_import)
13
+ end
14
+ end
15
+
16
+ # All logic are from csv_importer gem
17
+ def run_csv_importer(importer, file_import)
18
+ if importer.valid_header?
19
+ importer.run!
20
+ if importer.report.success?
21
+ file_import.update(status: :success, completed_at: DateTime.now)
22
+ else
23
+ identifier = importer.config.identifiers.first
24
+ invalid_items_array = importer.report.invalid_rows.map { |row| [row.line_number, row.model.send(identifier), row.errors] }
25
+ file_import.update(status: :failed, completed_at: DateTime.now, invalid_row_items: invalid_items_array)
26
+ end
27
+ else
28
+ file_import.update(status: :failed, completed_at: DateTime.now, invalid_row_items: [[1, 'invalid_header', {invalid_header: importer.report.message}]])
29
+ end
30
+ end
31
+
32
+ def run_custom_importer(importer, file_import)
33
+ importer.run!
34
+ invalid_items_array = importer.invalid_rows.map { |row| [row.line_number, row.identifier, row.errors] }
35
+ status = importer.invalid_rows.empty? ? :success : :failed
36
+ file_import.update(status: status, completed_at: DateTime.now, invalid_row_items: invalid_items_array)
37
+ end
38
+ end
@@ -0,0 +1,40 @@
1
+ module CmAdmin::FileImport
2
+ extend ActiveSupport::Concern
3
+ included do
4
+ cm_admin do
5
+ STATUS_TAG_COLOR = { in_progress: 'yellow-tag', success: 'success'}
6
+ actions only: [:index, :show]
7
+ set_icon 'fa fa-user'
8
+ cm_index do
9
+ page_title 'File Import'
10
+ page_description 'Manage all file import progress here'
11
+
12
+ column :id
13
+ column :imported_file_name, header: 'File'
14
+ column :created_at, header: 'Started At', field_type: :datetime, format: '%B %d, %Y, %H:%M %p'
15
+ column :completed_at, header: 'Completed At', field_type: :datetime, format: '%B %d, %Y, %H:%M %p'
16
+ column :associated_model_name, header: 'Model name'
17
+ column :added_by_name, header: 'Uploaded By'
18
+ column :status, field_type: :tag, tag_class: STATUS_TAG_COLOR
19
+ end
20
+
21
+ cm_show page_title: :id do
22
+ tab :profile, '' do
23
+ cm_show_section 'Import details' do
24
+ field :id, label: 'Import ID'
25
+ field :imported_file_name, label: 'File'
26
+ field :created_at, header: 'Started At', field_type: :datetime, format: '%B %d, %Y, %H:%M %p'
27
+ field :completed_at, header: 'Completed At', field_type: :datetime, format: '%B %d, %Y, %H:%M %p'
28
+ field :associated_model_name, header: 'Model name'
29
+ field :added_by_name, header: 'Uploaded By'
30
+ field :status, field_type: :tag, tag_class: STATUS_TAG_COLOR
31
+ end
32
+ cm_show_section 'Errors' do
33
+ field :invalid_row_items, field_type: :custom, helper_method: :formatted_error_message
34
+ end
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ class FileImport < ApplicationRecord
2
+ include CmAdmin::FileImport
3
+
4
+ belongs_to :added_by, polymorphic: true
5
+
6
+ enum status: { in_progress: 0, success: 1, failed: 2 }
7
+
8
+ has_one_attached :import_file
9
+
10
+ after_create_commit :process_uploaded_file
11
+
12
+ store_accessor :error_report, :invalid_row_items
13
+
14
+
15
+ def process_uploaded_file
16
+ FileImportProcessorJob.perform_later(self)
17
+ end
18
+
19
+ def imported_file_name
20
+ import_file.filename.to_s
21
+ end
22
+
23
+ def added_by_name
24
+ added_by.first_name
25
+ end
26
+
27
+ end
@@ -0,0 +1,11 @@
1
+ class CmAdmin::FileImportPolicy < ApplicationPolicy
2
+
3
+ def index?
4
+ true
5
+ end
6
+
7
+ def show?
8
+ true
9
+ end
10
+
11
+ end
@@ -1,8 +1,7 @@
1
1
  - edit_action = available_actions(cm_model, 'edit')
2
2
  - destroy_action = available_actions(cm_model, 'destroy')
3
3
  - custom_actions = available_actions(cm_model, 'custom_actions')
4
- - custom_actions_modals = custom_actions.select{ |act| act if act.display_type.eql?(:modal) }
5
-
4
+ - current_model = @associated_model || @model
6
5
  - if custom_actions.any? || edit_action.present? || destroy_action.present?
7
6
  td.row-action-cell
8
7
  .row-action-tool
@@ -13,13 +12,13 @@
13
12
  i.fa.fa-angle-down
14
13
  .popup-card.table-export-popup.hidden
15
14
  - if edit_action.present?
16
- = link_to "#{page_url('edit', ar_object)}" do
15
+ = link_to cm_admin.send("#{current_model.name.underscore}_edit_path", ar_object.id) do
17
16
  .popup-option
18
17
  span
19
18
  i.fa.fa-edit
20
19
  | Edit
21
20
  - if destroy_action.present?
22
- = link_to "#{page_url('destroy', ar_object)}", method: :delete do
21
+ = link_to cm_admin.send("#{current_model.name.underscore}_destroy_path", ar_object.id), method: :delete do
23
22
  .popup-option
24
23
  span
25
24
  i.fa.fa-trash
@@ -39,14 +38,3 @@
39
38
  span
40
39
  i class="#{custom_action.icon_name}"
41
40
  = custom_action.name.humanize
42
-
43
- - if custom_actions_modals.any?
44
- - custom_actions_modals.each do |custom_action|
45
- .modal.fade id="#{custom_action.name.classify}Modal-#{ar_object.id.to_s}" aria-hidden='true' aria-labelledby="#{custom_action.name.classify}ModalLabel" tabindex='1'
46
- .modal-dialog
47
- .modal-content
48
- .modal-header
49
- h5.modal-title id="#{custom_action.name.classify}ModalLabel" = custom_action.name.classify
50
- button.btn-close aria-label='Close' data-bs-dismiss='modal'
51
- .modal-body
52
- = render partial: custom_action.partial, locals: { ar_object: ar_object }
@@ -1,5 +1,8 @@
1
1
  .admin-table-index
2
2
  .table-top
3
+ - if @associated_model.filters.present? && @action.partial.nil?
4
+ .index-page__filters
5
+ == render partial: 'cm_admin/main/filters', locals: { filters: @associated_model.filters }
3
6
  p.table-top__total-count = "#{@associated_ar_object.pagy.count} #{@action.child_records.to_s.gsub('_', ' ')} found"
4
7
  .table-top__column-action
5
8
  - if @associated_model && @associated_model.available_actions.map(&:name).include?('new')
@@ -43,3 +46,5 @@
43
46
  .cm-pagination__lhs Showing #{@associated_ar_object.pagy.from} to #{@associated_ar_object.pagy.to} out of #{@associated_ar_object.pagy.count}
44
47
  .cm-pagination__rhs
45
48
  == render partial: 'cm_admin/main/cm_pagy_nav', locals: { pagy: @associated_ar_object.pagy }
49
+
50
+ = render partial: 'cm_admin/main/member_custom_action_modal', locals: { cm_model: @associated_model, ar_collection: @associated_ar_object }
@@ -0,0 +1,13 @@
1
+ - custom_actions = available_actions(cm_model, 'custom_actions')
2
+ - custom_actions_modals = custom_actions.select{ |act| act if act.display_type.eql?(:modal) }
3
+ - ar_collection.data.each do |ar_object|
4
+ - if custom_actions_modals.any?
5
+ - custom_actions_modals.each do |custom_action|
6
+ .modal.fade id="#{custom_action.name.classify}Modal-#{ar_object.id.to_s}" aria-hidden='true' aria-labelledby="#{custom_action.name.classify}ModalLabel" tabindex='1'
7
+ .modal-dialog
8
+ .modal-content
9
+ .modal-header
10
+ h5.modal-title id="#{custom_action.name.classify}ModalLabel" = custom_action.name.classify
11
+ button.btn-close aria-label='Close' data-bs-dismiss='modal'
12
+ .modal-body
13
+ = render partial: custom_action.partial, locals: { ar_object: ar_object }
@@ -27,7 +27,7 @@
27
27
  td.text-ellipsis
28
28
  span class="#{column.field_type.to_s} #{column.cm_css_class} "
29
29
  - if index == 0
30
- = link_to ar_object.send(column.field_name), "/cm_admin/#{ar_object.model_name.collection}/#{ar_object.id}"
30
+ = link_to ar_object.send(column.field_name), cm_admin.send("#{ar_object.model_name.singular}_show_path", ar_object.id)
31
31
  - else
32
32
  = show_field_value(ar_object, column)
33
33
  - if column.field_type == :drawer
@@ -39,3 +39,5 @@
39
39
  .cm-pagination__lhs Showing #{@ar_object.pagy.from} to #{@ar_object.pagy.to} out of #{@ar_object.pagy.count}
40
40
  .cm-pagination__rhs
41
41
  == render partial: 'cm_admin/main/cm_pagy_nav', locals: { pagy: @ar_object.pagy }
42
+
43
+ = render partial: 'cm_admin/main/member_custom_action_modal', locals: { cm_model: @model, ar_collection: @ar_object }
@@ -4,4 +4,4 @@ ul.nav.nav-pills
4
4
  - if nav_item.custom_action.empty? || (nav_item.custom_action.present? && policy([:cm_admin, @model.name.classify.constantize]).send(:"#{nav_item.custom_action}?"))
5
5
  li.nav-item
6
6
  - nav_item_action_name = nav_item.custom_action.present? ? nav_item.custom_action : 'show'
7
- = link_to nav_item.nav_item_name.to_s.titleize, "/cm_admin/#{@model.name.underscore.pluralize}/#{@ar_object.id}/#{nav_item.custom_action}", class: "nav-link #{ nav_item_action_name == action_name ? 'active' : ''}"
7
+ = link_to nav_item.nav_item_name.to_s.titleize, cm_admin.send("#{@ar_object.model_name.singular}_#{nav_item_action_name}_path", @ar_object.id), class: "nav-link #{ nav_item_action_name == action_name ? 'active' : ''}"
@@ -22,6 +22,8 @@
22
22
  li
23
23
  .popup-option.pointer data-bs-toggle='modal' data-bs-target='#exportmodal'
24
24
  span Export
25
+ - if @model.importer
26
+ = link_to 'Import', cm_admin.send(:"#{@model.name.underscore}_import_path"), class: 'primary-btn ml-2'
25
27
  - new_action = @model.available_actions.select{|act| act if act.action_type.eql?(:default) && act.name.eql?('new')}
26
28
  - if new_action.any? && policy([:cm_admin, @model.name.classify.constantize]).new?
27
29
  = link_to 'Add', cm_admin.send(:"#{@model.name.underscore}_new_path"), class: 'primary-btn ml-2'