@visns-studio/visns-components 5.0.14 → 5.0.16
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
CHANGED
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
76
76
|
},
|
|
77
77
|
"name": "@visns-studio/visns-components",
|
|
78
|
-
"version": "5.0.
|
|
78
|
+
"version": "5.0.16",
|
|
79
79
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
80
80
|
"main": "src/index.js",
|
|
81
81
|
"files": [
|
|
@@ -51,8 +51,11 @@ const GenericAuth = ({
|
|
|
51
51
|
const updateAuthStatus = async () => {
|
|
52
52
|
try {
|
|
53
53
|
const { data } = await CustomFetch('/ajax/user/profile', 'GET');
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
|
|
55
|
+
if (data && data.id) {
|
|
56
|
+
setUserProfile(data);
|
|
57
|
+
setIsAuthenticated(true);
|
|
58
|
+
}
|
|
56
59
|
setIsLoading(false);
|
|
57
60
|
|
|
58
61
|
if (location.pathname.split('/')[1] === 'login') {
|
|
@@ -29,6 +29,7 @@ import CustomFetch from '../Fetch';
|
|
|
29
29
|
import Form from '../Form';
|
|
30
30
|
import GenericDynamic from './GenericDynamic';
|
|
31
31
|
import GenericEditableTable from './GenericEditableTable';
|
|
32
|
+
import MultiSelect from '../MultiSelect';
|
|
32
33
|
import QrCode from '../QrCode';
|
|
33
34
|
import Table from '../DataGrid';
|
|
34
35
|
import TableFilter from '../TableFilter';
|
|
@@ -59,19 +60,152 @@ function GenericDetail({
|
|
|
59
60
|
const [config, setConfig] = useState({});
|
|
60
61
|
const [data, setData] = useState({});
|
|
61
62
|
const [dataReload, setDataReload] = useState(0);
|
|
63
|
+
const [passwordVisible, setPasswordVisible] = useState(false);
|
|
62
64
|
const [preloadedOptions, setPreloadedOptions] = useState({});
|
|
63
65
|
const [rowsSelected, setRowsSelected] = useState({});
|
|
64
66
|
const [subnav, setSubnav] = useState([]);
|
|
65
67
|
const [total, setTotal] = useState(0);
|
|
66
68
|
const [windowHeight, setWindowHeight] = useState(window.innerHeight);
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
/** Ocr States */
|
|
71
|
+
const [ocrRowsSelected, setOcrRowsSelected] = useState('');
|
|
72
|
+
|
|
73
|
+
/** Ocr Template Functionalities */
|
|
74
|
+
const handleOcrFileChange = async (e) => {
|
|
75
|
+
try {
|
|
76
|
+
const id = e.target.getAttribute('data-id');
|
|
77
|
+
|
|
78
|
+
const formData = new FormData();
|
|
79
|
+
formData.append('file', e.target.files[0]);
|
|
80
|
+
|
|
81
|
+
// Show a loading toast when the file is being processed
|
|
82
|
+
const loadingToast = toast.loading(
|
|
83
|
+
'Processing file... Please wait!'
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const csrfToken = document
|
|
87
|
+
.querySelector('meta[name="csrf-token"]')
|
|
88
|
+
.getAttribute('content');
|
|
89
|
+
const response = await fetch('/ajax/ocr/analyzeFile', {
|
|
90
|
+
method: 'POST',
|
|
91
|
+
headers: { 'X-CSRF-TOKEN': csrfToken },
|
|
92
|
+
body: formData,
|
|
93
|
+
});
|
|
69
94
|
|
|
95
|
+
if (response.ok) {
|
|
96
|
+
const result = await response.json();
|
|
97
|
+
setData((prevState) => ({
|
|
98
|
+
...prevState,
|
|
99
|
+
[id]: result,
|
|
100
|
+
}));
|
|
101
|
+
toast.update(loadingToast, {
|
|
102
|
+
render: 'File processed successfully!',
|
|
103
|
+
type: 'success',
|
|
104
|
+
isLoading: false,
|
|
105
|
+
autoClose: 3000,
|
|
106
|
+
});
|
|
107
|
+
} else {
|
|
108
|
+
toast.update(loadingToast, {
|
|
109
|
+
render: 'Error processing file.',
|
|
110
|
+
type: 'error',
|
|
111
|
+
isLoading: false,
|
|
112
|
+
autoClose: 3000,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error(error);
|
|
117
|
+
toast.update(loadingToast, {
|
|
118
|
+
render: 'Something went wrong. Please try again.',
|
|
119
|
+
type: 'error',
|
|
120
|
+
isLoading: false,
|
|
121
|
+
autoClose: 3000,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const handleOcrKeyChange = (e) => {
|
|
127
|
+
const {
|
|
128
|
+
value,
|
|
129
|
+
dataset: { id },
|
|
130
|
+
} = e.target;
|
|
131
|
+
|
|
132
|
+
let selectedKey = data[id].selectedKey || [];
|
|
133
|
+
|
|
134
|
+
if (selectedKey.includes(value)) {
|
|
135
|
+
selectedKey = selectedKey.filter((key) => key !== value);
|
|
136
|
+
} else {
|
|
137
|
+
selectedKey.push(value);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
setData((prevState) => ({
|
|
141
|
+
...prevState,
|
|
142
|
+
[id]: {
|
|
143
|
+
...prevState[id],
|
|
144
|
+
selectedKey,
|
|
145
|
+
},
|
|
146
|
+
}));
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const handleSelectedOcrKey = (value, action, id) => {
|
|
150
|
+
setOcrRowsSelected(value.value);
|
|
151
|
+
|
|
152
|
+
if (value.value) {
|
|
153
|
+
setData((prevState) => ({
|
|
154
|
+
...prevState,
|
|
155
|
+
[id]: {
|
|
156
|
+
...prevState[id],
|
|
157
|
+
dynamicFilename: `${prevState[id].dynamicFilename}_{{ ${value.value} }}`,
|
|
158
|
+
},
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const handleOcrInputChange = (e) => {
|
|
164
|
+
const {
|
|
165
|
+
name,
|
|
166
|
+
value,
|
|
167
|
+
dataset: { id },
|
|
168
|
+
} = e.target;
|
|
169
|
+
|
|
170
|
+
switch (name) {
|
|
171
|
+
case 'dynamicFilename':
|
|
172
|
+
setData((prevState) => ({
|
|
173
|
+
...prevState,
|
|
174
|
+
[id]: {
|
|
175
|
+
...prevState[id],
|
|
176
|
+
dynamicFilename: value,
|
|
177
|
+
},
|
|
178
|
+
}));
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const handleOcrTemplateSave = async (id) => {
|
|
184
|
+
try {
|
|
185
|
+
const res = await CustomFetch(
|
|
186
|
+
`${activeTabConfig.form.url}/${data.id}`,
|
|
187
|
+
'PUT',
|
|
188
|
+
{
|
|
189
|
+
...data,
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
if (res.data.error === '') {
|
|
194
|
+
toast.success('Data saved successfully');
|
|
195
|
+
handleReload();
|
|
196
|
+
} else {
|
|
197
|
+
toast.error(res.data.error);
|
|
198
|
+
}
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error(error);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/** General Functions */
|
|
70
205
|
const togglePasswordVisibility = () => {
|
|
71
206
|
setPasswordVisible(!passwordVisible);
|
|
72
207
|
};
|
|
73
208
|
|
|
74
|
-
/** General Functions */
|
|
75
209
|
const handleStageUpdate = async (stage, type) => {
|
|
76
210
|
try {
|
|
77
211
|
const activeTab = subnav.find((s) => s.show === true);
|
|
@@ -437,6 +571,94 @@ function GenericDetail({
|
|
|
437
571
|
}
|
|
438
572
|
};
|
|
439
573
|
|
|
574
|
+
const renderOcrTemplate = (id) => {
|
|
575
|
+
const ocrTemplateBuilder = data[id];
|
|
576
|
+
|
|
577
|
+
return (
|
|
578
|
+
<>
|
|
579
|
+
<input
|
|
580
|
+
type="file"
|
|
581
|
+
data-id={id}
|
|
582
|
+
onChange={handleOcrFileChange}
|
|
583
|
+
/>
|
|
584
|
+
|
|
585
|
+
{ocrTemplateBuilder &&
|
|
586
|
+
ocrTemplateBuilder.DeducedData &&
|
|
587
|
+
ocrTemplateBuilder.DeducedData.length > 0 && (
|
|
588
|
+
<table className={styles.schedulingTable}>
|
|
589
|
+
<thead>
|
|
590
|
+
<tr>
|
|
591
|
+
<th></th>
|
|
592
|
+
<th>Key</th>
|
|
593
|
+
<th>Value</th>
|
|
594
|
+
</tr>
|
|
595
|
+
</thead>
|
|
596
|
+
<tbody>
|
|
597
|
+
{Object.entries(
|
|
598
|
+
ocrTemplateBuilder.DeducedData[0]
|
|
599
|
+
).map(([key, value], index) => (
|
|
600
|
+
<tr key={`ocr-keys-${index}`}>
|
|
601
|
+
<td>
|
|
602
|
+
<input
|
|
603
|
+
type="checkbox"
|
|
604
|
+
name={key}
|
|
605
|
+
value={key}
|
|
606
|
+
data-id={id}
|
|
607
|
+
checked={
|
|
608
|
+
ocrTemplateBuilder?.selectedKey?.includes(
|
|
609
|
+
key
|
|
610
|
+
) || false
|
|
611
|
+
}
|
|
612
|
+
onChange={
|
|
613
|
+
handleOcrKeyChange
|
|
614
|
+
}
|
|
615
|
+
/>
|
|
616
|
+
</td>
|
|
617
|
+
<td>{key}</td>
|
|
618
|
+
<td>{value}</td>
|
|
619
|
+
</tr>
|
|
620
|
+
))}
|
|
621
|
+
</tbody>
|
|
622
|
+
</table>
|
|
623
|
+
)}
|
|
624
|
+
|
|
625
|
+
{ocrTemplateBuilder &&
|
|
626
|
+
ocrTemplateBuilder.selectedKey &&
|
|
627
|
+
ocrTemplateBuilder.selectedKey.length > 0 && (
|
|
628
|
+
<>
|
|
629
|
+
<strong>Selected Keys</strong>
|
|
630
|
+
<MultiSelect
|
|
631
|
+
className=""
|
|
632
|
+
inputValue={ocrRowsSelected}
|
|
633
|
+
multi={false}
|
|
634
|
+
onChange={handleSelectedOcrKey}
|
|
635
|
+
options={ocrTemplateBuilder.selectedKey.map(
|
|
636
|
+
(a) => ({
|
|
637
|
+
label: a,
|
|
638
|
+
value: a,
|
|
639
|
+
})
|
|
640
|
+
)}
|
|
641
|
+
placeholder="Select a Key to use"
|
|
642
|
+
settings={{ id: id }}
|
|
643
|
+
style={{}}
|
|
644
|
+
isCreatable={false}
|
|
645
|
+
creatableConfig={{}}
|
|
646
|
+
/>
|
|
647
|
+
<br />
|
|
648
|
+
<strong>File Name</strong>
|
|
649
|
+
<input
|
|
650
|
+
type="text"
|
|
651
|
+
value={data[id]?.dynamicFilename || ''}
|
|
652
|
+
data-id={id}
|
|
653
|
+
onChange={handleOcrInputChange}
|
|
654
|
+
name="dynamicFilename"
|
|
655
|
+
/>
|
|
656
|
+
</>
|
|
657
|
+
)}
|
|
658
|
+
</>
|
|
659
|
+
);
|
|
660
|
+
};
|
|
661
|
+
|
|
440
662
|
// Render based on the item's type
|
|
441
663
|
switch (type) {
|
|
442
664
|
case 'boolean':
|
|
@@ -461,6 +683,8 @@ function GenericDetail({
|
|
|
461
683
|
return renderLatestNotes();
|
|
462
684
|
case 'link':
|
|
463
685
|
return renderLink();
|
|
686
|
+
case 'ocrTemplate':
|
|
687
|
+
return renderOcrTemplate(id);
|
|
464
688
|
case 'options':
|
|
465
689
|
return renderOptions();
|
|
466
690
|
case 'password':
|
|
@@ -1064,6 +1288,7 @@ function GenericDetail({
|
|
|
1064
1288
|
|
|
1065
1289
|
{activeTabConfig.form && (
|
|
1066
1290
|
<div className={styles.polActions}>
|
|
1291
|
+
{/* Always render the "Edit" button if activeTabConfig.form exists */}
|
|
1067
1292
|
<button
|
|
1068
1293
|
className={styles.btn}
|
|
1069
1294
|
onClick={() =>
|
|
@@ -1075,21 +1300,57 @@ function GenericDetail({
|
|
|
1075
1300
|
>
|
|
1076
1301
|
Edit
|
|
1077
1302
|
</button>
|
|
1078
|
-
</div>
|
|
1079
|
-
)}
|
|
1080
1303
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1304
|
+
{/* Check if sections contain "ocrTemplate" type and render the "Save" button under the same polActions */}
|
|
1305
|
+
{activeTabConfig.sections &&
|
|
1306
|
+
activeTabConfig.sections
|
|
1307
|
+
.length > 0 &&
|
|
1308
|
+
activeTabConfig.sections.map(
|
|
1309
|
+
(section, sectionIndex) =>
|
|
1310
|
+
section.content &&
|
|
1311
|
+
section.content.some(
|
|
1312
|
+
(item) =>
|
|
1313
|
+
item.type ===
|
|
1314
|
+
'ocrTemplate'
|
|
1315
|
+
) && (
|
|
1316
|
+
<button
|
|
1317
|
+
key={`save-button-${sectionIndex}`}
|
|
1318
|
+
className={
|
|
1319
|
+
styles.btn
|
|
1320
|
+
}
|
|
1321
|
+
onClick={() => {
|
|
1322
|
+
// Handle the specific "ocrTemplate" item
|
|
1323
|
+
const ocrItem =
|
|
1324
|
+
section.content.find(
|
|
1325
|
+
(
|
|
1326
|
+
item
|
|
1327
|
+
) =>
|
|
1328
|
+
item.type ===
|
|
1329
|
+
'ocrTemplate'
|
|
1330
|
+
);
|
|
1331
|
+
handleOcrTemplateSave(
|
|
1332
|
+
ocrItem.id
|
|
1333
|
+
); // Now you can safely use ocrItem.id
|
|
1334
|
+
}}
|
|
1335
|
+
>
|
|
1336
|
+
Save
|
|
1337
|
+
</button>
|
|
1338
|
+
)
|
|
1339
|
+
)}
|
|
1340
|
+
|
|
1341
|
+
{/* Check if export exists and render the "Export" button under the same polActions */}
|
|
1342
|
+
{activeTabConfig.export && (
|
|
1343
|
+
<button
|
|
1344
|
+
className={styles.btn}
|
|
1345
|
+
onClick={() =>
|
|
1346
|
+
handleExport(
|
|
1347
|
+
activeTabConfig.export
|
|
1348
|
+
)
|
|
1349
|
+
}
|
|
1350
|
+
>
|
|
1351
|
+
Export
|
|
1352
|
+
</button>
|
|
1353
|
+
)}
|
|
1093
1354
|
</div>
|
|
1094
1355
|
)}
|
|
1095
1356
|
</>
|
|
@@ -662,6 +662,33 @@
|
|
|
662
662
|
text-align: right;
|
|
663
663
|
}
|
|
664
664
|
|
|
665
|
+
.schedulingTable td input[type='checkbox'] {
|
|
666
|
+
width: 20px !important;
|
|
667
|
+
height: 20px;
|
|
668
|
+
border-radius: 4px;
|
|
669
|
+
background-color: var(--item-color);
|
|
670
|
+
border: 2px solid rgba(var(--primary-rgb), 0.15);
|
|
671
|
+
position: relative;
|
|
672
|
+
cursor: pointer;
|
|
673
|
+
transition: background-color 0.3s, border-color 0.3s;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
.schedulingTable td input[type='checkbox']:checked {
|
|
677
|
+
background-color: var(--primary-color);
|
|
678
|
+
border-color: var(--highlight-color);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
.schedulingTable td input[type='checkbox']:focus {
|
|
682
|
+
outline: none;
|
|
683
|
+
box-shadow: 0 0 0 2px rgba(var(--primary-rgb), 0.2);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/* Custom hover effect for better user experience */
|
|
687
|
+
.schedulingTable td input[type='checkbox']:hover {
|
|
688
|
+
background-color: rgba(var(--primary-rgb), 0.1);
|
|
689
|
+
border-color: rgba(var(--primary-rgb), 0.25);
|
|
690
|
+
}
|
|
691
|
+
|
|
665
692
|
.noDataMessage {
|
|
666
693
|
text-align: center;
|
|
667
694
|
color: var(--secondary-color); /* Maintain a clear and neutral color */
|