cm-admin 2.3.4 → 2.4.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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/assets/javascripts/cm_admin/scaffolds.js +43 -28
  4. data/app/assets/javascripts/cm_admin/shared_scaffolds.js +109 -38
  5. data/app/assets/stylesheets/cm_admin/base/scaffold.scss +6 -0
  6. data/app/assets/stylesheets/cm_admin/cm_admin.css.scss +0 -1
  7. data/app/assets/stylesheets/cm_admin/components/_drawer.scss +17 -100
  8. data/app/assets/stylesheets/cm_admin/scaffold.scss +0 -14
  9. data/app/controllers/cm_admin/resource_controller.rb +45 -9
  10. data/app/javascript/stylesheets/cm_admin/application.scss +0 -1
  11. data/app/views/cm_admin/main/_associated_table.html.slim +7 -6
  12. data/app/views/cm_admin/main/_card.html.slim +0 -11
  13. data/app/views/cm_admin/main/_kanban.html.slim +0 -7
  14. data/app/views/cm_admin/main/_table.html.slim +6 -7
  15. data/app/views/cm_admin/main/edit.html.slim +1 -0
  16. data/app/views/cm_admin/main/new.html.slim +4 -3
  17. data/app/views/layouts/_cm_flash_message.html.slim +3 -6
  18. data/app/views/layouts/_drawer.html.slim +23 -0
  19. data/app/views/layouts/cm_admin.html.slim +1 -1
  20. data/config/importmap.rb +0 -1
  21. data/config/routes.rb +4 -0
  22. data/lib/cm_admin/model.rb +1 -1
  23. data/lib/cm_admin/models/action.rb +11 -7
  24. data/lib/cm_admin/models/form_field.rb +2 -1
  25. data/lib/cm_admin/version.rb +1 -1
  26. data/lib/cm_admin/view_helpers/form_helper.rb +37 -8
  27. data/lib/cm_admin/view_helpers/page_info_helper.rb +21 -16
  28. data/lib/cm_admin/view_helpers.rb +11 -0
  29. metadata +3 -4
  30. data/app/assets/stylesheets/cm_admin/dependency/jquery-jgrowl.min.css +0 -1
  31. data/app/views/cm_admin/main/_drawer.html.slim +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fec6548ce41685e85f8a77b0896f0602e8a38fd9829cf53933eb6bf703e3ee5a
4
- data.tar.gz: 190a6d31789e99487d33c1c78b3ca1576c1b52d77a6355cbb09c6fb4cb83d64c
3
+ metadata.gz: 78211818c7078c8cf9723ca166d19c9eaea3155544754a60fca317a8eaf86276
4
+ data.tar.gz: 7a53125a99db46cc8a7686076baf674eb971f20430bf680c5c0e105b06c89dc1
5
5
  SHA512:
6
- metadata.gz: 905008ecca1d18498dbb0995b2fe7eca6f1ad7b297e11aa6eb69b6a4956feb1b1805a01bf490b36e02a07a1cda2f842dfa25d8d5d3ab93ff7cc39f66797ac305
7
- data.tar.gz: 596bbde39d7551283e6eeab112680e1f12efc3d5abd7ebc356abc69564967caca2086860751436c2adb393ea8d6a695a277b851280d61d0d6c5edb5e3f596b2a
6
+ metadata.gz: a2da45fd35ae854471c2c0255359fc96df661ebcb482c452a65734daf3364465bfe7cbb5250488a13be7381a7c129af90288b31d5c364c65bd878523ed8c75b8
7
+ data.tar.gz: a7ed71594d873f6c525577f2f19a48efe68bb873f331ab9b49bec2b18b980ea03bdd86fcbd8d0d83d2eb78bb3a3805e97626806c83ac43b138b88dc376638335
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cm-admin (2.3.4)
4
+ cm-admin (2.4.0)
5
5
  caxlsx_rails
6
6
  cocoon (~> 1.2.15)
7
7
  csv-importer (~> 0.8.2)
@@ -15,7 +15,6 @@ import "moment";
15
15
  import "bootstrap";
16
16
  import "@popperjs/core";
17
17
  import "flatpickr";
18
- import "jgrowl";
19
18
  import "trix";
20
19
  import "@rails/actiontext";
21
20
  import Select2 from "select2";
@@ -23,7 +22,6 @@ Select2();
23
22
 
24
23
  // import '@nathanvda/cocoon'
25
24
  import "daterangepicker";
26
- import jqueryJgrowl from "jgrowl";
27
25
 
