@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.
@@ -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 csrfToken = document
158
- .querySelector('meta[name="csrf-token"]')
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
- headers: { 'X-CSRF-TOKEN': csrfToken },
163
- body: formData,
160
+ data: formData,
164
161
  });
165
162
 
166
- if (response.ok) {
167
- const result = await response.json();
163
+ const result = response.data;
168
164
 
169
- setData((prevState) => ({
170
- ...prevState,
171
- [id]: result,
172
- }));
173
- toast.update(loadingToast, {
174
- render: 'File processed successfully!',
175
- type: 'success',
176
- isLoading: false,
177
- autoClose: 3000,
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 fetch to retrieve the file as a blob
1146
- fetch(urlToDownload, {
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(async (response) => {
1154
- if (!response.ok) {
1155
- // Try to extract error message from response if possible
1156
- // Clone the response to read it as JSON first
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
- confirmAlert({
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
- <Form
2820
- closeModal={modalClose}
2821
- columnId={formId}
2822
- fetchTable={handleReload}
2823
- formSettings={formData}
2824
- formType={formType}
2825
- style={userProfile?.settings?.style || {}}
2826
- updateForm={setFormData}
2827
- userProfile={userProfile}
2828
- paramValue={
2829
- routeParams[urlParam] ? routeParams[urlParam] : 0
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
- confirmAlert({
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
- confirmAlert({
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
- confirmAlert({
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: '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: 200,
34
- maxWidth: 200,
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 className={`${styles.grid__full} ${styles.crmtitle}`}>
44
- <h1>Notifications</h1>
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: 2px;
33
+ padding: 16px;
28
34
  margin: 8px 0;
29
- box-shadow: 0 10px 50px rgba(var(--primary-rgb), 0.05);
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
- .swal2-confirm {
28
- background-color: #2563eb !important;
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: 6px 14px !important;
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%;
@@ -253,6 +253,11 @@
253
253
  z-index: 1000;
254
254
  }
255
255
 
256
+ .modalWide {
257
+ width: 80vw;
258
+ max-width: 1200px;
259
+ }
260
+
256
261
  .modal {
257
262
  width: 100%;
258
263
  position: relative;