@visns-studio/visns-components 5.0.10 → 5.0.12

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.12",
79
79
  "description": "Various packages to assist in the development of our Custom Applications.",
80
80
  "main": "src/index.js",
81
81
  "files": [
@@ -314,7 +314,7 @@ function Field({
314
314
  <span>{settings.label}</span>
315
315
  </div>
316
316
  );
317
- } else if (['richeditor'].includes(settings.type)) {
317
+ } else if (['richeditor', 'textarea'].includes(settings.type)) {
318
318
  return settings.label;
319
319
  }
320
320
  }
@@ -1664,6 +1664,7 @@ function Field({
1664
1664
  'signature',
1665
1665
  'table_radio',
1666
1666
  'table_text',
1667
+ 'textarea',
1667
1668
  'time',
1668
1669
  'toggle',
1669
1670
  ].includes(settings.type)
@@ -1882,7 +1883,9 @@ function Field({
1882
1883
  <div className={formItemClass} style={formItemStyle}>
1883
1884
  <label
1884
1885
  className={
1885
- settings.type === 'richeditor' ? '' : styles.fi__label
1886
+ ['richeditor', 'textarea'].includes(settings.type)
1887
+ ? ''
1888
+ : styles.fi__label
1886
1889
  }
1887
1890
  >
1888
1891
  {renderLabel()}
@@ -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
@@ -106,5 +106,9 @@
106
106
  background: rgba(#f4f4f4, 0.65);
107
107
  margin: 5px;
108
108
  border-radius: 5px;
109
+
110
+ p {
111
+ font-size: 0.85rem;
112
+ }
109
113
  }
110
114
  }
@@ -233,6 +233,10 @@
233
233
  background: rgba(#f4f4f4, 0.65);
234
234
  margin: 5px;
235
235
  border-radius: 5px;
236
+
237
+ p {
238
+ font-size: 0.85rem;
239
+ }
236
240
  }
237
241
  }
238
242
 
@@ -635,7 +639,7 @@
635
639
  }
636
640
 
637
641
  .schedulingTable th {
638
- background-color: var(--secondary-color); /* Updated header color */
642
+ background-color: var(--secondary-color);
639
643
  color: var(--item-color);
640
644
  font-weight: bold;
641
645
  }
@@ -644,34 +648,30 @@
644
648
  background-color: rgba(var(--primary-rgb), 0.05);
645
649
  }
646
650
 
647
- .categoryGroup {
648
- margin-top: 2rem;
649
- }
650
-
651
- h3 {
652
- font-size: 1.5em;
651
+ .categoryRow {
652
+ background-color: rgba(var(--primary-rgb), 0.1);
653
+ text-align: left;
654
+ font-weight: bold;
655
+ font-size: 1.2rem;
653
656
  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
657
  }
659
658
 
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);
659
+ .totalRow {
660
+ background-color: rgba(var(--primary-rgb), 0.05);
661
+ font-weight: bold;
662
+ text-align: right;
669
663
  }
670
664
 
671
665
  .noDataMessage {
672
666
  text-align: center;
673
- color: var(--secondary-color);
674
- font-size: 1.25em;
675
- padding: 1em;
676
- margin-top: 2em;
667
+ color: var(--secondary-color); /* Maintain a clear and neutral color */
668
+ font-size: 1.125em; /* Slightly reduce the font size for a cleaner look */
669
+ padding: 1.5em; /* Adjust padding for better spacing */
670
+ margin: 2em auto; /* Add auto margin for centering */
671
+ background: #f9f9f9; /* Subtle background to match the theme */
672
+ border: 1px solid #ddd; /* Light border for structure */
673
+ border-radius: 6px; /* Slight rounding for a modern feel */
674
+ max-width: 600px; /* Constrain width for a balanced layout */
675
+ box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1); /* Gentle shadow for depth */
676
+ font-style: italic; /* Optional: Add a subtle font style */
677
677
  }
@@ -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;
@@ -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: 999 !important;
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;
@@ -69,6 +69,10 @@
69
69
  background: rgba(#f4f4f4, 0.65);
70
70
  margin: 5px;
71
71
  border-radius: 5px;
72
+
73
+ p {
74
+ font-size: 0.85rem;
75
+ }
72
76
  }
73
77
  }
74
78
 
@@ -10,7 +10,9 @@
10
10
 
11
11
  html,
12
12
  body {
13
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
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, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
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
+ }