@visns-studio/visns-components 5.0.13 → 5.0.15
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 -1
- package/src/components/crm/Field.jsx +1 -0
- package/src/components/crm/generic/GenericDetail.jsx +278 -18
- package/src/components/crm/generic/GenericEditableTable.jsx +112 -46
- package/src/components/crm/generic/styles/GenericDetail.module.scss +27 -0
- package/src/components/crm/generic/styles/GenericEditableTable.module.scss +42 -0
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.15",
|
|
79
79
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
80
80
|
"main": "src/index.js",
|
|
81
81
|
"files": [
|
|
@@ -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
|
</>
|
|
@@ -1328,9 +1589,8 @@ function GenericDetail({
|
|
|
1328
1589
|
schedulingConfig={activeTabConfig}
|
|
1329
1590
|
data={data}
|
|
1330
1591
|
dataId={routeParams[urlParam]}
|
|
1331
|
-
|
|
1592
|
+
modalOpen={modalOpen}
|
|
1332
1593
|
preloadedOptions={preloadedOptions}
|
|
1333
|
-
userProfile={userProfile}
|
|
1334
1594
|
/>
|
|
1335
1595
|
);
|
|
1336
1596
|
default:
|
|
@@ -16,9 +16,8 @@ const GenericEditableTable = ({
|
|
|
16
16
|
schedulingConfig,
|
|
17
17
|
data,
|
|
18
18
|
dataId,
|
|
19
|
-
|
|
19
|
+
modalOpen,
|
|
20
20
|
preloadedOptions,
|
|
21
|
-
userProfile,
|
|
22
21
|
}) => {
|
|
23
22
|
const { columns, rows, form } = schedulingConfig;
|
|
24
23
|
|
|
@@ -56,29 +55,37 @@ const GenericEditableTable = ({
|
|
|
56
55
|
|
|
57
56
|
const groupCategoriesByType = (data) => {
|
|
58
57
|
const grouped = {};
|
|
59
|
-
|
|
58
|
+
|
|
59
|
+
data.forEach((category, categoryKey) => {
|
|
60
60
|
const categoryType =
|
|
61
|
-
category[rows.categoryKey.id]?.[rows.categoryKey.label] ||
|
|
62
|
-
|
|
61
|
+
category[rows.categoryKey.id]?.[rows.categoryKey.label] || null;
|
|
62
|
+
|
|
63
|
+
const categoryId = category[rows.categoryKey.id]?.id || null;
|
|
63
64
|
|
|
64
|
-
const categoryId
|
|
65
|
-
category[rows.categoryKey.id]?.id || Number.MAX_SAFE_INTEGER;
|
|
65
|
+
const groupKey = categoryId || 'no_category';
|
|
66
66
|
|
|
67
|
-
if (!grouped[
|
|
68
|
-
grouped[
|
|
69
|
-
id: categoryId,
|
|
70
|
-
category: categoryType,
|
|
67
|
+
if (!grouped[groupKey]) {
|
|
68
|
+
grouped[groupKey] = {
|
|
69
|
+
id: categoryId || 'no_category',
|
|
70
|
+
category: categoryType || '',
|
|
71
71
|
entries: [],
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
-
grouped[
|
|
74
|
+
grouped[groupKey].entries.push({
|
|
75
|
+
...category,
|
|
76
|
+
keyCounter: categoryKey,
|
|
77
|
+
});
|
|
75
78
|
});
|
|
76
79
|
|
|
77
80
|
Object.values(grouped).forEach((group) => {
|
|
78
81
|
group.entries.sort((a, b) => a.sort_order - b.sort_order);
|
|
79
82
|
});
|
|
80
83
|
|
|
81
|
-
|
|
84
|
+
// Ensure 'no_category' group is at the top
|
|
85
|
+
const groupedArray = Object.values(grouped);
|
|
86
|
+
return groupedArray.sort((a, b) =>
|
|
87
|
+
a.id === 'no_category' ? -1 : b.id === 'no_category' ? 1 : 0
|
|
88
|
+
);
|
|
82
89
|
};
|
|
83
90
|
|
|
84
91
|
const handleFieldChange = (value, fieldId, keyCounter) => {
|
|
@@ -89,7 +96,7 @@ const GenericEditableTable = ({
|
|
|
89
96
|
[fieldId]: value,
|
|
90
97
|
};
|
|
91
98
|
|
|
92
|
-
debouncedUpdate({
|
|
99
|
+
debouncedUpdate({ [rows.key]: updatedDetail });
|
|
93
100
|
|
|
94
101
|
return updatedDetail;
|
|
95
102
|
});
|
|
@@ -130,7 +137,7 @@ const GenericEditableTable = ({
|
|
|
130
137
|
: item;
|
|
131
138
|
});
|
|
132
139
|
|
|
133
|
-
debouncedUpdate({
|
|
140
|
+
debouncedUpdate({ [rows.key]: reordered });
|
|
134
141
|
return reordered;
|
|
135
142
|
}
|
|
136
143
|
}
|
|
@@ -139,6 +146,30 @@ const GenericEditableTable = ({
|
|
|
139
146
|
});
|
|
140
147
|
};
|
|
141
148
|
|
|
149
|
+
const handleDeleteRow = (entry) => {
|
|
150
|
+
confirmAlert({
|
|
151
|
+
title: 'Confirm to Delete',
|
|
152
|
+
message: 'Are you sure you want to delete this entry?',
|
|
153
|
+
buttons: [
|
|
154
|
+
{
|
|
155
|
+
label: 'Yes',
|
|
156
|
+
onClick: async () => {
|
|
157
|
+
setLocalData((prev) => {
|
|
158
|
+
const updatedDetail = prev.filter(
|
|
159
|
+
(item) => item.id !== entry.id
|
|
160
|
+
);
|
|
161
|
+
debouncedUpdate({ [rows.key]: updatedDetail });
|
|
162
|
+
return updatedDetail;
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
label: 'No',
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
|
|
142
173
|
const renderScheduledTableField = (entry, column, keyCounter) => {
|
|
143
174
|
if (!entry || !column) return null;
|
|
144
175
|
|
|
@@ -159,6 +190,21 @@ const GenericEditableTable = ({
|
|
|
159
190
|
style={{ textAlign: 'right' }}
|
|
160
191
|
/>
|
|
161
192
|
);
|
|
193
|
+
case 'date':
|
|
194
|
+
return (
|
|
195
|
+
<input
|
|
196
|
+
type="date"
|
|
197
|
+
value={entry[column.id] || ''}
|
|
198
|
+
onChange={(e) =>
|
|
199
|
+
handleFieldChange(
|
|
200
|
+
e.target.value,
|
|
201
|
+
column.id,
|
|
202
|
+
keyCounter
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
style={{ textAlign: 'right' }}
|
|
206
|
+
/>
|
|
207
|
+
);
|
|
162
208
|
case 'dropdown':
|
|
163
209
|
return (
|
|
164
210
|
<select
|
|
@@ -287,43 +333,49 @@ const GenericEditableTable = ({
|
|
|
287
333
|
</td>
|
|
288
334
|
))}
|
|
289
335
|
<td>
|
|
290
|
-
{
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
<
|
|
336
|
+
<div className={styles.tdactions}>
|
|
337
|
+
{idx > 0 && (
|
|
338
|
+
<ArrowUp
|
|
339
|
+
size={18}
|
|
340
|
+
onClick={() =>
|
|
341
|
+
handleSortChange(
|
|
342
|
+
group.id,
|
|
343
|
+
entry.id,
|
|
344
|
+
'up'
|
|
345
|
+
)
|
|
346
|
+
}
|
|
347
|
+
style={{
|
|
348
|
+
cursor: 'pointer',
|
|
349
|
+
}}
|
|
350
|
+
/>
|
|
351
|
+
)}
|
|
352
|
+
{idx <
|
|
353
|
+
group.entries.length -
|
|
354
|
+
1 && (
|
|
355
|
+
<ArrowDown
|
|
356
|
+
size={18}
|
|
357
|
+
onClick={() =>
|
|
358
|
+
handleSortChange(
|
|
359
|
+
group.id,
|
|
360
|
+
entry.id,
|
|
361
|
+
'down'
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
style={{
|
|
365
|
+
cursor: 'pointer',
|
|
366
|
+
}}
|
|
367
|
+
/>
|
|
368
|
+
)}
|
|
369
|
+
<TrashCan
|
|
307
370
|
size={18}
|
|
308
371
|
onClick={() =>
|
|
309
|
-
|
|
310
|
-
group.id,
|
|
311
|
-
entry.id,
|
|
312
|
-
'down'
|
|
313
|
-
)
|
|
372
|
+
handleDeleteRow(entry)
|
|
314
373
|
}
|
|
315
374
|
style={{
|
|
316
375
|
cursor: 'pointer',
|
|
317
376
|
}}
|
|
318
377
|
/>
|
|
319
|
-
|
|
320
|
-
<TrashCan
|
|
321
|
-
size={18}
|
|
322
|
-
onClick={() =>
|
|
323
|
-
handleDeleteRow(entry)
|
|
324
|
-
}
|
|
325
|
-
style={{ cursor: 'pointer' }}
|
|
326
|
-
/>
|
|
378
|
+
</div>
|
|
327
379
|
</td>
|
|
328
380
|
</tr>
|
|
329
381
|
))}
|
|
@@ -332,8 +384,22 @@ const GenericEditableTable = ({
|
|
|
332
384
|
</tbody>
|
|
333
385
|
</table>
|
|
334
386
|
) : (
|
|
335
|
-
<div
|
|
387
|
+
<div className={styles.noDataMessage}>
|
|
388
|
+
No data available. Please check your filters or try
|
|
389
|
+
reloading the data.
|
|
390
|
+
</div>
|
|
336
391
|
)}
|
|
392
|
+
|
|
393
|
+
<div className={styles.polActions}>
|
|
394
|
+
<button
|
|
395
|
+
className={styles.btn}
|
|
396
|
+
onClick={() => {
|
|
397
|
+
modalOpen('create', 0);
|
|
398
|
+
}}
|
|
399
|
+
>
|
|
400
|
+
Add
|
|
401
|
+
</button>
|
|
402
|
+
</div>
|
|
337
403
|
</div>
|
|
338
404
|
);
|
|
339
405
|
};
|
|
@@ -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 */
|
|
@@ -134,3 +134,45 @@
|
|
|
134
134
|
background: var(--highlight-color);
|
|
135
135
|
border: 1px solid rgba(var(--highlight-rgb), 1.05);
|
|
136
136
|
}
|
|
137
|
+
|
|
138
|
+
.tdactions {
|
|
139
|
+
width: 100%;
|
|
140
|
+
display: flex;
|
|
141
|
+
flex-wrap: nowrap;
|
|
142
|
+
justify-content: flex-start;
|
|
143
|
+
align-items: center;
|
|
144
|
+
gap: 0.1rem;
|
|
145
|
+
|
|
146
|
+
span {
|
|
147
|
+
display: flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
position: relative;
|
|
150
|
+
width: 18px;
|
|
151
|
+
height: 18px;
|
|
152
|
+
|
|
153
|
+
svg {
|
|
154
|
+
width: 18px;
|
|
155
|
+
height: 18px;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.noDataMessage {
|
|
161
|
+
text-align: center;
|
|
162
|
+
color: var(--primary-color);
|
|
163
|
+
font-size: 1rem;
|
|
164
|
+
padding: 2rem;
|
|
165
|
+
margin: 2rem auto;
|
|
166
|
+
background-color: var(--secondary-bg-color, #f8f8f8);
|
|
167
|
+
border: 1px solid rgba(var(--primary-rgb), 0.15);
|
|
168
|
+
border-radius: 6px;
|
|
169
|
+
max-width: 80%;
|
|
170
|
+
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
|
|
171
|
+
font-weight: 500;
|
|
172
|
+
font-family: var(--font-family, Arial, sans-serif);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.noDataMessage:hover {
|
|
176
|
+
background-color: rgba(var(--primary-rgb), 0.05);
|
|
177
|
+
border-color: rgba(var(--primary-rgb), 0.2);
|
|
178
|
+
}
|