@visns-studio/visns-components 5.0.8 → 5.0.9

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.8",
78
+ "version": "5.0.9",
79
79
  "description": "Various packages to assist in the development of our Custom Applications.",
80
80
  "main": "src/index.js",
81
81
  "files": [
@@ -681,7 +681,6 @@ function Field({
681
681
  className={inputClass[settings.id]}
682
682
  onChange={onChange}
683
683
  />
684
- {console.info(inputValue)}
685
684
  {inputValue && inputValue.length > 0 && (
686
685
  <ul className="file-list">
687
686
  {inputValue.map((file, index) => (
@@ -842,6 +842,8 @@ function Form({
842
842
  const fetchData = async () => {
843
843
  let _formData = { ...formData };
844
844
 
845
+ console.info('aaa', formType, columnId);
846
+
845
847
  if (formSettings.fields.length > 0) {
846
848
  formSettings.fields.forEach((item) => {
847
849
  const { id, value, type, parent, urlParam } = item;
@@ -883,23 +885,25 @@ function Form({
883
885
  method,
884
886
  payload = {};
885
887
 
886
- const getDataId = () => {
887
- for (const item of ajaxSetting.where) {
888
- if (
889
- item.hasOwnProperty('urlParam') &&
890
- item.urlParam === 'dataId'
891
- ) {
892
- return item.value;
888
+ if (ajaxSetting?.where) {
889
+ const getDataId = () => {
890
+ for (const item of ajaxSetting.where) {
891
+ if (
892
+ item.hasOwnProperty('urlParam') &&
893
+ item.urlParam === 'dataId'
894
+ ) {
895
+ return item.value;
896
+ }
893
897
  }
894
- }
895
- return 0;
896
- };
898
+ return 0;
899
+ };
900
+ }
897
901
 
898
902
  if (
899
903
  formSettings.hasOwnProperty('json') &&
900
904
  formSettings.json === true
901
905
  ) {
902
- let dataId = getDataId();
906
+ let dataId = paramValue || getDataId();
903
907
 
904
908
  urlSuffix = '/json/get';
905
909
  method = 'POST';
@@ -18,6 +18,7 @@ import {
18
18
  EyeOpen,
19
19
  EyeSlashed,
20
20
  File,
21
+ Pencil,
21
22
  TrashCan,
22
23
  } from 'akar-icons';
23
24
 
@@ -76,8 +77,6 @@ function GenericDetail({
76
77
  if (activeTab) {
77
78
  const activeTabConfig = tabs.find((t) => t.id === activeTab.id);
78
79
 
79
- console.info(tabs);
80
-
81
80
  if (type === 'view') {
82
81
  setActiveStage(stage.id);
83
82
  } else {
@@ -1321,6 +1320,8 @@ function GenericDetail({
1321
1320
  } else {
1322
1321
  return null;
1323
1322
  }
1323
+ case 'scheduling':
1324
+ return renderSchedulingTable(activeTabConfig);
1324
1325
  default:
1325
1326
  return null;
1326
1327
  }
@@ -1329,6 +1330,207 @@ function GenericDetail({
1329
1330
  }
1330
1331
  };
1331
1332
 
1333
+ const renderSchedulingTable = (config) => {
1334
+ const { columns, rows } = config;
1335
+ const scheduleData = data[rows.key]; // Access schedule data directly from the rows
1336
+
1337
+ // Group categories by their category (e.g., Concrete Work)
1338
+ const groupedCategories = groupCategoriesByType(scheduleData, rows);
1339
+
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
+ );
1351
+
1352
+ return (
1353
+ <div className={styles.gridtxt}>
1354
+ <div className={styles.gridtxt__header}>
1355
+ <span>{config.title || 'Project Scheduling'}</span>
1356
+ </div>
1357
+
1358
+ {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>
1385
+ </tr>
1386
+ </thead>
1387
+ <tbody>
1388
+ {group.entries.map((category, idx) => (
1389
+ <tr key={`row-${idx}`}>
1390
+ {columns.map((column) => (
1391
+ <td
1392
+ key={`column-${idx}-${column.id}`}
1393
+ style={
1394
+ column.type ===
1395
+ 'currency'
1396
+ ? {
1397
+ textAlign:
1398
+ 'right',
1399
+ }
1400
+ : {}
1401
+ }
1402
+ >
1403
+ {column.type === 'currency'
1404
+ ? formatCurrency(
1405
+ category[
1406
+ column.id
1407
+ ]
1408
+ )
1409
+ : category[column.id]
1410
+ ?.label ||
1411
+ category[column.id] ||
1412
+ ''}
1413
+ </td>
1414
+ ))}
1415
+ <td>
1416
+ <span
1417
+ data-tooltip-id="system-tooltip"
1418
+ data-tooltip-content="Edit"
1419
+ style={{
1420
+ cursor: 'pointer',
1421
+ }}
1422
+ onClick={() =>
1423
+ modalOpen(
1424
+ 'update',
1425
+ category.id
1426
+ )
1427
+ }
1428
+ >
1429
+ <Pencil
1430
+ size={18}
1431
+ strokeWidth={2}
1432
+ />
1433
+ </span>
1434
+ </td>
1435
+ </tr>
1436
+ ))}
1437
+ <tr>
1438
+ <td colSpan={nonTotal}>
1439
+ <strong>Total Cost</strong>
1440
+ </td>
1441
+ {columns.map((c) =>
1442
+ c.total ? (
1443
+ <td
1444
+ key={c.id}
1445
+ style={{
1446
+ textAlign: 'right',
1447
+ }}
1448
+ >
1449
+ {calculateTotalCost(
1450
+ group.entries,
1451
+ c.id
1452
+ )}
1453
+ </td>
1454
+ ) : null
1455
+ )}
1456
+ <td></td>
1457
+ </tr>
1458
+ </tbody>
1459
+ </table>
1460
+ </div>
1461
+ ))
1462
+ ) : (
1463
+ <div className={styles.noDataMessage}>
1464
+ <p>No scheduling data has been added.</p>
1465
+ </div>
1466
+ )}
1467
+
1468
+ {config.form && (
1469
+ <div className={styles.polActions}>
1470
+ <button
1471
+ className={styles.btn}
1472
+ onClick={() => modalOpen('create', 0)}
1473
+ >
1474
+ Add
1475
+ </button>
1476
+ </div>
1477
+ )}
1478
+ </div>
1479
+ );
1480
+ };
1481
+
1482
+ // Function to group categories by their type (e.g., Concrete Work, Miscellaneous, etc.)
1483
+ const groupCategoriesByType = (data, rows) => {
1484
+ const grouped = {};
1485
+
1486
+ if (data) {
1487
+ data.forEach((category) => {
1488
+ const categoryType =
1489
+ category[rows.categoryKey.id]?.[rows.categoryKey.label] ||
1490
+ 'Miscellaneous'; // Default to 'Miscellaneous' if no category is found
1491
+ if (!grouped[categoryType]) {
1492
+ grouped[categoryType] = {
1493
+ category: categoryType,
1494
+ entries: [],
1495
+ };
1496
+ }
1497
+ grouped[categoryType].entries.push(category);
1498
+ });
1499
+ }
1500
+
1501
+ return Object.values(grouped).sort((a, b) =>
1502
+ a.category.localeCompare(b.category)
1503
+ ); // Sort categories alphabetically
1504
+ };
1505
+
1506
+ // Function to calculate the total cost for a group
1507
+ const calculateTotalCost = (entries, id) => {
1508
+ let total = 0;
1509
+ 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;
1513
+ });
1514
+
1515
+ // Format the total cost with commas and return it
1516
+ return `$${total.toLocaleString('en-AU', {
1517
+ minimumFractionDigits: 2,
1518
+ maximumFractionDigits: 2,
1519
+ })}`;
1520
+ };
1521
+
1522
+ // Function to format currency values with commas and a currency symbol
1523
+ const formatCurrency = (value) => {
1524
+ if (value) {
1525
+ const cost = parseFloat(value.replace(/[^0-9.-]+/g, '')) || 0; // Remove any non-numeric characters
1526
+ return `$${cost.toLocaleString('en-AU', {
1527
+ minimumFractionDigits: 2,
1528
+ maximumFractionDigits: 2,
1529
+ })}`;
1530
+ }
1531
+ return ''; // Return empty if value is not provided
1532
+ };
1533
+
1332
1534
  /** General Hooks */
1333
1535
  useEffect(() => {
1334
1536
  const handleResize = () => {
@@ -1451,6 +1653,7 @@ function GenericDetail({
1451
1653
  });
1452
1654
  }
1453
1655
  break;
1656
+ case 'scheduling':
1454
1657
  case 'overview':
1455
1658
  setConfig({});
1456
1659
  if (activeTabConfig.form) {
@@ -1641,6 +1844,9 @@ function GenericDetail({
1641
1844
  style={userProfile?.settings?.style || {}}
1642
1845
  updateForm={setFormData}
1643
1846
  userProfile={userProfile}
1847
+ paramValue={
1848
+ routeParams[urlParam] ? routeParams[urlParam] : 0
1849
+ }
1644
1850
  />
1645
1851
  </Popup>
1646
1852
  </>
@@ -620,3 +620,58 @@
620
620
  color: rgba(var(--primary-rgb), 0.65);
621
621
  }
622
622
  }
623
+
624
+ .schedulingTable {
625
+ width: 100%;
626
+ border-collapse: collapse;
627
+ margin: 1em 0;
628
+ }
629
+
630
+ .schedulingTable th,
631
+ .schedulingTable td {
632
+ padding: 0.75rem;
633
+ text-align: left;
634
+ border: 1px solid rgba(var(--primary-rgb), 0.1);
635
+ }
636
+
637
+ .schedulingTable th {
638
+ background-color: var(--secondary-color); /* Updated header color */
639
+ color: var(--background-color);
640
+ font-weight: bold;
641
+ }
642
+
643
+ .schedulingTable td {
644
+ background-color: rgba(var(--primary-rgb), 0.05);
645
+ }
646
+
647
+ .categoryGroup {
648
+ margin-top: 2rem;
649
+ }
650
+
651
+ h3 {
652
+ font-size: 1.5em;
653
+ 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
+ }
659
+
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);
669
+ }
670
+
671
+ .noDataMessage {
672
+ text-align: center;
673
+ color: var(--secondary-color);
674
+ font-size: 1.25em;
675
+ padding: 1em;
676
+ margin-top: 2em;
677
+ }