@visns-studio/visns-components 5.0.0 → 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 +3 -1
- package/src/components/crm/AsyncSelect.jsx +121 -15
- package/src/components/crm/DataGrid.jsx +26 -5
- package/src/components/crm/Field.jsx +174 -148
- package/src/components/crm/Form.jsx +46 -33
- package/src/components/crm/MultiSelect.jsx +96 -29
- package/src/components/crm/Navigation.jsx +2 -81
- package/src/components/crm/QrCode.jsx +12 -8
- 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 +17 -6
- package/src/components/crm/generic/GenericDetail.jsx +509 -18
- package/src/components/crm/generic/GenericEditableTable.jsx +407 -0
- 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 -193
- 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 -96
- 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 +6 -89
- package/src/components/crm/styles/Field.module.scss +72 -42
- package/src/components/crm/styles/Form.module.scss +148 -379
- package/src/components/crm/styles/Navigation.module.scss +356 -501
- package/src/components/crm/styles/Notification.module.scss +1 -0
- package/src/components/crm/styles/QrCode.module.scss +4 -0
- 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
|
@@ -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,22 +19,27 @@ 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',
|
|
@@ -53,21 +60,156 @@ function GenericDetail({
|
|
|
53
60
|
|
|
54
61
|
/** General States */
|
|
55
62
|
const [activeStage, setActiveStage] = useState('');
|
|
63
|
+
const [activeTabConfig, setActiveTabConfig] = useState(null);
|
|
56
64
|
const [config, setConfig] = useState({});
|
|
57
65
|
const [data, setData] = useState({});
|
|
58
66
|
const [dataReload, setDataReload] = useState(0);
|
|
67
|
+
const [passwordVisible, setPasswordVisible] = useState(false);
|
|
68
|
+
const [preloadedOptions, setPreloadedOptions] = useState({});
|
|
59
69
|
const [rowsSelected, setRowsSelected] = useState({});
|
|
60
70
|
const [subnav, setSubnav] = useState([]);
|
|
61
71
|
const [total, setTotal] = useState(0);
|
|
62
72
|
const [windowHeight, setWindowHeight] = useState(window.innerHeight);
|
|
63
73
|
|
|
64
|
-
|
|
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
|
+
);
|
|
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
|
+
};
|
|
65
207
|
|
|
208
|
+
/** General Functions */
|
|
66
209
|
const togglePasswordVisibility = () => {
|
|
67
210
|
setPasswordVisible(!passwordVisible);
|
|
68
211
|
};
|
|
69
212
|
|
|
70
|
-
/** General Functions */
|
|
71
213
|
const handleStageUpdate = async (stage, type) => {
|
|
72
214
|
try {
|
|
73
215
|
const activeTab = subnav.find((s) => s.show === true);
|
|
@@ -95,6 +237,32 @@ function GenericDetail({
|
|
|
95
237
|
|
|
96
238
|
if (res.data.error === '') {
|
|
97
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');
|
|
98
266
|
} else {
|
|
99
267
|
toast.error(res.data.error);
|
|
100
268
|
}
|
|
@@ -106,6 +274,14 @@ function GenericDetail({
|
|
|
106
274
|
}
|
|
107
275
|
};
|
|
108
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
|
+
|
|
109
285
|
const handleCheckboxUpdate = async () => {
|
|
110
286
|
try {
|
|
111
287
|
if (currentConfigRef.current.functions.checkboxUpdate) {
|
|
@@ -299,6 +475,19 @@ function GenericDetail({
|
|
|
299
475
|
return formattedKeys.join(', ');
|
|
300
476
|
};
|
|
301
477
|
|
|
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);
|
|
489
|
+
};
|
|
490
|
+
|
|
302
491
|
const renderDate = () => formatDateValue(value);
|
|
303
492
|
|
|
304
493
|
const renderDateTime = () => formatDateTimeValue(value);
|
|
@@ -386,6 +575,94 @@ function GenericDetail({
|
|
|
386
575
|
}
|
|
387
576
|
};
|
|
388
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
|
+
|
|
389
666
|
// Render based on the item's type
|
|
390
667
|
switch (type) {
|
|
391
668
|
case 'boolean':
|
|
@@ -394,6 +671,8 @@ function GenericDetail({
|
|
|
394
671
|
return renderCheckbox();
|
|
395
672
|
case 'copyToClipboard':
|
|
396
673
|
return renderCopyToClipboard();
|
|
674
|
+
case 'currency':
|
|
675
|
+
return renderCurrency();
|
|
397
676
|
case 'date':
|
|
398
677
|
return renderDate();
|
|
399
678
|
case 'datetime':
|
|
@@ -408,6 +687,8 @@ function GenericDetail({
|
|
|
408
687
|
return renderLatestNotes();
|
|
409
688
|
case 'link':
|
|
410
689
|
return renderLink();
|
|
690
|
+
case 'ocrTemplate':
|
|
691
|
+
return renderOcrTemplate(id);
|
|
411
692
|
case 'options':
|
|
412
693
|
return renderOptions();
|
|
413
694
|
case 'password':
|
|
@@ -435,11 +716,82 @@ function GenericDetail({
|
|
|
435
716
|
|
|
436
717
|
if (activeTabConfig && activeTabConfig.type) {
|
|
437
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
|
+
);
|
|
438
768
|
case 'dropzone':
|
|
439
|
-
const downloadFile = (
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
+
});
|
|
443
795
|
};
|
|
444
796
|
|
|
445
797
|
const deleteFile = (e, id, name) => {
|
|
@@ -521,9 +873,13 @@ function GenericDetail({
|
|
|
521
873
|
key: response.key,
|
|
522
874
|
bucket: response.bucket,
|
|
523
875
|
filename: file.name,
|
|
876
|
+
file_name: file.name,
|
|
524
877
|
filesize: file.size,
|
|
878
|
+
file_size: file.size,
|
|
525
879
|
extension:
|
|
526
880
|
response.extension,
|
|
881
|
+
file_extension:
|
|
882
|
+
response.extension,
|
|
527
883
|
file_relationship:
|
|
528
884
|
activeTabConfig.file_relationship,
|
|
529
885
|
};
|
|
@@ -533,9 +889,30 @@ function GenericDetail({
|
|
|
533
889
|
'PUT',
|
|
534
890
|
requestBody,
|
|
535
891
|
(res) => {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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
|
+
}
|
|
539
916
|
}
|
|
540
917
|
);
|
|
541
918
|
});
|
|
@@ -635,7 +1012,7 @@ function GenericDetail({
|
|
|
635
1012
|
<li
|
|
636
1013
|
onClick={() => {
|
|
637
1014
|
downloadFile(
|
|
638
|
-
a
|
|
1015
|
+
a
|
|
639
1016
|
);
|
|
640
1017
|
}}
|
|
641
1018
|
key={'tf-' + b}
|
|
@@ -964,6 +1341,7 @@ function GenericDetail({
|
|
|
964
1341
|
|
|
965
1342
|
{activeTabConfig.form && (
|
|
966
1343
|
<div className={styles.polActions}>
|
|
1344
|
+
{/* Always render the "Edit" button if activeTabConfig.form exists */}
|
|
967
1345
|
<button
|
|
968
1346
|
className={styles.btn}
|
|
969
1347
|
onClick={() =>
|
|
@@ -975,6 +1353,57 @@ function GenericDetail({
|
|
|
975
1353
|
>
|
|
976
1354
|
Edit
|
|
977
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>
|
|
1391
|
+
)
|
|
1392
|
+
)}
|
|
1393
|
+
|
|
1394
|
+
{/* Check if export exists and render the "Export" button under the same polActions */}
|
|
1395
|
+
{activeTabConfig.export && (
|
|
1396
|
+
<button
|
|
1397
|
+
className={styles.btn}
|
|
1398
|
+
onClick={() =>
|
|
1399
|
+
handleExport(
|
|
1400
|
+
activeTabConfig.export
|
|
1401
|
+
)
|
|
1402
|
+
}
|
|
1403
|
+
>
|
|
1404
|
+
Export
|
|
1405
|
+
</button>
|
|
1406
|
+
)}
|
|
978
1407
|
</div>
|
|
979
1408
|
)}
|
|
980
1409
|
</>
|
|
@@ -1207,6 +1636,16 @@ function GenericDetail({
|
|
|
1207
1636
|
} else {
|
|
1208
1637
|
return null;
|
|
1209
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
|
+
);
|
|
1210
1649
|
default:
|
|
1211
1650
|
return null;
|
|
1212
1651
|
}
|
|
@@ -1216,6 +1655,49 @@ function GenericDetail({
|
|
|
1216
1655
|
};
|
|
1217
1656
|
|
|
1218
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
|
+
|
|
1219
1701
|
useEffect(() => {
|
|
1220
1702
|
const handleResize = () => {
|
|
1221
1703
|
setWindowHeight(window.innerHeight);
|
|
@@ -1337,6 +1819,7 @@ function GenericDetail({
|
|
|
1337
1819
|
});
|
|
1338
1820
|
}
|
|
1339
1821
|
break;
|
|
1822
|
+
case 'scheduling':
|
|
1340
1823
|
case 'overview':
|
|
1341
1824
|
setConfig({});
|
|
1342
1825
|
if (activeTabConfig.form) {
|
|
@@ -1377,8 +1860,17 @@ function GenericDetail({
|
|
|
1377
1860
|
|
|
1378
1861
|
tabs.forEach((tab) => {
|
|
1379
1862
|
if (tab.type && tab.type === 'dropzone') {
|
|
1380
|
-
if (
|
|
1381
|
-
|
|
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
|
+
}
|
|
1382
1874
|
}
|
|
1383
1875
|
}
|
|
1384
1876
|
});
|
|
@@ -1454,10 +1946,6 @@ function GenericDetail({
|
|
|
1454
1946
|
<div className={styles.oppstatus}>
|
|
1455
1947
|
<ul className={styles.opppath}>
|
|
1456
1948
|
{stages.data.map((stage) => {
|
|
1457
|
-
if (!stage.id) {
|
|
1458
|
-
return null; // Skip rendering this stage
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
1949
|
const isStageComplete =
|
|
1462
1950
|
data[stages.key] >= stage.id;
|
|
1463
1951
|
|
|
@@ -1522,6 +2010,9 @@ function GenericDetail({
|
|
|
1522
2010
|
style={userProfile?.settings?.style || {}}
|
|
1523
2011
|
updateForm={setFormData}
|
|
1524
2012
|
userProfile={userProfile}
|
|
2013
|
+
paramValue={
|
|
2014
|
+
routeParams[urlParam] ? routeParams[urlParam] : 0
|
|
2015
|
+
}
|
|
1525
2016
|
/>
|
|
1526
2017
|
</Popup>
|
|
1527
2018
|
</>
|