@visns-studio/visns-components 5.14.14 → 5.14.16

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
@@ -89,7 +89,7 @@
89
89
  "react-dom": "^17.0.0 || ^18.0.0"
90
90
  },
91
91
  "name": "@visns-studio/visns-components",
92
- "version": "5.14.14",
92
+ "version": "5.14.16",
93
93
  "description": "Various packages to assist in the development of our Custom Applications.",
94
94
  "main": "src/index.js",
95
95
  "files": [
@@ -24,6 +24,7 @@ import React, {
24
24
  useCallback,
25
25
  useEffect,
26
26
  useImperativeHandle,
27
+ useMemo,
27
28
  useRef,
28
29
  useState,
29
30
  } from 'react';
@@ -250,6 +251,29 @@ const DataGrid = forwardRef(
250
251
  ) => {
251
252
  const navigate = useNavigate();
252
253
 
254
+ /**
255
+ * DEBUG: Settings Configuration Tracking
256
+ * Monitor changes to settings prop to identify config update issues
257
+ */
258
+ useEffect(() => {
259
+ console.log('🔍 DataGrid settings prop changed:', {
260
+ settings: settings,
261
+ settingsLength: settings?.length || 0,
262
+ hasUpdateSetting:
263
+ settings?.some((s) => s.id === 'update') || false,
264
+ settingsIds: settings?.map((s) => s.id) || [],
265
+ timestamp: new Date().toISOString(),
266
+ });
267
+ }, [settings]);
268
+
269
+ useEffect(() => {
270
+ console.log('🔍 DataGrid form prop changed:', {
271
+ form: form,
272
+ primaryKey: form?.primaryKey,
273
+ timestamp: new Date().toISOString(),
274
+ });
275
+ }, [form]);
276
+
253
277
  /** Audio */
254
278
  const [audioSource, setAudioSource] = useState('');
255
279
 
@@ -285,6 +309,11 @@ const DataGrid = forwardRef(
285
309
 
286
310
  /** Modal Functions */
287
311
  const modalOpen = (formType, formId) => {
312
+ console.log('🔍 modalOpen called:', {
313
+ formType: formType,
314
+ formId: formId,
315
+ timestamp: new Date().toISOString(),
316
+ });
288
317
  setModalShow(true);
289
318
  setFormType(formType);
290
319
  setFormId(formId);
@@ -304,6 +333,32 @@ const DataGrid = forwardRef(
304
333
  const dataRef = useRef(null);
305
334
  const [dSearch, setDSearch] = useState('');
306
335
 
336
+ /** Optimized refs for settings and form to avoid unnecessary re-renders */
337
+ const settingsRef = useRef(settings);
338
+ const formRef = useRef(form);
339
+
340
+ // Keep refs updated with current values
341
+ useEffect(() => {
342
+ settingsRef.current = settings;
343
+ }, [settings]);
344
+
345
+ useEffect(() => {
346
+ formRef.current = form;
347
+ }, [form]);
348
+
349
+ // Memoize settings to avoid unnecessary re-renders when array reference changes but content is same
350
+ const memoizedSettings = useMemo(
351
+ () => settings,
352
+ [
353
+ settings?.length,
354
+ settings
355
+ ?.map(
356
+ (s) => `${s.id}-${s.roles?.join(',') || ''}-${s.active}`
357
+ )
358
+ .join('|'),
359
+ ]
360
+ );
361
+
307
362
  /** Group expansion state */
308
363
  const [collapsedGroups, setCollapsedGroups] = useState({});
309
364
 
@@ -1209,7 +1264,7 @@ const DataGrid = forwardRef(
1209
1264
 
1210
1265
  const handleReload = () => {
1211
1266
  gridRef.current.paginationProps.reload();
1212
-
1267
+
1213
1268
  // Force grid to recalculate row heights after reload
1214
1269
  // This fixes the row height rendering issue after filter changes
1215
1270
  setTimeout(() => {
@@ -1321,7 +1376,7 @@ const DataGrid = forwardRef(
1321
1376
  // Reload grid data if needed
1322
1377
  if (gridRef.current && gridRef.current.paginationProps) {
1323
1378
  gridRef.current.paginationProps.reload();
1324
-
1379
+
1325
1380
  // Force grid to recalculate row heights after reload
1326
1381
  setTimeout(() => {
1327
1382
  if (gridRef.current?.api) {
@@ -1463,112 +1518,198 @@ const DataGrid = forwardRef(
1463
1518
  }
1464
1519
  };
1465
1520
 
1466
- const onRowClick = useCallback((rowProps, event) => {
1467
- const cellElement = event.target.closest(
1468
- '.InovuaReactDataGrid__cell'
1469
- );
1521
+ /**
1522
+ * DEBUG: Row Click Analysis
1523
+ *
1524
+ * When row clicks aren't working, check the console for these debug messages:
1525
+ *
1526
+ * 1. "Row onClick triggered" - Initial row click detection
1527
+ * 2. "Group row check" - Whether it's a group row (should be false for data rows)
1528
+ * 3. "Cell element in row handler" - Cell element detection
1529
+ * 4. "Row handler column analysis" - Column identification
1530
+ * 5. "Non-checkbox column - calling onRowClick" - Normal row click path
1531
+ * 6. "DataGrid onRowClick triggered" - Main onRowClick function entry
1532
+ * 7. "Column analysis" - Column detection and type checking
1533
+ * 8. "Checking for update setting" - Modal opening prerequisites
1534
+ * 9. "Update setting found" - Whether update setting exists
1535
+ * 10. "Opening modal with" / "No update setting found" - Final modal decision
1536
+ * 11. "modalOpen called" - Modal function execution
1537
+ *
1538
+ * Common issues:
1539
+ * - Missing cell element: Check if click target is within grid
1540
+ * - Wrong column type: Check column configuration
1541
+ * - No update setting: Check config.settings for update permission
1542
+ * - Group row interference: Check if data is being treated as group row
1543
+ */
1544
+ const onRowClick = useCallback(
1545
+ (rowProps, event) => {
1546
+ console.log('🔍 DataGrid onRowClick triggered:', {
1547
+ rowData: rowProps.data,
1548
+ event: event,
1549
+ target: event.target,
1550
+ timestamp: new Date().toISOString(),
1551
+ });
1470
1552
 
1471
- // Add null check to prevent errors when cellElement is null
1472
- if (!cellElement) {
1473
- return; // Exit early if no cell element was found
1474
- }
1553
+ const cellElement = event.target.closest(
1554
+ '.InovuaReactDataGrid__cell'
1555
+ );
1475
1556
 
1476
- const columnIndex = Array.from(
1477
- cellElement.parentNode.children
1478
- ).indexOf(cellElement);
1557
+ console.log('🔍 Cell element found:', cellElement);
1479
1558
 
1480
- if (
1481
- rowProps.columns.length > 1 &&
1482
- columnIndex === rowProps.columns.length - 1
1483
- ) {
1484
- // Handle last column click
1485
- } else {
1486
- const column = rowProps.columns[columnIndex];
1559
+ // Add null check to prevent errors when cellElement is null
1560
+ if (!cellElement) {
1561
+ console.warn(
1562
+ '⚠️ No cell element found - row click aborted'
1563
+ );
1564
+ return; // Exit early if no cell element was found
1565
+ }
1566
+
1567
+ const columnIndex = Array.from(
1568
+ cellElement.parentNode.children
1569
+ ).indexOf(cellElement);
1570
+
1571
+ console.log('🔍 Column analysis:', {
1572
+ columnIndex: columnIndex,
1573
+ totalColumns: rowProps.columns.length,
1574
+ isLastColumn: columnIndex === rowProps.columns.length - 1,
1575
+ columns: rowProps.columns.map((col) => ({
1576
+ id: col.id,
1577
+ type: col.type,
1578
+ })),
1579
+ });
1487
1580
 
1488
1581
  if (
1489
- column.type !== 'dropdown' &&
1490
- column.type !== 'input_text'
1582
+ rowProps.columns.length > 1 &&
1583
+ columnIndex === rowProps.columns.length - 1
1491
1584
  ) {
1492
- if (column.id !== '__checkbox-column') {
1493
- if (column.link && column.link.hasOwnProperty('url')) {
1494
- if (column.link.url) {
1495
- // Helper function to safely retrieve nested values
1496
- const getNestedValue = (obj, key) =>
1497
- key
1498
- .split('.')
1499
- .reduce(
1500
- (acc, part) =>
1501
- acc ? acc[part] : undefined,
1502
- obj
1503
- );
1585
+ console.log('🔍 Last column clicked - no action');
1586
+ // Handle last column click
1587
+ } else {
1588
+ const column = rowProps.columns[columnIndex];
1589
+ console.log('🔍 Column clicked:', {
1590
+ columnId: column?.id,
1591
+ columnType: column?.type,
1592
+ hasLink: !!column?.link,
1593
+ });
1504
1594
 
1505
- const fileRelationArray = column.id.split('.');
1506
- const linkUrl = column.link.url;
1507
- const linkKey =
1508
- rowProps.columns[columnIndex].link.key;
1509
- const rowData = getNestedValue(
1510
- rowProps.data,
1511
- linkKey
1512
- );
1595
+ if (
1596
+ column.type !== 'dropdown' &&
1597
+ column.type !== 'input_text'
1598
+ ) {
1599
+ if (column.id !== '__checkbox-column') {
1600
+ if (
1601
+ column.link &&
1602
+ column.link.hasOwnProperty('url')
1603
+ ) {
1604
+ if (column.link.url) {
1605
+ // Helper function to safely retrieve nested values
1606
+ const getNestedValue = (obj, key) =>
1607
+ key
1608
+ .split('.')
1609
+ .reduce(
1610
+ (acc, part) =>
1611
+ acc ? acc[part] : undefined,
1612
+ obj
1613
+ );
1513
1614
 
1514
- if (column.link.hasOwnProperty('folder')) {
1515
- const linkFolder =
1516
- rowProps.columns[columnIndex].link
1517
- .folder;
1615
+ const fileRelationArray =
1616
+ column.id.split('.');
1617
+ const linkUrl = column.link.url;
1618
+ const linkKey =
1619
+ rowProps.columns[columnIndex].link.key;
1620
+ const rowData = getNestedValue(
1621
+ rowProps.data,
1622
+ linkKey
1623
+ );
1518
1624
 
1519
- // Adjusted to use the nested value for checking the file relation
1520
- if (
1521
- getNestedValue(
1522
- rowProps.data,
1523
- fileRelationArray[0]
1524
- )
1525
- ) {
1526
- if (linkFolder !== '') {
1527
- window.open(
1528
- `${linkUrl}${rowData}/${linkFolder}`
1529
- );
1530
- } else {
1531
- window.open(`${linkUrl}${rowData}`);
1625
+ if (column.link.hasOwnProperty('folder')) {
1626
+ const linkFolder =
1627
+ rowProps.columns[columnIndex].link
1628
+ .folder;
1629
+
1630
+ // Adjusted to use the nested value for checking the file relation
1631
+ if (
1632
+ getNestedValue(
1633
+ rowProps.data,
1634
+ fileRelationArray[0]
1635
+ )
1636
+ ) {
1637
+ if (linkFolder !== '') {
1638
+ window.open(
1639
+ `${linkUrl}${rowData}/${linkFolder}`
1640
+ );
1641
+ } else {
1642
+ window.open(
1643
+ `${linkUrl}${rowData}`
1644
+ );
1645
+ }
1532
1646
  }
1647
+ } else {
1648
+ navigate(`${linkUrl}${rowData}`);
1533
1649
  }
1534
1650
  } else {
1535
- navigate(`${linkUrl}${rowData}`);
1651
+ if (rowProps.data[column.id]) {
1652
+ window.open(
1653
+ rowProps.data[column.id],
1654
+ '_blank'
1655
+ );
1656
+ }
1536
1657
  }
1658
+ } else if (column.link && column.link.popup) {
1659
+ const linkPopup = column.link.popup;
1660
+ const linkKey =
1661
+ rowProps.columns[columnIndex].link.key;
1662
+ const rowData = rowProps.data[linkKey];
1663
+
1664
+ window.open(
1665
+ `${linkPopup}/${rowData}`,
1666
+ '',
1667
+ 'width=1440,height=1024,menubar=1,resizable=1,status=1,titlebar=1,toolbar=1'
1668
+ );
1537
1669
  } else {
1538
- if (rowProps.data[column.id]) {
1539
- window.open(
1540
- rowProps.data[column.id],
1541
- '_blank'
1542
- );
1543
- }
1544
- }
1545
- } else if (column.link && column.link.popup) {
1546
- const linkPopup = column.link.popup;
1547
- const linkKey =
1548
- rowProps.columns[columnIndex].link.key;
1549
- const rowData = rowProps.data[linkKey];
1550
-
1551
- window.open(
1552
- `${linkPopup}/${rowData}`,
1553
- '',
1554
- 'width=1440,height=1024,menubar=1,resizable=1,status=1,titlebar=1,toolbar=1'
1555
- );
1556
- } else {
1557
- const updateSetting = settings.find(
1558
- (setting) => setting.id === 'update'
1559
- );
1670
+ console.log('🔍 Checking for update setting:', {
1671
+ settings: settingsRef.current,
1672
+ form: formRef.current,
1673
+ primaryKey: formRef.current?.primaryKey,
1674
+ rowId: rowProps.data[
1675
+ formRef.current?.primaryKey
1676
+ ],
1677
+ });
1560
1678
 
1561
- if (updateSetting) {
1562
- modalOpen(
1563
- 'update',
1564
- rowProps.data[form.primaryKey]
1679
+ const updateSetting = settingsRef.current.find(
1680
+ (setting) => setting.id === 'update'
1565
1681
  );
1682
+
1683
+ console.log(
1684
+ '🔍 Update setting found:',
1685
+ updateSetting
1686
+ );
1687
+
1688
+ if (updateSetting) {
1689
+ console.log('✅ Opening modal with:', {
1690
+ type: 'update',
1691
+ id: rowProps.data[
1692
+ formRef.current.primaryKey
1693
+ ],
1694
+ });
1695
+ modalOpen(
1696
+ 'update',
1697
+ rowProps.data[
1698
+ formRef.current.primaryKey
1699
+ ]
1700
+ );
1701
+ } else {
1702
+ console.warn(
1703
+ '⚠️ No update setting found - modal will not open'
1704
+ );
1705
+ }
1566
1706
  }
1567
1707
  }
1568
1708
  }
1569
1709
  }
1570
- }
1571
- }, []);
1710
+ },
1711
+ [modalOpen, navigate]
1712
+ );
1572
1713
 
1573
1714
  const onRenderRow = useCallback(
1574
1715
  (rowProps) => {
@@ -1576,6 +1717,14 @@ const DataGrid = forwardRef(
1576
1717
  const { onClick } = rowProps;
1577
1718
 
1578
1719
  rowProps.onClick = (event) => {
1720
+ console.log('🔍 Row onClick triggered:', {
1721
+ rowId: rowProps.data?.id,
1722
+ rowData: rowProps.data,
1723
+ event: event,
1724
+ target: event.target,
1725
+ timestamp: new Date().toISOString(),
1726
+ });
1727
+
1579
1728
  // Check if this is a group row - group rows should only handle expand/collapse, not onRowClick
1580
1729
  const isGroupRow =
1581
1730
  rowProps.isGroupRow ||
@@ -1587,7 +1736,12 @@ const DataGrid = forwardRef(
1587
1736
  '.InovuaReactDataGrid__group-cell'
1588
1737
  );
1589
1738
 
1739
+ console.log('🔍 Group row check:', isGroupRow);
1740
+
1590
1741
  if (isGroupRow) {
1742
+ console.log(
1743
+ '🔍 Group row detected - calling original onClick only'
1744
+ );
1591
1745
  // For group rows, only allow the original onClick handler (for expand/collapse)
1592
1746
  // Do NOT call onRowClick as it should only apply to data rows
1593
1747
  if (onClick) {
@@ -1600,8 +1754,13 @@ const DataGrid = forwardRef(
1600
1754
  '.InovuaReactDataGrid__cell'
1601
1755
  );
1602
1756
 
1757
+ console.log('🔍 Cell element in row handler:', cellElement);
1758
+
1603
1759
  // Add null check to prevent errors when cellElement is null
1604
1760
  if (!cellElement) {
1761
+ console.warn(
1762
+ '⚠️ No cell element found in row handler - exiting'
1763
+ );
1605
1764
  return; // Exit early if no cell element was found
1606
1765
  }
1607
1766
 
@@ -1611,7 +1770,16 @@ const DataGrid = forwardRef(
1611
1770
 
1612
1771
  const column = rowProps.columns[columnIndex];
1613
1772
 
1773
+ console.log('🔍 Row handler column analysis:', {
1774
+ columnIndex: columnIndex,
1775
+ columnId: column?.id,
1776
+ isCheckboxColumn: column?.id === '__checkbox-column',
1777
+ });
1778
+
1614
1779
  if (column.id === '__checkbox-column') {
1780
+ console.log(
1781
+ '🔍 Checkbox column clicked - handling checkbox logic'
1782
+ );
1615
1783
  // For checkbox column clicks, we need to handle them specially
1616
1784
  // to ensure consistent behavior whether clicking checkbox or cell area
1617
1785
 
@@ -1657,10 +1825,17 @@ const DataGrid = forwardRef(
1657
1825
  onClick(event);
1658
1826
  }
1659
1827
  } else {
1828
+ console.log(
1829
+ '🔍 Non-checkbox column - calling onRowClick'
1830
+ );
1660
1831
  // For non-checkbox columns, handle normal row clicks
1661
1832
  // But do NOT trigger checkbox selection
1662
1833
  onRowClick(rowProps, event);
1663
1834
 
1835
+ console.log(
1836
+ '🔍 Calling original onClick handler:',
1837
+ !!onClick
1838
+ );
1664
1839
  // Call the original onClick handler
1665
1840
  if (onClick) {
1666
1841
  onClick(event);
@@ -2283,6 +2458,10 @@ const DataGrid = forwardRef(
2283
2458
  return null;
2284
2459
  }
2285
2460
  } else {
2461
+ console.log('🔍 Setting NOT rendered (allow=false):', {
2462
+ settingId: s.id,
2463
+ reason: 'filtered out by permissions or active conditions',
2464
+ });
2286
2465
  return null;
2287
2466
  }
2288
2467
 
@@ -2874,14 +3053,22 @@ const DataGrid = forwardRef(
2874
3053
 
2875
3054
  // Function to check if any settings would be visible for any row
2876
3055
  const hasVisibleSettings = () => {
2877
- if (settings.length === 0) return false;
2878
-
3056
+ if (memoizedSettings.length === 0) return false;
3057
+
2879
3058
  // Check if any setting would be visible based on role permissions
2880
- return settings.some(setting => {
3059
+ return memoizedSettings.some((setting) => {
2881
3060
  // Check role-based permissions
2882
- if (setting.roles && Array.isArray(setting.roles) && setting.roles.length > 0) {
2883
- const hasRequiredRole = setting.roles.some(requiredRole =>
2884
- userProfile?.roles?.some(userRole => userRole.name === requiredRole)
3061
+ if (
3062
+ setting.roles &&
3063
+ Array.isArray(setting.roles) &&
3064
+ setting.roles.length > 0
3065
+ ) {
3066
+ const hasRequiredRole = setting.roles.some(
3067
+ (requiredRole) =>
3068
+ userProfile?.roles?.some(
3069
+ (userRole) =>
3070
+ userRole.name === requiredRole
3071
+ )
2885
3072
  );
2886
3073
  return hasRequiredRole;
2887
3074
  }
@@ -2903,7 +3090,7 @@ const DataGrid = forwardRef(
2903
3090
  render: ({ data }) => {
2904
3091
  return (
2905
3092
  <div className={styles.tdactions}>
2906
- {settings.map((setting) =>
3093
+ {memoizedSettings.map((setting) =>
2907
3094
  renderSetting(setting, data)
2908
3095
  )}
2909
3096
  </div>
@@ -2986,7 +3173,7 @@ const DataGrid = forwardRef(
2986
3173
  .catch((error) => {
2987
3174
  console.error('Error in fetching dropdown data: ', error);
2988
3175
  });
2989
- }, [columns, filterDataSource]);
3176
+ }, [columns, filterDataSource, memoizedSettings]);
2990
3177
 
2991
3178
  useEffect(() => {
2992
3179
  if (setFilterData) {
@@ -3146,7 +3333,7 @@ const DataGrid = forwardRef(
3146
3333
  // Force reload of data
3147
3334
  if (gridRef.current && gridRef.current.paginationProps) {
3148
3335
  gridRef.current.paginationProps.reload();
3149
-
3336
+
3150
3337
  // Force grid to recalculate row heights after reload
3151
3338
  setTimeout(() => {
3152
3339
  if (gridRef.current?.api) {
@@ -3284,37 +3471,69 @@ const DataGrid = forwardRef(
3284
3471
  '';
3285
3472
 
3286
3473
  // Check for important marker
3287
- const checkGroupImportantMarker = (groupValue, importantMarkerConfig) => {
3288
- if (!importantMarkerConfig || !dataRef.current?.length || !ajaxSetting?.groupBy) {
3474
+ const checkGroupImportantMarker = (
3475
+ groupValue,
3476
+ importantMarkerConfig
3477
+ ) => {
3478
+ if (
3479
+ !importantMarkerConfig ||
3480
+ !dataRef.current?.length ||
3481
+ !ajaxSetting?.groupBy
3482
+ ) {
3289
3483
  return false;
3290
3484
  }
3291
-
3292
- const { id: fieldId, value: expectedValue } = importantMarkerConfig;
3293
- const groupField = ajaxSetting.groupBy[0];
3294
-
3485
+
3486
+ const {
3487
+ id: fieldId,
3488
+ value: expectedValue,
3489
+ } = importantMarkerConfig;
3490
+ const groupField =
3491
+ ajaxSetting.groupBy[0];
3492
+
3295
3493
  // Get all rows that belong to this group
3296
- const groupRows = dataRef.current.filter(
3297
- (row) => row[groupField] === groupValue
3298
- );
3299
-
3300
- return groupRows.some(row => {
3301
- const fieldValue = row[fieldId];
3302
-
3494
+ const groupRows =
3495
+ dataRef.current.filter(
3496
+ (row) =>
3497
+ row[groupField] ===
3498
+ groupValue
3499
+ );
3500
+
3501
+ return groupRows.some((row) => {
3502
+ const fieldValue =
3503
+ row[fieldId];
3504
+
3303
3505
  // Handle different value types
3304
- if (typeof expectedValue === 'boolean') {
3305
- return Boolean(fieldValue) === expectedValue;
3506
+ if (
3507
+ typeof expectedValue ===
3508
+ 'boolean'
3509
+ ) {
3510
+ return (
3511
+ Boolean(
3512
+ fieldValue
3513
+ ) === expectedValue
3514
+ );
3306
3515
  }
3307
-
3308
- return fieldValue === expectedValue;
3516
+
3517
+ return (
3518
+ fieldValue ===
3519
+ expectedValue
3520
+ );
3309
3521
  });
3310
3522
  };
3311
3523
 
3312
- const hasImportantMarker = checkGroupImportantMarker(
3313
- groupValue,
3314
- ajaxSetting?.groupBySetting?.importantMarker
3315
- );
3316
- const importantMarkerColor = ajaxSetting?.groupBySetting?.importantMarker?.colour;
3317
- const importantMarkerTooltip = ajaxSetting?.groupBySetting?.importantMarker?.tooltip || "This group contains items that require attention";
3524
+ const hasImportantMarker =
3525
+ checkGroupImportantMarker(
3526
+ groupValue,
3527
+ ajaxSetting?.groupBySetting
3528
+ ?.importantMarker
3529
+ );
3530
+ const importantMarkerColor =
3531
+ ajaxSetting?.groupBySetting
3532
+ ?.importantMarker?.colour;
3533
+ const importantMarkerTooltip =
3534
+ ajaxSetting?.groupBySetting
3535
+ ?.importantMarker?.tooltip ||
3536
+ 'This group contains items that require attention';
3318
3537
 
3319
3538
  // Use ReactDataGrid's built-in toggle functionality
3320
3539
  const collapsed =
@@ -3395,11 +3614,29 @@ const DataGrid = forwardRef(
3395
3614
 
3396
3615
  return (
3397
3616
  <div
3398
- className={`group-header-clean group-header-fixed-height ${styles.groupHeaderContainer} ${hasImportantMarker ? styles.groupHeaderImportant : ''}`}
3399
- style={hasImportantMarker ? {
3400
- borderLeftColor: importantMarkerColor || '#FF6961',
3401
- background: `linear-gradient(135deg, ${importantMarkerColor || '#FF6961'}15 0%, ${importantMarkerColor || '#FF6961'}08 100%)`
3402
- } : {}}
3617
+ className={`group-header-clean group-header-fixed-height ${
3618
+ styles.groupHeaderContainer
3619
+ } ${
3620
+ hasImportantMarker
3621
+ ? styles.groupHeaderImportant
3622
+ : ''
3623
+ }`}
3624
+ style={
3625
+ hasImportantMarker
3626
+ ? {
3627
+ borderLeftColor:
3628
+ importantMarkerColor ||
3629
+ '#FF6961',
3630
+ background: `linear-gradient(135deg, ${
3631
+ importantMarkerColor ||
3632
+ '#FF6961'
3633
+ }15 0%, ${
3634
+ importantMarkerColor ||
3635
+ '#FF6961'
3636
+ }08 100%)`,
3637
+ }
3638
+ : {}
3639
+ }
3403
3640
  onClick={
3404
3641
  handleGroupHeaderClick
3405
3642
  }
@@ -3577,16 +3814,25 @@ const DataGrid = forwardRef(
3577
3814
  <AlertCircle
3578
3815
  className="important-marker"
3579
3816
  style={{
3580
- display: 'inline-block',
3581
- marginLeft: '8px',
3582
- color: importantMarkerColor || '#FF6961',
3583
- position: 'relative',
3584
- top: '1px'
3817
+ display:
3818
+ 'inline-block',
3819
+ marginLeft:
3820
+ '8px',
3821
+ color:
3822
+ importantMarkerColor ||
3823
+ '#FF6961',
3824
+ position:
3825
+ 'relative',
3826
+ top: '1px',
3585
3827
  }}
3586
3828
  size={16}
3587
- title={importantMarkerTooltip}
3829
+ title={
3830
+ importantMarkerTooltip
3831
+ }
3588
3832
  data-tooltip-id="system-tooltip"
3589
- data-tooltip-content={importantMarkerTooltip}
3833
+ data-tooltip-content={
3834
+ importantMarkerTooltip
3835
+ }
3590
3836
  />
3591
3837
  )}
3592
3838
  </strong>
@@ -125,45 +125,49 @@ function Form({
125
125
 
126
126
  if (e.target.files && e.target.files.length > 0) {
127
127
  // Find the field settings to check if multiple is enabled
128
- const fieldSettings = formSettings?.fields?.find(field => field.id === id);
128
+ const fieldSettings = formSettings?.fields?.find(
129
+ (field) => field.id === id
130
+ );
129
131
  const isMultiple = fieldSettings?.multiple === true;
130
-
132
+
131
133
  // Debug logging for development
132
134
  if (process.env.NODE_ENV === 'development') {
133
135
  console.log('File input change:', {
134
136
  id,
135
137
  filesCount: e.target.files.length,
136
138
  isMultiple,
137
- fieldSettings
139
+ fieldSettings,
138
140
  });
139
141
  }
140
-
142
+
141
143
  if (isMultiple) {
142
144
  // For multiple file inputs, append new files to existing ones
143
145
  setFormData((prevState) => {
144
146
  const existingFiles = prevState[id] || [];
145
147
  const newFiles = Array.from(e.target.files);
146
-
148
+
147
149
  // Ensure existingFiles is always an array for multiple inputs
148
- const currentFiles = Array.isArray(existingFiles) ? existingFiles : [];
149
-
150
+ const currentFiles = Array.isArray(existingFiles)
151
+ ? existingFiles
152
+ : [];
153
+
150
154
  const updatedFiles = [...currentFiles, ...newFiles];
151
-
155
+
152
156
  // Debug logging for development
153
157
  if (process.env.NODE_ENV === 'development') {
154
158
  console.log('Multiple file update:', {
155
159
  existingCount: currentFiles.length,
156
160
  newCount: newFiles.length,
157
- totalCount: updatedFiles.length
161
+ totalCount: updatedFiles.length,
158
162
  });
159
163
  }
160
-
164
+
161
165
  return {
162
166
  ...prevState,
163
167
  [id]: updatedFiles,
164
168
  };
165
169
  });
166
-
170
+
167
171
  // Clear the input so the same files can be selected again
168
172
  setTimeout(() => {
169
173
  if (e.target) {
@@ -318,15 +322,19 @@ function Form({
318
322
  const handleSelectOption = () => {
319
323
  updateFormData({ [id]: inputValue });
320
324
 
321
- // Update any matching form data properties
322
- Object.keys(inputValue)
323
- .filter(
324
- (key) =>
325
- formData.hasOwnProperty(key) &&
326
- key !== 'id' &&
327
- key !== 'label'
328
- )
329
- .forEach((key) => updateFormData({ [key]: inputValue[key] }));
325
+ // Batch update version
326
+ const excludedKeys = new Set(['id', 'label', 'description']);
327
+ const updates = {};
328
+
329
+ for (const key in inputValue) {
330
+ if (key in formData && !excludedKeys.has(key)) {
331
+ updates[key] = inputValue[key];
332
+ }
333
+ }
334
+
335
+ if (Object.keys(updates).length > 0) {
336
+ updateFormData(updates);
337
+ }
330
338
 
331
339
  formSettings.fields.forEach((field) => {
332
340
  if (field.id === id && field.trigger) {
@@ -495,7 +503,7 @@ function Form({
495
503
  const handleFileDownload = async (fileData) => {
496
504
  try {
497
505
  console.info('File download data:', fileData);
498
-
506
+
499
507
  if (!fileData) {
500
508
  toast.error('No file data available for download');
501
509
  return;
@@ -503,7 +511,11 @@ function Form({
503
511
 
504
512
  // Handle different file data structures
505
513
  let downloadUrl = null;
506
- let fileName = fileData.filename || fileData.name || fileData.file_name || 'download';
514
+ let fileName =
515
+ fileData.filename ||
516
+ fileData.name ||
517
+ fileData.file_name ||
518
+ 'download';
507
519
 
508
520
  // Priority order for download methods:
509
521
  // 1. Direct file URL (S3, CDN, etc.)
@@ -518,7 +530,9 @@ function Form({
518
530
  downloadUrl = filePath;
519
531
  } else {
520
532
  // Construct URL - adjust this based on your API structure
521
- downloadUrl = `/api/files/download?path=${encodeURIComponent(filePath)}`;
533
+ downloadUrl = `/api/files/download?path=${encodeURIComponent(
534
+ filePath
535
+ )}`;
522
536
  }
523
537
  }
524
538
  // 3. Legacy key/uuid method
@@ -529,7 +543,7 @@ function Form({
529
543
  else if (fileData.id) {
530
544
  downloadUrl = `/api/files/download/${fileData.id}`;
531
545
  }
532
-
546
+
533
547
  if (!downloadUrl) {
534
548
  toast.error('Unable to determine download URL for this file');
535
549
  return;
@@ -540,14 +554,13 @@ function Form({
540
554
  link.href = downloadUrl;
541
555
  link.download = fileName;
542
556
  link.target = '_blank';
543
-
557
+
544
558
  // Add to DOM, click, and remove
545
559
  document.body.appendChild(link);
546
560
  link.click();
547
561
  document.body.removeChild(link);
548
-
562
+
549
563
  toast.success(`Downloading ${fileName}...`);
550
-
551
564
  } catch (error) {
552
565
  console.error('File download error:', error);
553
566
  toast.error('Failed to download file');
@@ -925,11 +938,19 @@ function Form({
925
938
 
926
939
  // Check if formData[item.id] is null, undefined, or an empty string AND there's no auto value
927
940
  // Note: 0 is considered a valid value for dropdowns/options
928
- if ((formData[item.id] === null || formData[item.id] === undefined || formData[item.id] === '') && !autoValue) {
941
+ if (
942
+ (formData[item.id] === null ||
943
+ formData[item.id] === undefined ||
944
+ formData[item.id] === '') &&
945
+ !autoValue
946
+ ) {
929
947
  // Check for required_check dependency
930
948
  if (
931
949
  item.required_check &&
932
- (formData[item.required_check] === null || formData[item.required_check] === undefined || formData[item.required_check] === '')
950
+ (formData[item.required_check] === null ||
951
+ formData[item.required_check] ===
952
+ undefined ||
953
+ formData[item.required_check] === '')
933
954
  ) {
934
955
  validation += `${item.label} is a required field.<br/>`;
935
956
  _inputClass[item.id] = styles.inputError;
@@ -943,7 +964,9 @@ function Form({
943
964
  } else if (
944
965
  item.required_rely &&
945
966
  formData[item.required_rely] &&
946
- (formData[item.id] === null || formData[item.id] === undefined || formData[item.id] === '')
967
+ (formData[item.id] === null ||
968
+ formData[item.id] === undefined ||
969
+ formData[item.id] === '')
947
970
  ) {
948
971
  // Check if the dependent required field is empty when it should be filled
949
972
  validation += `${item.label} is a required field.<br/>`;
@@ -994,9 +1017,13 @@ function Form({
994
1017
  method = formSettings.customMethod;
995
1018
  } else {
996
1019
  // Check if this is bulk edit mode
997
- if (bulkEditMode && bulkEditGroupData?.iconConfig?.formModal) {
1020
+ if (
1021
+ bulkEditMode &&
1022
+ bulkEditGroupData?.iconConfig?.formModal
1023
+ ) {
998
1024
  // Use bulk edit endpoint configuration
999
- const formModalConfig = bulkEditGroupData.iconConfig.formModal;
1025
+ const formModalConfig =
1026
+ bulkEditGroupData.iconConfig.formModal;
1000
1027
  url = formModalConfig.url || url;
1001
1028
  method = formModalConfig.method || 'POST';
1002
1029
  } else {
@@ -1020,7 +1047,9 @@ function Form({
1020
1047
  }`;
1021
1048
  } else {
1022
1049
  url += `/${
1023
- formData[formSettings.primaryKey]
1050
+ formData[
1051
+ formSettings.primaryKey
1052
+ ]
1024
1053
  }`;
1025
1054
  }
1026
1055
  method =
@@ -1179,9 +1208,11 @@ function Form({
1179
1208
  ...payload,
1180
1209
  bulkEdit: true,
1181
1210
  groupValue: bulkEditGroupData.groupValue,
1182
- groupKey: bulkEditGroupData.iconConfig.formModal?.groupKey || ajaxSetting?.groupBy?.[0],
1211
+ groupKey:
1212
+ bulkEditGroupData.iconConfig.formModal
1213
+ ?.groupKey || ajaxSetting?.groupBy?.[0],
1183
1214
  rowIds: bulkEditGroupData.groupRowIds,
1184
- ...bulkEditGroupData.iconConfig.formModal?.data
1215
+ ...bulkEditGroupData.iconConfig.formModal?.data,
1185
1216
  };
1186
1217
  }
1187
1218
 
@@ -1237,7 +1268,8 @@ function Form({
1237
1268
  if (resSaveAnother.data.error === '') {
1238
1269
  toast.success(saveAnother.message);
1239
1270
  if (fetchTable) {
1240
- const createdItem = res.data.data || res.data;
1271
+ const createdItem =
1272
+ res.data.data || res.data;
1241
1273
  fetchTable(createdItem);
1242
1274
  }
1243
1275
  }
@@ -1846,28 +1878,36 @@ function Form({
1846
1878
  />
1847
1879
  </div>
1848
1880
  {tableData?.dataSource?.length > 0 && (
1849
- <div
1881
+ <div
1850
1882
  className={`${styles.formItem} ${styles.fwItem}`}
1851
1883
  style={{
1852
- width: formSettings?.save?.return?.width
1853
- ? `min(${parseInt(formSettings.save.return.width)}px, 100vw - 40px)`
1884
+ width: formSettings?.save?.return?.width
1885
+ ? `min(${parseInt(
1886
+ formSettings.save.return.width
1887
+ )}px, 100vw - 40px)`
1854
1888
  : '100%',
1855
- maxWidth: formSettings?.save?.return?.width
1856
- ? `${parseInt(formSettings.save.return.width)}px`
1889
+ maxWidth: formSettings?.save?.return?.width
1890
+ ? `${parseInt(
1891
+ formSettings.save.return.width
1892
+ )}px`
1857
1893
  : '100%',
1858
1894
  overflow: 'auto',
1859
- margin: formSettings?.save?.return?.width ? '0 auto' : undefined
1895
+ margin: formSettings?.save?.return?.width
1896
+ ? '0 auto'
1897
+ : undefined,
1860
1898
  }}
1861
1899
  >
1862
1900
  <ReactDataGrid
1863
1901
  idProperty="form-table-result"
1864
1902
  columns={tableData.columns}
1865
1903
  dataSource={tableData.dataSource}
1866
- style={{
1904
+ style={{
1867
1905
  minHeight: 550,
1868
- width: formSettings?.save?.return?.width
1869
- ? `${parseInt(formSettings.save.return.width)}px`
1870
- : '100%'
1906
+ width: formSettings?.save?.return?.width
1907
+ ? `${parseInt(
1908
+ formSettings.save.return.width
1909
+ )}px`
1910
+ : '100%',
1871
1911
  }}
1872
1912
  showActiveRowIndicator={false}
1873
1913
  selected={selected}
@@ -2361,9 +2401,13 @@ function Form({
2361
2401
  <div className={styles.modal__header}>
2362
2402
  <h1>
2363
2403
  {bulkEditMode && bulkEditGroupData
2364
- ? `${bulkEditGroupData.iconConfig.formModal?.title || 'Bulk Edit'} - ${bulkEditGroupData.groupValue} (${bulkEditGroupData.groupRows.length} items)`
2365
- : formSettings[formType]?.title
2366
- }
2404
+ ? `${
2405
+ bulkEditGroupData.iconConfig.formModal
2406
+ ?.title || 'Bulk Edit'
2407
+ } - ${bulkEditGroupData.groupValue} (${
2408
+ bulkEditGroupData.groupRows.length
2409
+ } items)`
2410
+ : formSettings[formType]?.title}
2367
2411
  </h1>
2368
2412
  <button
2369
2413
  className={styles.modal__close}
@@ -2398,9 +2442,12 @@ function Form({
2398
2442
  }
2399
2443
  // In bulk edit mode, only show fields specified in the formModal configuration
2400
2444
  if (
2401
- bulkEditMode &&
2402
- bulkEditGroupData?.iconConfig?.formModal?.fields &&
2403
- !bulkEditGroupData.iconConfig.formModal.fields.includes(item.id)
2445
+ bulkEditMode &&
2446
+ bulkEditGroupData?.iconConfig?.formModal
2447
+ ?.fields &&
2448
+ !bulkEditGroupData.iconConfig.formModal.fields.includes(
2449
+ item.id
2450
+ )
2404
2451
  ) {
2405
2452
  return null;
2406
2453
  }