28
26
  document.addEventListener("turbo:load", function () {
29
27
  flatpickr("[data-behaviour='date-only']", {
@@ -38,19 +36,25 @@ document.addEventListener("turbo:load", function () {
38
36
  $(".select-2").select2({
39
37
  theme: "bootstrap-5",
40
38
  });
41
- jqueryJgrowl();
42
39
  setup_select_2_ajax();
43
- var el = $("[data-section='nested-form-body']")
44
- if(el[0]) {
45
- Sortable.create(el[0],{
46
- handle: '.drag-handle',
40
+ const bsToast = $('[data-behaviour="toast"]')[0];
41
+ if (bsToast) {
42
+ const toast = new bootstrap.Toast(bsToast);
43
+ toast.show();
44
+ }
45
+ var el = $("[data-section='nested-form-body']");
46
+ if (el[0]) {
47
+ Sortable.create(el[0], {
48
+ handle: ".drag-handle",
47
49
  animation: 150,
48
50
  onUpdate: function (evt) {
49
- var itemEl = evt.item
50
- $("[data-section='nested-form-body'] tr").each(function(index, el){
51
- $(el).find('.hidden-position').val(index+1)
52
- })
53
- }
51
+ var itemEl = evt.item;
52
+ $("[data-section='nested-form-body'] tr").each(function (index, el) {
53
+ $(el)
54
+ .find(".hidden-position")
55
+ .val(index + 1);
56
+ });
57
+ },
54
58
  });
55
59
  }
56
60
  });
@@ -84,18 +88,25 @@ $(document).on(
84
88
  "click",
85
89
  '[data-behaviour="permission-check-box"]',
86
90
  function (e) {
87
- var form_check = $(this).closest('.form-check')
88
- var form_field = $(this).closest('.form-field')
89
- if($(this).is(':checked')){
90
- form_check.find('.cm-select-tag').removeClass('hidden')
91
+ var form_check = $(this).closest(".form-check");
92
+ var form_field = $(this).closest(".form-field");
93
+ if ($(this).is(":checked")) {
94
+ form_check.find(".cm-select-tag").removeClass("hidden");
91
95
  } else {
92
- form_check.find('.cm-select-tag').addClass('hidden')
96
+ form_check.find(".cm-select-tag").addClass("hidden");
93
97
  }
94
- if (form_field.find('[data-behaviour="permission-check-box"]:checked').length == form_field.find('[data-behaviour="permission-check-box"]').length) {
95
- form_field.find('[data-behaviour="permission-check-all"]').prop('checked', true)
96
- }
97
- else {
98
- form_field.find('[data-behaviour="permission-check-all"]').prop('checked', false)
98
+ if (
99
+ form_field.find('[data-behaviour="permission-check-box"]:checked')
100
+ .length ==
101
+ form_field.find('[data-behaviour="permission-check-box"]').length
102
+ ) {
103
+ form_field
104
+ .find('[data-behaviour="permission-check-all"]')
105
+ .prop("checked", true);
106
+ } else {
107
+ form_field
108
+ .find('[data-behaviour="permission-check-all"]')
109
+ .prop("checked", false);
99
110
  }
100
111
  }
101
112
  );
@@ -104,13 +115,17 @@ $(document).on(
104
115
  "click",
105
116
  '[data-behaviour="permission-check-all"]',
106
117
  function (e) {
107
- var form_field = $(this).closest('.form-field')
108
- if($(this).is(':checked')){
109
- form_field.find('[data-behaviour="permission-check-box"]').prop('checked', true)
110
- form_field.find('.cm-select-tag').removeClass('hidden')
118
+ var form_field = $(this).closest(".form-field");
119
+ if ($(this).is(":checked")) {
120
+ form_field
121
+ .find('[data-behaviour="permission-check-box"]')
122
+ .prop("checked", true);
123
+ form_field.find(".cm-select-tag").removeClass("hidden");
111
124
  } else {
112
- form_field.find('[data-behaviour="permission-check-box"]').prop('checked', false)
113
- form_field.find('.cm-select-tag').addClass('hidden')
125
+ form_field
126
+ .find('[data-behaviour="permission-check-box"]')
127
+ .prop("checked", false);
128
+ form_field.find(".cm-select-tag").addClass("hidden");
114
129
  }
115
130
  }
116
131
  );
@@ -39,36 +39,107 @@ $(document).on("mouseleave", ".row-action-cell", function () {
39
39
  $(this).find(".table-export-popup").addClass("hidden");
40
40
  });
41
41
 
42
- $(document).on("click", ".drawer-btn", function (e) {
43
- e.stopPropagation();
44
- var drawer_el = $(this).parent().closest(".drawer").find(".cm-drawer");
45
- if (drawer_el.hasClass("hidden")) {
46
- drawer_el.removeClass("hidden");
47
- drawer_container = drawer_el.find(".drawer-container");
48
- if (drawer_container.hasClass("drawer-slide-out")) {
49
- drawer_container.removeClass("drawer-slide-out");
50
- }
51
- drawer_container.addClass("drawer-slide-in");
52
- } else {
53
- return drawer_el.addClass("hidden");
54
- }
42
+ $(document).on("click", '[data-behaviour="offcanvas"]', function (e) {
43
+ const drawerFetchUrl = $(this).attr("data-drawer-fetch-url");
44
+ const drawerContainer = $("[data-behaviour='cm-drawer-container']");
45
+
46
+ if (!drawerFetchUrl || !drawerContainer) return;
47
+
48
+ $.ajax({
49
+ url: drawerFetchUrl,
50
+ method: "GET",
51
+ success: function (response) {
52
+ drawerContainer.html(response);
53
+ const drawerForm = new bootstrap.Offcanvas(
54
+ drawerContainer.children().first()
55
+ );
56
+ drawerForm.show();
57
+ initializeComponents();
58
+ handleDrawerFormSubmission(drawerForm);
59
+ },
60
+ error: function (error) {
61
+ console.error("Error:", error);
62
+ },
63
+ });
55
64
  });
56
65
 
57
- $(document).on("click", ".drawer-close", function (e) {
58
- e.stopPropagation();
59
- $(".drawer-container").removeClass("drawer-slide-in");
60
- $(".drawer-container").addClass("drawer-slide-out");
61
- setTimeout(() => {
62
- $(".cm-drawer").addClass("hidden");
63
- }, 300);
66
+ $(document).on("click", '[data-bs-dismiss="offcanvas"]', function (e) {
67
+ $(document).off("submit", "[data-is-drawer-form='true']");
64
68
  });
65
69
 
70
+ function handleDrawerFormSubmission(drawerForm) {
71
+ $(document).on("submit", "[data-is-drawer-form='true']", function (e) {
72
+ e.preventDefault();
73
+ let url = $(this).attr("action");
74
+ if (url.charAt(url.length - 1) === "/") {
75
+ url = url.slice(0, -1);
76
+ }
77
+ url = `${url}.json`;
78
+ $.ajaxSetup({
79
+ headers: {
80
+ "X-CSRF-Token": $('meta[name="csrf-token"]').attr("content"),
81
+ },
82
+ });
83
+ const isMultipart = $(this).attr("enctype") === "multipart/form-data";
84
+ const ajaxOptions = {
85
+ url: url,
86
+ method: $(this).attr("method"),
87
+ data: null,
88
+ success: function (response) {
89
+ $('[data-behavior="flag-alert"]').addClass("hidden");
90
+ drawerForm.hide();
91
+ $(document).off("submit", "[data-is-drawer-form='true']");
92
+ const fromFieldId = $('[data-behavior="cm-drawer"]').attr(
93
+ "data-from-field-id"
94
+ );
95
+ const fromField = $(`#${fromFieldId}`);
96
+ toastMessage(response?.message);
97
+ const bsToast = $('[data-behaviour="drawer-toast"]')[0];
98
+ if (bsToast) {
99
+ const toast = new bootstrap.Toast(bsToast);
100
+ toast.show();
101
+ }
102
+ fromField.append(
103
+ `<option value="${response?.data?.id}">${response?.data?.name}</option>`
104
+ );
105
+ fromField.val(response?.data?.id).change();
106
+ },
107
+ error: function (error) {
108
+ $('[data-behavior="flag-alert"]').removeClass("hidden");
109
+ $('[data-behavior="error-list"]').html(error?.responseJSON?.message);
110
+ },
111
+ };
112
+ if (isMultipart) {
113
+ ajaxOptions["data"] = new FormData($(this)[0]);
114
+ ajaxOptions["contentType"] = false;
115
+ ajaxOptions["processData"] = false;
116
+ $.ajax(ajaxOptions);
117
+ } else {
118
+ ajaxOptions["data"] = $(this).serialize();
119
+ $.ajax(ajaxOptions);
120
+ }
121
+ });
122
+ }
123
+
124
+ function toastMessage(message) {
125
+ const toastHtml = `
126
+ <div class="cm-toast-container">
127
+ <div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-behaviour="drawer-toast">
128
+ <div class="d-flex">
129
+ <div class="toast-body">${message}</div>
130
+ <button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
131
+ </div>
132
+ </div>
133
+ </div>
134
+ `;
135
+ $('[data-behaviour="flash-container"]').html(toastHtml);
136
+ }
137
+
66
138
  $(document).on("change", '[data-field-type="linked-field"]', function (e) {
67
139
  e.stopPropagation();
68
140
  var params = {};
69
141
  params[$(this).data("field-name")] = $(this).val();
70
142
  var request_url = $(this).data("target-url") + "?" + $.param(params);
71
- console.log(request_url);
72
143
  $.ajax(request_url, {
73
144
  type: "GET",
74
145
  success: function (data) {
@@ -147,31 +218,31 @@ var replaceAccordionTitle = function (element) {
147
218
  .attr("data-bs-target", "#" + accordion_id);
148
219
  $(this).find(".accordion-collapse").attr("id", accordion_id);
149
220
  });
150
- initializeComponents();
221
+ initializeComponents();
151
222
  };
152
223
 
153
224
  export function initializeComponents() {
154
- $('.select-2').select2({
225
+ $(".select-2").select2({
155
226
  theme: "bootstrap-5",
156
227
  });
157
228
  flatpickr("[data-behaviour='date-only']", {
158
- dateFormat: "d-m-Y"
159
- })
229
+ dateFormat: "d-m-Y",
230
+ });
160
231
  flatpickr("[data-behaviour='date-time']", {
161
- enableTime: true
162
- })
232
+ enableTime: true,
233
+ });
163
234
  flatpickr("[data-behaviour='filter'][data-filter-type='date']", {
164
- mode: 'range'
165
- })
166
- var el = document.getElementsByClassName('columns-list')
167
- if(el[0]) {
168
- Sortable.create(el[0],{
169
- handle: '.dragger',
170
- animation: 150
235
+ mode: "range",
236
+ });
237
+ var el = document.getElementsByClassName("columns-list");
238
+ if (el[0]) {
239
+ Sortable.create(el[0], {
240
+ handle: ".dragger",
241
+ animation: 150,
171
242
  });
172
243
  }
173
244
 
174
- var headerElemHeight = $('.page-top-bar').height() + 64
175
- var calculatedHeight = "calc(100vh - " + headerElemHeight+"px"+")"
176
- $('.table-wrapper').css("maxHeight", calculatedHeight);
177
- }
245
+ var headerElemHeight = $(".page-top-bar").height() + 64;
246
+ var calculatedHeight = "calc(100vh - " + headerElemHeight + "px" + ")";
247
+ $(".table-wrapper").css("maxHeight", calculatedHeight);
248
+ }
@@ -126,3 +126,9 @@ a {
126
126
  padding: 0.375rem 0.75rem 0.375rem 0.75rem !important;
127
127
  background-position: right 0.75rem center !important;
128
128
  }
129
+
130
+ .cm-toast-container {
131
+ @extend .position-fixed, .bottom-0, .end-0, .p-3;
132
+
133
+ z-index: 11;
134
+ }
@@ -21,5 +21,4 @@
21
21
  @import "./base/alertbanner";
22
22
  @import "./components/index";
23
23
  @import "./dependency/flatpickr.min";
24
- @import "./dependency/jquery-jgrowl.min";
25
24
  @import "./scaffold";
@@ -1,110 +1,27 @@
1
- @import "../helpers/index.scss";
2
-
3
1
  .cm-drawer {
4
- position: fixed;
5
- top: 0;
6
- right: 0;
7
- bottom: 0;
8
- left: 0;
9
- width: 100%;
10
- height: 100%;
11
- z-index: 5;
12
- background-color: rgba(0, 0, 0, 0.7);
13
- &__container {
14
- position: relative;
15
- width: 360px;
16
- height: 100%;
17
- background: $white;
18
- box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.16);
19
- .header {
20
- display: flex;
21
- align-items: center;
22
- justify-content: space-between;
23
- padding: 24px;
24
- background: $grey-lightest-clr;
25
- &__lhs {
26
- .title {
27
- @include font($size: $t1-text, $color: $primary-text-clr, $weight: 600);
28
- font-family: $primary-font;
29
- line-height: 28px;
30
- margin-bottom: 8px;
31
- }
32
- .description {
33
- @include font($size: $t4-text, $color: $primary-text-clr);
34
- font-family: $primary-font;
35
- line-height: 22px;
36
- margin-bottom: 0;
37
- }
38
- }
39
- &__rhs {
40
- color: $ink-lighter-clr;
41
- }
42
- }
43
- .body {
44
- padding: 24px;
45
- .info-text {
46
- @include font($size: $t4-text, $color: $primary-text-clr);
47
- font-family: $primary-font;
48
- line-height: 22px;
49
- }
50
- }
51
- }
52
- }
53
-
54
- .drawer-slide-in {
55
- animation: slideInLeft 0.3s ease-in forwards;
2
+ @extend .offcanvas-end;
3
+ width: 640px !important;
56
4
  }
57
5
 
58
- .drawer-slide-out {
59
- animation: slideOutLeft 0.3s ease-in forwards;
6
+ .offcanvas-body {
7
+ padding: 0;
60
8
  }
61
9
 
62
- @keyframes slideInLeft {
63
- 0% {
64
- left: -400px;
65
- opacity: 0;
66
- }
67
- 100% {
68
- left: 0;
69
- opacity: 1;
70
- }
71
- }
72
-
73
- @keyframes slideOutLeft {
74
- 0% {
75
- left: 0;
76
- opacity: 1;
77
- }
78
- 100% {
79
- left: -400px;
80
- opacity: 0;
81
- }
10
+ .drawer-btn {
11
+ @extend .btn-link;
12
+ margin-left: auto;
13
+ margin-bottom: 0;
14
+ font-size: $t3-text !important;
82
15
  }
83
16
 
84
- .drawer-btn {
85
- cursor: pointer;
86
- margin-left: 8px;
87
- @include font($size: $t4-text, $color: $brand-color);
17
+ .drawer-btn-group {
18
+ @extend .d-flex, .flex-column, .gap-2;
19
+ position: absolute;
20
+ margin: 16px 0 0 -47px;
88
21
  }
89
22
 
90
- //Kanban drawer styles
91
- .kanban-drawer {
92
- .cm-drawer {
93
- display: flex;
94
- justify-content: flex-end;
95
- &__actions {
96
- display: flex;
97
- flex-direction: column;
98
- margin: 16px;
99
- }
100
- &__container {
101
- width: 624px;
102
- [class^="col-"] {
103
- width: 100% !important;
104
- }
105
- .show-page__inner {
106
- height: calc(100vh - 126px);
107
- }
108
- }
109
- }
23
+ .drawer-nav-btn {
24
+ @extend .btn-ghost;
25
+ width: 31px;
26
+ height: 31px;
110
27
  }
@@ -1,19 +1,5 @@
1
1
  @import "helpers/index.scss";
2
2
 
3
- .jGrowl {
4
- color: $ink-regular-clr !important;
5
- font-size: $t4-text !important;
6
- .success {
7
- background-color: $green-light-clr !important;
8
- }
9
- .notice {
10
- background-color: $blue-light-clr !important;
11
- }
12
- .error {
13
- background-color: $red-light-clr !important;
14
- }
15
- }
16
-
17
3
  .nested-field-wrapper {
18
4
  label {
19
5
  font-size: $t4-text;
@@ -85,9 +85,9 @@ module CmAdmin
85
85
  redirect_url = request.referrer || cm_admin.send("#{@model.name.underscore}_index_path")
86
86
  respond_to do |format|
87
87
  if @ar_object.destroy
88
- format.html { redirect_back fallback_location: redirect_url, notice: "#{action_name.titleize} #{@ar_object.class.name.downcase} is successful" }
88
+ format.html { redirect_back fallback_location: redirect_url, notice: "#{@model.formatted_name} was deleted" }
89
89
  else
90
- format.html { redirect_back fallback_location: redirect_url, notice: "#{action_name.titleize} #{@ar_object.class.name.downcase} is unsuccessful" }
90
+ format.html { redirect_back fallback_location: redirect_url, alert: "#{@model.formatted_name} could not be deleted" }
91
91
  end
92
92
  end
93
93
  end
@@ -114,12 +114,19 @@ module CmAdmin
114
114
  @bulk_action_processor = CmAdmin::BulkActionProcessor.new(@action, @model, params).perform_bulk_action
115
115
  respond_to do |format|
116
116
  if @bulk_action_processor.invalid_records.empty?
117
- format.html { redirect_to request.referrer, notice: "#{@action.name.humanize} is successful" }
117
+ flash[:notice] = "#{@action.formatted_name} was successful"
118
+ flash[:bulk_action_success] = "<b>#{@action.formatted_name} was successful</b>"
119
+ format.html { redirect_to request.referrer }
118
120
  else
119
121
  error_messages = @bulk_action_processor.invalid_records.map do |invalid_record|
120
122
  "<li>#{invalid_record.error_message}</li>"
121
123
  end.join
122
- format.html { redirect_to request.referrer, alert: "<b>#{@action.name.humanize} is unsuccessful</b><br /><ul>#{error_messages}</ul>" }
124
+ flash[:alert] = "#{@action.formatted_name} was unsuccessful"
125
+ flash[:bulk_action_error] = "<div>
126
+ <div><b>#{@action.formatted_name} encountered the following errors:</b></div>
127
+ <ul>#{error_messages}</ul>
128
+ </div>"
129
+ format.html { redirect_to request.referrer }
123
130
  end
124
131
  end
125
132
  end
@@ -161,13 +168,13 @@ module CmAdmin
161
168
  format.json { render json: response_object }
162
169
  elsif response_object.errors.empty?
163
170
  redirect_url = @model.current_action.redirection_url || @action.redirection_url || request.referrer || "/cm_admin/#{@model.ar_model.table_name}/#{@response_object.id}"
164
- format.html { redirect_to redirect_url, notice: "#{@action.name.titleize} is successful" }
171
+ format.html { redirect_to redirect_url, notice: "#{@action.formatted_name} was successful" }
165
172
  else
166
173
  error_messages = response_object.errors.full_messages.map { |error_message| "<li>#{error_message}</li>" }.join
167
- format.html { redirect_to request.referrer, alert: "<b>#{@action.name.titleize} is unsuccessful</b><br /><ul>#{error_messages}</ul>" }
174
+ format.html { redirect_to request.referrer, alert: "#{@action.formatted_name} was unsuccessful<br /><ul>#{error_messages}</ul>" }
168
175
  end
169
176
  rescue StandardError => e
170
- format.html { redirect_to request.referrer, alert: "<div><b>#{@action.name.titleize} is unsuccessful</b><br /><ul><li>#{e.message}</li></ul></div>" }
177
+ format.html { redirect_to request.referrer, alert: "<div>#{@action.formatted_name} was unsuccessful<br /><ul><li>#{e.message}</li></ul></div>" }
171
178
  end
172
179
  end
173
180
  end
@@ -191,6 +198,16 @@ module CmAdmin
191
198
  @model.default_sort_direction = nil
192
199
  end
193
200
 
201
+ def fetch_drawer
202
+ @model = Model.find_by({ name: controller_name.classify })
203
+ return if @model.blank?
204
+
205
+ action_page_title = CmAdmin::Models::Action.find_by(@model, name: 'new').page_title
206
+ drawer_title = action_page_title.presence || "New #{@model&.formatted_name}"
207
+ @ar_object = @model.ar_model.new
208
+ render partial: 'layouts/drawer', locals: { drawer_title:, from_field_id: params[:from_field_id] }
209
+ end
210
+
194
211
  def get_nested_table_fields(fields)
195
212
  nested_table_fields = []
196
213
  fields.each do |field|
@@ -233,9 +250,28 @@ module CmAdmin
233
250
  cm_admin.send("#{@model.name.underscore}_show_path", @ar_object)
234
251
  end
235
252
  ActiveStorage::Attachment.where(id: params['attachment_destroy_ids']).destroy_all if params['attachment_destroy_ids'].present?
236
- format.html { redirect_to redirect_url, allow_other_host: true, notice: "#{action_name.titleize} #{@ar_object.class.name.downcase} is successful" }
253
+ notice = if @current_action&.name == 'new'
254
+ "#{@model&.formatted_name} was created"
255
+ elsif @current_action&.name == 'edit'
256
+ "#{@model&.formatted_name} was updated"
257
+ else
258
+ "#{@action&.formatted_name} #{@model&.formatted_name} was successful"
259
+ end
260
+ format.html { redirect_to redirect_url, allow_other_host: true, notice: }
261
+ name = if @ar_object.respond_to?(:formatted_name)
262
+ @ar_object.formatted_name
263
+ elsif @ar_object.respond_to?(:name)
264
+ @ar_object.name
265
+ else
266
+ @ar_object&.id
267
+ end
268
+ format.json { render json: { message: notice, data: { id: @ar_object&.id, name: } }, status: :ok }
237
269
  else
238
- format.html { render '/cm_admin/main/new', notice: "#{action_name.titleize} #{@ar_object.class.name.downcase} is unsuccessful" }
270
+ format.html { render '/cm_admin/main/new', notice: "#{@action&.formatted_name} #{@model&.formatted_name} was unsuccessful" }
271
+ format.json do
272
+ formatted_error_response = @ar_object.errors.full_messages.map { |error_message| "<li>#{error_message}</li>" }.join
273
+ render json: { message: formatted_error_response }, status: :unprocessable_entity
274
+ end
239
275
  end
240
276
  end
241
277
  end
@@ -1,4 +1,3 @@
1
1
  @import "bootstrap/scss/bootstrap";
2
2
  @import "flatpickr/dist/flatpickr";
3
3
  @import "../../../../node_modules/daterangepicker/daterangepicker.css";
4
- @import "jgrowl/jquery.jgrowl";
@@ -13,12 +13,13 @@
13
13
  - if @associated_model.sort_columns.present?
14
14
  = render 'cm_admin/main/sort', model: @associated_model, ar_object: @associated_ar_object
15
15
 
16
- - if flash[:alert].present?
17
- .alert.alert-danger role="alert"
18
- = flash[:alert].html_safe
19
- - elsif flash[:notice].present?
20
- .alert.alert-success
21
- = flash[:notice].html_safe
16
+ - if flash[:bulk_action_error].present?
17
+ .alert.alert-danger.me-4 role="alert"
18
+ = flash[:bulk_action_error].html_safe
19
+ - if flash[:bulk_action_success].present?
20
+ .alert.alert-success.me-4 role="alert"
21
+ = flash[:bulk_action_success].html_safe
22
+
22
23
  - bulk_actions = actions_filter(@associated_model, @associated_ar_object, :bulk_action)
23
24
  - if bulk_actions.present?
24
25
  .table-top.hidden data-section="bulk-action"
@@ -8,17 +8,6 @@
8
8
  i.fa.fa-table
9
9
  a.btn.btn-ghost href="#{cm_admin.send("#{@model.name.underscore}_index_path")}?page=#{params[:page] || 1}&view_type=card"
10
10
  i.fa.fa-table-cells
11
- / button.secondary-btn.column-btn data-bs-target="#columnActionModal" data-bs-toggle="modal"
12
- / span
13
- / i.fa.fa-columns.bolder
14
- / span
15
- / i.fa.fa-angle-down
16
- - if flash[:alert].present?
17
- .alert.alert-danger role="alert"
18
- = flash[:alert].html_safe
19
- - elsif flash[:notice].present?
20
- .alert.alert-success
21
- = flash[:notice].html_safe
22
11
 
23
12
  - bulk_actions = actions_filter(@model, @ar_object, :bulk_action)
24
13
  - if bulk_actions.present?
@@ -1,10 +1,3 @@
1
- - if flash[:alert].present?
2
- .alert.alert-danger role="alert"
3
- = flash[:alert].html_safe
4
- - elsif flash[:notice].present?
5
- .alert.alert-success
6
- = flash[:notice].html_safe
7
-
8
1
  .kanban-container
9
2
  - column_names = @ar_object.data.keys
10
3
  = hidden_field_tag :kanban_column_names, column_names
@@ -7,16 +7,15 @@
7
7
  i.fa.fa-table
8
8
  a.btn.btn-ghost href="#{cm_admin.send("#{@model.name.underscore}_index_path")}?page=#{params[:page] || 1}&view_type=card"
9
9
  i.fa.fa-table-cells
10
-
11
10
  - if @model.sort_columns.present?
12
- = render 'cm_admin/main/sort', model: @model, ar_object: @ar_object
11
+ = render 'cm_admin/main/sort', model: @model, ar_object: @ar_object
13
12
 
14
- - if flash[:alert].present?
13
+ - if flash[:bulk_action_success].present?
14
+ .alert.alert-success.me-4 role="success"
15
+ = flash[:bulk_action_success].html_safe
16
+ - if flash[:bulk_action_error].present?
15
17
  .alert.alert-danger.me-4 role="alert"
16
- = flash[:alert].html_safe
17
- - elsif flash[:notice].present?
18
- .alert.alert-success.me-4
19
- = flash[:notice].html_safe
18
+ = flash[:bulk_action_error].html_safe
20
19
 
21
20
  - bulk_actions = actions_filter(@model, @ar_object, :bulk_action)
22
21
  - if bulk_actions.present?
@@ -1,3 +1,4 @@
1
+ .drawer-container data-behaviour="cm-drawer-container"
1
2
  .form-page
2
3
  .form-page__header
3
4
  == render 'cm_admin/main/top_navbar'
@@ -1,3 +1,4 @@
1
+ .drawer-container data-behaviour="cm-drawer-container"
1
2
  .form-page
2
3
  .form-page__header
3
4
  == render 'cm_admin/main/top_navbar'
@@ -5,9 +6,9 @@
5
6
  - if @ar_object.errors.present?
6
7
  .flag-alert.mb-4
7
8
  p.alert-header
8
- span
9
- i.fa.fa-exclamation-triangle
10
- | Attention
9
+ span
10
+ i.fa.fa-exclamation-triangle
11
+ | Attention
11
12
  .alert-body
12
13
  p.body-title
13
14
  | Error: unable to save #{@ar_object.class.name.downcase}
@@ -1,9 +1,6 @@
1
1
  - if flash[:notice].present?
2
- javascript:
3
- $.jGrowl("#{flash[:notice]}", { theme: 'notice' })
2
+ = toast_message(flash[:notice], 'notice')
4
3
  - elsif flash[:success].present?
5
- javascript:
6
- $.jGrowl("#{flash[:success]}", { theme: 'success' })
4
+ = toast_message(flash[:success], 'success')
7
5
  - elsif flash[:alert].present?
8
- javascript:
9
- $.jGrowl("#{flash[:alert].html_safe}", { theme: 'error' })
6
+ = toast_message(flash[:alert], 'alert')
@@ -0,0 +1,23 @@
1
+ .offcanvas.cm-drawer tabindex="-1" id="cm-drawer" aria-labelledby="offcanvasExampleLabel" data-behavior="cm-drawer" data-from-field-id="#{params[:from_field_id]}"
2
+ .drawer-btn-group
3
+ button.drawer-nav-btn type="button" data-bs-dismiss="offcanvas" aria-label="Close"
4
+ i.fa-solid.fa-x
5
+ .offcanvas-header
6
+ h4 = drawer_title
7
+ .offcanvas-body
8
+ .form-page
9
+ .form-page__body
10
+ .flag-alert.mb-4.hidden data-behavior="flag-alert"
11
+ p.alert-header
12
+ span
13
+ i.fa.fa-exclamation-triangle
14
+ | Attention
15
+ .alert-body
16
+ p.body-title
17
+ | Error: unable to save #{@ar_object.class.name.downcase}
18
+ p.body-info
19
+ ul data-behavior="error-list"
20
+ - @ar_object.errors.full_messages.each do |error_message|
21
+ li = error_message
22
+ - if @model.present?
23
+ = generate_form(@ar_object, @model, is_drawer_form: true)
@@ -66,7 +66,7 @@ html
66
66
  = yield
67
67
  - else
68
68
  = yield
69
+ div data-behaviour="flash-container"
69
70
  = render 'layouts/cm_flash_message'
70
71
  - unless (@current_action&.view_type == :kanban || params[:view_type] == 'kanban')
71
72
  = render 'layouts/custom_action_modals'
72
-
data/config/importmap.rb CHANGED
@@ -3,7 +3,6 @@ pin 'jquery', to: 'https://ga.jspm.io/npm:jquery@3.6.0/dist/jquery.js', preload:
3
3
  pin 'bootstrap', to: 'https://ga.jspm.io/npm:bootstrap@5.1.3/dist/js/bootstrap.esm.js', preload: true
4
4
  pin '@popperjs/core', to: 'https://ga.jspm.io/npm:@popperjs/core@2.11.5/lib/index.js', preload: true
5
5
  pin 'flatpickr', to: 'https://ga.jspm.io/npm:flatpickr@4.6.13/dist/esm/index.js'
6
- pin 'jgrowl', to: 'https://ga.jspm.io/npm:jgrowl@1.4.8/jquery.jgrowl.js'
7
6
  pin 'moment', to: 'https://ga.jspm.io/npm:moment@2.29.4/moment.js'
8
7
  pin 'trix', to: 'https://ga.jspm.io/npm:trix@2.1.3/dist/trix.esm.min.js'
9
8
  pin '@rails/actiontext', to: 'https://ga.jspm.io/npm:@rails/actiontext@7.1.3-4/app/assets/javascripts/actiontext.esm.js'
data/config/routes.rb CHANGED
@@ -23,6 +23,10 @@ CmAdmin::Engine.routes.draw do
23
23
  end
24
24
  end
25
25
 
26
+ scope model.name.tableize do
27
+ send(:get, 'fetch_drawer', to: "#{model.name.underscore}#fetch_drawer", as: "cm_fetch_drawer_#{model.name.underscore}")
28
+ end
29
+
26
30
  model.available_actions.sort_by { |act| act.name }.each do |act|
27
31
  scope model.name.tableize do
28
32
  # Define route only when action trail related field is present
@@ -122,7 +122,7 @@ module CmAdmin
122
122
  end
123
123
 
124
124
  def formatted_name
125
- @display_name != @name ? @display_name : @ar_model.table_name
125
+ @display_name != @name ? @display_name : @name.titleize
126
126
  end
127
127
 
128
128
  def alert_box(options = {})
@@ -5,9 +5,9 @@ module CmAdmin
5
5
  class Action
6
6
  include Actions::Blocks
7
7
  attr_accessor :name, :display_name, :verb, :layout_type, :layout, :partial, :path, :page_title, :page_description,
8
- :child_records, :is_nested_field, :nested_table_name, :parent, :display_if, :route_type, :code_block,
9
- :display_type, :action_type, :redirection_url, :sort_direction, :sort_column, :icon_name, :scopes, :view_type,
10
- :kanban_attr, :model_name, :redirect_to
8
+ :child_records, :is_nested_field, :nested_table_name, :parent, :display_if, :route_type, :code_block,
9
+ :display_type, :action_type, :redirection_url, :sort_direction, :sort_column, :icon_name, :scopes, :view_type,
10
+ :kanban_attr, :model_name, :redirect_to
11
11
 
12
12
  VALID_SORT_DIRECTION = Set[:asc, :desc].freeze
13
13
 
@@ -23,14 +23,14 @@ module CmAdmin
23
23
  end
24
24
  set_default_values
25
25
  attributes.each do |key, value|
26
- self.send("#{key.to_s}=", value)
26
+ send("#{key}=", value)
27
27
  end
28
- self.send("code_block=", block) if block_given?
28
+ send('code_block=', block) if block_given?
29
29
  end
30
30
 
31
31
  def set_default_values
32
32
  self.is_nested_field = false
33
- self.display_if = lambda { |arg| return true }
33
+ self.display_if = ->(_arg) { true }
34
34
  self.display_type = :button
35
35
  self.action_type = :default
36
36
  self.sort_column = :created_at
@@ -44,7 +44,7 @@ module CmAdmin
44
44
  self.kanban_attr = {}
45
45
  end
46
46
 
47
- def set_values(page_title, page_description, partial, redirect_to = nil, view_type=:table)
47
+ def set_values(page_title, page_description, partial, redirect_to = nil, view_type = :table)
48
48
  self.page_title = page_title
49
49
  self.page_description = page_description
50
50
  self.partial = partial
@@ -64,6 +64,10 @@ module CmAdmin
64
64
  end
65
65
  end
66
66
 
67
+ def formatted_name
68
+ display_name || name.titleize
69
+ end
70
+
67
71
  class << self
68
72
  def find_by(model, search_hash)
69
73
  model.available_actions.find { |i| i.name == search_hash[:name] }
@@ -7,7 +7,7 @@ module CmAdmin
7
7
 
8
8
  attr_accessor :field_name, :label, :header, :input_type, :collection, :disabled, :helper_method,
9
9
  :placeholder, :display_if, :html_attrs, :target, :col_size, :ajax_url, :helper_text,
10
- :image_preview
10
+ :image_preview, :can_create_new_entity
11
11
 
12
12
  VALID_INPUT_TYPES = %i[
13
13
  integer decimal string single_select multi_select date date_time text switch custom_single_select checkbox_group
@@ -34,6 +34,7 @@ module CmAdmin
34
34
  self.target = {}
35
35
  self.col_size = nil
36
36
  self.image_preview = false
37
+ self.can_create_new_entity = false
37
38
  end
38
39
 
39
40
  def set_default_placeholder
@@ -1,3 +1,3 @@
1
1
  module CmAdmin
2
- VERSION = '2.3.4'
2
+ VERSION = '2.4.0'
3
3
  end
@@ -7,8 +7,9 @@ module CmAdmin
7
7
  module FormHelper
8
8
  include FormFieldHelper
9
9
  REJECTABLE = %w[id created_at updated_at]
10
+ attr_accessor :is_drawer_form
10
11
 
11
- def generate_form(resource, cm_model)
12
+ def generate_form(resource, cm_model, is_drawer_form: false)
12
13
  if resource.new_record?
13
14
  action = :new
14
15
  method = :post
@@ -16,6 +17,7 @@ module CmAdmin
16
17
  action = :edit
17
18
  method = :patch
18
19
  end
20
+ @is_drawer_form = is_drawer_form
19
21
  return form_with_all_fields(resource, method) if cm_model.available_fields[action].empty?
20
22
 
21
23
  form_with_mentioned_fields(resource, cm_model.available_fields[action], method)
@@ -66,6 +68,7 @@ module CmAdmin
66
68
  def create_sections(resource, form_obj, section)
67
69
  content_tag :div, class: 'col form-container' do
68
70
  return render partial: section.partial, locals: { form_obj: } if section.partial
71
+
69
72
  concat content_tag(:p, section.section_name, class: 'form-title') unless section.parent_section.present?
70
73
  concat set_form_for_fields(resource, form_obj, section)
71
74
  end
@@ -116,10 +119,7 @@ module CmAdmin
116
119
  concat input_field_for_column(form_obj, field, is_required:)
117
120
  else
118
121
  concat(content_tag(:div, class: "form-field #{field.disabled ? 'disabled' : ''}") do
119
- if field.label && %i[check_box switch].exclude?(field.input_type)
120
- concat form_obj.label field.label, field.label, class: "field-label #{is_required ? 'required-label' : ''}"
121
- concat tag.br
122
- end
122
+ concat set_form_label(field, form_obj, is_required)
123
123
  concat input_field_for_column(form_obj, field, is_required:)
124
124
  concat tag.small field.helper_text, class: 'form-text text-muted' if field.helper_text.present?
125
125
  concat tag.p resource.errors[field.field_name].first if resource.errors[field.field_name].present?
@@ -128,6 +128,25 @@ module CmAdmin
128
128
  end
129
129
  end
130
130
 
131
+ def set_form_label(field, form_obj, is_required)
132
+ return unless field.label && %i[check_box switch].exclude?(field.input_type)
133
+
134
+ content_tag(:div, class: 'd-flex') do
135
+ concat form_obj.label field.label, field.label, class: "field-label #{is_required ? 'required-label' : ''}"
136
+ concat set_drawer_form_button(field, form_obj)
137
+ end
138
+ end
139
+
140
+ def set_drawer_form_button(field, form_obj)
141
+ model_name = find_model_name(form_obj, field)
142
+ ar_object = model_name.constantize.new if model_name.present?
143
+ return unless field.input_type.to_s == 'single_select' && model_name.present? && has_valid_policy(ar_object, 'new')
144
+
145
+ field_id = form_obj.field_id(field.field_name.to_sym)
146
+ drawer_fetch_url = "#{CmAdmin::Engine.mount_path}/#{model_name.tableize}/fetch_drawer?from_field_id=#{field_id}"
147
+ tag.div 'New Entity', class: 'drawer-btn', data: { behaviour: 'offcanvas', drawer_fetch_url: }
148
+ end
149
+
131
150
  def set_nested_section_form_fields(resource, form_obj, nested_sections)
132
151
  return if nested_sections.blank?
133
152
 
@@ -142,7 +161,7 @@ module CmAdmin
142
161
  def set_nested_form_fields(form_obj, section)
143
162
  content_tag(:div) do
144
163
  section.nested_table_fields.each do |nested_table_field|
145
- concat(render(partial: '/cm_admin/main/nested_table_form', locals: { f: form_obj, nested_table_field: nested_table_field }))
164
+ concat(render(partial: '/cm_admin/main/nested_table_form', locals: { f: form_obj, nested_table_field: }))
146
165
  end
147
166
  end
148
167
  end
@@ -150,7 +169,7 @@ module CmAdmin
150
169
  def set_form_with_sections(resource, entities, url, method)
151
170
  url_with_query_params = extract_query_params(url)
152
171
 
153
- form_for(resource, url: url_with_query_params || url, method: method, html: { class: "cm_#{resource.class.name.downcase}_form" }) do |form_obj|
172
+ form_for(resource, url: url_with_query_params || url, method:, html: { class: "cm_#{resource.class.name.downcase}_form" }, data: { is_drawer_form: @is_drawer_form }) do |form_obj|
154
173
  concat form_obj.text_field 'referrer', class: 'normal-input', hidden: true, value: params[:referrer], name: 'referrer' if params[:referrer]
155
174
  if params[:polymorphic_name].present?
156
175
  concat form_obj.text_field params[:polymorphic_name] + '_type', class: 'normal-input', hidden: true, value: params[:associated_class].classify
@@ -162,7 +181,7 @@ module CmAdmin
162
181
  concat split_form_into_section(resource, form_obj, entities)
163
182
  concat tag.br
164
183
  concat form_obj.submit 'Save', class: 'btn-cta', data: { behaviour: 'form_submit', form_class: "cm_#{form_obj.object.class.name.downcase}_form" }
165
- concat button_tag 'Discard', class: 'btn-secondary discard-form', data: { behaviour: 'discard_form' }
184
+ concat button_tag 'Discard', class: 'btn-secondary discard-form', data: { behaviour: 'discard_form' } unless @is_drawer_form
166
185
  end
167
186
  end
168
187
 
@@ -184,6 +203,16 @@ module CmAdmin
184
203
  end
185
204
  form_obj.object._validators[field.field_name].map(&:kind).include?(:presence) || associated_field.present?
186
205
  end
206
+
207
+ def find_model_name(form_obj, field)
208
+ return unless field.can_create_new_entity.presence
209
+ return field.can_create_new_entity[:model] if field.can_create_new_entity.is_a?(Hash) && field.can_create_new_entity[:model].present?
210
+
211
+ associated_field = form_obj.object.class.reflect_on_all_associations(:belongs_to).select do |association|
212
+ "#{association.name}_id" == field.field_name.to_s
213
+ end
214
+ associated_field&.first&.klass&.name
215
+ end
187
216
  end
188
217
  end
189
218
  end
@@ -38,21 +38,26 @@ module CmAdmin
38
38
  end
39
39
 
40
40
  def custom_action_items(custom_action, current_action_name)
41
- if custom_action.name.present? && policy([:cm_admin, @model.name.classify.constantize]).send(:"#{custom_action.name}?")
42
- scoped_model = "CmAdmin::#{@model.name}Policy::#{custom_action.name.classify}Scope".constantize.new(Current.user, @model.name.constantize).resolve
43
- if custom_action.display_if.call(@ar_object) && scoped_model.find_by(id: params[:id])
44
- case custom_action.display_type
45
- when :icon_only
46
- custom_action_icon(custom_action, current_action_name)
47
- when :button
48
- custom_action_button(custom_action, current_action_name)
49
- when :modal
50
- custom_modal_button(custom_action)
51
- when :page
52
- path = cm_admin.send("#{@model.name.underscore}_#{custom_action.name}_path", @ar_object.id, custom_action.url_params)
53
- link_to custom_action_title(custom_action), path, class: 'btn-secondary ms-2', method: custom_action.verb
54
- end
55
- end
41
+ return unless custom_action.name.present? && policy([:cm_admin, @model.name.classify.constantize]).send(:"#{custom_action.name}?")
42
+
43
+ scoped_model = "CmAdmin::#{@model.name}Policy::#{custom_action.name.classify}Scope".constantize.new(Current.user, @model.name.constantize).resolve
44
+ has_scoped_record = if current_action_name == 'index'
45
+ scoped_model.present?
46
+ else
47
+ scoped_model.find_by(id: params[:id]).present?
48
+ end
49
+ return unless custom_action.display_if.call(@ar_object) && has_scoped_record
50
+
51
+ case custom_action.display_type
52
+ when :icon_only
53
+ custom_action_icon(custom_action, current_action_name)
54
+ when :button
55
+ custom_action_button(custom_action, current_action_name)
56
+ when :modal
57
+ custom_modal_button(custom_action)
58
+ when :page
59
+ path = cm_admin.send("#{@model.name.underscore}_#{custom_action.name}_path", @ar_object.id, custom_action.url_params)
60
+ link_to custom_action_title(custom_action), path, class: 'btn-secondary ms-2', method: custom_action.verb
56
61
  end
57
62
  end
58
63
 
@@ -66,7 +71,7 @@ module CmAdmin
66
71
 
67
72
  def custom_action_button(custom_action, current_action_name)
68
73
  if current_action_name == "index"
69
- button_to custom_action_title(custom_action), @model.ar_model.table_name + '/' + custom_action.path, class: 'btn-secondary ms-2', method: custom_action.verb
74
+ button_to custom_action_title(custom_action), @model.ar_model.table_name + '/' + (custom_action.path || custom_action.name), class: 'btn-secondary ms-2', method: custom_action.verb
70
75
  elsif current_action_name == "show"
71
76
  button_to custom_action_title(custom_action), custom_action.path.gsub(':id', params[:id]), class: 'btn-secondary ms-2', method: custom_action.verb
72
77
  end
@@ -83,6 +83,17 @@ module CmAdmin
83
83
  "#{count} #{table_name.humanize.downcase} found"
84
84
  end
85
85
 
86
+ def toast_message(message, toast_type)
87
+ tag.div class: 'position-fixed bottom-0 end-0 p-3', style: 'z-index: 11' do
88
+ tag.div class: "toast #{toast_type == 'alert' ? 'text-white bg-danger' : ''}", role: 'alert', 'aria-live': 'assertive', 'aria-atomic': 'true', data: { behaviour: 'toast' } do
89
+ tag.div class: 'd-flex' do
90
+ concat tag.div message.html_safe, class: 'toast-body'
91
+ concat tag.button '', type: 'button', class: "btn-close me-2 m-auto #{toast_type == 'alert' ? 'btn-close-white' : ''}", data: { 'bs-dismiss': 'toast' }, 'aria-label': 'Close'
92
+ end
93
+ end
94
+ end
95
+ end
96
+
86
97
  def project_name_with_env
87
98
  return CmAdmin.config.project_name if Rails.env.production?
88
99
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cm-admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.4
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael
@@ -14,7 +14,7 @@ authors:
14
14
  autorequire:
15
15
  bindir: exe
16
16
  cert_chain: []
17
- date: 2024-10-09 00:00:00.000000000 Z
17
+ date: 2024-10-14 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: caxlsx_rails
@@ -344,7 +344,6 @@ files:
344
344
  - app/assets/stylesheets/cm_admin/dependency/bootstrap/scss/utilities/_api.scss
345
345
  - app/assets/stylesheets/cm_admin/dependency/bootstrap/scss/vendor/_rfs.scss
346
346
  - app/assets/stylesheets/cm_admin/dependency/flatpickr.min.css
347
- - app/assets/stylesheets/cm_admin/dependency/jquery-jgrowl.min.css
348
347
  - app/assets/stylesheets/cm_admin/helpers/_mixins.scss
349
348
  - app/assets/stylesheets/cm_admin/helpers/_variable.scss
350
349
  - app/assets/stylesheets/cm_admin/helpers/index.scss
@@ -375,7 +374,6 @@ files:
375
374
  - app/views/cm_admin/main/_card.html.slim
376
375
  - app/views/cm_admin/main/_cm_pagy_nav.html.slim
377
376
  - app/views/cm_admin/main/_custom_action_modal_form.html.slim
378
- - app/views/cm_admin/main/_drawer.html.slim
379
377
  - app/views/cm_admin/main/_filters.html.slim
380
378
  - app/views/cm_admin/main/_kanban.html.slim
381
379
  - app/views/cm_admin/main/_member_custom_action_modal.html.slim
@@ -406,6 +404,7 @@ files:
406
404
  - app/views/layouts/_custom_action_modal.html.slim
407
405
  - app/views/layouts/_custom_action_modals.html.slim
408
406
  - app/views/layouts/_destroy_action_modal.html.slim
407
+ - app/views/layouts/_drawer.html.slim
409
408
  - app/views/layouts/_left_sidebar_nav.html.slim
410
409
  - app/views/layouts/_quick_links.html.slim
411
410
  - app/views/layouts/cm_admin.html.slim
@@ -1 +0,0 @@
1
- .jGrowl{z-index:9999;color:#fff;font-size:12px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;position:fixed}.jGrowl.top-left{left:0;top:0}.jGrowl.top-right{right:0;top:0}.jGrowl.bottom-left{left:0;bottom:0}.jGrowl.bottom-right{right:0;bottom:0}.jGrowl.center{top:0;width:50%;left:25%}.jGrowl.center .jGrowl-closer,.jGrowl.center .jGrowl-notification{margin-left:auto;margin-right:auto}.jGrowl-notification{background-color:#000;opacity:.9;zoom:1;width:250px;padding:10px;margin:10px;text-align:left;display:none;border-radius:5px;min-height:40px}.jGrowl-notification .ui-state-highlight,.jGrowl-notification .ui-widget-content .ui-state-highlight,.jGrowl-notification .ui-widget-header .ui-state-highlight{border:1px solid #000;background:#000;color:#fff}.jGrowl-notification .jGrowl-header{font-weight:700;font-size:.85em}.jGrowl-notification .jGrowl-close{background-color:transparent;color:inherit;border:none;z-index:99;float:right;font-weight:700;font-size:1em;cursor:pointer}.jGrowl-closer{background-color:#000;opacity:.9;zoom:1;width:250px;padding:10px;margin:10px;text-align:left;display:none;border-radius:5px;padding-top:4px;padding-bottom:4px;cursor:pointer;font-size:.9em;font-weight:700;text-align:center}.jGrowl-closer .ui-state-highlight,.jGrowl-closer .ui-widget-content .ui-state-highlight,.jGrowl-closer .ui-widget-header .ui-state-highlight{border:1px solid #000;background:#000;color:#fff}@media print{.jGrowl{display:none}}
@@ -1,13 +0,0 @@
1
- .cm-drawer.hidden
2
- .cm-drawer__container#drawer-container
3
- .header
4
- .header__lhs
5
- h2.title Review
6
- p.description Theresa Webb
7
- .header__rhs
8
- i.fa.fa-times.drawer-close.pointer
9
- .body
10
- p.info-text
11
- | It was really an amazing experience being an Unschooler. Its really different from other e-learning websites in every aspects like certification, providing internship opportunities and many more. I am going to recommend Unschool for sure!
12
-
13
-