@visns-studio/visns-components 5.0.7 → 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.7",
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 {
@@ -135,6 +134,14 @@ function GenericDetail({
135
134
  }
136
135
  };
137
136
 
137
+ const handleExport = (config) => {
138
+ if (config.url && config.key) {
139
+ window.open(`${config.url}/${data[config.key]}`);
140
+ } else {
141
+ toast.error('Export URL not found');
142
+ }
143
+ };
144
+
138
145
  const handleCheckboxUpdate = async () => {
139
146
  try {
140
147
  if (currentConfigRef.current.functions.checkboxUpdate) {
@@ -1068,6 +1075,21 @@ function GenericDetail({
1068
1075
  </button>
1069
1076
  </div>
1070
1077
  )}
1078
+
1079
+ {activeTabConfig.export && (
1080
+ <div className={styles.polActions}>
1081
+ <button
1082
+ className={styles.btn}
1083
+ onClick={() =>
1084
+ handleExport(
1085
+ activeTabConfig.export
1086
+ )
1087
+ }
1088
+ >
1089
+ Export
1090
+ </button>
1091
+ </div>
1092
+ )}
1071
1093
  </>
1072
1094
  );
1073
1095
  case 'qrCode':
@@ -1298,6 +1320,8 @@ function GenericDetail({
1298
1320
  } else {
1299
1321
  return null;
1300
1322
  }
1323
+ case 'scheduling':
1324
+ return renderSchedulingTable(activeTabConfig);
1301
1325
  default:
1302
1326
  return null;
1303
1327
  }
@@ -1306,6 +1330,207 @@ function GenericDetail({
1306
1330
  }
1307
1331
  };
1308
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
+
1309
1534
  /** General Hooks */
1310
1535
  useEffect(() => {
1311
1536
  const handleResize = () => {
@@ -1428,6 +1653,7 @@ function GenericDetail({
1428
1653
  });
1429
1654
  }
1430
1655
  break;
1656
+ case 'scheduling':
1431
1657
  case 'overview':
1432
1658
  setConfig({});
1433
1659
  if (activeTabConfig.form) {
@@ -1618,6 +1844,9 @@ function GenericDetail({
1618
1844
  style={userProfile?.settings?.style || {}}
1619
1845
  updateForm={setFormData}
1620
1846
  userProfile={userProfile}
1847
+ paramValue={
1848
+ routeParams[urlParam] ? routeParams[urlParam] : 0
1849
+ }
1621
1850
  />
1622
1851
  </Popup>
1623
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
+ }