@visns-studio/visns-components 5.6.14 → 5.7.1
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.
- package/package.json +1 -5
- package/src/components/cms/DataGrid.jsx +70 -7
- package/src/components/cms/DropZone.jsx +3 -4
- package/src/components/cms/Form.jsx +3 -4
- package/src/components/cms/generic/CmsDetail.jsx +0 -2
- package/src/components/crm/Autocomplete.jsx +161 -127
- package/src/components/crm/DataGrid.jsx +218 -75
- package/src/components/crm/Download.jsx +15 -4
- package/src/components/crm/Fetch.jsx +69 -16
- package/src/components/crm/Form.jsx +3 -3
- package/src/components/crm/Notification.jsx +237 -106
- package/src/components/crm/QuickAction.jsx +318 -91
- package/src/components/crm/generic/GenericDashboard.jsx +3 -3
- package/src/components/crm/generic/GenericDetail.jsx +46 -130
- package/src/components/crm/generic/GenericDynamic.jsx +3 -3
- package/src/components/crm/generic/GenericEditableTable.jsx +3 -4
- package/src/components/crm/generic/GenericFormBuilder.jsx +3 -3
- package/src/components/crm/generic/NotificationList.jsx +76 -7
- package/src/components/crm/generic/styles/NotificationList.module.scss +90 -2
- package/src/components/crm/generic/styles/SweetAlertCustom.css +27 -3
- package/src/components/crm/styles/DataGrid.module.scss +5 -0
- package/src/components/crm/styles/Notification.module.scss +313 -96
- package/src/components/crm/styles/QuickAction.module.scss +168 -46
- package/src/components/styles/global.css +5 -0
- package/src/components/utils/ConfirmDialog.js +155 -0
- package/src/utils/fetchUtil.js +240 -0
|
@@ -1,36 +1,99 @@
|
|
|
1
1
|
.quickActions {
|
|
2
2
|
width: 100%;
|
|
3
|
-
background: rgba(var(--bg-rgb), 0.
|
|
4
|
-
padding: var(--padding);
|
|
5
|
-
border-top: solid
|
|
6
|
-
border-bottom: solid 1px rgba(var(--primary-rgb), 0.
|
|
3
|
+
background: rgba(var(--bg-rgb), 0.98);
|
|
4
|
+
padding: 0.75rem var(--padding);
|
|
5
|
+
border-top: solid 3px rgba(var(--primary-rgb), 0.1);
|
|
6
|
+
border-bottom: solid 1px rgba(var(--primary-rgb), 0.15);
|
|
7
7
|
color: var(--paragraph-color);
|
|
8
8
|
display: flex;
|
|
9
|
-
flex-wrap:
|
|
9
|
+
flex-wrap: wrap;
|
|
10
10
|
align-items: center;
|
|
11
|
-
box-shadow: 0
|
|
11
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
12
|
+
overflow: visible; /* Ensure dropdowns are visible */
|
|
13
|
+
position: relative; /* Create a stacking context */
|
|
14
|
+
z-index: 100; /* Ensure proper stacking */
|
|
15
|
+
gap: 0.5rem; /* Add consistent spacing between elements */
|
|
16
|
+
|
|
17
|
+
.actionIcon {
|
|
18
|
+
width: 32px;
|
|
19
|
+
height: 32px;
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
justify-content: center;
|
|
23
|
+
background-color: rgba(var(--primary-rgb), 0.08);
|
|
24
|
+
border-radius: 50%;
|
|
25
|
+
margin-right: 0.75rem;
|
|
26
|
+
position: relative;
|
|
27
|
+
color: rgba(var(--primary-rgb), 0.8);
|
|
28
|
+
transition: all 0.2s ease;
|
|
12
29
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
30
|
+
&:hover {
|
|
31
|
+
background-color: rgba(var(--primary-rgb), 0.12);
|
|
32
|
+
transform: scale(1.05);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
&::after {
|
|
36
|
+
content: '';
|
|
37
|
+
position: absolute;
|
|
38
|
+
right: -0.5rem;
|
|
39
|
+
top: 50%;
|
|
40
|
+
transform: translateY(-50%);
|
|
41
|
+
height: 60%;
|
|
42
|
+
width: 1px;
|
|
43
|
+
background-color: rgba(var(--primary-rgb), 0.15);
|
|
44
|
+
}
|
|
18
45
|
}
|
|
19
46
|
|
|
20
47
|
> button {
|
|
21
48
|
appearance: none;
|
|
22
|
-
border: 1px solid rgba(var(--primary-rgb), 0.
|
|
23
|
-
border-radius: var(--br);
|
|
24
|
-
background:
|
|
49
|
+
border: 1px solid rgba(var(--primary-rgb), 0.2);
|
|
50
|
+
border-radius: var(--br, 4px);
|
|
51
|
+
background: rgba(var(--bg-rgb), 1);
|
|
25
52
|
width: max-content;
|
|
26
53
|
color: var(--paragraph-color);
|
|
27
|
-
padding: 0.
|
|
28
|
-
margin: 0 0.
|
|
29
|
-
|
|
54
|
+
padding: 0.4rem 0.8rem;
|
|
55
|
+
margin: 0 0.2rem;
|
|
56
|
+
font-size: 0.85rem;
|
|
57
|
+
font-weight: 500;
|
|
58
|
+
transition: all 0.2s ease;
|
|
59
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
60
|
+
display: flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
justify-content: center;
|
|
63
|
+
min-height: 32px;
|
|
64
|
+
position: relative;
|
|
65
|
+
overflow: hidden;
|
|
66
|
+
|
|
67
|
+
&::before {
|
|
68
|
+
content: '';
|
|
69
|
+
position: absolute;
|
|
70
|
+
top: 0;
|
|
71
|
+
left: 0;
|
|
72
|
+
width: 100%;
|
|
73
|
+
height: 100%;
|
|
74
|
+
background: linear-gradient(
|
|
75
|
+
to bottom,
|
|
76
|
+
rgba(var(--primary-rgb), 0.05),
|
|
77
|
+
transparent
|
|
78
|
+
);
|
|
79
|
+
opacity: 0;
|
|
80
|
+
transition: opacity 0.2s ease;
|
|
81
|
+
}
|
|
30
82
|
|
|
31
83
|
&:hover {
|
|
32
|
-
background: var(--
|
|
33
|
-
transform: translateY(-
|
|
84
|
+
background: rgba(var(--bg-rgb), 1);
|
|
85
|
+
transform: translateY(-1px);
|
|
86
|
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.08);
|
|
87
|
+
border-color: rgba(var(--primary-rgb), 0.3);
|
|
88
|
+
|
|
89
|
+
&::before {
|
|
90
|
+
opacity: 1;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
&:active {
|
|
95
|
+
transform: translateY(0);
|
|
96
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
34
97
|
}
|
|
35
98
|
}
|
|
36
99
|
|
|
@@ -38,24 +101,34 @@
|
|
|
38
101
|
display: flex;
|
|
39
102
|
align-items: center;
|
|
40
103
|
background: transparent;
|
|
41
|
-
margin:
|
|
104
|
+
margin-left: auto; /* Push search to the right */
|
|
42
105
|
flex-grow: 1;
|
|
43
106
|
position: relative;
|
|
107
|
+
max-width: 300px; /* Limit width for better appearance */
|
|
108
|
+
overflow: visible; /* Allow dropdown to be visible */
|
|
44
109
|
|
|
45
110
|
input[type='text'] {
|
|
46
111
|
flex-grow: 1;
|
|
47
|
-
border:
|
|
112
|
+
border: 1px solid rgba(var(--primary-rgb), 0.2);
|
|
48
113
|
outline: none;
|
|
49
|
-
padding:
|
|
50
|
-
padding-right:
|
|
51
|
-
border-radius: var(--br);
|
|
114
|
+
padding: 0.5rem 0.75rem;
|
|
115
|
+
padding-right: 36px;
|
|
116
|
+
border-radius: var(--br, 4px);
|
|
52
117
|
height: 32px;
|
|
53
|
-
font-size:
|
|
118
|
+
font-size: 0.85rem;
|
|
54
119
|
width: 100%;
|
|
55
|
-
box-shadow:
|
|
120
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
121
|
+
transition: all 0.2s ease;
|
|
122
|
+
background-color: rgba(var(--bg-rgb), 1);
|
|
123
|
+
|
|
124
|
+
&::placeholder {
|
|
125
|
+
color: rgba(var(--paragraph-rgb), 0.5);
|
|
126
|
+
font-size: 0.85rem;
|
|
127
|
+
}
|
|
56
128
|
|
|
57
129
|
&:focus {
|
|
58
|
-
box-shadow: 0 0
|
|
130
|
+
box-shadow: 0 0 0 2px rgba(var(--primary-rgb), 0.15);
|
|
131
|
+
border-color: rgba(var(--primary-rgb), 0.4);
|
|
59
132
|
}
|
|
60
133
|
}
|
|
61
134
|
|
|
@@ -65,35 +138,73 @@
|
|
|
65
138
|
display: flex;
|
|
66
139
|
align-items: center;
|
|
67
140
|
justify-content: center;
|
|
141
|
+
cursor: pointer;
|
|
142
|
+
width: 24px;
|
|
143
|
+
height: 24px;
|
|
144
|
+
border-radius: 50%;
|
|
145
|
+
transition: background-color 0.2s ease;
|
|
146
|
+
|
|
147
|
+
&:hover {
|
|
148
|
+
background-color: rgba(var(--primary-rgb), 0.05);
|
|
149
|
+
}
|
|
68
150
|
}
|
|
69
151
|
|
|
70
152
|
.searchIcon {
|
|
71
|
-
width:
|
|
72
|
-
height:
|
|
153
|
+
width: 18px;
|
|
154
|
+
height: 18px;
|
|
155
|
+
color: rgba(var(--paragraph-rgb), 0.6);
|
|
156
|
+
transition: color 0.2s ease;
|
|
157
|
+
|
|
158
|
+
&:hover {
|
|
159
|
+
color: rgba(var(--primary-rgb), 0.8);
|
|
160
|
+
}
|
|
73
161
|
}
|
|
74
162
|
}
|
|
75
163
|
|
|
76
|
-
|
|
164
|
+
/* Search results styling */
|
|
165
|
+
:global(.AutocompletePlace-results) {
|
|
77
166
|
position: absolute;
|
|
78
167
|
top: 100%;
|
|
79
168
|
width: 100%;
|
|
80
|
-
background: var(--
|
|
81
|
-
box-shadow: 0
|
|
82
|
-
z-index:
|
|
169
|
+
background: var(--bg-color, white);
|
|
170
|
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
|
171
|
+
z-index: 9999; /* Ensure it appears on top */
|
|
83
172
|
max-height: 400px;
|
|
84
173
|
overflow-y: auto;
|
|
85
|
-
|
|
86
|
-
|
|
174
|
+
overflow-x: hidden; /* Prevent horizontal scrolling */
|
|
175
|
+
border-radius: calc(var(--br, 4px) - 1px);
|
|
176
|
+
margin-top: 6px;
|
|
177
|
+
border: 1px solid rgba(var(--primary-rgb), 0.1);
|
|
178
|
+
scrollbar-width: thin;
|
|
179
|
+
scrollbar-color: rgba(var(--primary-rgb), 0.2) transparent;
|
|
180
|
+
|
|
181
|
+
&::-webkit-scrollbar {
|
|
182
|
+
width: 6px;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
&::-webkit-scrollbar-track {
|
|
186
|
+
background: transparent;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
&::-webkit-scrollbar-thumb {
|
|
190
|
+
background-color: rgba(var(--primary-rgb), 0.2);
|
|
191
|
+
border-radius: 3px;
|
|
192
|
+
}
|
|
87
193
|
}
|
|
88
194
|
|
|
89
195
|
.resultItem {
|
|
90
|
-
padding: 0.5rem
|
|
196
|
+
padding: 0.5rem 0.75rem;
|
|
91
197
|
cursor: pointer;
|
|
92
|
-
transition:
|
|
198
|
+
transition: all 0.15s ease;
|
|
199
|
+
border-bottom: 1px solid rgba(var(--primary-rgb), 0.05);
|
|
200
|
+
white-space: nowrap;
|
|
201
|
+
overflow: hidden;
|
|
202
|
+
text-overflow: ellipsis;
|
|
203
|
+
width: 100%;
|
|
93
204
|
|
|
94
205
|
&:hover {
|
|
95
|
-
background-color:
|
|
96
|
-
|
|
206
|
+
background-color: rgba(var(--primary-rgb), 0.05);
|
|
207
|
+
padding-left: 1rem;
|
|
97
208
|
}
|
|
98
209
|
|
|
99
210
|
&:last-child {
|
|
@@ -102,25 +213,36 @@
|
|
|
102
213
|
|
|
103
214
|
h3 {
|
|
104
215
|
margin: 0;
|
|
105
|
-
font-weight:
|
|
106
|
-
color: var(--header-color);
|
|
107
|
-
font-size:
|
|
216
|
+
font-weight: 600;
|
|
217
|
+
color: var(--header-color, #333);
|
|
218
|
+
font-size: 0.8rem;
|
|
219
|
+
padding-bottom: 6px;
|
|
220
|
+
border-bottom: 1px solid rgba(var(--primary-rgb), 0.08);
|
|
221
|
+
text-transform: uppercase;
|
|
222
|
+
letter-spacing: 0.03em;
|
|
108
223
|
}
|
|
109
224
|
|
|
110
225
|
p {
|
|
111
226
|
margin: 0.25rem 0 0;
|
|
112
227
|
color: var(--paragraph-color);
|
|
113
|
-
font-size:
|
|
228
|
+
font-size: 0.85rem;
|
|
229
|
+
white-space: nowrap;
|
|
230
|
+
overflow: hidden;
|
|
231
|
+
text-overflow: ellipsis;
|
|
232
|
+
line-height: 1.4;
|
|
114
233
|
}
|
|
115
234
|
|
|
116
235
|
&.selected {
|
|
117
|
-
background-color: rgba(var(--primary-rgb), 0.
|
|
236
|
+
background-color: rgba(var(--primary-rgb), 0.08);
|
|
237
|
+
font-weight: 500;
|
|
238
|
+
padding-left: 1rem;
|
|
239
|
+
border-left: 2px solid rgba(var(--primary-rgb), 0.5);
|
|
118
240
|
}
|
|
119
241
|
}
|
|
120
242
|
|
|
121
243
|
.resultItemDivider {
|
|
122
244
|
height: 1px;
|
|
123
|
-
background-color: var(--
|
|
124
|
-
margin: 0.25rem 0;
|
|
245
|
+
background-color: rgba(var(--primary-rgb), 0.08);
|
|
246
|
+
margin: 0.25rem 0.75rem;
|
|
125
247
|
}
|
|
126
248
|
}
|
|
@@ -223,6 +223,11 @@ input[type]:not([type='search']):not([type='url']):not([type='hidden']):not(
|
|
|
223
223
|
backdrop-filter: blur(3px);
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
.modalWide {
|
|
227
|
+
width: 80vw !important;
|
|
228
|
+
max-width: 1200px !important;
|
|
229
|
+
}
|
|
230
|
+
|
|
226
231
|
.AutocompletePlace-results {
|
|
227
232
|
z-index: 999;
|
|
228
233
|
width: 500px;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import Swal from 'sweetalert2';
|
|
2
|
+
import '../crm/generic/styles/SweetAlertCustom.css';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A utility function that replaces react-confirm-alert with SweetAlert2
|
|
6
|
+
* while maintaining a similar look and feel.
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} options - Configuration options
|
|
9
|
+
* @param {string} options.title - The title of the confirmation dialog
|
|
10
|
+
* @param {string} options.message - The message to display in the dialog
|
|
11
|
+
* @param {Array} options.buttons - Array of button objects with label and onClick properties
|
|
12
|
+
* @param {Object} options.customUI - Custom UI function (ignored, for compatibility only)
|
|
13
|
+
* @param {boolean} options.closeOnClickOutside - Whether to close on outside click (default: true)
|
|
14
|
+
* @param {boolean} options.closeOnEscape - Whether to close on escape key (default: true)
|
|
15
|
+
* @param {string} options.keyCodeForClose - Key code for closing (ignored, for compatibility only)
|
|
16
|
+
* @param {boolean} options.willUnmount - Unmount callback (ignored, for compatibility only)
|
|
17
|
+
* @param {boolean} options.afterClose - After close callback (ignored, for compatibility only)
|
|
18
|
+
* @param {boolean} options.onClickOutside - On click outside callback (ignored, for compatibility only)
|
|
19
|
+
* @param {boolean} options.onKeypressEscape - On keypress escape callback (ignored, for compatibility only)
|
|
20
|
+
* @param {Object} options.overlayClassName - Overlay class name (ignored, for compatibility only)
|
|
21
|
+
* @param {Object} options.overlayStyle - Overlay style (ignored, for compatibility only)
|
|
22
|
+
* @param {Object} options.contentClassName - Content class name (ignored, for compatibility only)
|
|
23
|
+
* @param {Object} options.contentStyle - Content style (ignored, for compatibility only)
|
|
24
|
+
* @param {Object} options.bodyClassName - Body class name (ignored, for compatibility only)
|
|
25
|
+
* @param {Object} options.titleClassName - Title class name (ignored, for compatibility only)
|
|
26
|
+
* @param {Object} options.titleStyle - Title style (ignored, for compatibility only)
|
|
27
|
+
* @param {Object} options.messageClassName - Message class name (ignored, for compatibility only)
|
|
28
|
+
* @param {Object} options.messageStyle - Message style (ignored, for compatibility only)
|
|
29
|
+
* @param {Object} options.buttonClassName - Button class name (ignored, for compatibility only)
|
|
30
|
+
* @param {Object} options.buttonStyle - Button style (ignored, for compatibility only)
|
|
31
|
+
* @param {Object} options.childrenClassName - Children class name (ignored, for compatibility only)
|
|
32
|
+
*/
|
|
33
|
+
export const confirmDialog = (options) => {
|
|
34
|
+
const {
|
|
35
|
+
title = '',
|
|
36
|
+
message = '',
|
|
37
|
+
buttons = [],
|
|
38
|
+
closeOnClickOutside = true,
|
|
39
|
+
closeOnEscape = true,
|
|
40
|
+
data = null, // Optional data object from the selected row
|
|
41
|
+
} = options;
|
|
42
|
+
|
|
43
|
+
// Find Yes and No buttons if they exist
|
|
44
|
+
const yesButton = buttons.find((btn) => btn.label === 'Yes');
|
|
45
|
+
const noButton = buttons.find((btn) => btn.label === 'No');
|
|
46
|
+
|
|
47
|
+
// Default button text
|
|
48
|
+
const confirmButtonText = yesButton ? yesButton.label : 'Yes';
|
|
49
|
+
const cancelButtonText = noButton ? noButton.label : 'No';
|
|
50
|
+
|
|
51
|
+
// Enhanced message with item details if available
|
|
52
|
+
let enhancedMessage = message;
|
|
53
|
+
let itemDetails = '';
|
|
54
|
+
|
|
55
|
+
// Extract meaningful information from the data object if available
|
|
56
|
+
if (data) {
|
|
57
|
+
// Common identifiers that might be present in data objects
|
|
58
|
+
const identifiers = [
|
|
59
|
+
'name',
|
|
60
|
+
'label',
|
|
61
|
+
'title',
|
|
62
|
+
'email',
|
|
63
|
+
'username',
|
|
64
|
+
'id',
|
|
65
|
+
'code',
|
|
66
|
+
'reference',
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
// Find the first available identifier
|
|
70
|
+
for (const id of identifiers) {
|
|
71
|
+
if (
|
|
72
|
+
data[id] &&
|
|
73
|
+
typeof data[id] === 'string' &&
|
|
74
|
+
data[id].trim() !== ''
|
|
75
|
+
) {
|
|
76
|
+
itemDetails = data[id];
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// If we found an identifier, enhance the message
|
|
82
|
+
if (itemDetails && !message.includes(itemDetails)) {
|
|
83
|
+
// If the message already contains a question mark, add the details before it
|
|
84
|
+
if (message.includes('?')) {
|
|
85
|
+
enhancedMessage = message.replace('?', `"${itemDetails}"?`);
|
|
86
|
+
} else {
|
|
87
|
+
// Otherwise, append the details
|
|
88
|
+
enhancedMessage = `${message} "${itemDetails}"`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Configure SweetAlert2 options
|
|
94
|
+
const swalOptions = {
|
|
95
|
+
title: title,
|
|
96
|
+
html: enhancedMessage,
|
|
97
|
+
icon: 'question',
|
|
98
|
+
showCancelButton: true,
|
|
99
|
+
confirmButtonText: confirmButtonText,
|
|
100
|
+
cancelButtonText: cancelButtonText,
|
|
101
|
+
confirmButtonColor: 'var(--primary-color, #4f46e5)',
|
|
102
|
+
cancelButtonColor: '#6b7280',
|
|
103
|
+
allowOutsideClick: closeOnClickOutside,
|
|
104
|
+
allowEscapeKey: closeOnEscape,
|
|
105
|
+
reverseButtons: true,
|
|
106
|
+
focusCancel: false,
|
|
107
|
+
buttonsStyling: true,
|
|
108
|
+
showClass: {
|
|
109
|
+
popup: 'swal2-show',
|
|
110
|
+
backdrop: 'swal2-backdrop-show',
|
|
111
|
+
icon: 'swal2-icon-show',
|
|
112
|
+
},
|
|
113
|
+
customClass: {
|
|
114
|
+
container: 'swal2-container',
|
|
115
|
+
popup: 'swal2-popup',
|
|
116
|
+
header: 'swal2-header',
|
|
117
|
+
title: 'swal2-title',
|
|
118
|
+
closeButton: 'swal2-close-button',
|
|
119
|
+
icon: 'swal2-icon',
|
|
120
|
+
image: 'swal2-image',
|
|
121
|
+
htmlContainer: 'swal2-html-container',
|
|
122
|
+
input: 'swal2-input',
|
|
123
|
+
inputLabel: 'swal2-input-label',
|
|
124
|
+
validationMessage: 'swal2-validation-message',
|
|
125
|
+
actions: 'swal2-actions',
|
|
126
|
+
confirmButton: 'swal2-confirm',
|
|
127
|
+
denyButton: 'swal2-deny',
|
|
128
|
+
cancelButton: 'swal2-cancel',
|
|
129
|
+
loader: 'swal2-loader',
|
|
130
|
+
footer: 'swal2-footer',
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Show SweetAlert2 dialog
|
|
135
|
+
Swal.fire(swalOptions).then((result) => {
|
|
136
|
+
if (
|
|
137
|
+
result.isConfirmed &&
|
|
138
|
+
yesButton &&
|
|
139
|
+
typeof yesButton.onClick === 'function'
|
|
140
|
+
) {
|
|
141
|
+
yesButton.onClick();
|
|
142
|
+
} else if (
|
|
143
|
+
result.dismiss === Swal.DismissReason.cancel &&
|
|
144
|
+
noButton &&
|
|
145
|
+
typeof noButton.onClick === 'function'
|
|
146
|
+
) {
|
|
147
|
+
noButton.onClick();
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Export as default for compatibility with react-confirm-alert
|
|
153
|
+
export default {
|
|
154
|
+
confirmAlert: confirmDialog,
|
|
155
|
+
};
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch utility to replace axios with similar API
|
|
3
|
+
* Designed to work with Laravel backend
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates an axios-like response object from a fetch response
|
|
8
|
+
* @param {Response} response - The fetch Response object
|
|
9
|
+
* @param {any} data - The parsed response data
|
|
10
|
+
* @returns {Object} - An axios-like response object
|
|
11
|
+
*/
|
|
12
|
+
const createAxiosLikeResponse = async (response, data) => {
|
|
13
|
+
return {
|
|
14
|
+
data,
|
|
15
|
+
status: response.status,
|
|
16
|
+
statusText: response.statusText,
|
|
17
|
+
headers: response.headers,
|
|
18
|
+
config: {
|
|
19
|
+
url: response.url,
|
|
20
|
+
method: response.type,
|
|
21
|
+
},
|
|
22
|
+
request: null, // Not available in fetch
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Main fetch function that mimics axios API
|
|
28
|
+
* @param {Object|string} config - Axios-like config object or URL string
|
|
29
|
+
* @returns {Promise} - Promise that resolves to an axios-like response
|
|
30
|
+
*/
|
|
31
|
+
const fetchUtil = async (config) => {
|
|
32
|
+
// Handle string input (URL)
|
|
33
|
+
if (typeof config === 'string') {
|
|
34
|
+
config = { url: config };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
url,
|
|
39
|
+
method = 'GET',
|
|
40
|
+
headers = {},
|
|
41
|
+
data,
|
|
42
|
+
params,
|
|
43
|
+
responseType,
|
|
44
|
+
withCredentials = false,
|
|
45
|
+
} = config;
|
|
46
|
+
|
|
47
|
+
// Build request URL with query parameters
|
|
48
|
+
let requestUrl = url;
|
|
49
|
+
|
|
50
|
+
// For GET and HEAD requests, convert data to query parameters if it exists
|
|
51
|
+
if (
|
|
52
|
+
(method.toUpperCase() === 'GET' || method.toUpperCase() === 'HEAD') &&
|
|
53
|
+
data &&
|
|
54
|
+
typeof data === 'object' &&
|
|
55
|
+
!(data instanceof FormData)
|
|
56
|
+
) {
|
|
57
|
+
// Create params object if it doesn't exist
|
|
58
|
+
params = params || {};
|
|
59
|
+
// Merge data into params
|
|
60
|
+
Object.assign(params, data);
|
|
61
|
+
// Remove data since we've moved it to params
|
|
62
|
+
data = undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Add params to URL
|
|
66
|
+
if (params) {
|
|
67
|
+
const queryParams = new URLSearchParams();
|
|
68
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
69
|
+
if (value !== undefined && value !== null) {
|
|
70
|
+
queryParams.append(key, value);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
const queryString = queryParams.toString();
|
|
74
|
+
if (queryString) {
|
|
75
|
+
requestUrl += (url.includes('?') ? '&' : '?') + queryString;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Set up headers
|
|
80
|
+
const requestHeaders = new Headers({
|
|
81
|
+
...headers,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// If not explicitly set and sending data, default to JSON
|
|
85
|
+
if (
|
|
86
|
+
data &&
|
|
87
|
+
!requestHeaders.has('Content-Type') &&
|
|
88
|
+
!(data instanceof FormData)
|
|
89
|
+
) {
|
|
90
|
+
requestHeaders.set('Content-Type', 'application/json');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Add CSRF token for Laravel if available
|
|
94
|
+
const csrfToken = document
|
|
95
|
+
.querySelector('meta[name="csrf-token"]')
|
|
96
|
+
?.getAttribute('content');
|
|
97
|
+
if (csrfToken) {
|
|
98
|
+
// Laravel accepts the CSRF token in different headers
|
|
99
|
+
if (!requestHeaders.has('X-CSRF-TOKEN')) {
|
|
100
|
+
requestHeaders.set('X-CSRF-TOKEN', csrfToken);
|
|
101
|
+
}
|
|
102
|
+
if (!requestHeaders.has('X-XSRF-TOKEN')) {
|
|
103
|
+
requestHeaders.set('X-XSRF-TOKEN', csrfToken);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Add X-Requested-With header for Laravel to recognize AJAX requests
|
|
108
|
+
if (!requestHeaders.has('X-Requested-With')) {
|
|
109
|
+
requestHeaders.set('X-Requested-With', 'XMLHttpRequest');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Handle credentials
|
|
113
|
+
const credentials = withCredentials ? 'include' : 'same-origin';
|
|
114
|
+
|
|
115
|
+
// Prepare request options
|
|
116
|
+
const requestOptions = {
|
|
117
|
+
method: method.toUpperCase(),
|
|
118
|
+
headers: requestHeaders,
|
|
119
|
+
credentials,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Add body for non-GET requests
|
|
123
|
+
if (method.toUpperCase() !== 'GET' && method.toUpperCase() !== 'HEAD') {
|
|
124
|
+
// Handle Laravel's _method for PUT/PATCH/DELETE
|
|
125
|
+
if (['PUT', 'PATCH', 'DELETE'].includes(method.toUpperCase())) {
|
|
126
|
+
if (data instanceof FormData) {
|
|
127
|
+
data.append('_method', method.toUpperCase());
|
|
128
|
+
requestOptions.method = 'POST';
|
|
129
|
+
} else if (data && typeof data === 'object') {
|
|
130
|
+
requestOptions.body = JSON.stringify({
|
|
131
|
+
...data,
|
|
132
|
+
_method: method.toUpperCase(),
|
|
133
|
+
});
|
|
134
|
+
requestOptions.method = 'POST';
|
|
135
|
+
} else {
|
|
136
|
+
// If no data, create a body with just _method
|
|
137
|
+
requestOptions.body = JSON.stringify({
|
|
138
|
+
_method: method.toUpperCase(),
|
|
139
|
+
});
|
|
140
|
+
requestOptions.method = 'POST';
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
// For regular POST
|
|
144
|
+
if (data instanceof FormData) {
|
|
145
|
+
requestOptions.body = data;
|
|
146
|
+
} else if (data !== undefined) {
|
|
147
|
+
requestOptions.body = JSON.stringify(data);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const response = await fetch(requestUrl, requestOptions);
|
|
154
|
+
|
|
155
|
+
// Parse response based on content type or specified responseType
|
|
156
|
+
let responseData;
|
|
157
|
+
|
|
158
|
+
if (responseType === 'blob') {
|
|
159
|
+
responseData = await response.blob();
|
|
160
|
+
} else if (responseType === 'arraybuffer') {
|
|
161
|
+
responseData = await response.arrayBuffer();
|
|
162
|
+
} else if (responseType === 'text') {
|
|
163
|
+
responseData = await response.text();
|
|
164
|
+
} else {
|
|
165
|
+
// Default: try to parse as JSON, fallback to text
|
|
166
|
+
const contentType = response.headers.get('content-type');
|
|
167
|
+
if (contentType && contentType.includes('application/json')) {
|
|
168
|
+
responseData = await response.json();
|
|
169
|
+
} else {
|
|
170
|
+
responseData = await response.text();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const axiosLikeResponse = await createAxiosLikeResponse(
|
|
175
|
+
response,
|
|
176
|
+
responseData
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Handle error responses (4xx, 5xx)
|
|
180
|
+
if (!response.ok) {
|
|
181
|
+
const error = new Error(response.statusText || 'Request failed');
|
|
182
|
+
error.response = axiosLikeResponse;
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return axiosLikeResponse;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
// If the error already has a response property, it's from our code above
|
|
189
|
+
if (!error.response) {
|
|
190
|
+
// Network errors or other fetch errors
|
|
191
|
+
error.response = {
|
|
192
|
+
data: { message: error.message },
|
|
193
|
+
status: 0,
|
|
194
|
+
statusText: error.message,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// Add convenience methods to match axios API
|
|
202
|
+
['get', 'delete', 'head', 'options'].forEach((method) => {
|
|
203
|
+
fetchUtil[method] = (url, config = {}) => {
|
|
204
|
+
// If second parameter is data (object but not a config object with recognized properties)
|
|
205
|
+
if (
|
|
206
|
+
config &&
|
|
207
|
+
typeof config === 'object' &&
|
|
208
|
+
!config.headers &&
|
|
209
|
+
!config.params &&
|
|
210
|
+
!config.responseType &&
|
|
211
|
+
!config.withCredentials
|
|
212
|
+
) {
|
|
213
|
+
// It's likely data, not config
|
|
214
|
+
return fetchUtil({
|
|
215
|
+
method,
|
|
216
|
+
url,
|
|
217
|
+
params: config, // Use as query parameters
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return fetchUtil({
|
|
222
|
+
...config,
|
|
223
|
+
method,
|
|
224
|
+
url,
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
['post', 'put', 'patch'].forEach((method) => {
|
|
230
|
+
fetchUtil[method] = (url, data, config = {}) => {
|
|
231
|
+
return fetchUtil({
|
|
232
|
+
...config,
|
|
233
|
+
method,
|
|
234
|
+
url,
|
|
235
|
+
data,
|
|
236
|
+
});
|
|
237
|
+
};
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
export default fetchUtil;
|