@visns-studio/visns-components 5.13.15 → 5.13.17
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
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
89
89
|
},
|
|
90
90
|
"name": "@visns-studio/visns-components",
|
|
91
|
-
"version": "5.13.
|
|
91
|
+
"version": "5.13.17",
|
|
92
92
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
93
93
|
"main": "src/index.js",
|
|
94
94
|
"files": [
|
|
@@ -18,6 +18,62 @@ import styles from '../styles/GenericDashboard.module.scss';
|
|
|
18
18
|
|
|
19
19
|
const toSnakeCase = (str) => str.replace(/[\s-]+/g, '_').toLowerCase();
|
|
20
20
|
|
|
21
|
+
const renderProgressBar = (value, options = {}) => {
|
|
22
|
+
const percentage = parseFloat(value) || 0;
|
|
23
|
+
const { showPercentage = true, color = 'primary' } = options;
|
|
24
|
+
|
|
25
|
+
const colorMap = {
|
|
26
|
+
primary: '#007bff',
|
|
27
|
+
success: '#28a745',
|
|
28
|
+
warning: '#ffc107',
|
|
29
|
+
danger: '#dc3545',
|
|
30
|
+
info: '#17a2b8'
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const backgroundColor = colorMap[color] || colorMap.primary;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div style={{ display: 'flex', alignItems: 'center', width: '100%' }}>
|
|
37
|
+
<div
|
|
38
|
+
style={{
|
|
39
|
+
width: '100px',
|
|
40
|
+
height: '20px',
|
|
41
|
+
backgroundColor: '#e9ecef',
|
|
42
|
+
borderRadius: '10px',
|
|
43
|
+
overflow: 'hidden',
|
|
44
|
+
marginRight: showPercentage ? '8px' : '0'
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
<div
|
|
48
|
+
style={{
|
|
49
|
+
width: `${Math.min(Math.max(percentage, 0), 100)}%`,
|
|
50
|
+
height: '100%',
|
|
51
|
+
backgroundColor: backgroundColor,
|
|
52
|
+
borderRadius: '10px',
|
|
53
|
+
transition: 'width 0.3s ease'
|
|
54
|
+
}}
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
{showPercentage && (
|
|
58
|
+
<span style={{ fontSize: '12px', color: '#6c757d', minWidth: '35px' }}>
|
|
59
|
+
{percentage.toFixed(1)}%
|
|
60
|
+
</span>
|
|
61
|
+
)}
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const renderCellContent = (value, column, widget) => {
|
|
67
|
+
const columnFormatting = widget.columnFormatting || {};
|
|
68
|
+
const formatting = columnFormatting[column];
|
|
69
|
+
|
|
70
|
+
if (formatting && formatting.type === 'progressBar') {
|
|
71
|
+
return renderProgressBar(value, formatting);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return value;
|
|
75
|
+
};
|
|
76
|
+
|
|
21
77
|
function GenericDashboard({ setting, userProfile, dynamicDashboard = false }) {
|
|
22
78
|
const swapy = useRef(null);
|
|
23
79
|
const container = useRef(null);
|
|
@@ -1060,11 +1116,11 @@ function GenericDashboard({ setting, userProfile, dynamicDashboard = false }) {
|
|
|
1060
1116
|
<td
|
|
1061
1117
|
key={`table-data-${rowIndex}-${columnIndex}`}
|
|
1062
1118
|
>
|
|
1063
|
-
{
|
|
1064
|
-
row[
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
}
|
|
1119
|
+
{renderCellContent(
|
|
1120
|
+
row[column],
|
|
1121
|
+
column,
|
|
1122
|
+
widget
|
|
1123
|
+
)}
|
|
1068
1124
|
</td>
|
|
1069
1125
|
)
|
|
1070
1126
|
)}
|
|
@@ -642,28 +642,30 @@ function GenericDetail({
|
|
|
642
642
|
if (!url) return toast.error('Export URL not found');
|
|
643
643
|
|
|
644
644
|
let exportUrl = url;
|
|
645
|
-
|
|
645
|
+
|
|
646
646
|
// Replace {id} placeholder with actual ID value
|
|
647
647
|
if (key && data[key]) {
|
|
648
648
|
exportUrl = url.replace('{id}', data[key]);
|
|
649
649
|
} else if (url.includes('{id}') && data.id) {
|
|
650
650
|
exportUrl = url.replace('{id}', data.id);
|
|
651
651
|
}
|
|
652
|
-
|
|
652
|
+
|
|
653
653
|
// Legacy support: append key value to URL if no placeholder found
|
|
654
654
|
if (!url.includes('{id}') && key && data[key]) {
|
|
655
655
|
exportUrl = `${url}/${data[key]}`;
|
|
656
656
|
}
|
|
657
|
-
|
|
657
|
+
|
|
658
658
|
if (method === 'POST') {
|
|
659
659
|
// Create a form to submit POST request
|
|
660
660
|
const form = document.createElement('form');
|
|
661
661
|
form.method = 'POST';
|
|
662
662
|
form.action = exportUrl;
|
|
663
663
|
form.target = '_blank';
|
|
664
|
-
|
|
664
|
+
|
|
665
665
|
// Add CSRF token if available
|
|
666
|
-
const csrfToken = document
|
|
666
|
+
const csrfToken = document
|
|
667
|
+
.querySelector('meta[name="csrf-token"]')
|
|
668
|
+
?.getAttribute('content');
|
|
667
669
|
if (csrfToken) {
|
|
668
670
|
const csrfInput = document.createElement('input');
|
|
669
671
|
csrfInput.type = 'hidden';
|
|
@@ -671,7 +673,7 @@ function GenericDetail({
|
|
|
671
673
|
csrfInput.value = csrfToken;
|
|
672
674
|
form.appendChild(csrfInput);
|
|
673
675
|
}
|
|
674
|
-
|
|
676
|
+
|
|
675
677
|
document.body.appendChild(form);
|
|
676
678
|
form.submit();
|
|
677
679
|
document.body.removeChild(form);
|
|
@@ -1434,9 +1436,9 @@ function GenericDetail({
|
|
|
1434
1436
|
return renderTotal();
|
|
1435
1437
|
default:
|
|
1436
1438
|
if (id === 'proposalDynamic') {
|
|
1437
|
-
return size === 'full' && truncatedValue
|
|
1438
|
-
|
|
1439
|
-
<iframe
|
|
1439
|
+
return size === 'full' && truncatedValue ? (
|
|
1440
|
+
<div className={styles.proposalDynamicContainer}>
|
|
1441
|
+
<iframe
|
|
1440
1442
|
srcDoc={`
|
|
1441
1443
|
<!DOCTYPE html>
|
|
1442
1444
|
<html>
|
|
@@ -1493,18 +1495,21 @@ function GenericDetail({
|
|
|
1493
1495
|
width: '100%',
|
|
1494
1496
|
minHeight: '400px',
|
|
1495
1497
|
border: 'none',
|
|
1496
|
-
backgroundColor: 'transparent'
|
|
1498
|
+
backgroundColor: 'transparent',
|
|
1497
1499
|
}}
|
|
1498
1500
|
onLoad={(e) => {
|
|
1499
1501
|
const iframe = e.target;
|
|
1500
|
-
const doc =
|
|
1502
|
+
const doc =
|
|
1503
|
+
iframe.contentDocument ||
|
|
1504
|
+
iframe.contentWindow.document;
|
|
1501
1505
|
const height = doc.body.scrollHeight;
|
|
1502
1506
|
iframe.style.height = height + 'px';
|
|
1503
1507
|
}}
|
|
1504
1508
|
/>
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1509
|
+
</div>
|
|
1510
|
+
) : (
|
|
1511
|
+
<div className={styles.proposalDynamicContainer}>
|
|
1512
|
+
<iframe
|
|
1508
1513
|
srcDoc={`
|
|
1509
1514
|
<!DOCTYPE html>
|
|
1510
1515
|
<html>
|
|
@@ -1531,16 +1536,19 @@ function GenericDetail({
|
|
|
1531
1536
|
width: '100%',
|
|
1532
1537
|
minHeight: '200px',
|
|
1533
1538
|
border: 'none',
|
|
1534
|
-
backgroundColor: 'transparent'
|
|
1539
|
+
backgroundColor: 'transparent',
|
|
1535
1540
|
}}
|
|
1536
1541
|
onLoad={(e) => {
|
|
1537
1542
|
const iframe = e.target;
|
|
1538
|
-
const doc =
|
|
1543
|
+
const doc =
|
|
1544
|
+
iframe.contentDocument ||
|
|
1545
|
+
iframe.contentWindow.document;
|
|
1539
1546
|
const height = doc.body.scrollHeight;
|
|
1540
1547
|
iframe.style.height = height + 'px';
|
|
1541
1548
|
}}
|
|
1542
1549
|
/>
|
|
1543
|
-
|
|
1550
|
+
</div>
|
|
1551
|
+
);
|
|
1544
1552
|
}
|
|
1545
1553
|
return size === 'full' && truncatedValue
|
|
1546
1554
|
? parse(`<br /><p>${truncatedValue}</p>`)
|
|
@@ -2672,7 +2680,8 @@ function GenericDetail({
|
|
|
2672
2680
|
)
|
|
2673
2681
|
}
|
|
2674
2682
|
>
|
|
2675
|
-
{activeTabConfig.export
|
|
2683
|
+
{activeTabConfig.export
|
|
2684
|
+
.label || 'Export'}
|
|
2676
2685
|
</button>
|
|
2677
2686
|
)}
|
|
2678
2687
|
|
|
@@ -3004,21 +3013,27 @@ function GenericDetail({
|
|
|
3004
3013
|
// Replace URL parameters like {key} with actual values
|
|
3005
3014
|
const replaceUrlParams = (url) => {
|
|
3006
3015
|
if (!url) return url;
|
|
3007
|
-
|
|
3016
|
+
|
|
3008
3017
|
// Handle the new urlKey parameter
|
|
3009
|
-
const urlKey =
|
|
3010
|
-
|
|
3018
|
+
const urlKey =
|
|
3019
|
+
activeTabConfig.urlKey || 'dataId';
|
|
3020
|
+
|
|
3011
3021
|
let replacementValue;
|
|
3012
3022
|
if (urlKey === 'dataId') {
|
|
3013
3023
|
// Use routeParams for dataId (backward compatibility)
|
|
3014
|
-
replacementValue =
|
|
3024
|
+
replacementValue =
|
|
3025
|
+
routeParams[urlParam] || '';
|
|
3015
3026
|
} else {
|
|
3016
3027
|
// Use data object for other keys, supporting dot notation
|
|
3017
|
-
replacementValue = urlKey
|
|
3018
|
-
|
|
3019
|
-
|
|
3028
|
+
replacementValue = urlKey
|
|
3029
|
+
.split('.')
|
|
3030
|
+
.reduce((obj, key) => {
|
|
3031
|
+
return obj && obj[key] !== undefined
|
|
3032
|
+
? obj[key]
|
|
3033
|
+
: '';
|
|
3034
|
+
}, data);
|
|
3020
3035
|
}
|
|
3021
|
-
|
|
3036
|
+
|
|
3022
3037
|
// Replace both {key} and {dataId} patterns for backward compatibility
|
|
3023
3038
|
return url
|
|
3024
3039
|
.replace(/{key}/g, replacementValue)
|
|
@@ -3050,7 +3065,9 @@ function GenericDetail({
|
|
|
3050
3065
|
entityData={data}
|
|
3051
3066
|
routeParams={routeParams}
|
|
3052
3067
|
fetchData={handleReload}
|
|
3053
|
-
showDescription={
|
|
3068
|
+
showDescription={
|
|
3069
|
+
activeTabConfig.showDescription
|
|
3070
|
+
}
|
|
3054
3071
|
/>
|
|
3055
3072
|
);
|
|
3056
3073
|
case 'scheduling':
|
|
@@ -3062,6 +3079,7 @@ function GenericDetail({
|
|
|
3062
3079
|
modalOpen={modalOpen}
|
|
3063
3080
|
preloadedOptions={preloadedOptions}
|
|
3064
3081
|
setData={setData}
|
|
3082
|
+
userProfile={userProfile}
|
|
3065
3083
|
/>
|
|
3066
3084
|
);
|
|
3067
3085
|
case 'custom':
|
|
@@ -210,6 +210,7 @@ const GenericEditableTable = ({
|
|
|
210
210
|
dataId,
|
|
211
211
|
preloadedOptions,
|
|
212
212
|
setData, // callback to update the parent state
|
|
213
|
+
userProfile, // user profile for permission checks
|
|
213
214
|
}) => {
|
|
214
215
|
const isInitialLoad = useRef(true);
|
|
215
216
|
const { columns, rows, form, title } = schedulingConfig;
|
|
@@ -217,6 +218,21 @@ const GenericEditableTable = ({
|
|
|
217
218
|
const hasColourColumn = columns.some((col) => col.id === 'colours');
|
|
218
219
|
const hasMarginRow = rows && rows.marginRow;
|
|
219
220
|
|
|
221
|
+
// Check if user has permission to view margin rows
|
|
222
|
+
const hasMarginPermission = () => {
|
|
223
|
+
if (!rows?.marginRoles || !Array.isArray(rows.marginRoles)) {
|
|
224
|
+
return true; // Show by default if no marginRoles specified
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!userProfile?.roles) {
|
|
228
|
+
return false; // Hide if marginRoles exist but no user profile
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return userProfile.roles.some(role =>
|
|
232
|
+
rows.marginRoles.includes(role.name)
|
|
233
|
+
);
|
|
234
|
+
};
|
|
235
|
+
|
|
220
236
|
// dataSets: each item => { created_at: string, data: array of rows }
|
|
221
237
|
const [dataSets, setDataSets] = useState([]);
|
|
222
238
|
// Index of the currently active dataset
|
|
@@ -1312,95 +1328,99 @@ const GenericEditableTable = ({
|
|
|
1312
1328
|
<td></td>
|
|
1313
1329
|
</tr>
|
|
1314
1330
|
|
|
1315
|
-
{/*
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1331
|
+
{/* Margin - Show only if user has permission */}
|
|
1332
|
+
{hasMarginPermission() && (
|
|
1333
|
+
<>
|
|
1334
|
+
<tr style={{ backgroundColor: '#f8f9fa' }}>
|
|
1335
|
+
<td colSpan={6} style={{ padding: '8px' }}>
|
|
1336
|
+
{rows?.marginLabel || 'LKD'} Margin (in %)
|
|
1337
|
+
</td>
|
|
1338
|
+
<td
|
|
1339
|
+
colSpan={2}
|
|
1340
|
+
style={{ padding: '8px', textAlign: 'center' }}
|
|
1341
|
+
>
|
|
1342
|
+
<input
|
|
1343
|
+
type="number"
|
|
1344
|
+
value={marginPercentage}
|
|
1345
|
+
onChange={(e) =>
|
|
1346
|
+
handleMarginPercentageChange(
|
|
1347
|
+
parseFloat(e.target.value) || 0
|
|
1348
|
+
)
|
|
1349
|
+
}
|
|
1350
|
+
placeholder="0"
|
|
1351
|
+
style={{
|
|
1352
|
+
padding: '4px',
|
|
1353
|
+
border: '1px solid #ccc',
|
|
1354
|
+
borderRadius: '3px',
|
|
1355
|
+
textAlign: 'center',
|
|
1356
|
+
marginRight: '4px',
|
|
1357
|
+
}}
|
|
1358
|
+
/>
|
|
1359
|
+
</td>
|
|
1360
|
+
<td
|
|
1361
|
+
colSpan={2}
|
|
1362
|
+
style={{ padding: '8px', textAlign: 'right' }}
|
|
1363
|
+
>
|
|
1364
|
+
{marginAmount.toLocaleString('en-AU', {
|
|
1365
|
+
style: 'currency',
|
|
1366
|
+
currency: 'AUD',
|
|
1367
|
+
})}
|
|
1368
|
+
</td>
|
|
1369
|
+
<td></td>
|
|
1370
|
+
</tr>
|
|
1353
1371
|
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1372
|
+
{/* Total Project Cost */}
|
|
1373
|
+
<tr style={{ backgroundColor: '#e9ecef', fontWeight: 'bold' }}>
|
|
1374
|
+
<td colSpan={6} style={{ padding: '8px' }}>
|
|
1375
|
+
Total {rows?.marginLabel || 'LKD'} Project Cost
|
|
1376
|
+
</td>
|
|
1377
|
+
<td
|
|
1378
|
+
colSpan={4}
|
|
1379
|
+
style={{ padding: '8px', textAlign: 'right' }}
|
|
1380
|
+
>
|
|
1381
|
+
{totalLkdProjectCost.toLocaleString('en-AU', {
|
|
1382
|
+
style: 'currency',
|
|
1383
|
+
currency: 'AUD',
|
|
1384
|
+
})}
|
|
1385
|
+
</td>
|
|
1386
|
+
<td></td>
|
|
1387
|
+
</tr>
|
|
1370
1388
|
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1389
|
+
{/* Total GST */}
|
|
1390
|
+
<tr style={{ backgroundColor: '#e9ecef' }}>
|
|
1391
|
+
<td colSpan={6} style={{ padding: '8px' }}>
|
|
1392
|
+
Total GST
|
|
1393
|
+
</td>
|
|
1394
|
+
<td
|
|
1395
|
+
colSpan={4}
|
|
1396
|
+
style={{ padding: '8px', textAlign: 'right' }}
|
|
1397
|
+
>
|
|
1398
|
+
{totalLkdGst.toLocaleString('en-AU', {
|
|
1399
|
+
style: 'currency',
|
|
1400
|
+
currency: 'AUD',
|
|
1401
|
+
})}
|
|
1402
|
+
</td>
|
|
1403
|
+
<td></td>
|
|
1404
|
+
</tr>
|
|
1387
1405
|
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1406
|
+
{/* Total Project Cost (Including GST) */}
|
|
1407
|
+
<tr style={{ backgroundColor: '#e9ecef', fontWeight: 'bold' }}>
|
|
1408
|
+
<td colSpan={6} style={{ padding: '8px' }}>
|
|
1409
|
+
Total {rows?.marginLabel || 'LKD'} Project Cost (Including GST)
|
|
1410
|
+
</td>
|
|
1411
|
+
<td
|
|
1412
|
+
colSpan={4}
|
|
1413
|
+
style={{ padding: '8px', textAlign: 'right' }}
|
|
1414
|
+
>
|
|
1415
|
+
{totalLkdWithGst.toLocaleString('en-AU', {
|
|
1416
|
+
style: 'currency',
|
|
1417
|
+
currency: 'AUD',
|
|
1418
|
+
})}
|
|
1419
|
+
</td>
|
|
1420
|
+
<td></td>
|
|
1421
|
+
</tr>
|
|
1422
|
+
</>
|
|
1423
|
+
)}
|
|
1404
1424
|
</>
|
|
1405
1425
|
);
|
|
1406
1426
|
};
|