@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
|
@@ -28,12 +28,12 @@ import parse from 'html-react-parser';
|
|
|
28
28
|
import Popup from 'reactjs-popup';
|
|
29
29
|
import { motion } from 'framer-motion';
|
|
30
30
|
import Dropzone from 'react-dropzone';
|
|
31
|
-
import { confirmAlert } from 'react-confirm-alert';
|
|
32
31
|
import truncate from 'truncate';
|
|
33
32
|
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
|
34
33
|
import { saveAs } from 'file-saver';
|
|
35
34
|
import { toast } from 'react-toastify';
|
|
36
35
|
import imageCompression from 'browser-image-compression';
|
|
36
|
+
import fetchUtil from '../../../utils/fetchUtil';
|
|
37
37
|
import {
|
|
38
38
|
CircleCheck,
|
|
39
39
|
Copy,
|
|
@@ -43,8 +43,8 @@ import {
|
|
|
43
43
|
Pencil,
|
|
44
44
|
TrashCan,
|
|
45
45
|
} from 'akar-icons';
|
|
46
|
+
import { confirmDialog } from '../../utils/ConfirmDialog';
|
|
46
47
|
|
|
47
|
-
import 'react-confirm-alert/src/react-confirm-alert.css';
|
|
48
48
|
import 'react-big-calendar/lib/css/react-big-calendar.css';
|
|
49
49
|
import 'react-big-calendar/lib/addons/dragAndDrop/styles.css';
|
|
50
50
|
|
|
@@ -154,36 +154,24 @@ function GenericDetail({
|
|
|
154
154
|
'Processing file... Please wait!'
|
|
155
155
|
);
|
|
156
156
|
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
.getAttribute('content');
|
|
160
|
-
const response = await fetch('/ajax/ocr/analyzeFile', {
|
|
157
|
+
const response = await fetchUtil({
|
|
158
|
+
url: '/ajax/ocr/analyzeFile',
|
|
161
159
|
method: 'POST',
|
|
162
|
-
|
|
163
|
-
body: formData,
|
|
160
|
+
data: formData,
|
|
164
161
|
});
|
|
165
162
|
|
|
166
|
-
|
|
167
|
-
const result = await response.json();
|
|
163
|
+
const result = response.data;
|
|
168
164
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
} else {
|
|
180
|
-
toast.update(loadingToast, {
|
|
181
|
-
render: 'Error processing file.',
|
|
182
|
-
type: 'error',
|
|
183
|
-
isLoading: false,
|
|
184
|
-
autoClose: 3000,
|
|
185
|
-
});
|
|
186
|
-
}
|
|
165
|
+
setData((prevState) => ({
|
|
166
|
+
...prevState,
|
|
167
|
+
[id]: result,
|
|
168
|
+
}));
|
|
169
|
+
toast.update(loadingToast, {
|
|
170
|
+
render: 'File processed successfully!',
|
|
171
|
+
type: 'success',
|
|
172
|
+
isLoading: false,
|
|
173
|
+
autoClose: 3000,
|
|
174
|
+
});
|
|
187
175
|
} catch (error) {
|
|
188
176
|
console.error(error);
|
|
189
177
|
toast.update(loadingToast, {
|
|
@@ -1142,100 +1130,20 @@ function GenericDetail({
|
|
|
1142
1130
|
file_url ||
|
|
1143
1131
|
`/ajax/files/downloadContent/${id}`;
|
|
1144
1132
|
|
|
1145
|
-
// Use
|
|
1146
|
-
|
|
1133
|
+
// Use fetchUtil to retrieve the file as a blob
|
|
1134
|
+
fetchUtil({
|
|
1135
|
+
url: urlToDownload,
|
|
1147
1136
|
method: 'GET',
|
|
1148
1137
|
headers: {
|
|
1149
1138
|
'Content-Type':
|
|
1150
1139
|
'application/octet-stream', // Specify content type as binary
|
|
1151
1140
|
},
|
|
1141
|
+
responseType: 'blob',
|
|
1152
1142
|
})
|
|
1153
|
-
.then(
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
const clonedResponse =
|
|
1158
|
-
response.clone();
|
|
1159
|
-
|
|
1160
|
-
try {
|
|
1161
|
-
// First try to parse as JSON
|
|
1162
|
-
const errorData =
|
|
1163
|
-
await clonedResponse.json();
|
|
1164
|
-
|
|
1165
|
-
if (
|
|
1166
|
-
errorData &&
|
|
1167
|
-
errorData.error
|
|
1168
|
-
) {
|
|
1169
|
-
throw new Error(
|
|
1170
|
-
errorData.error
|
|
1171
|
-
);
|
|
1172
|
-
} else if (
|
|
1173
|
-
errorData &&
|
|
1174
|
-
errorData.message
|
|
1175
|
-
) {
|
|
1176
|
-
throw new Error(
|
|
1177
|
-
errorData.message
|
|
1178
|
-
);
|
|
1179
|
-
} else if (
|
|
1180
|
-
errorData &&
|
|
1181
|
-
errorData.value
|
|
1182
|
-
) {
|
|
1183
|
-
throw new Error(
|
|
1184
|
-
errorData.value
|
|
1185
|
-
);
|
|
1186
|
-
} else {
|
|
1187
|
-
// JSON parsed but no known error fields
|
|
1188
|
-
throw new Error(
|
|
1189
|
-
`HTTP error! Status: ${response.status}`
|
|
1190
|
-
);
|
|
1191
|
-
}
|
|
1192
|
-
} catch (jsonError) {
|
|
1193
|
-
// If we can't parse as JSON, try to get text
|
|
1194
|
-
try {
|
|
1195
|
-
const textResponse =
|
|
1196
|
-
response.clone();
|
|
1197
|
-
const errorText =
|
|
1198
|
-
await textResponse.text();
|
|
1199
|
-
|
|
1200
|
-
// Check if the text looks like JSON but wasn't parsed correctly
|
|
1201
|
-
if (
|
|
1202
|
-
errorText.includes(
|
|
1203
|
-
'"error":'
|
|
1204
|
-
)
|
|
1205
|
-
) {
|
|
1206
|
-
// Try to extract the error message with regex
|
|
1207
|
-
const errorMatch =
|
|
1208
|
-
errorText.match(
|
|
1209
|
-
/"error":"([^"]+)"/i
|
|
1210
|
-
);
|
|
1211
|
-
if (
|
|
1212
|
-
errorMatch &&
|
|
1213
|
-
errorMatch[1]
|
|
1214
|
-
) {
|
|
1215
|
-
throw new Error(
|
|
1216
|
-
errorMatch[1]
|
|
1217
|
-
);
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
// If we got text but couldn't extract a specific error
|
|
1222
|
-
if (
|
|
1223
|
-
errorText &&
|
|
1224
|
-
errorText.length < 100
|
|
1225
|
-
) {
|
|
1226
|
-
throw new Error(
|
|
1227
|
-
errorText
|
|
1228
|
-
);
|
|
1229
|
-
}
|
|
1230
|
-
} catch (textError) {
|
|
1231
|
-
// If all else fails, fall back to status
|
|
1232
|
-
throw new Error(
|
|
1233
|
-
`HTTP error! Status: ${response.status}`
|
|
1234
|
-
);
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
return response.blob(); // Convert response to Blob
|
|
1143
|
+
.then((response) => {
|
|
1144
|
+
// fetchUtil already handles response.ok check and throws errors
|
|
1145
|
+
// Just return the blob data directly
|
|
1146
|
+
return response.data;
|
|
1239
1147
|
})
|
|
1240
1148
|
.then((blob) => {
|
|
1241
1149
|
// Validate that we have a valid blob with content
|
|
@@ -1277,11 +1185,12 @@ function GenericDetail({
|
|
|
1277
1185
|
if (e) {
|
|
1278
1186
|
e.stopPropagation();
|
|
1279
1187
|
|
|
1280
|
-
|
|
1188
|
+
confirmDialog({
|
|
1281
1189
|
title: 'Confirm to Delete File',
|
|
1282
1190
|
message:
|
|
1283
1191
|
'Are you sure you want to delete ' +
|
|
1284
1192
|
name,
|
|
1193
|
+
data: { name: name }, // Add context data
|
|
1285
1194
|
buttons: [
|
|
1286
1195
|
{
|
|
1287
1196
|
label: 'Yes',
|
|
@@ -2815,20 +2724,27 @@ function GenericDetail({
|
|
|
2815
2724
|
open={modalShow}
|
|
2816
2725
|
onClose={modalClose}
|
|
2817
2726
|
closeOnDocumentClick={false}
|
|
2727
|
+
contentStyle={{ width: '80vw', maxWidth: '1200px' }}
|
|
2818
2728
|
>
|
|
2819
|
-
<
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2729
|
+
<div className="modalwrap top--modal modalWide">
|
|
2730
|
+
<div className="modal">
|
|
2731
|
+
<Form
|
|
2732
|
+
closeModal={modalClose}
|
|
2733
|
+
columnId={formId}
|
|
2734
|
+
fetchTable={handleReload}
|
|
2735
|
+
formSettings={formData}
|
|
2736
|
+
formType={formType}
|
|
2737
|
+
style={userProfile?.settings?.style || {}}
|
|
2738
|
+
updateForm={setFormData}
|
|
2739
|
+
userProfile={userProfile}
|
|
2740
|
+
paramValue={
|
|
2741
|
+
routeParams[urlParam]
|
|
2742
|
+
? routeParams[urlParam]
|
|
2743
|
+
: 0
|
|
2744
|
+
}
|
|
2745
|
+
/>
|
|
2746
|
+
</div>
|
|
2747
|
+
</div>
|
|
2832
2748
|
</Popup>
|
|
2833
2749
|
</>
|
|
2834
2750
|
);
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import moment from 'moment';
|
|
3
|
-
import 'react-confirm-alert/src/react-confirm-alert.css';
|
|
4
3
|
import { toast } from 'react-toastify';
|
|
5
|
-
import { confirmAlert } from 'react-confirm-alert';
|
|
6
4
|
import { saveAs } from 'file-saver';
|
|
7
5
|
import parse from 'html-react-parser';
|
|
8
6
|
import { Backspace, CloudDownload } from 'akar-icons';
|
|
7
|
+
import { confirmDialog } from '../../utils/ConfirmDialog';
|
|
9
8
|
|
|
10
9
|
import CustomFetch from '../Fetch';
|
|
11
10
|
import Download from '../Download';
|
|
@@ -687,9 +686,10 @@ const GenericDynamic = ({
|
|
|
687
686
|
}
|
|
688
687
|
};
|
|
689
688
|
|
|
690
|
-
|
|
689
|
+
confirmDialog({
|
|
691
690
|
title: 'Delete File',
|
|
692
691
|
message: 'Are you sure?',
|
|
692
|
+
data: { name: 'this file' }, // Add some context
|
|
693
693
|
buttons: [
|
|
694
694
|
{
|
|
695
695
|
label: 'Yes',
|
|
@@ -2,14 +2,12 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
|
2
2
|
import ReactDOM from 'react-dom';
|
|
3
3
|
import debounce from 'lodash.debounce';
|
|
4
4
|
import { toast } from 'react-toastify';
|
|
5
|
-
import { confirmAlert } from 'react-confirm-alert';
|
|
6
5
|
import { TrashCan, ArrowUp, ArrowDown, Copy, Water } from 'akar-icons';
|
|
7
6
|
import { CompactPicker } from 'react-color';
|
|
7
|
+
import { confirmDialog } from '../../utils/ConfirmDialog';
|
|
8
8
|
|
|
9
9
|
import CustomFetch from '../Fetch';
|
|
10
10
|
import MultiSelect from '../MultiSelect';
|
|
11
|
-
|
|
12
|
-
import 'react-confirm-alert/src/react-confirm-alert.css';
|
|
13
11
|
import styles from './styles/GenericEditableTable.module.scss';
|
|
14
12
|
|
|
15
13
|
// Updated ColourPicker with an expanded pastel palette
|
|
@@ -593,9 +591,10 @@ const GenericEditableTable = ({
|
|
|
593
591
|
|
|
594
592
|
// 8) Deletion
|
|
595
593
|
const handleDeleteRow = (entry) => {
|
|
596
|
-
|
|
594
|
+
confirmDialog({
|
|
597
595
|
title: 'Confirm to Delete',
|
|
598
596
|
message: 'Are you sure you want to delete this entry?',
|
|
597
|
+
data: entry, // Pass the row data
|
|
599
598
|
buttons: [
|
|
600
599
|
{
|
|
601
600
|
label: 'Yes',
|
|
@@ -4,17 +4,16 @@ import React, { useEffect, useRef, useState } from 'react';
|
|
|
4
4
|
import { useParams } from 'react-router-dom';
|
|
5
5
|
import Popup from 'reactjs-popup';
|
|
6
6
|
import { arrayMoveImmutable } from 'array-move';
|
|
7
|
-
import { confirmAlert } from 'react-confirm-alert';
|
|
8
7
|
import parse from 'html-react-parser';
|
|
9
8
|
import Toggle from 'react-toggle';
|
|
10
9
|
import { toast } from 'react-toastify';
|
|
11
10
|
import { v4 as uuidv4 } from 'uuid';
|
|
12
11
|
import { Editor } from '@tinymce/tinymce-react';
|
|
13
12
|
import { CirclePlus, CircleX, TrashCan } from 'akar-icons';
|
|
13
|
+
import { confirmDialog } from '../../utils/ConfirmDialog';
|
|
14
14
|
|
|
15
15
|
import 'react-toggle/style.css';
|
|
16
16
|
import 'react-datepicker/dist/react-datepicker.css';
|
|
17
|
-
import 'react-confirm-alert/src/react-confirm-alert.css';
|
|
18
17
|
|
|
19
18
|
import Breadcrumb from '../Breadcrumb';
|
|
20
19
|
import CustomFetch from '../Fetch';
|
|
@@ -278,9 +277,10 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
|
|
|
278
277
|
};
|
|
279
278
|
|
|
280
279
|
const handleDeleteField = (id, label) => {
|
|
281
|
-
|
|
280
|
+
confirmDialog({
|
|
282
281
|
title: `Delete "${label}" Field`,
|
|
283
282
|
message: 'Are you sure you want to delete this field?',
|
|
283
|
+
data: { name: label, id: id }, // Add context data
|
|
284
284
|
buttons: [
|
|
285
285
|
{
|
|
286
286
|
label: 'Yes',
|
|
@@ -1,8 +1,37 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState, useCallback } from 'react';
|
|
2
|
+
import { useNavigate } from 'react-router-dom';
|
|
2
3
|
import Table from '../DataGrid';
|
|
4
|
+
import { Bell, Check, ArrowLeft } from 'akar-icons';
|
|
5
|
+
import { toast } from 'react-toastify';
|
|
6
|
+
import CustomFetch from '../Fetch';
|
|
3
7
|
import styles from './styles/NotificationList.module.scss';
|
|
4
8
|
|
|
5
9
|
const NotificationList = () => {
|
|
10
|
+
const navigate = useNavigate();
|
|
11
|
+
const [loading, setLoading] = useState(false);
|
|
12
|
+
|
|
13
|
+
const markAllAsRead = useCallback(() => {
|
|
14
|
+
setLoading(true);
|
|
15
|
+
|
|
16
|
+
CustomFetch(
|
|
17
|
+
'/ajax/user/notification/markallasread',
|
|
18
|
+
'POST',
|
|
19
|
+
{},
|
|
20
|
+
function () {
|
|
21
|
+
toast.success('All notifications marked as read');
|
|
22
|
+
setLoading(false);
|
|
23
|
+
// Refresh the table
|
|
24
|
+
window.location.reload();
|
|
25
|
+
},
|
|
26
|
+
function (error) {
|
|
27
|
+
setLoading(false);
|
|
28
|
+
toast.error(
|
|
29
|
+
String(error) || 'Failed to mark all notifications as read'
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
6
35
|
const setting = {
|
|
7
36
|
ajaxSetting: {
|
|
8
37
|
url: '/ajax/user/notifications/table',
|
|
@@ -23,25 +52,49 @@ const NotificationList = () => {
|
|
|
23
52
|
{
|
|
24
53
|
id: 'data',
|
|
25
54
|
jsonData: 'label',
|
|
26
|
-
label: '
|
|
55
|
+
label: 'Message',
|
|
27
56
|
type: 'json',
|
|
57
|
+
flex: 1,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'created_at',
|
|
61
|
+
label: 'Created At',
|
|
62
|
+
type: 'datetime',
|
|
63
|
+
minWidth: 180,
|
|
64
|
+
maxWidth: 180,
|
|
28
65
|
},
|
|
29
66
|
{
|
|
30
67
|
id: 'read_at',
|
|
31
68
|
label: 'Read At',
|
|
32
69
|
type: 'datetime',
|
|
33
|
-
minWidth:
|
|
34
|
-
maxWidth:
|
|
70
|
+
minWidth: 180,
|
|
71
|
+
maxWidth: 180,
|
|
35
72
|
},
|
|
36
73
|
],
|
|
37
74
|
settings: [],
|
|
38
75
|
};
|
|
76
|
+
|
|
39
77
|
return (
|
|
40
|
-
<div>
|
|
78
|
+
<div className={styles.notificationListContainer}>
|
|
41
79
|
<div className={styles.grid}>
|
|
42
80
|
<div className={styles.grid__row}>
|
|
43
|
-
<div
|
|
44
|
-
|
|
81
|
+
<div
|
|
82
|
+
className={`${styles.grid__full} ${styles.headerContainer}`}
|
|
83
|
+
>
|
|
84
|
+
<div className={styles.headerContent}>
|
|
85
|
+
<div className={styles.titleSection}>
|
|
86
|
+
<Bell size={24} className={styles.titleIcon} />
|
|
87
|
+
<h1>Notifications</h1>
|
|
88
|
+
</div>
|
|
89
|
+
<button
|
|
90
|
+
className={styles.markAllReadButton}
|
|
91
|
+
onClick={markAllAsRead}
|
|
92
|
+
disabled={loading}
|
|
93
|
+
>
|
|
94
|
+
<Check size={16} />
|
|
95
|
+
<span>Mark all as read</span>
|
|
96
|
+
</button>
|
|
97
|
+
</div>
|
|
45
98
|
</div>
|
|
46
99
|
</div>
|
|
47
100
|
</div>
|
|
@@ -53,10 +106,26 @@ const NotificationList = () => {
|
|
|
53
106
|
form={setting.form}
|
|
54
107
|
columns={setting.columns}
|
|
55
108
|
settings={setting.settings}
|
|
109
|
+
gridHeight={600}
|
|
56
110
|
/>
|
|
57
111
|
</div>
|
|
58
112
|
</div>
|
|
59
113
|
</div>
|
|
114
|
+
<div className={styles.grid}>
|
|
115
|
+
<div className={styles.grid__row}>
|
|
116
|
+
<div className={styles.grid__full}>
|
|
117
|
+
<div className={styles.backButtonContainer}>
|
|
118
|
+
<button
|
|
119
|
+
className={styles.backButton}
|
|
120
|
+
onClick={() => navigate('/')}
|
|
121
|
+
>
|
|
122
|
+
<ArrowLeft size={16} />
|
|
123
|
+
<span>Back to Dashboard</span>
|
|
124
|
+
</button>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
60
129
|
</div>
|
|
61
130
|
);
|
|
62
131
|
};
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
.notificationListContainer {
|
|
2
|
+
width: 100%;
|
|
3
|
+
max-width: 1200px;
|
|
4
|
+
margin: 0 auto;
|
|
5
|
+
}
|
|
6
|
+
|
|
1
7
|
.grid {
|
|
2
8
|
width: 100%;
|
|
3
9
|
display: flex;
|
|
@@ -24,7 +30,89 @@
|
|
|
24
30
|
box-sizing: border-box;
|
|
25
31
|
overflow: visible;
|
|
26
32
|
position: relative;
|
|
27
|
-
padding:
|
|
33
|
+
padding: 16px;
|
|
28
34
|
margin: 8px 0;
|
|
29
|
-
box-shadow: 0
|
|
35
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.headerContainer {
|
|
39
|
+
padding: 16px 24px;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.headerContent {
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
justify-content: space-between;
|
|
46
|
+
width: 100%;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.titleSection {
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
gap: 12px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.titleIcon {
|
|
56
|
+
color: var(--primary-color);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.titleSection h1 {
|
|
60
|
+
margin: 0;
|
|
61
|
+
font-size: 24px;
|
|
62
|
+
font-weight: 600;
|
|
63
|
+
color: var(--heading-color, #333);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.markAllReadButton {
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
gap: 8px;
|
|
70
|
+
background-color: var(--primary-color);
|
|
71
|
+
color: white;
|
|
72
|
+
border: none;
|
|
73
|
+
border-radius: 6px;
|
|
74
|
+
padding: 10px 16px;
|
|
75
|
+
font-size: 14px;
|
|
76
|
+
font-weight: 500;
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
transition: all 0.2s ease;
|
|
79
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.markAllReadButton:hover {
|
|
83
|
+
background-color: var(--primary-color-darker, #0056b3);
|
|
84
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.markAllReadButton:disabled {
|
|
88
|
+
opacity: 0.6;
|
|
89
|
+
cursor: not-allowed;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.backButtonContainer {
|
|
93
|
+
display: flex;
|
|
94
|
+
justify-content: center;
|
|
95
|
+
padding: 16px 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.backButton {
|
|
99
|
+
display: flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
gap: 8px;
|
|
102
|
+
background-color: var(--primary-color);
|
|
103
|
+
color: white;
|
|
104
|
+
border: none;
|
|
105
|
+
border-radius: 6px;
|
|
106
|
+
padding: 12px 24px;
|
|
107
|
+
font-size: 14px;
|
|
108
|
+
font-weight: 500;
|
|
109
|
+
cursor: pointer;
|
|
110
|
+
transition: all 0.2s ease;
|
|
111
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
112
|
+
min-width: 200px;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.backButton:hover {
|
|
116
|
+
background-color: var(--primary-color-darker, #0056b3);
|
|
117
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
|
30
118
|
}
|
|
@@ -24,18 +24,42 @@
|
|
|
24
24
|
line-height: 1.4 !important;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
/* Button styles for symmetrical appearance */
|
|
28
|
+
.swal2-actions {
|
|
29
|
+
display: flex !important;
|
|
30
|
+
justify-content: center !important;
|
|
31
|
+
gap: 10px !important;
|
|
32
|
+
width: 100% !important;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.swal2-confirm,
|
|
36
|
+
.swal2-cancel {
|
|
37
|
+
flex: 1 !important;
|
|
38
|
+
max-width: 120px !important;
|
|
39
|
+
min-width: 100px !important;
|
|
29
40
|
border-radius: 4px !important;
|
|
30
41
|
font-weight: 500 !important;
|
|
31
|
-
padding:
|
|
42
|
+
padding: 8px 16px !important;
|
|
32
43
|
font-size: 13px !important;
|
|
44
|
+
margin: 0 !important;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.swal2-confirm {
|
|
48
|
+
background-color: #2563eb !important;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.swal2-cancel {
|
|
52
|
+
background-color: #6b7280 !important;
|
|
33
53
|
}
|
|
34
54
|
|
|
35
55
|
.swal2-confirm:focus {
|
|
36
56
|
box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.3) !important;
|
|
37
57
|
}
|
|
38
58
|
|
|
59
|
+
.swal2-cancel:focus {
|
|
60
|
+
box-shadow: 0 0 0 2px rgba(107, 114, 128, 0.3) !important;
|
|
61
|
+
}
|
|
62
|
+
|
|
39
63
|
/* Make the modal more compact */
|
|
40
64
|
.swal2-popup.swal2-modal {
|
|
41
65
|
max-width: 90%;
|