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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/app/assets/javascripts/cm_admin/scaffolds.js +43 -28
- data/app/assets/javascripts/cm_admin/shared_scaffolds.js +109 -38
- data/app/assets/stylesheets/cm_admin/base/scaffold.scss +6 -0
- data/app/assets/stylesheets/cm_admin/cm_admin.css.scss +0 -1
- data/app/assets/stylesheets/cm_admin/components/_drawer.scss +17 -100
- data/app/assets/stylesheets/cm_admin/scaffold.scss +0 -14
- data/app/controllers/cm_admin/resource_controller.rb +45 -9
- data/app/javascript/stylesheets/cm_admin/application.scss +0 -1
- data/app/views/cm_admin/main/_associated_table.html.slim +7 -6
- data/app/views/cm_admin/main/_card.html.slim +0 -11
- data/app/views/cm_admin/main/_kanban.html.slim +0 -7
- data/app/views/cm_admin/main/_table.html.slim +6 -7
- data/app/views/cm_admin/main/edit.html.slim +1 -0
- data/app/views/cm_admin/main/new.html.slim +4 -3
- data/app/views/layouts/_cm_flash_message.html.slim +3 -6
- data/app/views/layouts/_drawer.html.slim +23 -0
- data/app/views/layouts/cm_admin.html.slim +1 -1
- data/config/importmap.rb +0 -1
- data/config/routes.rb +4 -0
- data/lib/cm_admin/model.rb +1 -1
- data/lib/cm_admin/models/action.rb +11 -7
- data/lib/cm_admin/models/form_field.rb +2 -1
- data/lib/cm_admin/version.rb +1 -1
- data/lib/cm_admin/view_helpers/form_helper.rb +37 -8
- data/lib/cm_admin/view_helpers/page_info_helper.rb +21 -16
- data/lib/cm_admin/view_helpers.rb +11 -0
- metadata +3 -4
- data/app/assets/stylesheets/cm_admin/dependency/jquery-jgrowl.min.css +0 -1
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 78211818c7078c8cf9723ca166d19c9eaea3155544754a60fca317a8eaf86276
|
4
|
+
data.tar.gz: 7a53125a99db46cc8a7686076baf674eb971f20430bf680c5c0e105b06c89dc1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2da45fd35ae854471c2c0255359fc96df661ebcb482c452a65734daf3364465bfe7cbb5250488a13be7381a7c129af90288b31d5c364c65bd878523ed8c75b8
|
7
|
+
data.tar.gz: a7ed71594d873f6c525577f2f19a48efe68bb873f331ab9b49bec2b18b980ea03bdd86fcbd8d0d83d2eb78bb3a3805e97626806c83ac43b138b88dc376638335
|
data/Gemfile.lock
CHANGED
@@ -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
|
-
|
44
|
-
if(
|
45
|
-
|
46
|
-
|
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)
|
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(
|
88
|
-
var form_field = $(this).closest(
|
89
|
-
if($(this).is(
|
90
|
-
form_check.find(
|
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(
|
96
|
+
form_check.find(".cm-select-tag").addClass("hidden");
|
93
97
|
}
|
94
|
-
if (
|
95
|
-
form_field.find('[data-behaviour="permission-check-
|
96
|
-
|
97
|
-
|
98
|
-
|
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(
|
108
|
-
if($(this).is(
|
109
|
-
form_field
|
110
|
-
|
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
|
113
|
-
|
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",
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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",
|
58
|
-
|
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
|
-
|
221
|
+
initializeComponents();
|
151
222
|
};
|
152
223
|
|
153
224
|
export function initializeComponents() {
|
154
|
-
$(
|
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:
|
165
|
-
})
|
166
|
-
var el = document.getElementsByClassName(
|
167
|
-
if(el[0]) {
|
168
|
-
Sortable.create(el[0],{
|
169
|
-
handle:
|
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 = $(
|
175
|
-
var calculatedHeight = "calc(100vh - " + headerElemHeight+"px"+")"
|
176
|
-
$(
|
177
|
-
}
|
245
|
+
var headerElemHeight = $(".page-top-bar").height() + 64;
|
246
|
+
var calculatedHeight = "calc(100vh - " + headerElemHeight + "px" + ")";
|
247
|
+
$(".table-wrapper").css("maxHeight", calculatedHeight);
|
248
|
+
}
|
@@ -1,110 +1,27 @@
|
|
1
|
-
@import "../helpers/index.scss";
|
2
|
-
|
3
1
|
.cm-drawer {
|
4
|
-
|
5
|
-
|
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
|
-
.
|
59
|
-
|
6
|
+
.offcanvas-body {
|
7
|
+
padding: 0;
|
60
8
|
}
|
61
9
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
91
|
-
.
|
92
|
-
|
93
|
-
|
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: "#{
|
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,
|
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
|
-
|
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
|
-
|
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.
|
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: "
|
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
|
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
|
-
|
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: "#{
|
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
|
@@ -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[:
|
17
|
-
.alert.alert-danger role="alert"
|
18
|
-
= flash[:
|
19
|
-
-
|
20
|
-
.alert.alert-success
|
21
|
-
= flash[:
|
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
|
-
|
11
|
+
= render 'cm_admin/main/sort', model: @model, ar_object: @ar_object
|
13
12
|
|
14
|
-
- if flash[:
|
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[:
|
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'
|
@@ -5,9 +6,9 @@
|
|
5
6
|
- if @ar_object.errors.present?
|
6
7
|
.flag-alert.mb-4
|
7
8
|
p.alert-header
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
3
|
-
$.jGrowl("#{flash[:notice]}", { theme: 'notice' })
|
2
|
+
= toast_message(flash[:notice], 'notice')
|
4
3
|
- elsif flash[:success].present?
|
5
|
-
|
6
|
-
$.jGrowl("#{flash[:success]}", { theme: 'success' })
|
4
|
+
= toast_message(flash[:success], 'success')
|
7
5
|
- elsif flash[:alert].present?
|
8
|
-
|
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)
|
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
|
data/lib/cm_admin/model.rb
CHANGED
@@ -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
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
26
|
+
send("#{key}=", value)
|
27
27
|
end
|
28
|
-
|
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 =
|
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
|
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
|
data/lib/cm_admin/version.rb
CHANGED
@@ -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
|
-
|
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:
|
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
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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.
|
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-
|
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
|
-
|