@visns-studio/visns-components 5.0.11 → 5.0.13
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 +11 -2
- package/src/components/crm/Form.jsx +8 -5
- package/src/components/crm/MultiSelect.jsx +43 -32
- package/src/components/crm/auth/Login.jsx +9 -1
- package/src/components/crm/generic/GenericDetail.jsx +49 -289
- package/src/components/crm/generic/GenericEditableTable.jsx +341 -0
- package/src/components/crm/generic/styles/AuditLog.module.scss +4 -0
- package/src/components/crm/generic/styles/GenericDetail.module.scss +4 -0
- package/src/components/crm/generic/styles/GenericDynamic.module.scss +4 -0
- package/src/components/crm/generic/styles/GenericEditableTable.module.scss +136 -0
- package/src/components/crm/generic/styles/GenericFormBuilder.module.scss +4 -0
- package/src/components/crm/styles/Field.module.scss +5 -1
- package/src/components/crm/styles/Navigation.module.scss +3 -3
- package/src/components/crm/styles/QrCode.module.scss +4 -0
- package/src/components/styles/global.css +12 -3
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.13",
|
|
79
79
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
80
80
|
"main": "src/index.js",
|
|
81
81
|
"files": [
|
|
@@ -207,6 +207,12 @@ function Field({
|
|
|
207
207
|
filter.orderBy = s.orderBy;
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
if (a.where) {
|
|
211
|
+
forEach(a.where, (w) => {
|
|
212
|
+
filter.where.push(w);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
210
216
|
CustomFetch(s.child.url, 'POST', filter, function (result) {
|
|
211
217
|
childDropdownCallback({
|
|
212
218
|
id: s.child.id,
|
|
@@ -314,7 +320,7 @@ function Field({
|
|
|
314
320
|
<span>{settings.label}</span>
|
|
315
321
|
</div>
|
|
316
322
|
);
|
|
317
|
-
} else if (['richeditor'].includes(settings.type)) {
|
|
323
|
+
} else if (['richeditor', 'textarea'].includes(settings.type)) {
|
|
318
324
|
return settings.label;
|
|
319
325
|
}
|
|
320
326
|
}
|
|
@@ -1664,6 +1670,7 @@ function Field({
|
|
|
1664
1670
|
'signature',
|
|
1665
1671
|
'table_radio',
|
|
1666
1672
|
'table_text',
|
|
1673
|
+
'textarea',
|
|
1667
1674
|
'time',
|
|
1668
1675
|
'toggle',
|
|
1669
1676
|
].includes(settings.type)
|
|
@@ -1882,7 +1889,9 @@ function Field({
|
|
|
1882
1889
|
<div className={formItemClass} style={formItemStyle}>
|
|
1883
1890
|
<label
|
|
1884
1891
|
className={
|
|
1885
|
-
|
|
1892
|
+
['richeditor', 'textarea'].includes(settings.type)
|
|
1893
|
+
? ''
|
|
1894
|
+
: styles.fi__label
|
|
1886
1895
|
}
|
|
1887
1896
|
>
|
|
1888
1897
|
{renderLabel()}
|
|
@@ -621,8 +621,6 @@ function Form({
|
|
|
621
621
|
}
|
|
622
622
|
});
|
|
623
623
|
|
|
624
|
-
console.info(_inputClass);
|
|
625
|
-
|
|
626
624
|
setInputClass(_inputClass);
|
|
627
625
|
}
|
|
628
626
|
|
|
@@ -672,7 +670,14 @@ function Form({
|
|
|
672
670
|
|
|
673
671
|
if (!_.isEmpty(formData)) {
|
|
674
672
|
Object.keys(formData).forEach((item) => {
|
|
675
|
-
|
|
673
|
+
const value = formData[item];
|
|
674
|
+
|
|
675
|
+
// Check if it's a single File or an array of File objects
|
|
676
|
+
if (
|
|
677
|
+
value instanceof File ||
|
|
678
|
+
(Array.isArray(value) &&
|
|
679
|
+
value.every((file) => file instanceof File))
|
|
680
|
+
) {
|
|
676
681
|
fileUpload = true;
|
|
677
682
|
fileKey = item;
|
|
678
683
|
|
|
@@ -842,8 +847,6 @@ function Form({
|
|
|
842
847
|
const fetchData = async () => {
|
|
843
848
|
let _formData = { ...formData };
|
|
844
849
|
|
|
845
|
-
console.info('aaa', formType, columnId);
|
|
846
|
-
|
|
847
850
|
if (formSettings.fields.length > 0) {
|
|
848
851
|
formSettings.fields.forEach((item) => {
|
|
849
852
|
const { id, value, type, parent, urlParam } = item;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import CustomFetch from './Fetch';
|
|
3
|
-
import SelectList from './SelectList';
|
|
4
3
|
import Select, { createFilter, components } from 'react-select';
|
|
5
4
|
import CreatableSelect from 'react-select/creatable';
|
|
6
5
|
import styles from './styles/AsyncSelect.module.scss';
|
|
7
6
|
|
|
8
|
-
// Custom Option component to add a second line of text
|
|
9
7
|
const CustomOption = (props) => (
|
|
10
8
|
<components.Option {...props}>
|
|
11
9
|
<div>
|
|
@@ -34,34 +32,49 @@ function MultiSelect({
|
|
|
34
32
|
const [selectOptions, setSelectOptions] = useState([]);
|
|
35
33
|
const [selectValue, setSelectValue] = useState([]);
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
35
|
+
// Memoize transformed options
|
|
36
|
+
const memoizedOptions = useMemo(() => {
|
|
37
|
+
return options
|
|
38
|
+
.filter((a) => a?.label || a?.name || a?.description) // Filter out options with no label, name, or description
|
|
39
|
+
.map((a) => ({
|
|
40
|
+
value: a?.id,
|
|
41
|
+
label: a?.label || a?.name || a?.description || 'Unknown',
|
|
42
|
+
description: a?.description || '',
|
|
43
|
+
...a,
|
|
44
|
+
}));
|
|
47
45
|
}, [options]);
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const _values = normalizeInputValue(inputValue).map((a) => ({
|
|
56
|
-
value: a.id,
|
|
57
|
-
label: a.label || a.name || a.description || 'Unknown',
|
|
58
|
-
description: a.description || '', // Include description field
|
|
59
|
-
...a,
|
|
60
|
-
}));
|
|
47
|
+
// Memoize transformed input values
|
|
48
|
+
const memoizedInputValue = useMemo(() => {
|
|
49
|
+
const normalizeInputValue = (value) =>
|
|
50
|
+
Array.isArray(value) ? value : value ? [value] : [];
|
|
61
51
|
|
|
62
|
-
|
|
52
|
+
return normalizeInputValue(inputValue)
|
|
53
|
+
.filter((a) => a?.label || a?.name || a?.description) // Filter out values with no label, name, or description
|
|
54
|
+
.map((a) => ({
|
|
55
|
+
value: a?.id,
|
|
56
|
+
label: a?.label || a?.name || a?.description || 'Unknown',
|
|
57
|
+
description: a?.description || '',
|
|
58
|
+
...a,
|
|
59
|
+
}));
|
|
63
60
|
}, [inputValue]);
|
|
64
61
|
|
|
62
|
+
// Set options when memoizedOptions changes
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (JSON.stringify(selectOptions) !== JSON.stringify(memoizedOptions)) {
|
|
65
|
+
setSelectOptions(memoizedOptions);
|
|
66
|
+
}
|
|
67
|
+
}, [memoizedOptions, selectOptions]);
|
|
68
|
+
|
|
69
|
+
// Set value when memoizedInputValue changes
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (
|
|
72
|
+
JSON.stringify(selectValue) !== JSON.stringify(memoizedInputValue)
|
|
73
|
+
) {
|
|
74
|
+
setSelectValue(memoizedInputValue);
|
|
75
|
+
}
|
|
76
|
+
}, [memoizedInputValue, selectValue]);
|
|
77
|
+
|
|
65
78
|
const handleCreate = async (inputValue) => {
|
|
66
79
|
if (creatableConfig.url && creatableConfig.method) {
|
|
67
80
|
const res = await CustomFetch(
|
|
@@ -74,17 +87,15 @@ function MultiSelect({
|
|
|
74
87
|
const newOption = {
|
|
75
88
|
value: res.data.id,
|
|
76
89
|
label: res.data.label || inputValue,
|
|
77
|
-
description: res.data.description || 'Newly created item',
|
|
90
|
+
description: res.data.description || 'Newly created item',
|
|
78
91
|
...res.data,
|
|
79
92
|
};
|
|
80
93
|
|
|
81
|
-
// Update options and trigger onChange with the new option
|
|
82
94
|
setSelectOptions((prevOptions) => [...prevOptions, newOption]);
|
|
83
95
|
setSelectValue((prevValues) =>
|
|
84
96
|
multi ? [...prevValues, newOption] : [newOption]
|
|
85
97
|
);
|
|
86
98
|
|
|
87
|
-
// Trigger onChange with the new option
|
|
88
99
|
onChange(
|
|
89
100
|
multi ? [...selectValue, newOption] : newOption,
|
|
90
101
|
{ action: 'create-option' },
|
|
@@ -103,7 +114,7 @@ function MultiSelect({
|
|
|
103
114
|
isSearchable
|
|
104
115
|
isMulti={multi}
|
|
105
116
|
filterOption={createFilter({ ignoreAccents: false })}
|
|
106
|
-
components={{ Option: CustomOption }}
|
|
117
|
+
components={{ Option: CustomOption }}
|
|
107
118
|
options={selectOptions}
|
|
108
119
|
onChange={(inputValue, action) => {
|
|
109
120
|
onChange(inputValue, action, settings.id);
|
|
@@ -125,8 +136,8 @@ function MultiSelect({
|
|
|
125
136
|
}),
|
|
126
137
|
}}
|
|
127
138
|
value={selectValue}
|
|
128
|
-
onCreateOption={isCreatable ? handleCreate : undefined}
|
|
129
|
-
placeholder={placeholder
|
|
139
|
+
onCreateOption={isCreatable ? handleCreate : undefined}
|
|
140
|
+
placeholder={placeholder || 'Select...'}
|
|
130
141
|
/>
|
|
131
142
|
);
|
|
132
143
|
}
|
|
@@ -28,7 +28,15 @@ const Login = ({ logo, providers, setSystemAuth, setUserProfile }) => {
|
|
|
28
28
|
let content =
|
|
29
29
|
providers && providers.length > 0 ? (
|
|
30
30
|
<div className="formItem fwItem lastItem forgotten">
|
|
31
|
-
<span
|
|
31
|
+
<span
|
|
32
|
+
style={{
|
|
33
|
+
display: 'block',
|
|
34
|
+
textAlign: 'center',
|
|
35
|
+
width: '100%',
|
|
36
|
+
}}
|
|
37
|
+
>
|
|
38
|
+
OR
|
|
39
|
+
</span>
|
|
32
40
|
</div>
|
|
33
41
|
) : null;
|
|
34
42
|
|
|
@@ -1,6 +1,6 @@
|
|
|
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
5
|
import moment from 'moment';
|
|
6
6
|
import parse from 'html-react-parser';
|
|
@@ -28,8 +28,8 @@ import Breadcrumb from '../Breadcrumb';
|
|
|
28
28
|
import CustomFetch from '../Fetch';
|
|
29
29
|
import Form from '../Form';
|
|
30
30
|
import GenericDynamic from './GenericDynamic';
|
|
31
|
+
import GenericEditableTable from './GenericEditableTable';
|
|
31
32
|
import QrCode from '../QrCode';
|
|
32
|
-
import SortableList from '../sorting/List';
|
|
33
33
|
import Table from '../DataGrid';
|
|
34
34
|
import TableFilter from '../TableFilter';
|
|
35
35
|
|
|
@@ -55,9 +55,11 @@ function GenericDetail({
|
|
|
55
55
|
|
|
56
56
|
/** General States */
|
|
57
57
|
const [activeStage, setActiveStage] = useState('');
|
|
58
|
+
const [activeTabConfig, setActiveTabConfig] = useState(null);
|
|
58
59
|
const [config, setConfig] = useState({});
|
|
59
60
|
const [data, setData] = useState({});
|
|
60
61
|
const [dataReload, setDataReload] = useState(0);
|
|
62
|
+
const [preloadedOptions, setPreloadedOptions] = useState({});
|
|
61
63
|
const [rowsSelected, setRowsSelected] = useState({});
|
|
62
64
|
const [subnav, setSubnav] = useState([]);
|
|
63
65
|
const [total, setTotal] = useState(0);
|
|
@@ -1321,9 +1323,15 @@ function GenericDetail({
|
|
|
1321
1323
|
return null;
|
|
1322
1324
|
}
|
|
1323
1325
|
case 'scheduling':
|
|
1324
|
-
return
|
|
1325
|
-
|
|
1326
|
-
|
|
1326
|
+
return (
|
|
1327
|
+
<GenericEditableTable
|
|
1328
|
+
schedulingConfig={activeTabConfig}
|
|
1329
|
+
data={data}
|
|
1330
|
+
dataId={routeParams[urlParam]}
|
|
1331
|
+
handleReload={handleReload}
|
|
1332
|
+
preloadedOptions={preloadedOptions}
|
|
1333
|
+
userProfile={userProfile}
|
|
1334
|
+
/>
|
|
1327
1335
|
);
|
|
1328
1336
|
default:
|
|
1329
1337
|
return null;
|
|
@@ -1333,298 +1341,50 @@ function GenericDetail({
|
|
|
1333
1341
|
}
|
|
1334
1342
|
};
|
|
1335
1343
|
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
// Group categories by their type (e.g., Concrete Work, Design Services)
|
|
1341
|
-
const groupedCategories = groupCategoriesByType(scheduleData, rows);
|
|
1342
|
-
|
|
1343
|
-
// Count the number of total columns
|
|
1344
|
-
const totalColumns = columns.filter((column) => column.total).length;
|
|
1345
|
-
|
|
1346
|
-
return (
|
|
1347
|
-
<div className={styles.gridtxt}>
|
|
1348
|
-
<div className={styles.gridtxt__header}>
|
|
1349
|
-
<span>{config.title || 'Estimation Schedule'}</span>
|
|
1350
|
-
</div>
|
|
1344
|
+
/** General Hooks */
|
|
1345
|
+
useEffect(() => {
|
|
1346
|
+
if (tabs && tabs.length > 0) {
|
|
1347
|
+
const activeTab = subnav.find((s) => s.show === true);
|
|
1351
1348
|
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
width: column.width || 'auto',
|
|
1361
|
-
textAlign:
|
|
1362
|
-
column.type === 'currency'
|
|
1363
|
-
? 'right'
|
|
1364
|
-
: 'left',
|
|
1365
|
-
}}
|
|
1366
|
-
>
|
|
1367
|
-
{column.label}
|
|
1368
|
-
</th>
|
|
1369
|
-
))}
|
|
1370
|
-
<th style={{ width: '5%' }}></th>
|
|
1371
|
-
</tr>
|
|
1372
|
-
</thead>
|
|
1373
|
-
<tbody>
|
|
1374
|
-
{groupedCategories.map((group) => (
|
|
1375
|
-
<React.Fragment key={group.category}>
|
|
1376
|
-
{/* Category Row */}
|
|
1377
|
-
<tr className={styles.categoryRow}>
|
|
1378
|
-
<td colSpan={columns.length + 1}>
|
|
1379
|
-
<strong>{group.category}</strong>
|
|
1380
|
-
</td>
|
|
1381
|
-
</tr>
|
|
1382
|
-
|
|
1383
|
-
{/* Data Rows */}
|
|
1384
|
-
{group.entries.map((entry, idx) => (
|
|
1385
|
-
<tr
|
|
1386
|
-
key={`row-${group.category}-${idx}`}
|
|
1387
|
-
>
|
|
1388
|
-
{columns.map((column) => (
|
|
1389
|
-
<td
|
|
1390
|
-
key={`column-${column.id}-${idx}`}
|
|
1391
|
-
style={{
|
|
1392
|
-
textAlign:
|
|
1393
|
-
column.type ===
|
|
1394
|
-
'currency'
|
|
1395
|
-
? 'right'
|
|
1396
|
-
: 'left',
|
|
1397
|
-
}}
|
|
1398
|
-
>
|
|
1399
|
-
{column.type === 'currency'
|
|
1400
|
-
? formatCurrency(
|
|
1401
|
-
entry[column.id]
|
|
1402
|
-
)
|
|
1403
|
-
: entry[column.id]
|
|
1404
|
-
?.label ||
|
|
1405
|
-
entry[column.id] ||
|
|
1406
|
-
''}
|
|
1407
|
-
</td>
|
|
1408
|
-
))}
|
|
1409
|
-
<td>
|
|
1410
|
-
{/* Edit Icon */}
|
|
1411
|
-
<span
|
|
1412
|
-
data-tooltip-id="system-tooltip"
|
|
1413
|
-
data-tooltip-content="Edit"
|
|
1414
|
-
style={{
|
|
1415
|
-
cursor: 'pointer',
|
|
1416
|
-
marginRight: '10px',
|
|
1417
|
-
}}
|
|
1418
|
-
onClick={() =>
|
|
1419
|
-
modalOpen(
|
|
1420
|
-
'update',
|
|
1421
|
-
entry.id
|
|
1422
|
-
)
|
|
1423
|
-
}
|
|
1424
|
-
>
|
|
1425
|
-
<Pencil
|
|
1426
|
-
size={18}
|
|
1427
|
-
strokeWidth={2}
|
|
1428
|
-
/>
|
|
1429
|
-
</span>
|
|
1430
|
-
|
|
1431
|
-
{/* Delete Icon */}
|
|
1432
|
-
<span
|
|
1433
|
-
data-tooltip-id="system-tooltip"
|
|
1434
|
-
data-tooltip-content="Delete"
|
|
1435
|
-
style={{
|
|
1436
|
-
cursor: 'pointer',
|
|
1437
|
-
}}
|
|
1438
|
-
onClick={() =>
|
|
1439
|
-
handleDeleteRow(
|
|
1440
|
-
entry,
|
|
1441
|
-
config,
|
|
1442
|
-
dataId
|
|
1443
|
-
)
|
|
1444
|
-
}
|
|
1445
|
-
>
|
|
1446
|
-
<TrashCan
|
|
1447
|
-
size={18}
|
|
1448
|
-
strokeWidth={2}
|
|
1449
|
-
/>
|
|
1450
|
-
</span>
|
|
1451
|
-
</td>
|
|
1452
|
-
</tr>
|
|
1453
|
-
))}
|
|
1454
|
-
|
|
1455
|
-
{/* Total Cost Row */}
|
|
1456
|
-
<tr className={styles.totalRow}>
|
|
1457
|
-
<td
|
|
1458
|
-
colSpan={
|
|
1459
|
-
columns.length - totalColumns
|
|
1460
|
-
}
|
|
1461
|
-
>
|
|
1462
|
-
<strong>Total Cost</strong>
|
|
1463
|
-
</td>
|
|
1464
|
-
{columns.map((c) =>
|
|
1465
|
-
c.total ? (
|
|
1466
|
-
<td
|
|
1467
|
-
key={c.id}
|
|
1468
|
-
style={{
|
|
1469
|
-
textAlign: 'right',
|
|
1470
|
-
}}
|
|
1471
|
-
>
|
|
1472
|
-
{calculateTotalCost(
|
|
1473
|
-
group.entries,
|
|
1474
|
-
c.id
|
|
1475
|
-
)}
|
|
1476
|
-
</td>
|
|
1477
|
-
) : null
|
|
1478
|
-
)}
|
|
1479
|
-
<td></td>
|
|
1480
|
-
</tr>
|
|
1481
|
-
</React.Fragment>
|
|
1482
|
-
))}
|
|
1483
|
-
</tbody>
|
|
1484
|
-
</table>
|
|
1485
|
-
) : (
|
|
1486
|
-
<div className={styles.noDataMessage}>
|
|
1487
|
-
<p>No scheduling data has been added.</p>
|
|
1488
|
-
</div>
|
|
1489
|
-
)}
|
|
1490
|
-
|
|
1491
|
-
{config.form && (
|
|
1492
|
-
<div className={styles.polActions}>
|
|
1493
|
-
<button
|
|
1494
|
-
className={styles.btn}
|
|
1495
|
-
onClick={() => modalOpen('create', 0)}
|
|
1496
|
-
>
|
|
1497
|
-
Add
|
|
1498
|
-
</button>
|
|
1499
|
-
</div>
|
|
1500
|
-
)}
|
|
1501
|
-
</div>
|
|
1502
|
-
);
|
|
1503
|
-
};
|
|
1349
|
+
if (activeTab) {
|
|
1350
|
+
const newActiveTabConfig = tabs.find(
|
|
1351
|
+
(t) => t.id === activeTab.id
|
|
1352
|
+
);
|
|
1353
|
+
setActiveTabConfig(newActiveTabConfig); // Update state
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}, [subnav, tabs]);
|
|
1504
1357
|
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
{
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
deleteForm.url = `${c.form.url}/json/delete`;
|
|
1525
|
-
deleteForm.method = 'POST';
|
|
1526
|
-
deleteForm.payload = {
|
|
1527
|
-
id: d[c.form.primaryKey],
|
|
1528
|
-
dataId: dataId,
|
|
1529
|
-
key: c.form.jsonKey || '',
|
|
1530
|
-
};
|
|
1531
|
-
|
|
1532
|
-
CustomFetch(
|
|
1533
|
-
deleteForm.url,
|
|
1534
|
-
deleteForm.method,
|
|
1535
|
-
deleteForm.payload,
|
|
1536
|
-
(result) => {
|
|
1537
|
-
if (result.error === '') {
|
|
1538
|
-
handleReload();
|
|
1539
|
-
|
|
1540
|
-
toast.success(
|
|
1541
|
-
`The selected item has been deleted.`
|
|
1542
|
-
);
|
|
1543
|
-
} else {
|
|
1544
|
-
toast.error(
|
|
1545
|
-
<div>{parse(result.error)}</div>
|
|
1546
|
-
);
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1358
|
+
useEffect(() => {
|
|
1359
|
+
const fetchDropdownOptions = async () => {
|
|
1360
|
+
if (activeTabConfig?.columns) {
|
|
1361
|
+
const ajaxColumns = activeTabConfig.columns.filter(
|
|
1362
|
+
(column) =>
|
|
1363
|
+
['dropdown-ajax', 'multi-dropdown-ajax'].includes(
|
|
1364
|
+
column.type
|
|
1365
|
+
) && column.url
|
|
1366
|
+
);
|
|
1367
|
+
|
|
1368
|
+
const options = {};
|
|
1369
|
+
for (const column of ajaxColumns) {
|
|
1370
|
+
try {
|
|
1371
|
+
const res = await CustomFetch(column.url, 'POST');
|
|
1372
|
+
options[column.id] = res.data.data || res.data || []; // Assume API returns options in the expected format
|
|
1373
|
+
} catch (error) {
|
|
1374
|
+
console.error(
|
|
1375
|
+
`Error fetching options for ${column.id}:`,
|
|
1376
|
+
error
|
|
1549
1377
|
);
|
|
1550
|
-
}
|
|
1551
|
-
},
|
|
1552
|
-
{
|
|
1553
|
-
label: 'No',
|
|
1554
|
-
onClick: () => {},
|
|
1555
|
-
},
|
|
1556
|
-
],
|
|
1557
|
-
});
|
|
1558
|
-
};
|
|
1559
|
-
|
|
1560
|
-
// Function to group categories by their type (e.g., Concrete Work, Miscellaneous, etc.)
|
|
1561
|
-
const groupCategoriesByType = (data, rows) => {
|
|
1562
|
-
const grouped = {};
|
|
1563
|
-
|
|
1564
|
-
if (data) {
|
|
1565
|
-
data.forEach((category) => {
|
|
1566
|
-
const categoryType =
|
|
1567
|
-
category[rows.categoryKey.id]?.[rows.categoryKey.label] ||
|
|
1568
|
-
'Miscellaneous'; // Default to 'Miscellaneous' if no category is found
|
|
1569
|
-
|
|
1570
|
-
const categoryId =
|
|
1571
|
-
category[rows.categoryKey.id]?.id ||
|
|
1572
|
-
Number.MAX_SAFE_INTEGER; // Default to max integer if no id is found
|
|
1573
|
-
|
|
1574
|
-
if (!grouped[categoryId]) {
|
|
1575
|
-
grouped[categoryId] = {
|
|
1576
|
-
id: categoryId,
|
|
1577
|
-
category: categoryType,
|
|
1578
|
-
entries: [],
|
|
1579
|
-
};
|
|
1378
|
+
}
|
|
1580
1379
|
}
|
|
1581
|
-
grouped[categoryId].entries.push(category);
|
|
1582
|
-
});
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
return Object.values(grouped).sort((a, b) => a.id - b.id); // Sort categories by id
|
|
1586
|
-
};
|
|
1587
1380
|
|
|
1588
|
-
|
|
1589
|
-
const calculateTotalCost = (entries, id) => {
|
|
1590
|
-
if (!entries || entries.length === 0 || !id) {
|
|
1591
|
-
return '$0.00'; // Return $0.00 if entries are empty or id is not provided
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
let total = 0;
|
|
1595
|
-
|
|
1596
|
-
entries.forEach((entry) => {
|
|
1597
|
-
if (entry && entry[id]) {
|
|
1598
|
-
// Check if the entry and the specific field exist
|
|
1599
|
-
// Parse the cost from the field and remove non-numeric characters
|
|
1600
|
-
const cost =
|
|
1601
|
-
parseFloat(
|
|
1602
|
-
entry[id].toString().replace(/[^0-9.-]+/g, '')
|
|
1603
|
-
) || 0;
|
|
1604
|
-
total += cost;
|
|
1381
|
+
setPreloadedOptions(options);
|
|
1605
1382
|
}
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
// Format the total cost with commas and return it
|
|
1609
|
-
return `$${total.toLocaleString('en-AU', {
|
|
1610
|
-
minimumFractionDigits: 2,
|
|
1611
|
-
maximumFractionDigits: 2,
|
|
1612
|
-
})}`;
|
|
1613
|
-
};
|
|
1383
|
+
};
|
|
1614
1384
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
if (value) {
|
|
1618
|
-
const cost = parseFloat(value.replace(/[^0-9.-]+/g, '')) || 0; // Remove any non-numeric characters
|
|
1619
|
-
return `$${cost.toLocaleString('en-AU', {
|
|
1620
|
-
minimumFractionDigits: 2,
|
|
1621
|
-
maximumFractionDigits: 2,
|
|
1622
|
-
})}`;
|
|
1623
|
-
}
|
|
1624
|
-
return ''; // Return empty if value is not provided
|
|
1625
|
-
};
|
|
1385
|
+
fetchDropdownOptions();
|
|
1386
|
+
}, [activeTabConfig]); // Depend on activeTabConfig
|
|
1626
1387
|
|
|
1627
|
-
/** General Hooks */
|
|
1628
1388
|
useEffect(() => {
|
|
1629
1389
|
const handleResize = () => {
|
|
1630
1390
|
setWindowHeight(window.innerHeight);
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import debounce from 'lodash.debounce';
|
|
3
|
+
import { toast } from 'react-toastify';
|
|
4
|
+
import Popup from 'reactjs-popup';
|
|
5
|
+
import { confirmAlert } from 'react-confirm-alert';
|
|
6
|
+
import { TrashCan, ArrowUp, ArrowDown } from 'akar-icons';
|
|
7
|
+
|
|
8
|
+
import CustomFetch from '../Fetch';
|
|
9
|
+
import Form from '../Form';
|
|
10
|
+
import MultiSelect from '../MultiSelect';
|
|
11
|
+
|
|
12
|
+
import 'react-confirm-alert/src/react-confirm-alert.css';
|
|
13
|
+
import styles from './styles/GenericEditableTable.module.scss';
|
|
14
|
+
|
|
15
|
+
const GenericEditableTable = ({
|
|
16
|
+
schedulingConfig,
|
|
17
|
+
data,
|
|
18
|
+
dataId,
|
|
19
|
+
handleReload,
|
|
20
|
+
preloadedOptions,
|
|
21
|
+
userProfile,
|
|
22
|
+
}) => {
|
|
23
|
+
const { columns, rows, form } = schedulingConfig;
|
|
24
|
+
|
|
25
|
+
const [localData, setLocalData] = useState([]);
|
|
26
|
+
const [groupedCategories, setGroupedCategories] = useState([]);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const enrichedData = data[rows.key]?.map((entry, index) => ({
|
|
30
|
+
...entry,
|
|
31
|
+
sort_order: entry.sort_order ?? index + 1,
|
|
32
|
+
}));
|
|
33
|
+
setLocalData(enrichedData || []);
|
|
34
|
+
}, [data, rows.key]);
|
|
35
|
+
|
|
36
|
+
const debouncedUpdate = useMemo(
|
|
37
|
+
() =>
|
|
38
|
+
debounce(async (updatedData) => {
|
|
39
|
+
try {
|
|
40
|
+
const res = await CustomFetch(
|
|
41
|
+
`${form.url}/${dataId}`,
|
|
42
|
+
'PUT',
|
|
43
|
+
updatedData
|
|
44
|
+
);
|
|
45
|
+
if (!res.error) {
|
|
46
|
+
toast.success('Template updated successfully');
|
|
47
|
+
} else {
|
|
48
|
+
toast.error(res.error);
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('Error updating field:', error);
|
|
52
|
+
}
|
|
53
|
+
}, 1000),
|
|
54
|
+
[]
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const groupCategoriesByType = (data) => {
|
|
58
|
+
const grouped = {};
|
|
59
|
+
data.forEach((category) => {
|
|
60
|
+
const categoryType =
|
|
61
|
+
category[rows.categoryKey.id]?.[rows.categoryKey.label] ||
|
|
62
|
+
'Miscellaneous';
|
|
63
|
+
|
|
64
|
+
const categoryId =
|
|
65
|
+
category[rows.categoryKey.id]?.id || Number.MAX_SAFE_INTEGER;
|
|
66
|
+
|
|
67
|
+
if (!grouped[categoryId]) {
|
|
68
|
+
grouped[categoryId] = {
|
|
69
|
+
id: categoryId,
|
|
70
|
+
category: categoryType,
|
|
71
|
+
entries: [],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
grouped[categoryId].entries.push(category);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
Object.values(grouped).forEach((group) => {
|
|
78
|
+
group.entries.sort((a, b) => a.sort_order - b.sort_order);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return Object.values(grouped);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const handleFieldChange = (value, fieldId, keyCounter) => {
|
|
85
|
+
setLocalData((prev) => {
|
|
86
|
+
const updatedDetail = [...prev];
|
|
87
|
+
updatedDetail[keyCounter] = {
|
|
88
|
+
...updatedDetail[keyCounter],
|
|
89
|
+
[fieldId]: value,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
debouncedUpdate({ detail: updatedDetail });
|
|
93
|
+
|
|
94
|
+
return updatedDetail;
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const handleSortChange = (groupId, entryId, direction) => {
|
|
99
|
+
setLocalData((prev) => {
|
|
100
|
+
const updatedDetail = [...prev];
|
|
101
|
+
const group = groupCategoriesByType(updatedDetail).find(
|
|
102
|
+
(group) => group.id === groupId
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
if (group) {
|
|
106
|
+
const index = group.entries.findIndex(
|
|
107
|
+
(entry) => entry.id === entryId
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
if (
|
|
111
|
+
(direction === 'up' && index > 0) ||
|
|
112
|
+
(direction === 'down' && index < group.entries.length - 1)
|
|
113
|
+
) {
|
|
114
|
+
const swapIndex =
|
|
115
|
+
direction === 'up' ? index - 1 : index + 1;
|
|
116
|
+
[
|
|
117
|
+
group.entries[index].sort_order,
|
|
118
|
+
group.entries[swapIndex].sort_order,
|
|
119
|
+
] = [
|
|
120
|
+
group.entries[swapIndex].sort_order,
|
|
121
|
+
group.entries[index].sort_order,
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
const reordered = updatedDetail.map((item) => {
|
|
125
|
+
const entry = group.entries.find(
|
|
126
|
+
(e) => e.id === item.id
|
|
127
|
+
);
|
|
128
|
+
return entry
|
|
129
|
+
? { ...item, sort_order: entry.sort_order }
|
|
130
|
+
: item;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
debouncedUpdate({ detail: reordered });
|
|
134
|
+
return reordered;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return updatedDetail;
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const renderScheduledTableField = (entry, column, keyCounter) => {
|
|
143
|
+
if (!entry || !column) return null;
|
|
144
|
+
|
|
145
|
+
switch (column.type) {
|
|
146
|
+
case 'currency':
|
|
147
|
+
case 'number':
|
|
148
|
+
return (
|
|
149
|
+
<input
|
|
150
|
+
type="text"
|
|
151
|
+
value={entry[column.id] || ''}
|
|
152
|
+
onChange={(e) =>
|
|
153
|
+
handleFieldChange(
|
|
154
|
+
e.target.value,
|
|
155
|
+
column.id,
|
|
156
|
+
keyCounter
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
style={{ textAlign: 'right' }}
|
|
160
|
+
/>
|
|
161
|
+
);
|
|
162
|
+
case 'dropdown':
|
|
163
|
+
return (
|
|
164
|
+
<select
|
|
165
|
+
value={entry[column.id] || ''}
|
|
166
|
+
onChange={(e) =>
|
|
167
|
+
handleFieldChange(
|
|
168
|
+
e.target.value,
|
|
169
|
+
column.id,
|
|
170
|
+
keyCounter
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
>
|
|
174
|
+
{column.options.map((option) => (
|
|
175
|
+
<option key={option.id} value={option.id}>
|
|
176
|
+
{option.label}
|
|
177
|
+
</option>
|
|
178
|
+
))}
|
|
179
|
+
</select>
|
|
180
|
+
);
|
|
181
|
+
case 'dropdown-ajax':
|
|
182
|
+
const options = preloadedOptions[column.id] || [];
|
|
183
|
+
return (
|
|
184
|
+
<select
|
|
185
|
+
value={entry[column.id] || ''}
|
|
186
|
+
onChange={(e) =>
|
|
187
|
+
handleFieldChange(
|
|
188
|
+
e.target.value,
|
|
189
|
+
column.id,
|
|
190
|
+
keyCounter
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
>
|
|
194
|
+
<option>Select an Option</option>
|
|
195
|
+
{options.map((option) => (
|
|
196
|
+
<option key={option.id} value={option.id}>
|
|
197
|
+
{option.label}
|
|
198
|
+
</option>
|
|
199
|
+
))}
|
|
200
|
+
</select>
|
|
201
|
+
);
|
|
202
|
+
case 'multi-dropdown-ajax':
|
|
203
|
+
const multiOptions = preloadedOptions[column.id] || [];
|
|
204
|
+
return (
|
|
205
|
+
<MultiSelect
|
|
206
|
+
settings={{
|
|
207
|
+
id: column.id,
|
|
208
|
+
}}
|
|
209
|
+
className={styles.selectFullWidth}
|
|
210
|
+
multi={false}
|
|
211
|
+
onChange={(value) =>
|
|
212
|
+
handleFieldChange(value, column.id, keyCounter)
|
|
213
|
+
}
|
|
214
|
+
inputValue={entry[column.id] || ''}
|
|
215
|
+
options={multiOptions}
|
|
216
|
+
/>
|
|
217
|
+
);
|
|
218
|
+
default:
|
|
219
|
+
return (
|
|
220
|
+
<input
|
|
221
|
+
type="text"
|
|
222
|
+
defaultValue={entry[column.id] || ''}
|
|
223
|
+
onChange={(e) =>
|
|
224
|
+
handleFieldChange(
|
|
225
|
+
e.target.value,
|
|
226
|
+
column.id,
|
|
227
|
+
keyCounter
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
/>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
setGroupedCategories(groupCategoriesByType(localData));
|
|
237
|
+
}, [localData]);
|
|
238
|
+
|
|
239
|
+
return (
|
|
240
|
+
<div className={styles.gridtxt}>
|
|
241
|
+
<div className={styles.gridtxt__header}>
|
|
242
|
+
<span>{schedulingConfig.title || 'Estimation Schedule'}</span>
|
|
243
|
+
</div>
|
|
244
|
+
{groupedCategories.length > 0 ? (
|
|
245
|
+
<table className={styles.schedulingTable}>
|
|
246
|
+
<thead>
|
|
247
|
+
<tr>
|
|
248
|
+
{columns.map((column) => (
|
|
249
|
+
<th
|
|
250
|
+
key={column.id}
|
|
251
|
+
style={{ width: column.width || 'auto' }}
|
|
252
|
+
>
|
|
253
|
+
{column.label}
|
|
254
|
+
</th>
|
|
255
|
+
))}
|
|
256
|
+
<th style={{ width: '5%' }}></th>
|
|
257
|
+
</tr>
|
|
258
|
+
</thead>
|
|
259
|
+
<tbody>
|
|
260
|
+
{groupedCategories.map((group) => (
|
|
261
|
+
<React.Fragment key={group.id}>
|
|
262
|
+
<tr className={styles.categoryRow}>
|
|
263
|
+
<td colSpan={columns.length + 1}>
|
|
264
|
+
{group.category}
|
|
265
|
+
</td>
|
|
266
|
+
</tr>
|
|
267
|
+
{group.entries.map((entry, idx) => (
|
|
268
|
+
<tr key={entry.id}>
|
|
269
|
+
{columns.map((column) => (
|
|
270
|
+
<td
|
|
271
|
+
key={column.id}
|
|
272
|
+
style={{
|
|
273
|
+
width:
|
|
274
|
+
column.width || 'auto',
|
|
275
|
+
textAlign:
|
|
276
|
+
column.type ===
|
|
277
|
+
'currency'
|
|
278
|
+
? 'right'
|
|
279
|
+
: 'left',
|
|
280
|
+
}}
|
|
281
|
+
>
|
|
282
|
+
{renderScheduledTableField(
|
|
283
|
+
entry,
|
|
284
|
+
column,
|
|
285
|
+
entry.keyCounter
|
|
286
|
+
)}
|
|
287
|
+
</td>
|
|
288
|
+
))}
|
|
289
|
+
<td>
|
|
290
|
+
{idx > 0 && (
|
|
291
|
+
<ArrowUp
|
|
292
|
+
size={18}
|
|
293
|
+
onClick={() =>
|
|
294
|
+
handleSortChange(
|
|
295
|
+
group.id,
|
|
296
|
+
entry.id,
|
|
297
|
+
'up'
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
style={{
|
|
301
|
+
cursor: 'pointer',
|
|
302
|
+
}}
|
|
303
|
+
/>
|
|
304
|
+
)}
|
|
305
|
+
{idx < group.entries.length - 1 && (
|
|
306
|
+
<ArrowDown
|
|
307
|
+
size={18}
|
|
308
|
+
onClick={() =>
|
|
309
|
+
handleSortChange(
|
|
310
|
+
group.id,
|
|
311
|
+
entry.id,
|
|
312
|
+
'down'
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
style={{
|
|
316
|
+
cursor: 'pointer',
|
|
317
|
+
}}
|
|
318
|
+
/>
|
|
319
|
+
)}
|
|
320
|
+
<TrashCan
|
|
321
|
+
size={18}
|
|
322
|
+
onClick={() =>
|
|
323
|
+
handleDeleteRow(entry)
|
|
324
|
+
}
|
|
325
|
+
style={{ cursor: 'pointer' }}
|
|
326
|
+
/>
|
|
327
|
+
</td>
|
|
328
|
+
</tr>
|
|
329
|
+
))}
|
|
330
|
+
</React.Fragment>
|
|
331
|
+
))}
|
|
332
|
+
</tbody>
|
|
333
|
+
</table>
|
|
334
|
+
) : (
|
|
335
|
+
<div>No data available.</div>
|
|
336
|
+
)}
|
|
337
|
+
</div>
|
|
338
|
+
);
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
export default GenericEditableTable;
|
|
@@ -277,6 +277,10 @@
|
|
|
277
277
|
border: 1px solid rgba(var(--highlight-color-rgb), 0.85);
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
+
.formItem textarea {
|
|
281
|
+
line-height: 1.65 !important;
|
|
282
|
+
}
|
|
283
|
+
|
|
280
284
|
input[type='file'] {
|
|
281
285
|
background: var(--item-color) !important;
|
|
282
286
|
border-radius: var (--br) !important;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
.schedulingTable {
|
|
2
|
+
width: 100%;
|
|
3
|
+
border-collapse: collapse;
|
|
4
|
+
margin: 1em 0;
|
|
5
|
+
|
|
6
|
+
th,
|
|
7
|
+
td {
|
|
8
|
+
padding: 0.75rem;
|
|
9
|
+
text-align: left;
|
|
10
|
+
border: 1px solid rgba(var(--primary-rgb), 0.1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
th {
|
|
14
|
+
background-color: var(--secondary-color);
|
|
15
|
+
color: var(--item-color);
|
|
16
|
+
font-weight: bold;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
td {
|
|
20
|
+
background-color: rgba(var(--primary-rgb), 0.05);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.categoryRow {
|
|
25
|
+
background-color: rgba(var(--primary-rgb), 0.1);
|
|
26
|
+
text-align: left;
|
|
27
|
+
font-weight: bold;
|
|
28
|
+
font-size: 1.2rem;
|
|
29
|
+
color: var(--primary-color);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.totalRow {
|
|
33
|
+
background-color: rgba(var(--primary-rgb), 0.05);
|
|
34
|
+
font-weight: bold;
|
|
35
|
+
text-align: right;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.noDataMessage {
|
|
39
|
+
text-align: center;
|
|
40
|
+
color: var(--secondary-color);
|
|
41
|
+
font-size: 1.125em;
|
|
42
|
+
padding: 1.5em;
|
|
43
|
+
margin: 2em auto;
|
|
44
|
+
background: #f9f9f9;
|
|
45
|
+
border: 1px solid #ddd;
|
|
46
|
+
border-radius: 6px;
|
|
47
|
+
max-width: 600px;
|
|
48
|
+
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
|
|
49
|
+
font-style: italic;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.polActions {
|
|
53
|
+
position: fixed;
|
|
54
|
+
bottom: 15px;
|
|
55
|
+
right: 20px;
|
|
56
|
+
width: auto;
|
|
57
|
+
|
|
58
|
+
button {
|
|
59
|
+
display: block;
|
|
60
|
+
padding: 0.35em 1em;
|
|
61
|
+
margin: 0 0.15em;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.gridtxt {
|
|
66
|
+
&__header {
|
|
67
|
+
width: 100%;
|
|
68
|
+
display: block;
|
|
69
|
+
background: rgba(var(--primary-rgb), 0.05);
|
|
70
|
+
box-sizing: border-box;
|
|
71
|
+
padding: 10px 20px;
|
|
72
|
+
border-radius: var(--br);
|
|
73
|
+
margin-bottom: 0;
|
|
74
|
+
font-weight: 700;
|
|
75
|
+
text-align: center;
|
|
76
|
+
color: var(--paragraph-color);
|
|
77
|
+
|
|
78
|
+
span {
|
|
79
|
+
font-weight: 700;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
> ul {
|
|
84
|
+
width: 100%;
|
|
85
|
+
display: flex;
|
|
86
|
+
justify-content: flex-start;
|
|
87
|
+
flex-wrap: wrap;
|
|
88
|
+
list-style: none;
|
|
89
|
+
padding: 0.85rem;
|
|
90
|
+
margin: 0;
|
|
91
|
+
|
|
92
|
+
li {
|
|
93
|
+
flex: 0 0 calc(50% - 10px);
|
|
94
|
+
padding: 0.65rem;
|
|
95
|
+
border: 1px solid rgba(var(--primary-rgb), 0.095);
|
|
96
|
+
color: var(--paragraph-color);
|
|
97
|
+
background: rgba(var(--primary-rgb), 0.015);
|
|
98
|
+
margin: 3px;
|
|
99
|
+
border-radius: var(--br);
|
|
100
|
+
line-height: 1.45;
|
|
101
|
+
font-size: 0.85rem;
|
|
102
|
+
|
|
103
|
+
&.fw-grid-item {
|
|
104
|
+
flex: 0 0 calc(100% - 10px);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
&.notecolor {
|
|
108
|
+
border: 1px solid var(--secondary-color);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.btn {
|
|
115
|
+
width: max-content;
|
|
116
|
+
display: inline-block;
|
|
117
|
+
position: relative;
|
|
118
|
+
padding: 0.65rem 1rem;
|
|
119
|
+
cursor: pointer;
|
|
120
|
+
font-size: 1rem;
|
|
121
|
+
color: var(--tertiary-color);
|
|
122
|
+
text-decoration: none;
|
|
123
|
+
overflow: hidden;
|
|
124
|
+
background: var(--primary-color);
|
|
125
|
+
border: 1px solid rgba(var(--primary-color--rgb), 1.1);
|
|
126
|
+
border-radius: var(--br);
|
|
127
|
+
outline: none;
|
|
128
|
+
transition: all 0.2s cubic-bezier(0.85, 0, 0.15, 1) 0s;
|
|
129
|
+
font-size: 1.25em;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.btn:hover {
|
|
133
|
+
color: var(--primary-color);
|
|
134
|
+
background: var(--highlight-color);
|
|
135
|
+
border: 1px solid rgba(var(--highlight-rgb), 1.05);
|
|
136
|
+
}
|
|
@@ -270,6 +270,10 @@
|
|
|
270
270
|
border: 1px solid rgba(var(--primary-color-rgb), 0.85);
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
.formItem textarea {
|
|
274
|
+
line-height: 1.65 !important;
|
|
275
|
+
}
|
|
276
|
+
|
|
273
277
|
input[type='file'] {
|
|
274
278
|
background: var(--item-color) !important;
|
|
275
279
|
border-radius: var (--br) !important;
|
|
@@ -144,7 +144,7 @@ select:not(:placeholder-shown) + .fi__span {
|
|
|
144
144
|
background: var(--tertiary-color) !important;
|
|
145
145
|
font-weight: 400 !important;
|
|
146
146
|
transition: all 0.5s cubic-bezier(0.5, 0.5, 0, 1) !important;
|
|
147
|
-
z-index:
|
|
147
|
+
z-index: 1 !important;
|
|
148
148
|
color: rgba(var(--paragraph-rgb), 0.65) !important;
|
|
149
149
|
}
|
|
150
150
|
|
|
@@ -221,6 +221,10 @@ select:not(:placeholder-shown) + .fi__span {
|
|
|
221
221
|
border: 1px solid rgba(var(--paragraph-color-rgb), 0.85);
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
.formItem textarea {
|
|
225
|
+
line-height: 1.65 !important;
|
|
226
|
+
}
|
|
227
|
+
|
|
224
228
|
input[type='file'] {
|
|
225
229
|
background: white !important;
|
|
226
230
|
border-radius: var(--br) !important;
|
|
@@ -92,13 +92,13 @@
|
|
|
92
92
|
&.active a,
|
|
93
93
|
&:hover span,
|
|
94
94
|
&.active span {
|
|
95
|
-
background: rgba(var(--
|
|
96
|
-
color: var(--
|
|
95
|
+
background: rgba(var(--nav-active-rgb), 1);
|
|
96
|
+
color: var(--highlight-color);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
&:hover svg,
|
|
100
100
|
&.active svg {
|
|
101
|
-
color: var(--
|
|
101
|
+
color: var(--highlight-color);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
> ul {
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
|
|
11
11
|
html,
|
|
12
12
|
body {
|
|
13
|
-
font-family: -apple-system, BlinkMacSystemFont,
|
|
13
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
|
14
|
+
Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
|
15
|
+
'Segoe UI Symbol';
|
|
14
16
|
height: 100%;
|
|
15
17
|
text-rendering: geometricPrecision;
|
|
16
18
|
font-stretch: 75% 125%;
|
|
@@ -19,7 +21,9 @@ body {
|
|
|
19
21
|
body {
|
|
20
22
|
background: var(--bg-color);
|
|
21
23
|
font-weight: var(--para-weight);
|
|
22
|
-
font-family: -apple-system, BlinkMacSystemFont,
|
|
24
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
|
25
|
+
Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
|
26
|
+
'Segoe UI Symbol';
|
|
23
27
|
font-style: normal;
|
|
24
28
|
overflow-x: hidden;
|
|
25
29
|
overscroll-behavior: none;
|
|
@@ -113,6 +117,11 @@ input[type]:not([type='search']):not([type='url']):not([type='hidden']):not(
|
|
|
113
117
|
padding: 0 !important;
|
|
114
118
|
}
|
|
115
119
|
|
|
120
|
+
.popup-overlay {
|
|
121
|
+
background: rgba(0, 0, 0, 0.65) !important;
|
|
122
|
+
backdrop-filter: blur(3px);
|
|
123
|
+
}
|
|
124
|
+
|
|
116
125
|
.AutocompletePlace-results {
|
|
117
126
|
z-index: 999;
|
|
118
127
|
width: 500px;
|
|
@@ -191,4 +200,4 @@ input[type]:not([type='search']):not([type='url']):not([type='hidden']):not(
|
|
|
191
200
|
|
|
192
201
|
.visns-select__menu {
|
|
193
202
|
z-index: 999 !important;
|
|
194
|
-
}
|
|
203
|
+
}
|