cm-admin 0.7.8 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +22 -5
- data/app/assets/images/image_not_available.png +0 -0
- data/app/assets/stylesheets/cm_admin/cm_admin.css.scss +1 -0
- data/app/assets/stylesheets/cm_admin/pages/import_page.scss +125 -0
- data/app/controllers/cm_admin/resource_controller.rb +19 -0
- data/app/helpers/cm_admin/application_helper.rb +41 -0
- data/app/jobs/file_import_processor_job.rb +38 -0
- data/app/models/concerns/cm_admin/file_import.rb +40 -0
- data/app/models/file_import.rb +27 -0
- data/app/policies/cm_admin/file_import_policy.rb +11 -0
- data/app/views/cm_admin/main/_actions_dropdown.html.slim +3 -15
- data/app/views/cm_admin/main/_associated_table.html.slim +2 -0
- data/app/views/cm_admin/main/_member_custom_action_modal.html.slim +13 -0
- data/app/views/cm_admin/main/_table.html.slim +2 -0
- data/app/views/cm_admin/main/_top_navbar.html.slim +2 -0
- data/app/views/cm_admin/main/import_form.html.slim +35 -0
- data/cm_admin.gemspec +1 -0
- data/config/routes.rb +6 -0
- data/lib/cm_admin/model.rb +7 -1
- data/lib/cm_admin/models/column.rb +3 -1
- data/lib/cm_admin/models/field.rb +4 -2
- data/lib/cm_admin/models/form_field.rb +2 -1
- data/lib/cm_admin/models/importer.rb +17 -0
- data/lib/cm_admin/version.rb +1 -1
- data/lib/cm_admin/view_helpers/field_display_helper.rb +8 -0
- data/lib/cm_admin/view_helpers/form_field_helper.rb +2 -2
- data/lib/cm_admin.rb +2 -1
- data/lib/generators/cm_admin/install_generator.rb +2 -0
- data/lib/generators/cm_admin/templates/cm_admin_initializer.rb +1 -1
- data/tmp/cache/webpacker/last-compilation-digest-development +1 -1
- metadata +28 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d4d1a58010d3994e7df3ef12930bcbd41d28a6e5a5fe12803dbe0b129272ba3
|
4
|
+
data.tar.gz: 97a80e2e67fbe2a67702b755f1772b0218ff892ca0bfd1d43655f561020b3665
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d8a20aeb76b4533e1134adbaa126c034ed8fc3f5b8366a1191b031363e109b5dae719a8414ee92eb316440aa605ceb780da126b319c9fb163b5999fe94bf396c
|
7
|
+
data.tar.gz: f857581be503ef9e20ae8512b869923db95322256c2586584fa65a2058148e00abd14c291dc8fda6a28fabfb8fb74f3fdc7fa66cdef23bab020ad101b03df222
|
data/Gemfile.lock
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
cm-admin (0.
|
4
|
+
cm-admin (0.8.1)
|
5
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)
|
@@ -32,6 +33,10 @@ GEM
|
|
32
33
|
minitest (>= 5.1)
|
33
34
|
tzinfo (~> 2.0)
|
34
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)
|
35
40
|
builder (3.2.4)
|
36
41
|
caxlsx (3.2.0)
|
37
42
|
htmlentities (~> 4.3, >= 4.3.4)
|
@@ -42,22 +47,29 @@ GEM
|
|
42
47
|
actionpack (>= 3.1)
|
43
48
|
caxlsx (>= 3.0)
|
44
49
|
cocoon (1.2.15)
|
50
|
+
coercible (1.0.0)
|
51
|
+
descendants_tracker (~> 0.0.1)
|
45
52
|
concurrent-ruby (1.1.10)
|
46
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)
|
47
58
|
diff-lcs (1.4.4)
|
48
|
-
erubi (1.
|
59
|
+
erubi (1.11.0)
|
49
60
|
htmlentities (4.3.4)
|
50
61
|
i18n (1.11.0)
|
51
62
|
concurrent-ruby (~> 1.0)
|
63
|
+
ice_nine (0.11.2)
|
52
64
|
local_time (2.1.0)
|
53
|
-
loofah (2.
|
65
|
+
loofah (2.19.0)
|
54
66
|
crass (~> 1.0.2)
|
55
67
|
nokogiri (>= 1.5.9)
|
56
68
|
marcel (1.0.2)
|
57
69
|
method_source (1.0.0)
|
58
70
|
mini_portile2 (2.8.0)
|
59
71
|
minitest (5.16.2)
|
60
|
-
nokogiri (1.13.
|
72
|
+
nokogiri (1.13.8)
|
61
73
|
mini_portile2 (~> 2.8.0)
|
62
74
|
racc (~> 1.4)
|
63
75
|
pagy (4.11.0)
|
@@ -68,7 +80,7 @@ GEM
|
|
68
80
|
activesupport (>= 3.0.0)
|
69
81
|
racc (1.6.0)
|
70
82
|
rack (2.2.4)
|
71
|
-
rack-proxy (0.7.
|
83
|
+
rack-proxy (0.7.4)
|
72
84
|
rack
|
73
85
|
rack-test (2.0.2)
|
74
86
|
rack (>= 1.3)
|
@@ -120,10 +132,15 @@ GEM
|
|
120
132
|
tilt (>= 2.0.6, < 2.1)
|
121
133
|
temple (0.8.2)
|
122
134
|
thor (1.2.1)
|
135
|
+
thread_safe (0.3.6)
|
123
136
|
tilt (2.0.10)
|
124
137
|
tzinfo (2.0.4)
|
125
138
|
concurrent-ruby (~> 1.0)
|
126
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)
|
127
144
|
webpacker (5.4.3)
|
128
145
|
activesupport (>= 5.2)
|
129
146
|
rack-proxy (>= 0.6.1)
|
Binary file
|
@@ -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,6 +71,25 @@ 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
|
@@ -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
|
@@ -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
|
+
content = file_import.import_file.download
|
7
|
+
importer = model.importer.class_name.classify.constantize.new(content: content)
|
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
|
@@ -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
|
-
-
|
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 "#{
|
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 "#{
|
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 }
|
@@ -46,3 +46,5 @@
|
|
46
46
|
.cm-pagination__lhs Showing #{@associated_ar_object.pagy.from} to #{@associated_ar_object.pagy.to} out of #{@associated_ar_object.pagy.count}
|
47
47
|
.cm-pagination__rhs
|
48
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 }
|
@@ -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 }
|
@@ -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'
|
@@ -0,0 +1,35 @@
|
|
1
|
+
.import-page
|
2
|
+
.import-page__header
|
3
|
+
h1.header-title #{@model.name} Import
|
4
|
+
p.header-description To import #{@model.name.underscore} data using the form below.
|
5
|
+
.import-page__body
|
6
|
+
h6.body-title Import data
|
7
|
+
- if flash[:notice]
|
8
|
+
.success-card
|
9
|
+
h6.success-title
|
10
|
+
i.fa-solid.fa-circle-check
|
11
|
+
| SUCCESS
|
12
|
+
p.success-msg Your file has been uploaded and it will be processed soon.
|
13
|
+
/ p.success-msg-info An email will be sent once the process is completed. If there are any problems with the import, we'll let you know through an email.
|
14
|
+
.actions-wrapper
|
15
|
+
a.secondary-btn href="#{cm_admin.send(:"#{@model.name.underscore}_import_path")}" Import new data
|
16
|
+
/ button.cta-btn.ml-2 Back to Page_Name
|
17
|
+
- else
|
18
|
+
= simple_form_for(FileImport.new, url: "/admin/#{@model.ar_model.table_name}/import", method: :post, html: { class: "csv-import-form" }) do |f|
|
19
|
+
.form-card
|
20
|
+
= f.input :associated_model_name, as: :hidden, placeholder: 'Enter product title', label: false, input_html: { value: @model.name }
|
21
|
+
= f.input :import_file, as: :file, placeholder: 'Enter product title', label: false
|
22
|
+
.note-card
|
23
|
+
h6.note-title
|
24
|
+
i.fa.fa-info-circle
|
25
|
+
| Note
|
26
|
+
.steps-wrapper
|
27
|
+
h6.steps-title Follow these steps to import your data
|
28
|
+
ul.steps-list
|
29
|
+
li
|
30
|
+
| Download this
|
31
|
+
a href="#" template file
|
32
|
+
li Add your data on the file without changing the format
|
33
|
+
li Save the file as a csv, xls or xlsx
|
34
|
+
li Upload the file in the field above
|
35
|
+
= f.button :submit, class: "cta-btn import-btn", value: 'Import data'
|
data/cm_admin.gemspec
CHANGED
data/config/routes.rb
CHANGED
@@ -10,6 +10,12 @@ CmAdmin::Engine.routes.draw do
|
|
10
10
|
|
11
11
|
# Defining action routes for each model
|
12
12
|
CmAdmin.config.cm_admin_models.each do |model|
|
13
|
+
if model.importer
|
14
|
+
scope model.name.tableize do
|
15
|
+
send(:get, 'import', to: "#{model.name.underscore}#import_form", as: "#{model.name.underscore}_import_form")
|
16
|
+
send(:post, 'import', to: "#{model.name.underscore}#import", as: "#{model.name.underscore}_import")
|
17
|
+
end
|
18
|
+
end
|
13
19
|
model.available_actions.sort_by {|act| act.name}.each do |act|
|
14
20
|
scope model.name.tableize do
|
15
21
|
send(act.verb, act.path.present? ? act.path : act.name, to: "#{model.name.underscore}##{act.name}", as: "#{model.name.underscore}_#{act.name}")
|
data/lib/cm_admin/model.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require_relative 'constants'
|
2
2
|
require_relative 'models/action'
|
3
|
+
require_relative 'models/importer'
|
3
4
|
require_relative 'models/custom_action'
|
4
5
|
require_relative 'models/field'
|
5
6
|
require_relative 'models/form_field'
|
@@ -15,6 +16,7 @@ require 'axlsx'
|
|
15
16
|
require 'cocoon'
|
16
17
|
require 'pundit'
|
17
18
|
require 'local_time'
|
19
|
+
require 'csv_importer'
|
18
20
|
|
19
21
|
module CmAdmin
|
20
22
|
class Model
|
@@ -23,7 +25,7 @@ module CmAdmin
|
|
23
25
|
include Models::DslMethod
|
24
26
|
attr_accessor :available_actions, :actions_set, :available_fields, :permitted_fields,
|
25
27
|
:current_action, :params, :filters, :available_tabs, :icon_name
|
26
|
-
attr_reader :name, :ar_model, :is_visible_on_sidebar
|
28
|
+
attr_reader :name, :ar_model, :is_visible_on_sidebar, :importer
|
27
29
|
|
28
30
|
def initialize(entity, &block)
|
29
31
|
@name = entity.name
|
@@ -80,6 +82,10 @@ module CmAdmin
|
|
80
82
|
@actions_set = true
|
81
83
|
end
|
82
84
|
|
85
|
+
def importable(class_name:, importer_type:)
|
86
|
+
@importer = CmAdmin::Models::Importer.new(class_name, importer_type)
|
87
|
+
end
|
88
|
+
|
83
89
|
def visible_on_sidebar(visible_option)
|
84
90
|
@is_visible_on_sidebar = visible_option
|
85
91
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module CmAdmin
|
2
2
|
module Models
|
3
3
|
class Column
|
4
|
-
attr_accessor :field_name, :field_type, :header, :format, :prefix, :suffix, :exportable, :round,
|
4
|
+
attr_accessor :field_name, :field_type, :header, :format, :prefix, :suffix, :exportable, :round, :height, :width,
|
5
5
|
:cm_css_class, :link, :url, :custom_method, :helper_method, :managable, :lockable, :drawer_partial, :tag_class
|
6
6
|
|
7
7
|
def initialize(field_name, attributes = {})
|
@@ -13,6 +13,8 @@ module CmAdmin
|
|
13
13
|
|
14
14
|
#formatting header (either field_name or value present in header attribute)
|
15
15
|
self.send("header=", format_header)
|
16
|
+
self.height = 50 if self.field_type == :image && self.height.nil?
|
17
|
+
self.width = 50 if self.field_type == :image && self.width.nil?
|
16
18
|
end
|
17
19
|
|
18
20
|
#returns a string value as a header (either field_name or value present in header attribute)
|
@@ -2,8 +2,8 @@ module CmAdmin
|
|
2
2
|
module Models
|
3
3
|
class Field
|
4
4
|
|
5
|
-
attr_accessor :field_name, :label, :header, :field_type, :format, :precision,
|
6
|
-
:helper_method, :preview, :custom_link, :precision, :prefix, :suffix, :tag_class
|
5
|
+
attr_accessor :field_name, :label, :header, :field_type, :format, :precision, :height,
|
6
|
+
:width, :helper_method, :preview, :custom_link, :precision, :prefix, :suffix, :tag_class
|
7
7
|
|
8
8
|
def initialize(field_name, attributes = {})
|
9
9
|
@field_name = field_name
|
@@ -11,6 +11,8 @@ module CmAdmin
|
|
11
11
|
attributes.each do |key, value|
|
12
12
|
self.send("#{key.to_s}=", value)
|
13
13
|
end
|
14
|
+
self.height = 50 if self.field_type == :image && self.height.nil?
|
15
|
+
self.width = 50 if self.field_type == :image && self.width.nil?
|
14
16
|
end
|
15
17
|
|
16
18
|
def set_default_values
|
@@ -5,17 +5,18 @@ module CmAdmin
|
|
5
5
|
VALID_INPUT_TYPES = [:integer, :decimal, :string, :single_select, :multi_select, :date, :date_time, :text, :single_file_upload, :multi_file_upload, :hidden, :rich_text].freeze
|
6
6
|
|
7
7
|
def initialize(field_name, input_type, attributes = {})
|
8
|
-
raise ArgumentError, "Kindly select a valid filter type like #{VALID_INPUT_TYPES.sort.to_sentence(last_word_connector: ', or ')} instead of #{input_type} for column #{field_name}" unless VALID_INPUT_TYPES.include?(input_type.to_sym)
|
9
8
|
@field_name = field_name
|
10
9
|
set_default_values
|
11
10
|
attributes.each do |key, value|
|
12
11
|
self.send("#{key.to_s}=", value)
|
13
12
|
end
|
13
|
+
raise ArgumentError, "Kindly select a valid input type like #{VALID_INPUT_TYPES.sort.to_sentence(last_word_connector: ', or ')} instead of #{self.input_type} for form field #{field_name}" unless VALID_INPUT_TYPES.include?(self.input_type.to_sym)
|
14
14
|
end
|
15
15
|
|
16
16
|
def set_default_values
|
17
17
|
self.disabled = false
|
18
18
|
self.label = self.field_name.to_s.titleize
|
19
|
+
self.input_type = :string
|
19
20
|
end
|
20
21
|
end
|
21
22
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module CmAdmin
|
2
|
+
module Models
|
3
|
+
class Importer
|
4
|
+
|
5
|
+
attr_accessor :class_name, :importer_type
|
6
|
+
|
7
|
+
VALID_IMPORTER_TYPES = [:csv_importer, :custom_importer]
|
8
|
+
|
9
|
+
def initialize(class_name, importer_type=:csv_importer)
|
10
|
+
raise ArgumentError, "Kindly select a valid importer type like #{VALID_IMPORTER_TYPES.sort.to_sentence(last_word_connector: ', or ')} instead of #{importer_type}" unless VALID_IMPORTER_TYPES.include?(importer_type.to_sym)
|
11
|
+
@class_name = class_name
|
12
|
+
@importer_type = importer_type
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/cm_admin/version.rb
CHANGED
@@ -61,6 +61,14 @@ module CmAdmin
|
|
61
61
|
concat content_tag(:div, ar_object.send(field.field_name).to_s, class: 'text-ellipsis')
|
62
62
|
concat content_tag(:div, 'View', class: 'drawer-btn')
|
63
63
|
end
|
64
|
+
when :image
|
65
|
+
content_tag(:div, class: 'd-flex') do
|
66
|
+
if ar_object.send(field.field_name).attached?
|
67
|
+
image_tag(ar_object.send(field.field_name).url, height: field.height, width: field.height)
|
68
|
+
else
|
69
|
+
image_tag('/assets/image_not_available', height: 50, width: 50)
|
70
|
+
end
|
71
|
+
end
|
64
72
|
end
|
65
73
|
end
|
66
74
|
|
@@ -13,9 +13,9 @@ module CmAdmin
|
|
13
13
|
when :string
|
14
14
|
return f.text_field field.field_name, class: "normal-input #{required_class}", disabled: field.disabled, value: value, placeholder: "Enter #{field.field_name.to_s.downcase.gsub('_', ' ')}"
|
15
15
|
when :single_select
|
16
|
-
return f.select field.field_name, options_for_select(select_collection_value(field),
|
16
|
+
return f.select field.field_name, options_for_select(select_collection_value(field), f.object.send(field.field_name)), {include_blank: "Select #{field.field_name.to_s.downcase.gsub('_', ' ')}"}, class: "normal-input #{required_class} select-2", disabled: field.disabled
|
17
17
|
when :multi_select
|
18
|
-
return f.select field.field_name, options_for_select(select_collection_value(field),
|
18
|
+
return f.select field.field_name, options_for_select(select_collection_value(field), f.object.send(field.field_name)), {include_blank: "Select #{field.field_name.to_s.downcase.gsub('_', ' ')}"}, class: "normal-input #{required_class} select-2", disabled: field.disabled, multiple: true
|
19
19
|
when :date
|
20
20
|
return f.text_field field.field_name, class: "normal-input #{required_class}", disabled: field.disabled, value: value&.strftime('%d-%m-%Y'), placeholder: "Enter #{field.field_name.to_s.downcase.gsub('_', ' ')}", data: { behaviour: 'date-only' }
|
21
21
|
when :date_time
|
data/lib/cm_admin.rb
CHANGED
@@ -35,7 +35,8 @@ module CmAdmin
|
|
35
35
|
def initialize_model(entity, &block)
|
36
36
|
if entity.is_a?(Class)
|
37
37
|
return if CmAdmin::Model.find_by({name: entity.name})
|
38
|
-
|
38
|
+
cm_model = CmAdmin::Model.new(entity, &block)
|
39
|
+
config.cm_admin_models << cm_model
|
39
40
|
end
|
40
41
|
end
|
41
42
|
end
|
@@ -14,6 +14,8 @@ module CmAdmin
|
|
14
14
|
remove_file 'app/assets/stylesheets/actiontext.scss'
|
15
15
|
copy_file 'application_policy.rb', 'app/policies/application_policy.rb'
|
16
16
|
route 'mount CmAdmin::Engine => "/admin"'
|
17
|
+
generate 'migration', 'CreateFileImport associated_model_name:string added_by:references{polymorphic} error_report:jsonb completed_at:datetime status:integer'
|
18
|
+
rake 'db:migrate'
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
31533361827c4faa12bf892403c629479a81246a
|
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cm-admin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- sajinmp
|
8
8
|
- anbublacky
|
9
9
|
- AdityaTiwari2102
|
10
|
-
autorequire:
|
10
|
+
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date: 2022-
|
13
|
+
date: 2022-10-12 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: caxlsx_rails
|
@@ -110,6 +110,20 @@ dependencies:
|
|
110
110
|
- - "~>"
|
111
111
|
- !ruby/object:Gem::Version
|
112
112
|
version: 5.4.3
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: csv-importer
|
115
|
+
requirement: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - "~>"
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: 0.8.2
|
120
|
+
type: :runtime
|
121
|
+
prerelease: false
|
122
|
+
version_requirements: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - "~>"
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: 0.8.2
|
113
127
|
description: This is an admin panel gem
|
114
128
|
email:
|
115
129
|
- sajinprasadkm@gmail.com
|
@@ -128,6 +142,7 @@ files:
|
|
128
142
|
- LICENSE.txt
|
129
143
|
- README.md
|
130
144
|
- Rakefile
|
145
|
+
- app/assets/images/image_not_available.png
|
131
146
|
- app/assets/images/logo.png
|
132
147
|
- app/assets/stylesheets/cm_admin/base/auth.scss
|
133
148
|
- app/assets/stylesheets/cm_admin/base/common.scss
|
@@ -155,6 +170,7 @@ files:
|
|
155
170
|
- app/assets/stylesheets/cm_admin/helpers/_mixins.scss
|
156
171
|
- app/assets/stylesheets/cm_admin/helpers/_variable.scss
|
157
172
|
- app/assets/stylesheets/cm_admin/helpers/index.scss
|
173
|
+
- app/assets/stylesheets/cm_admin/pages/import_page.scss
|
158
174
|
- app/assets/stylesheets/cm_admin/scaffold.scss
|
159
175
|
- app/controllers/cm_admin/application_controller.rb
|
160
176
|
- app/controllers/cm_admin/exports_controller.rb
|
@@ -169,11 +185,16 @@ files:
|
|
169
185
|
- app/javascript/packs/cm_admin/quick_search.js
|
170
186
|
- app/javascript/packs/cm_admin/scaffolds.js
|
171
187
|
- app/javascript/stylesheets/cm_admin/application.scss
|
188
|
+
- app/jobs/file_import_processor_job.rb
|
189
|
+
- app/models/concerns/cm_admin/file_import.rb
|
190
|
+
- app/models/file_import.rb
|
191
|
+
- app/policies/cm_admin/file_import_policy.rb
|
172
192
|
- app/views/cm_admin/main/_actions_dropdown.html.slim
|
173
193
|
- app/views/cm_admin/main/_associated_table.html.slim
|
174
194
|
- app/views/cm_admin/main/_cm_pagy_nav.html.slim
|
175
195
|
- app/views/cm_admin/main/_drawer.html.slim
|
176
196
|
- app/views/cm_admin/main/_filters.html.slim
|
197
|
+
- app/views/cm_admin/main/_member_custom_action_modal.html.slim
|
177
198
|
- app/views/cm_admin/main/_nested_fields.html.slim
|
178
199
|
- app/views/cm_admin/main/_nested_table_form.html.slim
|
179
200
|
- app/views/cm_admin/main/_table.html.slim
|
@@ -183,6 +204,7 @@ files:
|
|
183
204
|
- app/views/cm_admin/main/associated_show.html.slim
|
184
205
|
- app/views/cm_admin/main/dashboard.html.slim
|
185
206
|
- app/views/cm_admin/main/edit.html.slim
|
207
|
+
- app/views/cm_admin/main/import_form.html.slim
|
186
208
|
- app/views/cm_admin/main/index.html.slim
|
187
209
|
- app/views/cm_admin/main/new.html.slim
|
188
210
|
- app/views/cm_admin/main/show.html.slim
|
@@ -225,6 +247,7 @@ files:
|
|
225
247
|
- lib/cm_admin/models/field.rb
|
226
248
|
- lib/cm_admin/models/filter.rb
|
227
249
|
- lib/cm_admin/models/form_field.rb
|
250
|
+
- lib/cm_admin/models/importer.rb
|
228
251
|
- lib/cm_admin/models/tab.rb
|
229
252
|
- lib/cm_admin/utils.rb
|
230
253
|
- lib/cm_admin/version.rb
|
@@ -278,7 +301,7 @@ licenses:
|
|
278
301
|
metadata:
|
279
302
|
homepage_uri: https://github.com/commutatus/cm-admin
|
280
303
|
source_code_uri: https://github.com/commutatus/cm-admin
|
281
|
-
post_install_message:
|
304
|
+
post_install_message:
|
282
305
|
rdoc_options: []
|
283
306
|
require_paths:
|
284
307
|
- lib
|
@@ -294,7 +317,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
294
317
|
version: '0'
|
295
318
|
requirements: []
|
296
319
|
rubygems_version: 3.2.3
|
297
|
-
signing_key:
|
320
|
+
signing_key:
|
298
321
|
specification_version: 4
|
299
322
|
summary: This is an admin panel gem
|
300
323
|
test_files: []
|