@visns-studio/visns-components 5.7.0 → 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.
@@ -33,6 +33,7 @@ import { CopyToClipboard } from 'react-copy-to-clipboard';
33
33
  import { saveAs } from 'file-saver';
34
34
  import { toast } from 'react-toastify';
35
35
  import imageCompression from 'browser-image-compression';
36
+ import fetchUtil from '../../../utils/fetchUtil';
36
37
  import {
37
38
  CircleCheck,
38
39
  Copy,
@@ -153,36 +154,24 @@ function GenericDetail({
153
154
  'Processing file... Please wait!'
154
155
  );
155
156
 
156
- const csrfToken = document
157
- .querySelector('meta[name="csrf-token"]')
158
- .getAttribute('content');
159
- const response = await fetch('/ajax/ocr/analyzeFile', {
157
+ const response = await fetchUtil({
158
+ url: '/ajax/ocr/analyzeFile',
160
159
  method: 'POST',
161
- headers: { 'X-CSRF-TOKEN': csrfToken },
162
- body: formData,
160
+ data: formData,
163
161
  });
164
162
 
165
- if (response.ok) {
166
- const result = await response.json();
163
+ const result = response.data;
167
164
 
168
- setData((prevState) => ({
169
- ...prevState,
170
- [id]: result,
171
- }));
172
- toast.update(loadingToast, {
173
- render: 'File processed successfully!',
174
- type: 'success',
175
- isLoading: false,
176
- autoClose: 3000,
177
- });
178
- } else {
179
- toast.update(loadingToast, {
180
- render: 'Error processing file.',
181
- type: 'error',
182
- isLoading: false,
183
- autoClose: 3000,
184
- });
185
- }
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
+ });
186
175
  } catch (error) {
187
176
  console.error(error);
188
177
  toast.update(loadingToast, {
@@ -1141,100 +1130,20 @@ function GenericDetail({
1141
1130
  file_url ||
1142
1131
  `/ajax/files/downloadContent/${id}`;
1143
1132
 
1144
- // Use fetch to retrieve the file as a blob
1145
- fetch(urlToDownload, {
1133
+ // Use fetchUtil to retrieve the file as a blob
1134
+ fetchUtil({
1135
+ url: urlToDownload,
1146
1136
  method: 'GET',
1147
1137
  headers: {
1148
1138
  'Content-Type':
1149
1139
  'application/octet-stream', // Specify content type as binary
1150
1140
  },
1141
+ responseType: 'blob',
1151
1142
  })
1152
- .then(async (response) => {
1153
- if (!response.ok) {
1154
- // Try to extract error message from response if possible
1155
- // Clone the response to read it as JSON first
1156
- const clonedResponse =
1157
- response.clone();
1158
-
1159
- try {
1160
- // First try to parse as JSON
1161
- const errorData =
1162
- await clonedResponse.json();
1163
-
1164
- if (
1165
- errorData &&
1166
- errorData.error
1167
- ) {
1168
- throw new Error(
1169
- errorData.error
1170
- );
1171
- } else if (
1172
- errorData &&
1173
- errorData.message
1174
- ) {
1175
- throw new Error(
1176
- errorData.message
1177
- );
1178
- } else if (
1179
- errorData &&
1180
- errorData.value
1181
- ) {
1182
- throw new Error(
1183
- errorData.value
1184
- );
1185
- } else {
1186
- // JSON parsed but no known error fields
1187
- throw new Error(
1188
- `HTTP error! Status: ${response.status}`
1189
- );
1190
- }
1191
- } catch (jsonError) {
1192
- // If we can't parse as JSON, try to get text
1193
- try {
1194
- const textResponse =
1195
- response.clone();
1196
- const errorText =
1197
- await textResponse.text();
1198
-
1199
- // Check if the text looks like JSON but wasn't parsed correctly
1200
- if (
1201
- errorText.includes(
1202
- '"error":'
1203
- )
1204
- ) {
1205
- // Try to extract the error message with regex
1206
- const errorMatch =
1207
- errorText.match(
1208
- /"error":"([^"]+)"/i
1209
- );
1210
- if (
1211
- errorMatch &&
1212
- errorMatch[1]
1213
- ) {
1214
- throw new Error(
1215
- errorMatch[1]
1216
- );
1217
- }
1218
- }
1219
-
1220
- // If we got text but couldn't extract a specific error
1221
- if (
1222
- errorText &&
1223
- errorText.length < 100
1224
- ) {
1225
- throw new Error(
1226
- errorText
1227
- );
1228
- }
1229
- } catch (textError) {
1230
- // If all else fails, fall back to status
1231
- throw new Error(
1232
- `HTTP error! Status: ${response.status}`
1233
- );
1234
- }
1235
- }
1236
- }
1237
- 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;
1238
1147
  })
1239
1148
  .then((blob) => {
1240
1149
  // Validate that we have a valid blob with content
@@ -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,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
  }
@@ -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;