cm-admin 0.7.7 → 0.8.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 (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'