@visns-studio/visns-components 5.0.10 → 5.0.11
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.11",
|
|
79
79
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
80
80
|
"main": "src/index.js",
|
|
81
81
|
"files": [
|
|
@@ -1321,7 +1321,10 @@ function GenericDetail({
|
|
|
1321
1321
|
return null;
|
|
1322
1322
|
}
|
|
1323
1323
|
case 'scheduling':
|
|
1324
|
-
return renderSchedulingTable(
|
|
1324
|
+
return renderSchedulingTable(
|
|
1325
|
+
activeTabConfig,
|
|
1326
|
+
routeParams[urlParam]
|
|
1327
|
+
);
|
|
1325
1328
|
default:
|
|
1326
1329
|
return null;
|
|
1327
1330
|
}
|
|
@@ -1330,99 +1333,92 @@ function GenericDetail({
|
|
|
1330
1333
|
}
|
|
1331
1334
|
};
|
|
1332
1335
|
|
|
1333
|
-
const renderSchedulingTable = (config) => {
|
|
1336
|
+
const renderSchedulingTable = (config, dataId) => {
|
|
1334
1337
|
const { columns, rows } = config;
|
|
1335
1338
|
const scheduleData = data[rows.key]; // Access schedule data directly from the rows
|
|
1336
1339
|
|
|
1337
|
-
// Group categories by their
|
|
1340
|
+
// Group categories by their type (e.g., Concrete Work, Design Services)
|
|
1338
1341
|
const groupedCategories = groupCategoriesByType(scheduleData, rows);
|
|
1339
1342
|
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
if (column.total) {
|
|
1343
|
-
acc.total++;
|
|
1344
|
-
} else {
|
|
1345
|
-
acc.nonTotal++;
|
|
1346
|
-
}
|
|
1347
|
-
return acc;
|
|
1348
|
-
},
|
|
1349
|
-
{ total: 0, nonTotal: 0 }
|
|
1350
|
-
);
|
|
1343
|
+
// Count the number of total columns
|
|
1344
|
+
const totalColumns = columns.filter((column) => column.total).length;
|
|
1351
1345
|
|
|
1352
1346
|
return (
|
|
1353
1347
|
<div className={styles.gridtxt}>
|
|
1354
1348
|
<div className={styles.gridtxt__header}>
|
|
1355
|
-
<span>{config.title || '
|
|
1349
|
+
<span>{config.title || 'Estimation Schedule'}</span>
|
|
1356
1350
|
</div>
|
|
1357
1351
|
|
|
1358
1352
|
{groupedCategories.length > 0 ? (
|
|
1359
|
-
|
|
1360
|
-
<
|
|
1361
|
-
<
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
{
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
<
|
|
1353
|
+
<table className={styles.schedulingTable}>
|
|
1354
|
+
<thead>
|
|
1355
|
+
<tr>
|
|
1356
|
+
{columns.map((column) => (
|
|
1357
|
+
<th
|
|
1358
|
+
key={column.id}
|
|
1359
|
+
style={{
|
|
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>
|
|
1385
1381
|
</tr>
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
{group.entries.map((
|
|
1389
|
-
<tr
|
|
1382
|
+
|
|
1383
|
+
{/* Data Rows */}
|
|
1384
|
+
{group.entries.map((entry, idx) => (
|
|
1385
|
+
<tr
|
|
1386
|
+
key={`row-${group.category}-${idx}`}
|
|
1387
|
+
>
|
|
1390
1388
|
{columns.map((column) => (
|
|
1391
1389
|
<td
|
|
1392
|
-
key={`column-${
|
|
1393
|
-
style={
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
: {}
|
|
1401
|
-
}
|
|
1390
|
+
key={`column-${column.id}-${idx}`}
|
|
1391
|
+
style={{
|
|
1392
|
+
textAlign:
|
|
1393
|
+
column.type ===
|
|
1394
|
+
'currency'
|
|
1395
|
+
? 'right'
|
|
1396
|
+
: 'left',
|
|
1397
|
+
}}
|
|
1402
1398
|
>
|
|
1403
1399
|
{column.type === 'currency'
|
|
1404
1400
|
? formatCurrency(
|
|
1405
|
-
|
|
1406
|
-
column.id
|
|
1407
|
-
]
|
|
1401
|
+
entry[column.id]
|
|
1408
1402
|
)
|
|
1409
|
-
:
|
|
1403
|
+
: entry[column.id]
|
|
1410
1404
|
?.label ||
|
|
1411
|
-
|
|
1405
|
+
entry[column.id] ||
|
|
1412
1406
|
''}
|
|
1413
1407
|
</td>
|
|
1414
1408
|
))}
|
|
1415
1409
|
<td>
|
|
1410
|
+
{/* Edit Icon */}
|
|
1416
1411
|
<span
|
|
1417
1412
|
data-tooltip-id="system-tooltip"
|
|
1418
1413
|
data-tooltip-content="Edit"
|
|
1419
1414
|
style={{
|
|
1420
1415
|
cursor: 'pointer',
|
|
1416
|
+
marginRight: '10px',
|
|
1421
1417
|
}}
|
|
1422
1418
|
onClick={() =>
|
|
1423
1419
|
modalOpen(
|
|
1424
1420
|
'update',
|
|
1425
|
-
|
|
1421
|
+
entry.id
|
|
1426
1422
|
)
|
|
1427
1423
|
}
|
|
1428
1424
|
>
|
|
@@ -1431,11 +1427,38 @@ function GenericDetail({
|
|
|
1431
1427
|
strokeWidth={2}
|
|
1432
1428
|
/>
|
|
1433
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>
|
|
1434
1451
|
</td>
|
|
1435
1452
|
</tr>
|
|
1436
1453
|
))}
|
|
1437
|
-
|
|
1438
|
-
|
|
1454
|
+
|
|
1455
|
+
{/* Total Cost Row */}
|
|
1456
|
+
<tr className={styles.totalRow}>
|
|
1457
|
+
<td
|
|
1458
|
+
colSpan={
|
|
1459
|
+
columns.length - totalColumns
|
|
1460
|
+
}
|
|
1461
|
+
>
|
|
1439
1462
|
<strong>Total Cost</strong>
|
|
1440
1463
|
</td>
|
|
1441
1464
|
{columns.map((c) =>
|
|
@@ -1455,10 +1478,10 @@ function GenericDetail({
|
|
|
1455
1478
|
)}
|
|
1456
1479
|
<td></td>
|
|
1457
1480
|
</tr>
|
|
1458
|
-
</
|
|
1459
|
-
|
|
1460
|
-
</
|
|
1461
|
-
|
|
1481
|
+
</React.Fragment>
|
|
1482
|
+
))}
|
|
1483
|
+
</tbody>
|
|
1484
|
+
</table>
|
|
1462
1485
|
) : (
|
|
1463
1486
|
<div className={styles.noDataMessage}>
|
|
1464
1487
|
<p>No scheduling data has been added.</p>
|
|
@@ -1479,6 +1502,61 @@ function GenericDetail({
|
|
|
1479
1502
|
);
|
|
1480
1503
|
};
|
|
1481
1504
|
|
|
1505
|
+
const handleDeleteRow = (d, c, dataId) => {
|
|
1506
|
+
confirmAlert({
|
|
1507
|
+
title: 'Delete Item',
|
|
1508
|
+
message:
|
|
1509
|
+
d.description && d.description !== ''
|
|
1510
|
+
? d.description
|
|
1511
|
+
: `Are you sure you want to delete ${
|
|
1512
|
+
d.name ? d.name : d.label ? d.label : 'this item'
|
|
1513
|
+
}?`,
|
|
1514
|
+
buttons: [
|
|
1515
|
+
{
|
|
1516
|
+
label: 'Yes',
|
|
1517
|
+
onClick: () => {
|
|
1518
|
+
let deleteForm = {
|
|
1519
|
+
url: '',
|
|
1520
|
+
method: 'DELETE',
|
|
1521
|
+
payload: {},
|
|
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
|
+
}
|
|
1549
|
+
);
|
|
1550
|
+
},
|
|
1551
|
+
},
|
|
1552
|
+
{
|
|
1553
|
+
label: 'No',
|
|
1554
|
+
onClick: () => {},
|
|
1555
|
+
},
|
|
1556
|
+
],
|
|
1557
|
+
});
|
|
1558
|
+
};
|
|
1559
|
+
|
|
1482
1560
|
// Function to group categories by their type (e.g., Concrete Work, Miscellaneous, etc.)
|
|
1483
1561
|
const groupCategoriesByType = (data, rows) => {
|
|
1484
1562
|
const grouped = {};
|
|
@@ -1488,28 +1566,43 @@ function GenericDetail({
|
|
|
1488
1566
|
const categoryType =
|
|
1489
1567
|
category[rows.categoryKey.id]?.[rows.categoryKey.label] ||
|
|
1490
1568
|
'Miscellaneous'; // Default to 'Miscellaneous' if no category is found
|
|
1491
|
-
|
|
1492
|
-
|
|
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,
|
|
1493
1577
|
category: categoryType,
|
|
1494
1578
|
entries: [],
|
|
1495
1579
|
};
|
|
1496
1580
|
}
|
|
1497
|
-
grouped[
|
|
1581
|
+
grouped[categoryId].entries.push(category);
|
|
1498
1582
|
});
|
|
1499
1583
|
}
|
|
1500
1584
|
|
|
1501
|
-
return Object.values(grouped).sort((a, b) =>
|
|
1502
|
-
a.category.localeCompare(b.category)
|
|
1503
|
-
); // Sort categories alphabetically
|
|
1585
|
+
return Object.values(grouped).sort((a, b) => a.id - b.id); // Sort categories by id
|
|
1504
1586
|
};
|
|
1505
1587
|
|
|
1506
1588
|
// Function to calculate the total cost for a group
|
|
1507
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
|
+
|
|
1508
1594
|
let total = 0;
|
|
1595
|
+
|
|
1509
1596
|
entries.forEach((entry) => {
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
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;
|
|
1605
|
+
}
|
|
1513
1606
|
});
|
|
1514
1607
|
|
|
1515
1608
|
// Format the total cost with commas and return it
|
|
@@ -635,7 +635,7 @@
|
|
|
635
635
|
}
|
|
636
636
|
|
|
637
637
|
.schedulingTable th {
|
|
638
|
-
background-color: var(--secondary-color);
|
|
638
|
+
background-color: var(--secondary-color);
|
|
639
639
|
color: var(--item-color);
|
|
640
640
|
font-weight: bold;
|
|
641
641
|
}
|
|
@@ -644,34 +644,30 @@
|
|
|
644
644
|
background-color: rgba(var(--primary-rgb), 0.05);
|
|
645
645
|
}
|
|
646
646
|
|
|
647
|
-
.
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
font-size: 1.5em;
|
|
647
|
+
.categoryRow {
|
|
648
|
+
background-color: rgba(var(--primary-rgb), 0.1);
|
|
649
|
+
text-align: left;
|
|
650
|
+
font-weight: bold;
|
|
651
|
+
font-size: 1.2rem;
|
|
653
652
|
color: var(--primary-color);
|
|
654
|
-
font-weight: 700;
|
|
655
|
-
display: flex;
|
|
656
|
-
justify-content: space-between; /* Ensure space for the Add button */
|
|
657
|
-
align-items: center;
|
|
658
653
|
}
|
|
659
654
|
|
|
660
|
-
.
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
.categoryGroup td {
|
|
667
|
-
padding: 0.75rem;
|
|
668
|
-
border: 1px solid rgba(var(--primary-rgb), 0.1);
|
|
655
|
+
.totalRow {
|
|
656
|
+
background-color: rgba(var(--primary-rgb), 0.05);
|
|
657
|
+
font-weight: bold;
|
|
658
|
+
text-align: right;
|
|
669
659
|
}
|
|
670
660
|
|
|
671
661
|
.noDataMessage {
|
|
672
662
|
text-align: center;
|
|
673
|
-
color: var(--secondary-color);
|
|
674
|
-
font-size: 1.
|
|
675
|
-
padding:
|
|
676
|
-
margin
|
|
663
|
+
color: var(--secondary-color); /* Maintain a clear and neutral color */
|
|
664
|
+
font-size: 1.125em; /* Slightly reduce the font size for a cleaner look */
|
|
665
|
+
padding: 1.5em; /* Adjust padding for better spacing */
|
|
666
|
+
margin: 2em auto; /* Add auto margin for centering */
|
|
667
|
+
background: #f9f9f9; /* Subtle background to match the theme */
|
|
668
|
+
border: 1px solid #ddd; /* Light border for structure */
|
|
669
|
+
border-radius: 6px; /* Slight rounding for a modern feel */
|
|
670
|
+
max-width: 600px; /* Constrain width for a balanced layout */
|
|
671
|
+
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1); /* Gentle shadow for depth */
|
|
672
|
+
font-style: italic; /* Optional: Add a subtle font style */
|
|
677
673
|
}
|