@visns-studio/visns-components 4.10.48 → 5.0.1-8.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 +29 -29
- package/src/components/cms/Field.jsx +39 -63
- package/src/components/crm/AsyncSelect.jsx +121 -15
- package/src/components/crm/Breadcrumb.jsx +2 -18
- package/src/components/crm/DataGrid.jsx +51 -424
- package/src/components/crm/Field.jsx +185 -358
- package/src/components/crm/Form.jsx +104 -92
- package/src/components/crm/MultiSelect.jsx +66 -45
- package/src/components/crm/Navigation.jsx +9 -92
- package/src/components/crm/QuickAction.jsx +1 -0
- package/src/components/crm/auth/Login.jsx +9 -1
- package/src/components/crm/auth/Profile.jsx +4 -2
- package/src/components/crm/auth/styles/Login.module.scss +52 -82
- package/src/components/crm/auth/styles/Profile.module.scss +150 -44
- package/src/components/crm/auth/styles/Reset.module.scss +55 -37
- package/src/components/crm/auth/styles/Verify.module.scss +55 -76
- package/src/components/crm/generic/GenericAuth.jsx +12 -5
- package/src/components/crm/generic/GenericDashboard.jsx +30 -340
- package/src/components/crm/generic/GenericDetail.jsx +534 -86
- package/src/components/crm/generic/GenericDynamic.jsx +76 -94
- package/src/components/crm/generic/GenericEditableTable.jsx +407 -0
- package/src/components/crm/generic/GenericFormBuilder.jsx +19 -79
- package/src/components/crm/generic/GenericIndex.jsx +1 -2
- package/src/components/crm/generic/styles/AuditLog.module.scss +4 -37
- package/src/components/crm/generic/styles/AuditLogs.module.scss +0 -51
- package/src/components/crm/generic/styles/GenericDetail.module.scss +263 -244
- package/src/components/crm/generic/styles/GenericDynamic.module.scss +100 -198
- package/src/components/crm/generic/styles/GenericEditableTable.module.scss +178 -0
- package/src/components/crm/generic/styles/GenericFormBuilder.module.scss +63 -101
- package/src/components/crm/generic/styles/GenericIndex.module.scss +46 -66
- package/src/components/crm/generic/styles/GenericMain.module.scss +7 -7
- package/src/components/crm/generic/styles/GenericSort.module.scss +0 -36
- package/src/components/crm/generic/styles/NotificationList.module.scss +1 -32
- package/src/components/crm/styles/Autocomplete.module.scss +12 -20
- package/src/components/crm/styles/DataGrid.module.scss +28 -86
- package/src/components/crm/styles/Field.module.scss +72 -252
- package/src/components/crm/styles/Form.module.scss +141 -450
- package/src/components/crm/styles/Navigation.module.scss +356 -507
- package/src/components/crm/styles/Notification.module.scss +1 -0
- package/src/components/crm/styles/QrCode.module.scss +4 -18
- package/src/components/crm/styles/SwitchAccount.module.scss +0 -1
- package/src/components/crm/styles/TableFilter.module.scss +2 -1
- package/src/components/styles/global.css +164 -34
- package/src/index.js +0 -8
- package/src/components/crm/generic/styles/GenericDashboard.module.scss +0 -325
- package/src/components/crm/sketch/SketchField.jsx +0 -395
- package/src/components/crm/sketch/arrow.jsx +0 -108
- package/src/components/crm/sketch/circle.jsx +0 -75
- package/src/components/crm/sketch/default-tool.jsx +0 -16
- package/src/components/crm/sketch/fabrictool.jsx +0 -22
- package/src/components/crm/sketch/history.jsx +0 -144
- package/src/components/crm/sketch/json/config.json +0 -14
- package/src/components/crm/sketch/line.jsx +0 -64
- package/src/components/crm/sketch/pan.jsx +0 -48
- package/src/components/crm/sketch/pencil.jsx +0 -36
- package/src/components/crm/sketch/rectangle-label-object.jsx +0 -69
- package/src/components/crm/sketch/rectangle-label.jsx +0 -93
- package/src/components/crm/sketch/rectangle.jsx +0 -76
- package/src/components/crm/sketch/select.jsx +0 -16
- package/src/components/crm/sketch/tools.jsx +0 -11
- package/src/components/crm/sketch/utils.jsx +0 -67
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import '../../styles/global.css';
|
|
2
2
|
|
|
3
|
-
import React, {
|
|
3
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { Link, useParams } from 'react-router-dom';
|
|
5
|
+
import { Calendar, momentLocalizer } from 'react-big-calendar';
|
|
5
6
|
import moment from 'moment';
|
|
6
7
|
import parse from 'html-react-parser';
|
|
7
8
|
import Popup from 'reactjs-popup';
|
|
@@ -10,6 +11,7 @@ import Dropzone from 'react-dropzone';
|
|
|
10
11
|
import { confirmAlert } from 'react-confirm-alert';
|
|
11
12
|
import truncate from 'truncate';
|
|
12
13
|
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
|
14
|
+
import { saveAs } from 'file-saver';
|
|
13
15
|
import { toast } from 'react-toastify';
|
|
14
16
|
import {
|
|
15
17
|
CircleCheck,
|
|
@@ -17,28 +19,32 @@ import {
|
|
|
17
19
|
EyeOpen,
|
|
18
20
|
EyeSlashed,
|
|
19
21
|
File,
|
|
22
|
+
Pencil,
|
|
20
23
|
TrashCan,
|
|
21
24
|
} from 'akar-icons';
|
|
22
25
|
|
|
23
26
|
import 'react-confirm-alert/src/react-confirm-alert.css';
|
|
27
|
+
import 'react-big-calendar/lib/css/react-big-calendar.css';
|
|
24
28
|
|
|
25
29
|
import Breadcrumb from '../Breadcrumb';
|
|
26
30
|
import CustomFetch from '../Fetch';
|
|
27
31
|
import Form from '../Form';
|
|
28
32
|
import GenericDynamic from './GenericDynamic';
|
|
33
|
+
import GenericEditableTable from './GenericEditableTable';
|
|
34
|
+
import MultiSelect from '../MultiSelect';
|
|
29
35
|
import QrCode from '../QrCode';
|
|
30
|
-
import SortableList from '../sorting/List';
|
|
31
36
|
import Table from '../DataGrid';
|
|
32
37
|
import TableFilter from '../TableFilter';
|
|
33
38
|
|
|
34
39
|
import styles from './styles/GenericDetail.module.scss';
|
|
35
40
|
|
|
41
|
+
const localizer = momentLocalizer(moment);
|
|
42
|
+
|
|
36
43
|
function GenericDetail({
|
|
37
44
|
extraUrlParam,
|
|
38
45
|
layout = 'full',
|
|
39
46
|
setting,
|
|
40
47
|
setActiveNav = null,
|
|
41
|
-
themeType,
|
|
42
48
|
urlParam,
|
|
43
49
|
userProfile,
|
|
44
50
|
}) {
|
|
@@ -54,21 +60,156 @@ function GenericDetail({
|
|
|
54
60
|
|
|
55
61
|
/** General States */
|
|
56
62
|
const [activeStage, setActiveStage] = useState('');
|
|
63
|
+
const [activeTabConfig, setActiveTabConfig] = useState(null);
|
|
57
64
|
const [config, setConfig] = useState({});
|
|
58
65
|
const [data, setData] = useState({});
|
|
59
66
|
const [dataReload, setDataReload] = useState(0);
|
|
67
|
+
const [passwordVisible, setPasswordVisible] = useState(false);
|
|
68
|
+
const [preloadedOptions, setPreloadedOptions] = useState({});
|
|
60
69
|
const [rowsSelected, setRowsSelected] = useState({});
|
|
61
70
|
const [subnav, setSubnav] = useState([]);
|
|
62
71
|
const [total, setTotal] = useState(0);
|
|
63
72
|
const [windowHeight, setWindowHeight] = useState(window.innerHeight);
|
|
64
73
|
|
|
65
|
-
|
|
74
|
+
/** Ocr States */
|
|
75
|
+
const [ocrRowsSelected, setOcrRowsSelected] = useState('');
|
|
76
|
+
|
|
77
|
+
/** Ocr Template Functionalities */
|
|
78
|
+
const handleOcrFileChange = async (e) => {
|
|
79
|
+
try {
|
|
80
|
+
const id = e.target.getAttribute('data-id');
|
|
81
|
+
|
|
82
|
+
const formData = new FormData();
|
|
83
|
+
formData.append('file', e.target.files[0]);
|
|
84
|
+
|
|
85
|
+
// Show a loading toast when the file is being processed
|
|
86
|
+
const loadingToast = toast.loading(
|
|
87
|
+
'Processing file... Please wait!'
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const csrfToken = document
|
|
91
|
+
.querySelector('meta[name="csrf-token"]')
|
|
92
|
+
.getAttribute('content');
|
|
93
|
+
const response = await fetch('/ajax/ocr/analyzeFile', {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: { 'X-CSRF-TOKEN': csrfToken },
|
|
96
|
+
body: formData,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (response.ok) {
|
|
100
|
+
const result = await response.json();
|
|
101
|
+
setData((prevState) => ({
|
|
102
|
+
...prevState,
|
|
103
|
+
[id]: result,
|
|
104
|
+
}));
|
|
105
|
+
toast.update(loadingToast, {
|
|
106
|
+
render: 'File processed successfully!',
|
|
107
|
+
type: 'success',
|
|
108
|
+
isLoading: false,
|
|
109
|
+
autoClose: 3000,
|
|
110
|
+
});
|
|
111
|
+
} else {
|
|
112
|
+
toast.update(loadingToast, {
|
|
113
|
+
render: 'Error processing file.',
|
|
114
|
+
type: 'error',
|
|
115
|
+
isLoading: false,
|
|
116
|
+
autoClose: 3000,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error(error);
|
|
121
|
+
toast.update(loadingToast, {
|
|
122
|
+
render: 'Something went wrong. Please try again.',
|
|
123
|
+
type: 'error',
|
|
124
|
+
isLoading: false,
|
|
125
|
+
autoClose: 3000,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const handleOcrKeyChange = (e) => {
|
|
131
|
+
const {
|
|
132
|
+
value,
|
|
133
|
+
dataset: { id },
|
|
134
|
+
} = e.target;
|
|
135
|
+
|
|
136
|
+
let selectedKey = data[id].selectedKey || [];
|
|
137
|
+
|
|
138
|
+
if (selectedKey.includes(value)) {
|
|
139
|
+
selectedKey = selectedKey.filter((key) => key !== value);
|
|
140
|
+
} else {
|
|
141
|
+
selectedKey.push(value);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
setData((prevState) => ({
|
|
145
|
+
...prevState,
|
|
146
|
+
[id]: {
|
|
147
|
+
...prevState[id],
|
|
148
|
+
selectedKey,
|
|
149
|
+
},
|
|
150
|
+
}));
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const handleSelectedOcrKey = (value, action, id) => {
|
|
154
|
+
setOcrRowsSelected(value.value);
|
|
155
|
+
|
|
156
|
+
if (value.value) {
|
|
157
|
+
setData((prevState) => ({
|
|
158
|
+
...prevState,
|
|
159
|
+
[id]: {
|
|
160
|
+
...prevState[id],
|
|
161
|
+
dynamicFilename: `${prevState[id].dynamicFilename}_{{ ${value.value} }}`,
|
|
162
|
+
},
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const handleOcrInputChange = (e) => {
|
|
168
|
+
const {
|
|
169
|
+
name,
|
|
170
|
+
value,
|
|
171
|
+
dataset: { id },
|
|
172
|
+
} = e.target;
|
|
173
|
+
|
|
174
|
+
switch (name) {
|
|
175
|
+
case 'dynamicFilename':
|
|
176
|
+
setData((prevState) => ({
|
|
177
|
+
...prevState,
|
|
178
|
+
[id]: {
|
|
179
|
+
...prevState[id],
|
|
180
|
+
dynamicFilename: value,
|
|
181
|
+
},
|
|
182
|
+
}));
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const handleOcrTemplateSave = async (id) => {
|
|
188
|
+
try {
|
|
189
|
+
const res = await CustomFetch(
|
|
190
|
+
`${activeTabConfig.form.url}/${data.id}`,
|
|
191
|
+
'PUT',
|
|
192
|
+
{
|
|
193
|
+
...data,
|
|
194
|
+
}
|
|
195
|
+
);
|
|
66
196
|
|
|
197
|
+
if (res.data.error === '') {
|
|
198
|
+
toast.success('Data saved successfully');
|
|
199
|
+
handleReload();
|
|
200
|
+
} else {
|
|
201
|
+
toast.error(res.data.error);
|
|
202
|
+
}
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error(error);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
/** General Functions */
|
|
67
209
|
const togglePasswordVisibility = () => {
|
|
68
210
|
setPasswordVisible(!passwordVisible);
|
|
69
211
|
};
|
|
70
212
|
|
|
71
|
-
/** General Functions */
|
|
72
213
|
const handleStageUpdate = async (stage, type) => {
|
|
73
214
|
try {
|
|
74
215
|
const activeTab = subnav.find((s) => s.show === true);
|
|
@@ -96,6 +237,32 @@ function GenericDetail({
|
|
|
96
237
|
|
|
97
238
|
if (res.data.error === '') {
|
|
98
239
|
handleReload();
|
|
240
|
+
toast.success('Stage updated successfully');
|
|
241
|
+
} else {
|
|
242
|
+
toast.error(res.data.error);
|
|
243
|
+
}
|
|
244
|
+
} else if (
|
|
245
|
+
stages &&
|
|
246
|
+
stages.url &&
|
|
247
|
+
stages.url !== '' &&
|
|
248
|
+
stages.key &&
|
|
249
|
+
stages.key !== '' &&
|
|
250
|
+
stages.urlParam &&
|
|
251
|
+
stages.urlParam !== ''
|
|
252
|
+
) {
|
|
253
|
+
const url = `${stages.url}/${routeParams[urlParam]}`;
|
|
254
|
+
|
|
255
|
+
const res = await CustomFetch(
|
|
256
|
+
url,
|
|
257
|
+
stages.method || 'POST',
|
|
258
|
+
{
|
|
259
|
+
[stages.key]: stage.id,
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
if (res.data.error === '') {
|
|
264
|
+
handleReload();
|
|
265
|
+
toast.success('Stage updated successfully');
|
|
99
266
|
} else {
|
|
100
267
|
toast.error(res.data.error);
|
|
101
268
|
}
|
|
@@ -107,6 +274,14 @@ function GenericDetail({
|
|
|
107
274
|
}
|
|
108
275
|
};
|
|
109
276
|
|
|
277
|
+
const handleExport = (config) => {
|
|
278
|
+
if (config.url && config.key) {
|
|
279
|
+
window.open(`${config.url}/${data[config.key]}`);
|
|
280
|
+
} else {
|
|
281
|
+
toast.error('Export URL not found');
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
110
285
|
const handleCheckboxUpdate = async () => {
|
|
111
286
|
try {
|
|
112
287
|
if (currentConfigRef.current.functions.checkboxUpdate) {
|
|
@@ -192,7 +367,6 @@ function GenericDetail({
|
|
|
192
367
|
relationKey,
|
|
193
368
|
size,
|
|
194
369
|
truncateLength,
|
|
195
|
-
checkboxLabel,
|
|
196
370
|
} = item;
|
|
197
371
|
const value =
|
|
198
372
|
relation && relation.length > 0 && type !== 'total'
|
|
@@ -265,22 +439,16 @@ function GenericDetail({
|
|
|
265
439
|
return '';
|
|
266
440
|
}
|
|
267
441
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
442
|
+
const formatKey = (key) => {
|
|
443
|
+
return key
|
|
444
|
+
.toLowerCase()
|
|
445
|
+
.split('_')
|
|
446
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
447
|
+
.join(' ');
|
|
448
|
+
};
|
|
275
449
|
|
|
276
450
|
const keys = Object.keys(value)
|
|
277
|
-
.filter(
|
|
278
|
-
(key) =>
|
|
279
|
-
(typeof value[key] === 'boolean' ||
|
|
280
|
-
typeof value[key] === 'string') &&
|
|
281
|
-
value[key] &&
|
|
282
|
-
key !== 'notes'
|
|
283
|
-
)
|
|
451
|
+
.filter((key) => value[key] && key !== 'notes')
|
|
284
452
|
.sort((a, b) => {
|
|
285
453
|
if (a === 'other') return 1;
|
|
286
454
|
if (b === 'other') return -1;
|
|
@@ -288,44 +456,48 @@ function GenericDetail({
|
|
|
288
456
|
});
|
|
289
457
|
|
|
290
458
|
const formattedKeys = keys.map((key) => {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
459
|
+
let formattedKey = formatKey(key);
|
|
460
|
+
if (key === 'other') {
|
|
461
|
+
const otherValue = getValueFromData(
|
|
462
|
+
data,
|
|
463
|
+
[...relation],
|
|
464
|
+
`${id}_other`,
|
|
465
|
+
type
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
if (otherValue) {
|
|
469
|
+
formattedKey += `: ${otherValue}`;
|
|
470
|
+
}
|
|
297
471
|
}
|
|
298
|
-
|
|
299
|
-
// Return the label for boolean values
|
|
300
|
-
return label;
|
|
472
|
+
return formattedKey;
|
|
301
473
|
});
|
|
302
474
|
|
|
303
475
|
return formattedKeys.join(', ');
|
|
304
476
|
};
|
|
305
477
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
478
|
+
const renderCurrency = () => {
|
|
479
|
+
// Check if the value is a valid number
|
|
480
|
+
const parsedValue = parseFloat(value);
|
|
481
|
+
if (isNaN(parsedValue)) return ''; // Return empty string if invalid
|
|
482
|
+
|
|
483
|
+
const formatter = new Intl.NumberFormat('en-AU', {
|
|
484
|
+
style: 'currency',
|
|
485
|
+
currency: 'AUD',
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
return formatter.format(parsedValue);
|
|
313
489
|
};
|
|
314
490
|
|
|
315
491
|
const renderDate = () => formatDateValue(value);
|
|
316
492
|
|
|
317
493
|
const renderDateTime = () => formatDateTimeValue(value);
|
|
318
494
|
|
|
319
|
-
const renderFile = () =>
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
return isValidUrl ? (
|
|
324
|
-
<a href={value} target="_blank" rel="noopener noreferrer">
|
|
495
|
+
const renderFile = () =>
|
|
496
|
+
value ? (
|
|
497
|
+
<a href={value} target="_blank">
|
|
325
498
|
Download
|
|
326
499
|
</a>
|
|
327
500
|
) : null;
|
|
328
|
-
};
|
|
329
501
|
|
|
330
502
|
const renderExist = () => (truncatedValue ? 'Yes' : 'No');
|
|
331
503
|
|
|
@@ -403,6 +575,94 @@ function GenericDetail({
|
|
|
403
575
|
}
|
|
404
576
|
};
|
|
405
577
|
|
|
578
|
+
const renderOcrTemplate = (id) => {
|
|
579
|
+
const ocrTemplateBuilder = data[id];
|
|
580
|
+
|
|
581
|
+
return (
|
|
582
|
+
<>
|
|
583
|
+
<input
|
|
584
|
+
type="file"
|
|
585
|
+
data-id={id}
|
|
586
|
+
onChange={handleOcrFileChange}
|
|
587
|
+
/>
|
|
588
|
+
|
|
589
|
+
{ocrTemplateBuilder &&
|
|
590
|
+
ocrTemplateBuilder.DeducedData &&
|
|
591
|
+
ocrTemplateBuilder.DeducedData.length > 0 && (
|
|
592
|
+
<table className={styles.schedulingTable}>
|
|
593
|
+
<thead>
|
|
594
|
+
<tr>
|
|
595
|
+
<th></th>
|
|
596
|
+
<th>Key</th>
|
|
597
|
+
<th>Value</th>
|
|
598
|
+
</tr>
|
|
599
|
+
</thead>
|
|
600
|
+
<tbody>
|
|
601
|
+
{Object.entries(
|
|
602
|
+
ocrTemplateBuilder.DeducedData[0]
|
|
603
|
+
).map(([key, value], index) => (
|
|
604
|
+
<tr key={`ocr-keys-${index}`}>
|
|
605
|
+
<td>
|
|
606
|
+
<input
|
|
607
|
+
type="checkbox"
|
|
608
|
+
name={key}
|
|
609
|
+
value={key}
|
|
610
|
+
data-id={id}
|
|
611
|
+
checked={
|
|
612
|
+
ocrTemplateBuilder?.selectedKey?.includes(
|
|
613
|
+
key
|
|
614
|
+
) || false
|
|
615
|
+
}
|
|
616
|
+
onChange={
|
|
617
|
+
handleOcrKeyChange
|
|
618
|
+
}
|
|
619
|
+
/>
|
|
620
|
+
</td>
|
|
621
|
+
<td>{key}</td>
|
|
622
|
+
<td>{value}</td>
|
|
623
|
+
</tr>
|
|
624
|
+
))}
|
|
625
|
+
</tbody>
|
|
626
|
+
</table>
|
|
627
|
+
)}
|
|
628
|
+
|
|
629
|
+
{ocrTemplateBuilder &&
|
|
630
|
+
ocrTemplateBuilder.selectedKey &&
|
|
631
|
+
ocrTemplateBuilder.selectedKey.length > 0 && (
|
|
632
|
+
<>
|
|
633
|
+
<strong>Selected Keys</strong>
|
|
634
|
+
<MultiSelect
|
|
635
|
+
className=""
|
|
636
|
+
inputValue={ocrRowsSelected}
|
|
637
|
+
multi={false}
|
|
638
|
+
onChange={handleSelectedOcrKey}
|
|
639
|
+
options={ocrTemplateBuilder.selectedKey.map(
|
|
640
|
+
(a) => ({
|
|
641
|
+
label: a,
|
|
642
|
+
value: a,
|
|
643
|
+
})
|
|
644
|
+
)}
|
|
645
|
+
placeholder="Select a Key to use"
|
|
646
|
+
settings={{ id: id }}
|
|
647
|
+
style={{}}
|
|
648
|
+
isCreatable={false}
|
|
649
|
+
creatableConfig={{}}
|
|
650
|
+
/>
|
|
651
|
+
<br />
|
|
652
|
+
<strong>File Name</strong>
|
|
653
|
+
<input
|
|
654
|
+
type="text"
|
|
655
|
+
value={data[id]?.dynamicFilename || ''}
|
|
656
|
+
data-id={id}
|
|
657
|
+
onChange={handleOcrInputChange}
|
|
658
|
+
name="dynamicFilename"
|
|
659
|
+
/>
|
|
660
|
+
</>
|
|
661
|
+
)}
|
|
662
|
+
</>
|
|
663
|
+
);
|
|
664
|
+
};
|
|
665
|
+
|
|
406
666
|
// Render based on the item's type
|
|
407
667
|
switch (type) {
|
|
408
668
|
case 'boolean':
|
|
@@ -411,6 +671,8 @@ function GenericDetail({
|
|
|
411
671
|
return renderCheckbox();
|
|
412
672
|
case 'copyToClipboard':
|
|
413
673
|
return renderCopyToClipboard();
|
|
674
|
+
case 'currency':
|
|
675
|
+
return renderCurrency();
|
|
414
676
|
case 'date':
|
|
415
677
|
return renderDate();
|
|
416
678
|
case 'datetime':
|
|
@@ -425,6 +687,8 @@ function GenericDetail({
|
|
|
425
687
|
return renderLatestNotes();
|
|
426
688
|
case 'link':
|
|
427
689
|
return renderLink();
|
|
690
|
+
case 'ocrTemplate':
|
|
691
|
+
return renderOcrTemplate(id);
|
|
428
692
|
case 'options':
|
|
429
693
|
return renderOptions();
|
|
430
694
|
case 'password':
|
|
@@ -452,11 +716,82 @@ function GenericDetail({
|
|
|
452
716
|
|
|
453
717
|
if (activeTabConfig && activeTabConfig.type) {
|
|
454
718
|
switch (activeTabConfig.type) {
|
|
719
|
+
case 'calendar':
|
|
720
|
+
const events =
|
|
721
|
+
data?.[activeTabConfig?.dataKey]
|
|
722
|
+
?.map((event) => {
|
|
723
|
+
// Safely access keys using optional chaining and defaults
|
|
724
|
+
const keys =
|
|
725
|
+
activeTabConfig?.config?.keys || {};
|
|
726
|
+
const eventStart = event?.[keys.start]
|
|
727
|
+
? moment(event[keys.start])
|
|
728
|
+
: null;
|
|
729
|
+
const eventEnd = event?.[keys.end]
|
|
730
|
+
? moment(event[keys.end])
|
|
731
|
+
: null;
|
|
732
|
+
|
|
733
|
+
// Only include valid events
|
|
734
|
+
if (eventStart && eventEnd) {
|
|
735
|
+
return {
|
|
736
|
+
title:
|
|
737
|
+
event?.[keys.title] ||
|
|
738
|
+
'Untitled Event', // Default title if not provided
|
|
739
|
+
start: eventStart,
|
|
740
|
+
end: eventEnd,
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
return null; // Filter out invalid events
|
|
745
|
+
})
|
|
746
|
+
?.filter(Boolean) || [];
|
|
747
|
+
|
|
748
|
+
return (
|
|
749
|
+
<Calendar
|
|
750
|
+
localizer={localizer}
|
|
751
|
+
events={events}
|
|
752
|
+
startAccessor="start"
|
|
753
|
+
endAccessor="end"
|
|
754
|
+
style={{
|
|
755
|
+
height:
|
|
756
|
+
activeTabConfig.config.height ||
|
|
757
|
+
500,
|
|
758
|
+
}}
|
|
759
|
+
views={
|
|
760
|
+
activeTabConfig.config.views || [
|
|
761
|
+
'month',
|
|
762
|
+
'week',
|
|
763
|
+
'day',
|
|
764
|
+
]
|
|
765
|
+
}
|
|
766
|
+
/>
|
|
767
|
+
);
|
|
455
768
|
case 'dropzone':
|
|
456
|
-
const downloadFile = (
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
769
|
+
const downloadFile = (file) => {
|
|
770
|
+
const { id, file_url, file_name } = file;
|
|
771
|
+
|
|
772
|
+
// If file_url exists, use it for the download, else use the id to download
|
|
773
|
+
const urlToDownload =
|
|
774
|
+
file_url || `/ajax/files/download/${id}`;
|
|
775
|
+
|
|
776
|
+
// Use fetch to retrieve the file as a blob
|
|
777
|
+
fetch(urlToDownload, {
|
|
778
|
+
method: 'GET',
|
|
779
|
+
headers: {
|
|
780
|
+
'Content-Type':
|
|
781
|
+
'application/octet-stream', // Specify content type as binary
|
|
782
|
+
},
|
|
783
|
+
})
|
|
784
|
+
.then((response) => response.blob()) // Convert response to Blob
|
|
785
|
+
.then((blob) => {
|
|
786
|
+
// Use FileSaver to download the file with the specified name
|
|
787
|
+
saveAs(blob, file_name);
|
|
788
|
+
})
|
|
789
|
+
.catch((error) => {
|
|
790
|
+
console.error(
|
|
791
|
+
'Error downloading the file:',
|
|
792
|
+
error
|
|
793
|
+
);
|
|
794
|
+
});
|
|
460
795
|
};
|
|
461
796
|
|
|
462
797
|
const deleteFile = (e, id, name) => {
|
|
@@ -538,9 +873,13 @@ function GenericDetail({
|
|
|
538
873
|
key: response.key,
|
|
539
874
|
bucket: response.bucket,
|
|
540
875
|
filename: file.name,
|
|
876
|
+
file_name: file.name,
|
|
541
877
|
filesize: file.size,
|
|
878
|
+
file_size: file.size,
|
|
542
879
|
extension:
|
|
543
880
|
response.extension,
|
|
881
|
+
file_extension:
|
|
882
|
+
response.extension,
|
|
544
883
|
file_relationship:
|
|
545
884
|
activeTabConfig.file_relationship,
|
|
546
885
|
};
|
|
@@ -550,9 +889,30 @@ function GenericDetail({
|
|
|
550
889
|
'PUT',
|
|
551
890
|
requestBody,
|
|
552
891
|
(res) => {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
892
|
+
if (
|
|
893
|
+
activeTabConfig.file_relationship &&
|
|
894
|
+
res.data[
|
|
895
|
+
activeTabConfig
|
|
896
|
+
.file_relationship
|
|
897
|
+
]
|
|
898
|
+
) {
|
|
899
|
+
setFiles(
|
|
900
|
+
res.data[
|
|
901
|
+
activeTabConfig
|
|
902
|
+
.file_relationship
|
|
903
|
+
]
|
|
904
|
+
);
|
|
905
|
+
} else {
|
|
906
|
+
if (
|
|
907
|
+
res?.data
|
|
908
|
+
?.files
|
|
909
|
+
) {
|
|
910
|
+
setFiles(
|
|
911
|
+
res.data
|
|
912
|
+
.files
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
556
916
|
}
|
|
557
917
|
);
|
|
558
918
|
});
|
|
@@ -652,7 +1012,7 @@ function GenericDetail({
|
|
|
652
1012
|
<li
|
|
653
1013
|
onClick={() => {
|
|
654
1014
|
downloadFile(
|
|
655
|
-
a
|
|
1015
|
+
a
|
|
656
1016
|
);
|
|
657
1017
|
}}
|
|
658
1018
|
key={'tf-' + b}
|
|
@@ -979,34 +1339,66 @@ function GenericDetail({
|
|
|
979
1339
|
)}
|
|
980
1340
|
</div>
|
|
981
1341
|
|
|
982
|
-
{
|
|
983
|
-
activeTabConfig.export) && (
|
|
1342
|
+
{activeTabConfig.form && (
|
|
984
1343
|
<div className={styles.polActions}>
|
|
985
|
-
{activeTabConfig.form
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1344
|
+
{/* Always render the "Edit" button if activeTabConfig.form exists */}
|
|
1345
|
+
<button
|
|
1346
|
+
className={styles.btn}
|
|
1347
|
+
onClick={() =>
|
|
1348
|
+
modalOpen(
|
|
1349
|
+
'update',
|
|
1350
|
+
routeParams[urlParam]
|
|
1351
|
+
)
|
|
1352
|
+
}
|
|
1353
|
+
>
|
|
1354
|
+
Edit
|
|
1355
|
+
</button>
|
|
1356
|
+
|
|
1357
|
+
{/* Check if sections contain "ocrTemplate" type and render the "Save" button under the same polActions */}
|
|
1358
|
+
{activeTabConfig.sections &&
|
|
1359
|
+
activeTabConfig.sections
|
|
1360
|
+
.length > 0 &&
|
|
1361
|
+
activeTabConfig.sections.map(
|
|
1362
|
+
(section, sectionIndex) =>
|
|
1363
|
+
section.content &&
|
|
1364
|
+
section.content.some(
|
|
1365
|
+
(item) =>
|
|
1366
|
+
item.type ===
|
|
1367
|
+
'ocrTemplate'
|
|
1368
|
+
) && (
|
|
1369
|
+
<button
|
|
1370
|
+
key={`save-button-${sectionIndex}`}
|
|
1371
|
+
className={
|
|
1372
|
+
styles.btn
|
|
1373
|
+
}
|
|
1374
|
+
onClick={() => {
|
|
1375
|
+
// Handle the specific "ocrTemplate" item
|
|
1376
|
+
const ocrItem =
|
|
1377
|
+
section.content.find(
|
|
1378
|
+
(
|
|
1379
|
+
item
|
|
1380
|
+
) =>
|
|
1381
|
+
item.type ===
|
|
1382
|
+
'ocrTemplate'
|
|
1383
|
+
);
|
|
1384
|
+
handleOcrTemplateSave(
|
|
1385
|
+
ocrItem.id
|
|
1386
|
+
); // Now you can safely use ocrItem.id
|
|
1387
|
+
}}
|
|
1388
|
+
>
|
|
1389
|
+
Save
|
|
1390
|
+
</button>
|
|
994
1391
|
)
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
</button>
|
|
999
|
-
)}
|
|
1392
|
+
)}
|
|
1393
|
+
|
|
1394
|
+
{/* Check if export exists and render the "Export" button under the same polActions */}
|
|
1000
1395
|
{activeTabConfig.export && (
|
|
1001
1396
|
<button
|
|
1002
1397
|
className={styles.btn}
|
|
1003
1398
|
onClick={() =>
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
'_blank'
|
|
1008
|
-
)
|
|
1009
|
-
: (window.location.href = `${activeTabConfig.export.url}/${routeParams[urlParam]}`)
|
|
1399
|
+
handleExport(
|
|
1400
|
+
activeTabConfig.export
|
|
1401
|
+
)
|
|
1010
1402
|
}
|
|
1011
1403
|
>
|
|
1012
1404
|
Export
|
|
@@ -1244,6 +1636,16 @@ function GenericDetail({
|
|
|
1244
1636
|
} else {
|
|
1245
1637
|
return null;
|
|
1246
1638
|
}
|
|
1639
|
+
case 'scheduling':
|
|
1640
|
+
return (
|
|
1641
|
+
<GenericEditableTable
|
|
1642
|
+
schedulingConfig={activeTabConfig}
|
|
1643
|
+
data={data}
|
|
1644
|
+
dataId={routeParams[urlParam]}
|
|
1645
|
+
modalOpen={modalOpen}
|
|
1646
|
+
preloadedOptions={preloadedOptions}
|
|
1647
|
+
/>
|
|
1648
|
+
);
|
|
1247
1649
|
default:
|
|
1248
1650
|
return null;
|
|
1249
1651
|
}
|
|
@@ -1253,6 +1655,49 @@ function GenericDetail({
|
|
|
1253
1655
|
};
|
|
1254
1656
|
|
|
1255
1657
|
/** General Hooks */
|
|
1658
|
+
useEffect(() => {
|
|
1659
|
+
if (tabs && tabs.length > 0) {
|
|
1660
|
+
const activeTab = subnav.find((s) => s.show === true);
|
|
1661
|
+
|
|
1662
|
+
if (activeTab) {
|
|
1663
|
+
const newActiveTabConfig = tabs.find(
|
|
1664
|
+
(t) => t.id === activeTab.id
|
|
1665
|
+
);
|
|
1666
|
+
setActiveTabConfig(newActiveTabConfig); // Update state
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
}, [subnav, tabs]);
|
|
1670
|
+
|
|
1671
|
+
useEffect(() => {
|
|
1672
|
+
const fetchDropdownOptions = async () => {
|
|
1673
|
+
if (activeTabConfig?.columns) {
|
|
1674
|
+
const ajaxColumns = activeTabConfig.columns.filter(
|
|
1675
|
+
(column) =>
|
|
1676
|
+
['dropdown-ajax', 'multi-dropdown-ajax'].includes(
|
|
1677
|
+
column.type
|
|
1678
|
+
) && column.url
|
|
1679
|
+
);
|
|
1680
|
+
|
|
1681
|
+
const options = {};
|
|
1682
|
+
for (const column of ajaxColumns) {
|
|
1683
|
+
try {
|
|
1684
|
+
const res = await CustomFetch(column.url, 'POST');
|
|
1685
|
+
options[column.id] = res.data.data || res.data || []; // Assume API returns options in the expected format
|
|
1686
|
+
} catch (error) {
|
|
1687
|
+
console.error(
|
|
1688
|
+
`Error fetching options for ${column.id}:`,
|
|
1689
|
+
error
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
setPreloadedOptions(options);
|
|
1695
|
+
}
|
|
1696
|
+
};
|
|
1697
|
+
|
|
1698
|
+
fetchDropdownOptions();
|
|
1699
|
+
}, [activeTabConfig]); // Depend on activeTabConfig
|
|
1700
|
+
|
|
1256
1701
|
useEffect(() => {
|
|
1257
1702
|
const handleResize = () => {
|
|
1258
1703
|
setWindowHeight(window.innerHeight);
|
|
@@ -1374,6 +1819,7 @@ function GenericDetail({
|
|
|
1374
1819
|
});
|
|
1375
1820
|
}
|
|
1376
1821
|
break;
|
|
1822
|
+
case 'scheduling':
|
|
1377
1823
|
case 'overview':
|
|
1378
1824
|
setConfig({});
|
|
1379
1825
|
if (activeTabConfig.form) {
|
|
@@ -1414,8 +1860,17 @@ function GenericDetail({
|
|
|
1414
1860
|
|
|
1415
1861
|
tabs.forEach((tab) => {
|
|
1416
1862
|
if (tab.type && tab.type === 'dropzone') {
|
|
1417
|
-
if (
|
|
1418
|
-
|
|
1863
|
+
if (
|
|
1864
|
+
tab.file_relationship &&
|
|
1865
|
+
tab.file_relationship !== ''
|
|
1866
|
+
) {
|
|
1867
|
+
if (res[tab.file_relationship]) {
|
|
1868
|
+
setFiles(res[tab.file_relationship]);
|
|
1869
|
+
}
|
|
1870
|
+
} else {
|
|
1871
|
+
if (res.files) {
|
|
1872
|
+
setFiles(res.files);
|
|
1873
|
+
}
|
|
1419
1874
|
}
|
|
1420
1875
|
}
|
|
1421
1876
|
});
|
|
@@ -1491,10 +1946,6 @@ function GenericDetail({
|
|
|
1491
1946
|
<div className={styles.oppstatus}>
|
|
1492
1947
|
<ul className={styles.opppath}>
|
|
1493
1948
|
{stages.data.map((stage) => {
|
|
1494
|
-
if (!stage.id) {
|
|
1495
|
-
return null; // Skip rendering this stage
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
1949
|
const isStageComplete =
|
|
1499
1950
|
data[stages.key] >= stage.id;
|
|
1500
1951
|
|
|
@@ -1536,13 +1987,7 @@ function GenericDetail({
|
|
|
1536
1987
|
|
|
1537
1988
|
<div className={styles.grid}>
|
|
1538
1989
|
<div className={styles.grid__subrow}>
|
|
1539
|
-
<div
|
|
1540
|
-
className={
|
|
1541
|
-
themeType === 'alternate'
|
|
1542
|
-
? styles.grid__subnav__alternate
|
|
1543
|
-
: styles.grid__subnav
|
|
1544
|
-
}
|
|
1545
|
-
>
|
|
1990
|
+
<div className={styles.grid__subnav}>
|
|
1546
1991
|
<TableFilter filters={subnav} setFilters={setSubnav} />
|
|
1547
1992
|
</div>
|
|
1548
1993
|
<div className={styles.grid__subcontent}>
|
|
@@ -1565,6 +2010,9 @@ function GenericDetail({
|
|
|
1565
2010
|
style={userProfile?.settings?.style || {}}
|
|
1566
2011
|
updateForm={setFormData}
|
|
1567
2012
|
userProfile={userProfile}
|
|
2013
|
+
paramValue={
|
|
2014
|
+
routeParams[urlParam] ? routeParams[urlParam] : 0
|
|
2015
|
+
}
|
|
1568
2016
|
/>
|
|
1569
2017
|
</Popup>
|
|
1570
2018
|
</>
|