@visns-studio/visns-components 5.0.1-8.1 → 5.0.2
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 -3
- package/src/components/crm/AsyncSelect.jsx +15 -121
- package/src/components/crm/DataGrid.jsx +5 -25
- package/src/components/crm/Field.jsx +148 -174
- package/src/components/crm/Form.jsx +28 -41
- package/src/components/crm/MultiSelect.jsx +29 -96
- package/src/components/crm/Navigation.jsx +40 -2
- package/src/components/crm/QrCode.jsx +8 -12
- package/src/components/crm/auth/Login.jsx +1 -9
- package/src/components/crm/auth/styles/Login.module.scss +3 -4
- package/src/components/crm/auth/styles/Profile.module.scss +14 -56
- package/src/components/crm/generic/GenericAuth.jsx +2 -5
- package/src/components/crm/generic/GenericDetail.jsx +18 -509
- package/src/components/crm/generic/styles/AuditLog.module.scss +0 -4
- package/src/components/crm/generic/styles/GenericDetail.module.scss +121 -261
- package/src/components/crm/generic/styles/GenericDynamic.module.scss +67 -35
- package/src/components/crm/generic/styles/GenericFormBuilder.module.scss +43 -4
- package/src/components/crm/generic/styles/GenericIndex.module.scss +1 -1
- package/src/components/crm/styles/Autocomplete.module.scss +20 -12
- package/src/components/crm/styles/DataGrid.module.scss +3 -2
- package/src/components/crm/styles/Field.module.scss +13 -19
- package/src/components/crm/styles/Form.module.scss +5 -5
- package/src/components/crm/styles/Navigation.module.scss +61 -11
- package/src/components/crm/styles/QrCode.module.scss +0 -4
- package/src/components/styles/global.css +5 -95
- package/src/components/crm/generic/GenericEditableTable.jsx +0 -407
- package/src/components/crm/generic/styles/GenericEditableTable.module.scss +0 -178
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import '../../styles/global.css';
|
|
2
2
|
|
|
3
|
-
import React, { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { Link, useParams } from 'react-router-dom';
|
|
5
|
-
import { Calendar, momentLocalizer } from 'react-big-calendar';
|
|
6
5
|
import moment from 'moment';
|
|
7
6
|
import parse from 'html-react-parser';
|
|
8
7
|
import Popup from 'reactjs-popup';
|
|
@@ -11,7 +10,6 @@ import Dropzone from 'react-dropzone';
|
|
|
11
10
|
import { confirmAlert } from 'react-confirm-alert';
|
|
12
11
|
import truncate from 'truncate';
|
|
13
12
|
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
|
14
|
-
import { saveAs } from 'file-saver';
|
|
15
13
|
import { toast } from 'react-toastify';
|
|
16
14
|
import {
|
|
17
15
|
CircleCheck,
|
|
@@ -19,27 +17,22 @@ import {
|
|
|
19
17
|
EyeOpen,
|
|
20
18
|
EyeSlashed,
|
|
21
19
|
File,
|
|
22
|
-
Pencil,
|
|
23
20
|
TrashCan,
|
|
24
21
|
} from 'akar-icons';
|
|
25
22
|
|
|
26
23
|
import 'react-confirm-alert/src/react-confirm-alert.css';
|
|
27
|
-
import 'react-big-calendar/lib/css/react-big-calendar.css';
|
|
28
24
|
|
|
29
25
|
import Breadcrumb from '../Breadcrumb';
|
|
30
26
|
import CustomFetch from '../Fetch';
|
|
31
27
|
import Form from '../Form';
|
|
32
28
|
import GenericDynamic from './GenericDynamic';
|
|
33
|
-
import GenericEditableTable from './GenericEditableTable';
|
|
34
|
-
import MultiSelect from '../MultiSelect';
|
|
35
29
|
import QrCode from '../QrCode';
|
|
30
|
+
import SortableList from '../sorting/List';
|
|
36
31
|
import Table from '../DataGrid';
|
|
37
32
|
import TableFilter from '../TableFilter';
|
|
38
33
|
|
|
39
34
|
import styles from './styles/GenericDetail.module.scss';
|
|
40
35
|
|
|
41
|
-
const localizer = momentLocalizer(moment);
|
|
42
|
-
|
|
43
36
|
function GenericDetail({
|
|
44
37
|
extraUrlParam,
|
|
45
38
|
layout = 'full',
|
|
@@ -60,156 +53,21 @@ function GenericDetail({
|
|
|
60
53
|
|
|
61
54
|
/** General States */
|
|
62
55
|
const [activeStage, setActiveStage] = useState('');
|
|
63
|
-
const [activeTabConfig, setActiveTabConfig] = useState(null);
|
|
64
56
|
const [config, setConfig] = useState({});
|
|
65
57
|
const [data, setData] = useState({});
|
|
66
58
|
const [dataReload, setDataReload] = useState(0);
|
|
67
|
-
const [passwordVisible, setPasswordVisible] = useState(false);
|
|
68
|
-
const [preloadedOptions, setPreloadedOptions] = useState({});
|
|
69
59
|
const [rowsSelected, setRowsSelected] = useState({});
|
|
70
60
|
const [subnav, setSubnav] = useState([]);
|
|
71
61
|
const [total, setTotal] = useState(0);
|
|
72
62
|
const [windowHeight, setWindowHeight] = useState(window.innerHeight);
|
|
73
63
|
|
|
74
|
-
|
|
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
|
-
};
|
|
64
|
+
const [passwordVisible, setPasswordVisible] = useState(false);
|
|
207
65
|
|
|
208
|
-
/** General Functions */
|
|
209
66
|
const togglePasswordVisibility = () => {
|
|
210
67
|
setPasswordVisible(!passwordVisible);
|
|
211
68
|
};
|
|
212
69
|
|
|
70
|
+
/** General Functions */
|
|
213
71
|
const handleStageUpdate = async (stage, type) => {
|
|
214
72
|
try {
|
|
215
73
|
const activeTab = subnav.find((s) => s.show === true);
|
|
@@ -237,32 +95,6 @@ function GenericDetail({
|
|
|
237
95
|
|
|
238
96
|
if (res.data.error === '') {
|
|
239
97
|
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');
|
|
266
98
|
} else {
|
|
267
99
|
toast.error(res.data.error);
|
|
268
100
|
}
|
|
@@ -274,14 +106,6 @@ function GenericDetail({
|
|
|
274
106
|
}
|
|
275
107
|
};
|
|
276
108
|
|
|
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
|
-
|
|
285
109
|
const handleCheckboxUpdate = async () => {
|
|
286
110
|
try {
|
|
287
111
|
if (currentConfigRef.current.functions.checkboxUpdate) {
|
|
@@ -475,19 +299,6 @@ function GenericDetail({
|
|
|
475
299
|
return formattedKeys.join(', ');
|
|
476
300
|
};
|
|
477
301
|
|
|
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
|
-
|
|
491
302
|
const renderDate = () => formatDateValue(value);
|
|
492
303
|
|
|
493
304
|
const renderDateTime = () => formatDateTimeValue(value);
|
|
@@ -575,94 +386,6 @@ function GenericDetail({
|
|
|
575
386
|
}
|
|
576
387
|
};
|
|
577
388
|
|
|
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
|
-
|
|
666
389
|
// Render based on the item's type
|
|
667
390
|
switch (type) {
|
|
668
391
|
case 'boolean':
|
|
@@ -671,8 +394,6 @@ function GenericDetail({
|
|
|
671
394
|
return renderCheckbox();
|
|
672
395
|
case 'copyToClipboard':
|
|
673
396
|
return renderCopyToClipboard();
|
|
674
|
-
case 'currency':
|
|
675
|
-
return renderCurrency();
|
|
676
397
|
case 'date':
|
|
677
398
|
return renderDate();
|
|
678
399
|
case 'datetime':
|
|
@@ -687,8 +408,6 @@ function GenericDetail({
|
|
|
687
408
|
return renderLatestNotes();
|
|
688
409
|
case 'link':
|
|
689
410
|
return renderLink();
|
|
690
|
-
case 'ocrTemplate':
|
|
691
|
-
return renderOcrTemplate(id);
|
|
692
411
|
case 'options':
|
|
693
412
|
return renderOptions();
|
|
694
413
|
case 'password':
|
|
@@ -716,82 +435,11 @@ function GenericDetail({
|
|
|
716
435
|
|
|
717
436
|
if (activeTabConfig && activeTabConfig.type) {
|
|
718
437
|
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
|
-
);
|
|
768
438
|
case 'dropzone':
|
|
769
|
-
const downloadFile = (
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
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
|
-
});
|
|
439
|
+
const downloadFile = (id) => {
|
|
440
|
+
window.open(
|
|
441
|
+
`/ajax/files/download/${id}/${activeTabConfig.folder}`
|
|
442
|
+
);
|
|
795
443
|
};
|
|
796
444
|
|
|
797
445
|
const deleteFile = (e, id, name) => {
|
|
@@ -873,13 +521,9 @@ function GenericDetail({
|
|
|
873
521
|
key: response.key,
|
|
874
522
|
bucket: response.bucket,
|
|
875
523
|
filename: file.name,
|
|
876
|
-
file_name: file.name,
|
|
877
524
|
filesize: file.size,
|
|
878
|
-
file_size: file.size,
|
|
879
525
|
extension:
|
|
880
526
|
response.extension,
|
|
881
|
-
file_extension:
|
|
882
|
-
response.extension,
|
|
883
527
|
file_relationship:
|
|
884
528
|
activeTabConfig.file_relationship,
|
|
885
529
|
};
|
|
@@ -889,30 +533,9 @@ function GenericDetail({
|
|
|
889
533
|
'PUT',
|
|
890
534
|
requestBody,
|
|
891
535
|
(res) => {
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
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
|
-
}
|
|
536
|
+
setFiles(
|
|
537
|
+
res.data.files
|
|
538
|
+
);
|
|
916
539
|
}
|
|
917
540
|
);
|
|
918
541
|
});
|
|
@@ -1012,7 +635,7 @@ function GenericDetail({
|
|
|
1012
635
|
<li
|
|
1013
636
|
onClick={() => {
|
|
1014
637
|
downloadFile(
|
|
1015
|
-
a
|
|
638
|
+
a.id
|
|
1016
639
|
);
|
|
1017
640
|
}}
|
|
1018
641
|
key={'tf-' + b}
|
|
@@ -1341,7 +964,6 @@ function GenericDetail({
|
|
|
1341
964
|
|
|
1342
965
|
{activeTabConfig.form && (
|
|
1343
966
|
<div className={styles.polActions}>
|
|
1344
|
-
{/* Always render the "Edit" button if activeTabConfig.form exists */}
|
|
1345
967
|
<button
|
|
1346
968
|
className={styles.btn}
|
|
1347
969
|
onClick={() =>
|
|
@@ -1353,57 +975,6 @@ function GenericDetail({
|
|
|
1353
975
|
>
|
|
1354
976
|
Edit
|
|
1355
977
|
</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
|
-
)}
|
|
1407
978
|
</div>
|
|
1408
979
|
)}
|
|
1409
980
|
</>
|
|
@@ -1636,16 +1207,6 @@ function GenericDetail({
|
|
|
1636
1207
|
} else {
|
|
1637
1208
|
return null;
|
|
1638
1209
|
}
|
|
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
|
-
);
|
|
1649
1210
|
default:
|
|
1650
1211
|
return null;
|
|
1651
1212
|
}
|
|
@@ -1655,49 +1216,6 @@ function GenericDetail({
|
|
|
1655
1216
|
};
|
|
1656
1217
|
|
|
1657
1218
|
/** 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
|
-
|
|
1701
1219
|
useEffect(() => {
|
|
1702
1220
|
const handleResize = () => {
|
|
1703
1221
|
setWindowHeight(window.innerHeight);
|
|
@@ -1819,7 +1337,6 @@ function GenericDetail({
|
|
|
1819
1337
|
});
|
|
1820
1338
|
}
|
|
1821
1339
|
break;
|
|
1822
|
-
case 'scheduling':
|
|
1823
1340
|
case 'overview':
|
|
1824
1341
|
setConfig({});
|
|
1825
1342
|
if (activeTabConfig.form) {
|
|
@@ -1860,17 +1377,8 @@ function GenericDetail({
|
|
|
1860
1377
|
|
|
1861
1378
|
tabs.forEach((tab) => {
|
|
1862
1379
|
if (tab.type && tab.type === 'dropzone') {
|
|
1863
|
-
if (
|
|
1864
|
-
|
|
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
|
-
}
|
|
1380
|
+
if (res.files) {
|
|
1381
|
+
setFiles(res.files);
|
|
1874
1382
|
}
|
|
1875
1383
|
}
|
|
1876
1384
|
});
|
|
@@ -1946,6 +1454,10 @@ function GenericDetail({
|
|
|
1946
1454
|
<div className={styles.oppstatus}>
|
|
1947
1455
|
<ul className={styles.opppath}>
|
|
1948
1456
|
{stages.data.map((stage) => {
|
|
1457
|
+
if (!stage.id) {
|
|
1458
|
+
return null; // Skip rendering this stage
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1949
1461
|
const isStageComplete =
|
|
1950
1462
|
data[stages.key] >= stage.id;
|
|
1951
1463
|
|
|
@@ -2010,9 +1522,6 @@ function GenericDetail({
|
|
|
2010
1522
|
style={userProfile?.settings?.style || {}}
|
|
2011
1523
|
updateForm={setFormData}
|
|
2012
1524
|
userProfile={userProfile}
|
|
2013
|
-
paramValue={
|
|
2014
|
-
routeParams[urlParam] ? routeParams[urlParam] : 0
|
|
2015
|
-
}
|
|
2016
1525
|
/>
|
|
2017
1526
|
</Popup>
|
|
2018
1527
|
</>
|