@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.10",
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(activeTabConfig);
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 category (e.g., Concrete Work)
1340
+ // Group categories by their type (e.g., Concrete Work, Design Services)
1338
1341
  const groupedCategories = groupCategoriesByType(scheduleData, rows);
1339
1342
 
1340
- const { total, nonTotal } = columns.reduce(
1341
- (acc, column) => {
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 || 'Project Scheduling'}</span>
1349
+ <span>{config.title || 'Estimation Schedule'}</span>
1356
1350
  </div>
1357
1351
 
1358
1352
  {groupedCategories.length > 0 ? (
1359
- groupedCategories.map((group, index) => (
1360
- <div key={index} className={styles.categoryGroup}>
1361
- <h3>{group.category}</h3>
1362
- <table className={styles.schedulingTable}>
1363
- <thead>
1364
- <tr>
1365
- {columns.map((column) => (
1366
- <th
1367
- key={column.id}
1368
- style={
1369
- column.width
1370
- ? {
1371
- width: column.width,
1372
- textAlign:
1373
- column.type ===
1374
- 'currency'
1375
- ? 'right'
1376
- : 'left',
1377
- }
1378
- : {}
1379
- }
1380
- >
1381
- {column.label}
1382
- </th>
1383
- ))}
1384
- <th style={{ width: '5%' }}></th>
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
- </thead>
1387
- <tbody>
1388
- {group.entries.map((category, idx) => (
1389
- <tr key={`row-${idx}`}>
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-${idx}-${column.id}`}
1393
- style={
1394
- column.type ===
1395
- 'currency'
1396
- ? {
1397
- textAlign:
1398
- 'right',
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
- category[
1406
- column.id
1407
- ]
1401
+ entry[column.id]
1408
1402
  )
1409
- : category[column.id]
1403
+ : entry[column.id]
1410
1404
  ?.label ||
1411
- category[column.id] ||
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
- category.id
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
- <tr>
1438
- <td colSpan={nonTotal}>
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
- </tbody>
1459
- </table>
1460
- </div>
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
- if (!grouped[categoryType]) {
1492
- grouped[categoryType] = {
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[categoryType].entries.push(category);
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
- // Parse the cost from the 'materials' field and remove non-numeric characters
1511
- const cost = parseFloat(entry[id].replace(/[^0-9.-]+/g, '')) || 0;
1512
- total += cost;
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); /* Updated header 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
- .categoryGroup {
648
- margin-top: 2rem;
649
- }
650
-
651
- h3 {
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
- .categoryGroup table {
661
- width: 100%;
662
- border-collapse: collapse;
663
- margin-top: 1rem;
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.25em;
675
- padding: 1em;
676
- margin-top: 2em;
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
  }