@visns-studio/visns-components 5.9.4 → 5.9.5

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
@@ -22,7 +22,7 @@
22
22
  "awesome-debounce-promise": "^2.1.0",
23
23
  "browser-image-compression": "^2.0.2",
24
24
  "dayjs": "^1.11.13",
25
- "fabric": "^6.6.6",
25
+ "fabric": "^6.6.7",
26
26
  "file-saver": "^2.0.5",
27
27
  "framer-motion": "^12.15.0",
28
28
  "html-react-parser": "^5.2.5",
@@ -67,8 +67,8 @@
67
67
  "yet-another-react-lightbox": "^3.23.2"
68
68
  },
69
69
  "devDependencies": {
70
- "@babel/core": "^7.27.3",
71
- "@babel/plugin-transform-runtime": "^7.27.3",
70
+ "@babel/core": "^7.27.4",
71
+ "@babel/plugin-transform-runtime": "^7.27.4",
72
72
  "@babel/preset-env": "^7.27.2",
73
73
  "@babel/preset-react": "^7.27.1",
74
74
  "babel-loader": "^10.0.0",
@@ -78,7 +78,7 @@
78
78
  "mini-css-extract-plugin": "^2.9.2",
79
79
  "react": "^18.3.1",
80
80
  "react-dom": "^18.3.1",
81
- "sass": "^1.89.0",
81
+ "sass": "^1.89.1",
82
82
  "sass-loader": "^16.0.5"
83
83
  },
84
84
  "peerDependencies": {
@@ -86,7 +86,7 @@
86
86
  "react-dom": "^17.0.0 || ^18.0.0"
87
87
  },
88
88
  "name": "@visns-studio/visns-components",
89
- "version": "5.9.4",
89
+ "version": "5.9.5",
90
90
  "description": "Various packages to assist in the development of our Custom Applications.",
91
91
  "main": "src/index.js",
92
92
  "files": [
@@ -653,22 +653,14 @@ function Form({
653
653
  // especially when grouping is active
654
654
  if (dataRef.current && Array.isArray(dataRef.current)) {
655
655
  dataRef.current.forEach((obj) => {
656
- if (
657
- obj &&
658
- obj.id !== undefined &&
659
- obj.id !== null
660
- ) {
656
+ if (obj && obj.id !== undefined && obj.id !== null) {
661
657
  s[obj.id] = obj;
662
658
  }
663
659
  });
664
660
  } else if (param.data && Array.isArray(param.data)) {
665
661
  // Fallback to param.data if dataRef.current is not available
666
662
  param.data.forEach((obj) => {
667
- if (
668
- obj &&
669
- obj.id !== undefined &&
670
- obj.id !== null
671
- ) {
663
+ if (obj && obj.id !== undefined && obj.id !== null) {
672
664
  s[obj.id] = obj;
673
665
  }
674
666
  });
@@ -1130,10 +1122,19 @@ function Form({
1130
1122
  navigate(saveExit.redirectUrl);
1131
1123
  }
1132
1124
  }
1125
+
1126
+ toast.success(
1127
+ formSettings?.save?.toast?.success ||
1128
+ (method === 'POST'
1129
+ ? 'Entry successfully saved into the system.'
1130
+ : 'Entry successfully updated.')
1131
+ );
1133
1132
  } else {
1134
1133
  toast.success(
1135
1134
  formSettings?.save?.toast?.success ||
1136
- 'Entry successfully saved into the system.'
1135
+ (method === 'POST'
1136
+ ? 'Entry successfully saved into the system.'
1137
+ : 'Entry successfully updated.')
1137
1138
  );
1138
1139
  }
1139
1140
 
@@ -1151,8 +1152,10 @@ function Form({
1151
1152
  formSettings?.save?.return?.type === 'table' &&
1152
1153
  formSettings?.save?.return?.param
1153
1154
  ) {
1154
- if ( formSettings.save.return.props ) {
1155
- setTableProps(formSettings.save.return.props);
1155
+ if (formSettings.save.return.props) {
1156
+ setTableProps(
1157
+ formSettings.save.return.props
1158
+ );
1156
1159
  }
1157
1160
 
1158
1161
  if (
@@ -1719,48 +1722,20 @@ function Form({
1719
1722
  selected={selected}
1720
1723
  onSelectionChange={handleSelectionChange}
1721
1724
  {...(tableProps && tableProps.groupBy
1722
- ? {
1723
- defaultGroupBy: tableProps.groupBy,
1724
- defaultCollapsed: true,
1725
- collapsedGroups: collapsedGroups,
1726
- onGroupToggle: (groupData) => {
1727
- // Handle group expand/collapse events
1728
- // Update collapsed groups state
1729
- const groupValue =
1730
- typeof groupData === 'string'
1731
- ? groupData
1732
- : groupData?.value ||
1733
- groupData?.name ||
1734
- '';
1735
-
1736
- setCollapsedGroups((prev) => {
1737
- const newState = { ...prev };
1738
- if (newState[groupValue]) {
1739
- delete newState[groupValue];
1740
- } else {
1741
- newState[groupValue] = true;
1742
- }
1743
- console.log(
1744
- 'Updated collapsed groups:',
1745
- newState
1746
- );
1747
- return newState;
1748
- });
1749
- },
1750
- renderGroupTitle: (groupData) => {
1751
- const groupValue =
1752
- typeof groupData === 'string'
1753
- ? groupData
1754
- : groupData?.value ||
1755
- groupData?.name ||
1756
- '';
1757
-
1758
- // Use ReactDataGrid's built-in toggle functionality
1759
- const collapsed =
1760
- collapsedGroups[groupValue] ||
1761
- false;
1762
- const toggleGroup = () => {
1763
- // Update the collapsed state
1725
+ ? {
1726
+ defaultGroupBy: tableProps.groupBy,
1727
+ defaultCollapsed: true,
1728
+ collapsedGroups: collapsedGroups,
1729
+ onGroupToggle: (groupData) => {
1730
+ // Handle group expand/collapse events
1731
+ // Update collapsed groups state
1732
+ const groupValue =
1733
+ typeof groupData === 'string'
1734
+ ? groupData
1735
+ : groupData?.value ||
1736
+ groupData?.name ||
1737
+ '';
1738
+
1764
1739
  setCollapsedGroups((prev) => {
1765
1740
  const newState = { ...prev };
1766
1741
  if (newState[groupValue]) {
@@ -1772,339 +1747,400 @@ function Form({
1772
1747
  groupValue
1773
1748
  ] = true;
1774
1749
  }
1775
- return newState;
1776
- });
1777
- };
1778
- const groupCount =
1779
- typeof groupData === 'object'
1780
- ? groupData?.count || 0
1781
- : 0;
1782
- const hasCheckboxColumn =
1783
- tableData.columns.some(
1784
- (col) =>
1785
- col.id ===
1786
- '__checkbox-column'
1787
- );
1788
-
1789
- // Handle group header click for selection
1790
- const handleGroupHeaderClick = (
1791
- e
1792
- ) => {
1793
- // Prevent rapid clicks
1794
- if (
1795
- groupClickTimeoutRef.current
1796
- ) {
1797
1750
  console.log(
1798
- '🖱️ Ignoring rapid click'
1751
+ 'Updated collapsed groups:',
1752
+ newState
1799
1753
  );
1800
- return;
1801
- }
1802
-
1803
- // Don't trigger if clicking on the expand button or checkbox
1804
- if (
1805
- e.target.closest(
1806
- '.group-expand-btn'
1807
- ) ||
1808
- e.target.closest(
1809
- 'input[type="checkbox"]'
1810
- )
1811
- ) {
1812
- console.log(
1813
- '🖱️ Ignoring click on expand button or checkbox'
1754
+ return newState;
1755
+ });
1756
+ },
1757
+ renderGroupTitle: (groupData) => {
1758
+ const groupValue =
1759
+ typeof groupData === 'string'
1760
+ ? groupData
1761
+ : groupData?.value ||
1762
+ groupData?.name ||
1763
+ '';
1764
+
1765
+ // Use ReactDataGrid's built-in toggle functionality
1766
+ const collapsed =
1767
+ collapsedGroups[groupValue] ||
1768
+ false;
1769
+ const toggleGroup = () => {
1770
+ // Update the collapsed state
1771
+ setCollapsedGroups((prev) => {
1772
+ const newState = {
1773
+ ...prev,
1774
+ };
1775
+ if (
1776
+ newState[groupValue]
1777
+ ) {
1778
+ delete newState[
1779
+ groupValue
1780
+ ];
1781
+ } else {
1782
+ newState[
1783
+ groupValue
1784
+ ] = true;
1785
+ }
1786
+ return newState;
1787
+ });
1788
+ };
1789
+ const groupCount =
1790
+ typeof groupData === 'object'
1791
+ ? groupData?.count || 0
1792
+ : 0;
1793
+ const hasCheckboxColumn =
1794
+ tableData.columns.some(
1795
+ (col) =>
1796
+ col.id ===
1797
+ '__checkbox-column'
1814
1798
  );
1815
- return;
1816
- }
1817
-
1818
- // Set timeout to prevent rapid clicks
1819
- groupClickTimeoutRef.current =
1820
- setTimeout(() => {
1821
- groupClickTimeoutRef.current =
1822
- null;
1823
- }, 300);
1824
-
1825
- // Toggle selection of all items in this group
1826
- const isCurrentlySelected =
1827
- isGroupSelected(groupValue);
1828
-
1829
- handleGroupSelectionChange(
1830
- groupValue,
1831
- !isCurrentlySelected
1832
- );
1833
- };
1834
1799
 
1835
- return (
1836
- <div
1837
- className="group-header-clean group-header-fixed-height"
1838
- onClick={
1839
- handleGroupHeaderClick
1800
+ // Handle group header click for selection
1801
+ const handleGroupHeaderClick = (
1802
+ e
1803
+ ) => {
1804
+ // Prevent rapid clicks
1805
+ if (
1806
+ groupClickTimeoutRef.current
1807
+ ) {
1808
+ console.log(
1809
+ '🖱️ Ignoring rapid click'
1810
+ );
1811
+ return;
1812
+ }
1813
+
1814
+ // Don't trigger if clicking on the expand button or checkbox
1815
+ if (
1816
+ e.target.closest(
1817
+ '.group-expand-btn'
1818
+ ) ||
1819
+ e.target.closest(
1820
+ 'input[type="checkbox"]'
1821
+ )
1822
+ ) {
1823
+ console.log(
1824
+ '🖱️ Ignoring click on expand button or checkbox'
1825
+ );
1826
+ return;
1840
1827
  }
1841
- style={{
1842
- display: 'flex',
1843
- alignItems: 'center',
1844
- justifyContent:
1845
- 'space-between',
1846
- width: '100%',
1847
- height: '34px', // Compact height
1848
- padding: '6px 12px', // Reduced padding for compact design
1849
- background:
1850
- 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
1851
- borderLeft:
1852
- '4px solid var(--primary-color, #3b82f6)',
1853
- position: 'relative',
1854
- overflow: 'hidden',
1855
- boxShadow:
1856
- '0 1px 3px rgba(0,0,0,0.1)',
1857
- borderRadius:
1858
- '0 4px 4px 0',
1859
- cursor: 'pointer',
1860
- boxSizing: 'border-box',
1861
- lineHeight: '1.1', // Compact line height
1862
- flexShrink: 0,
1863
- transition:
1864
- 'background-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease',
1865
- }}
1866
- onMouseEnter={(e) => {
1867
- e.currentTarget.style.background =
1868
- 'linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%)';
1869
- e.currentTarget.style.boxShadow =
1870
- '0 2px 8px rgba(0,0,0,0.15)';
1871
- e.currentTarget.style.transform =
1872
- 'translateY(-1px)';
1873
- }}
1874
- onMouseLeave={(e) => {
1875
- e.currentTarget.style.background =
1876
- 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)';
1877
- e.currentTarget.style.boxShadow =
1878
- '0 1px 3px rgba(0,0,0,0.1)';
1879
- e.currentTarget.style.transform =
1880
- 'translateY(0)';
1881
- }}
1882
- >
1883
- {/* Left side - Group info and expand/collapse */}
1828
+
1829
+ // Set timeout to prevent rapid clicks
1830
+ groupClickTimeoutRef.current =
1831
+ setTimeout(() => {
1832
+ groupClickTimeoutRef.current =
1833
+ null;
1834
+ }, 300);
1835
+
1836
+ // Toggle selection of all items in this group
1837
+ const isCurrentlySelected =
1838
+ isGroupSelected(
1839
+ groupValue
1840
+ );
1841
+
1842
+ handleGroupSelectionChange(
1843
+ groupValue,
1844
+ !isCurrentlySelected
1845
+ );
1846
+ };
1847
+
1848
+ return (
1884
1849
  <div
1850
+ className="group-header-clean group-header-fixed-height"
1851
+ onClick={
1852
+ handleGroupHeaderClick
1853
+ }
1885
1854
  style={{
1886
1855
  display: 'flex',
1887
1856
  alignItems: 'center',
1888
- flex: 1,
1889
- zIndex: 2,
1857
+ justifyContent:
1858
+ 'space-between',
1859
+ width: '100%',
1860
+ height: '34px', // Compact height
1861
+ padding: '6px 12px', // Reduced padding for compact design
1862
+ background:
1863
+ 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
1864
+ borderLeft:
1865
+ '4px solid var(--primary-color, #3b82f6)',
1890
1866
  position: 'relative',
1867
+ overflow: 'hidden',
1868
+ boxShadow:
1869
+ '0 1px 3px rgba(0,0,0,0.1)',
1870
+ borderRadius:
1871
+ '0 4px 4px 0',
1872
+ cursor: 'pointer',
1873
+ boxSizing:
1874
+ 'border-box',
1875
+ lineHeight: '1.1', // Compact line height
1876
+ flexShrink: 0,
1877
+ transition:
1878
+ 'background-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease',
1879
+ }}
1880
+ onMouseEnter={(e) => {
1881
+ e.currentTarget.style.background =
1882
+ 'linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%)';
1883
+ e.currentTarget.style.boxShadow =
1884
+ '0 2px 8px rgba(0,0,0,0.15)';
1885
+ e.currentTarget.style.transform =
1886
+ 'translateY(-1px)';
1887
+ }}
1888
+ onMouseLeave={(e) => {
1889
+ e.currentTarget.style.background =
1890
+ 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)';
1891
+ e.currentTarget.style.boxShadow =
1892
+ '0 1px 3px rgba(0,0,0,0.1)';
1893
+ e.currentTarget.style.transform =
1894
+ 'translateY(0)';
1891
1895
  }}
1892
1896
  >
1893
- <button
1894
- className="group-expand-btn"
1895
- onClick={(e) => {
1896
- e.preventDefault();
1897
- e.stopPropagation();
1898
- if (
1899
- typeof toggleGroup ===
1900
- 'function'
1901
- ) {
1902
- toggleGroup();
1903
- } else {
1904
- console.warn(
1905
- 'toggleGroup is not a function:',
1906
- toggleGroup
1907
- );
1908
- }
1909
- }}
1910
- style={{
1911
- background:
1912
- 'var(--primary-color, #3b82f6)',
1913
- border: 'none',
1914
- borderRadius:
1915
- '3px',
1916
- color: 'white',
1917
- width: '20px', // More compact
1918
- height: '20px', // More compact
1919
- display: 'flex',
1920
- alignItems:
1921
- 'center',
1922
- justifyContent:
1923
- 'center',
1924
- marginRight:
1925
- '8px', // Reduced margin
1926
- cursor: 'pointer',
1927
- fontSize: '11px', // Smaller font
1928
- fontWeight:
1929
- 'bold',
1930
- boxShadow:
1931
- '0 1px 2px rgba(0,0,0,0.1)',
1932
- transition:
1933
- 'all 0.15s ease',
1934
- }}
1935
- onMouseEnter={(e) => {
1936
- e.target.style.transform =
1937
- 'scale(1.05)';
1938
- e.target.style.background =
1939
- 'var(--secondary-color, #2563eb)';
1940
- }}
1941
- onMouseLeave={(e) => {
1942
- e.target.style.transform =
1943
- 'scale(1)';
1944
- e.target.style.background =
1945
- 'var(--primary-color, #3b82f6)';
1946
- }}
1947
- >
1948
- {collapsed
1949
- ? '+'
1950
- : '−'}
1951
- </button>
1897
+ {/* Left side - Group info and expand/collapse */}
1952
1898
  <div
1953
1899
  style={{
1954
1900
  display: 'flex',
1955
1901
  alignItems:
1956
- 'center', // Changed to center for compact layout
1957
- gap: '8px', // Add gap between elements
1902
+ 'center',
1903
+ flex: 1,
1904
+ zIndex: 2,
1905
+ position:
1906
+ 'relative',
1958
1907
  }}
1959
1908
  >
1960
- <strong
1909
+ <button
1910
+ className="group-expand-btn"
1911
+ onClick={(e) => {
1912
+ e.preventDefault();
1913
+ e.stopPropagation();
1914
+ if (
1915
+ typeof toggleGroup ===
1916
+ 'function'
1917
+ ) {
1918
+ toggleGroup();
1919
+ } else {
1920
+ console.warn(
1921
+ 'toggleGroup is not a function:',
1922
+ toggleGroup
1923
+ );
1924
+ }
1925
+ }}
1961
1926
  style={{
1927
+ background:
1928
+ 'var(--primary-color, #3b82f6)',
1929
+ border: 'none',
1930
+ borderRadius:
1931
+ '3px',
1932
+ color: 'white',
1933
+ width: '20px', // More compact
1934
+ height: '20px', // More compact
1935
+ display:
1936
+ 'flex',
1937
+ alignItems:
1938
+ 'center',
1939
+ justifyContent:
1940
+ 'center',
1941
+ marginRight:
1942
+ '8px', // Reduced margin
1943
+ cursor: 'pointer',
1962
1944
  fontSize:
1963
- '0.85rem', // Slightly smaller for compact design
1964
- color: 'var(--secondary-color, #1f2937)',
1945
+ '11px', // Smaller font
1965
1946
  fontWeight:
1966
- '600',
1967
- textTransform:
1968
- 'uppercase',
1969
- letterSpacing:
1970
- '0.025em',
1947
+ 'bold',
1948
+ boxShadow:
1949
+ '0 1px 2px rgba(0,0,0,0.1)',
1950
+ transition:
1951
+ 'all 0.15s ease',
1952
+ }}
1953
+ onMouseEnter={(
1954
+ e
1955
+ ) => {
1956
+ e.target.style.transform =
1957
+ 'scale(1.05)';
1958
+ e.target.style.background =
1959
+ 'var(--secondary-color, #2563eb)';
1960
+ }}
1961
+ onMouseLeave={(
1962
+ e
1963
+ ) => {
1964
+ e.target.style.transform =
1965
+ 'scale(1)';
1966
+ e.target.style.background =
1967
+ 'var(--primary-color, #3b82f6)';
1971
1968
  }}
1972
1969
  >
1973
- {groupValue}
1974
- </strong>
1975
- {groupCount > 0 && (
1976
- <span
1970
+ {collapsed
1971
+ ? '+'
1972
+ : '−'}
1973
+ </button>
1974
+ <div
1975
+ style={{
1976
+ display:
1977
+ 'flex',
1978
+ alignItems:
1979
+ 'center', // Changed to center for compact layout
1980
+ gap: '8px', // Add gap between elements
1981
+ }}
1982
+ >
1983
+ <strong
1977
1984
  style={{
1978
- color: '#6b7280',
1979
1985
  fontSize:
1980
- '0.75rem', // Smaller for compact design
1986
+ '0.85rem', // Slightly smaller for compact design
1987
+ color: 'var(--secondary-color, #1f2937)',
1981
1988
  fontWeight:
1982
- '500',
1989
+ '600',
1990
+ textTransform:
1991
+ 'uppercase',
1992
+ letterSpacing:
1993
+ '0.025em',
1994
+ }}
1995
+ >
1996
+ {groupValue}
1997
+ </strong>
1998
+ {groupCount >
1999
+ 0 && (
2000
+ <span
2001
+ style={{
2002
+ color: '#6b7280',
2003
+ fontSize:
2004
+ '0.75rem', // Smaller for compact design
2005
+ fontWeight:
2006
+ '500',
2007
+ }}
2008
+ >
2009
+ (
2010
+ {
2011
+ groupCount
2012
+ }{' '}
2013
+ {groupCount ===
2014
+ 1
2015
+ ? 'item'
2016
+ : 'items'}
2017
+ )
2018
+ </span>
2019
+ )}
2020
+ {/* Click indicator */}
2021
+ <span
2022
+ style={{
2023
+ color: '#9ca3af',
2024
+ fontSize:
2025
+ '0.7rem',
2026
+ fontStyle:
2027
+ 'italic',
2028
+ opacity: 0.8,
2029
+ marginLeft:
2030
+ 'auto',
2031
+ userSelect:
2032
+ 'none',
1983
2033
  }}
1984
2034
  >
1985
- ({groupCount}{' '}
1986
- {groupCount ===
1987
- 1
1988
- ? 'item'
1989
- : 'items'}
1990
- )
2035
+ Click to
2036
+ select/unselect
2037
+ all
1991
2038
  </span>
1992
- )}
1993
- {/* Click indicator */}
1994
- <span
2039
+ </div>
2040
+ </div>
2041
+
2042
+ {/* Right side - Group selection */}
2043
+ {hasCheckboxColumn && (
2044
+ <div
1995
2045
  style={{
1996
- color: '#9ca3af',
2046
+ display:
2047
+ 'flex',
2048
+ alignItems:
2049
+ 'center',
2050
+ gap: '4px', // Reduced gap for compact design
1997
2051
  fontSize:
1998
- '0.7rem',
1999
- fontStyle:
2000
- 'italic',
2001
- opacity: 0.8,
2002
- marginLeft:
2003
- 'auto',
2004
- userSelect:
2005
- 'none',
2052
+ '0.75rem', // Smaller font for compact design
2053
+ background:
2054
+ 'rgba(255, 255, 255, 0.9)',
2055
+ padding:
2056
+ '3px 8px', // Reduced padding for compact design
2057
+ borderRadius:
2058
+ '12px', // Smaller border radius
2059
+ border: '1px solid rgba(59, 130, 246, 0.3)',
2060
+ zIndex: 2,
2061
+ position:
2062
+ 'relative',
2063
+ boxShadow:
2064
+ '0 1px 2px rgba(0,0,0,0.05)',
2006
2065
  }}
2007
2066
  >
2008
- Click to
2009
- select/unselect
2010
- all
2011
- </span>
2012
- </div>
2013
- </div>
2067
+ <input
2068
+ type="checkbox"
2069
+ onChange={(
2070
+ e
2071
+ ) => {
2072
+ e.stopPropagation();
2073
+ handleGroupSelectionChange(
2074
+ groupValue,
2075
+ e
2076
+ .target
2077
+ .checked
2078
+ );
2079
+ }}
2080
+ checked={isGroupSelected(
2081
+ groupValue
2082
+ )}
2083
+ style={{
2084
+ width: '14px', // Smaller checkbox for compact design
2085
+ height: '14px',
2086
+ cursor: 'pointer',
2087
+ accentColor:
2088
+ 'var(--primary-color, #3b82f6)',
2089
+ }}
2090
+ />
2091
+ <span
2092
+ style={{
2093
+ cursor: 'pointer',
2094
+ userSelect:
2095
+ 'none',
2096
+ color: 'var(--secondary-color, #1f2937)',
2097
+ fontWeight:
2098
+ '500',
2099
+ fontSize:
2100
+ '0.7rem', // Smaller font for compact design
2101
+ }}
2102
+ onClick={(
2103
+ e
2104
+ ) => {
2105
+ e.stopPropagation();
2106
+ const checkbox =
2107
+ e
2108
+ .target
2109
+ .previousElementSibling;
2110
+ checkbox.checked =
2111
+ !checkbox.checked;
2112
+ handleGroupSelectionChange(
2113
+ groupValue,
2114
+ checkbox.checked
2115
+ );
2116
+ }}
2117
+ >
2118
+ Select All
2119
+ </span>
2120
+ </div>
2121
+ )}
2014
2122
 
2015
- {/* Right side - Group selection */}
2016
- {hasCheckboxColumn && (
2123
+ {/* Background pattern */}
2017
2124
  <div
2018
2125
  style={{
2019
- display: 'flex',
2020
- alignItems:
2021
- 'center',
2022
- gap: '4px', // Reduced gap for compact design
2023
- fontSize:
2024
- '0.75rem', // Smaller font for compact design
2025
- background:
2026
- 'rgba(255, 255, 255, 0.9)',
2027
- padding:
2028
- '3px 8px', // Reduced padding for compact design
2029
- borderRadius:
2030
- '12px', // Smaller border radius
2031
- border: '1px solid rgba(59, 130, 246, 0.3)',
2032
- zIndex: 2,
2033
2126
  position:
2034
- 'relative',
2035
- boxShadow:
2036
- '0 1px 2px rgba(0,0,0,0.05)',
2127
+ 'absolute',
2128
+ top: 0,
2129
+ left: 0,
2130
+ right: 0,
2131
+ bottom: 0,
2132
+ background:
2133
+ 'repeating-linear-gradient(45deg, transparent, transparent 2px, rgba(59, 130, 246, 0.03) 2px, rgba(59, 130, 246, 0.03) 4px)',
2134
+ zIndex: 1,
2135
+ pointerEvents:
2136
+ 'none',
2037
2137
  }}
2038
- >
2039
- <input
2040
- type="checkbox"
2041
- onChange={(e) => {
2042
- e.stopPropagation();
2043
- handleGroupSelectionChange(
2044
- groupValue,
2045
- e.target
2046
- .checked
2047
- );
2048
- }}
2049
- checked={isGroupSelected(
2050
- groupValue
2051
- )}
2052
- style={{
2053
- width: '14px', // Smaller checkbox for compact design
2054
- height: '14px',
2055
- cursor: 'pointer',
2056
- accentColor:
2057
- 'var(--primary-color, #3b82f6)',
2058
- }}
2059
- />
2060
- <span
2061
- style={{
2062
- cursor: 'pointer',
2063
- userSelect:
2064
- 'none',
2065
- color: 'var(--secondary-color, #1f2937)',
2066
- fontWeight:
2067
- '500',
2068
- fontSize:
2069
- '0.7rem', // Smaller font for compact design
2070
- }}
2071
- onClick={(e) => {
2072
- e.stopPropagation();
2073
- const checkbox =
2074
- e.target
2075
- .previousElementSibling;
2076
- checkbox.checked =
2077
- !checkbox.checked;
2078
- handleGroupSelectionChange(
2079
- groupValue,
2080
- checkbox.checked
2081
- );
2082
- }}
2083
- >
2084
- Select All
2085
- </span>
2086
- </div>
2087
- )}
2088
-
2089
- {/* Background pattern */}
2090
- <div
2091
- style={{
2092
- position: 'absolute',
2093
- top: 0,
2094
- left: 0,
2095
- right: 0,
2096
- bottom: 0,
2097
- background:
2098
- 'repeating-linear-gradient(45deg, transparent, transparent 2px, rgba(59, 130, 246, 0.03) 2px, rgba(59, 130, 246, 0.03) 4px)',
2099
- zIndex: 1,
2100
- pointerEvents: 'none',
2101
- }}
2102
- />
2103
- </div>
2104
- );
2105
- },
2106
- }
2107
- : {})}
2138
+ />
2139
+ </div>
2140
+ );
2141
+ },
2142
+ }
2143
+ : {})}
2108
2144
  />
2109
2145
  </div>
2110
2146
  )}
@@ -15,10 +15,12 @@ import parse from 'html-react-parser';
15
15
  import moment from 'moment';
16
16
  import numeral from 'numeral';
17
17
  import { CloudDownload } from 'akar-icons';
18
+ import Popup from 'reactjs-popup';
18
19
 
19
20
  import CustomFetch from '../Fetch';
20
21
  import Download from '../Download';
21
22
  import Field from '../Field';
23
+ import Form from '../Form';
22
24
  import Table from '../DataGrid';
23
25
  import TableFilter from '../TableFilter';
24
26
 
@@ -1485,6 +1487,11 @@ function GenericIndex({
1485
1487
  const [tableData, setTableData] = useState([]);
1486
1488
  const [tableInfo, setTableInfo] = useState({});
1487
1489
  const [total, setTotal] = useState(0);
1490
+
1491
+ /** Custom Action Modal States */
1492
+ const [customActionModalShow, setCustomActionModalShow] = useState(false);
1493
+ const [customActionFormData, setCustomActionFormData] = useState({});
1494
+ const [customActionConfig, setCustomActionConfig] = useState(null);
1488
1495
  const windowHeight = useWindowHeight();
1489
1496
  const { config, setConfig, subnav, setSubnav } = useConfig(
1490
1497
  setting,
@@ -1526,20 +1533,110 @@ function GenericIndex({
1526
1533
  [setting, config]
1527
1534
  );
1528
1535
 
1536
+ /** Custom Action Modal Functions */
1537
+ const handleCustomActionModal = useCallback(
1538
+ (customAction) => {
1539
+ try {
1540
+ // Get form configuration from root level (config.form)
1541
+ const rootFormConfig = config.form;
1542
+
1543
+ if (!rootFormConfig || !rootFormConfig.fields) {
1544
+ toast.error('Form configuration not found.');
1545
+ return;
1546
+ }
1547
+
1548
+ // Filter out hidden fields from root-level form configuration
1549
+ const visibleFields = rootFormConfig.fields.filter(
1550
+ (field) => field.type !== 'hidden'
1551
+ );
1552
+
1553
+ // Add hidden field for selected rows
1554
+ const selectedRowIds = Object.keys(rowsSelected);
1555
+ const fieldsWithRows = [
1556
+ ...visibleFields,
1557
+ {
1558
+ id: 'selected_rows',
1559
+ type: 'hidden',
1560
+ value: selectedRowIds,
1561
+ },
1562
+ ];
1563
+
1564
+ // Prepare form data for the modal using root-level form config
1565
+ const modalFormData = {
1566
+ ...rootFormConfig,
1567
+ fields: fieldsWithRows,
1568
+ // Use custom URL and method for submission
1569
+ customUrl: customAction.url,
1570
+ customMethod: customAction.method || 'POST',
1571
+ customDataId: false, // Don't append ID to URL
1572
+ create: {
1573
+ title: customAction.label,
1574
+ url: customAction.url,
1575
+ method: customAction.method || 'POST',
1576
+ },
1577
+ primaryKey: 'id',
1578
+ };
1579
+
1580
+ setCustomActionConfig(customAction);
1581
+ setCustomActionFormData(modalFormData);
1582
+ setCustomActionModalShow(true);
1583
+ } catch (error) {
1584
+ console.error('Error opening custom action modal:', error);
1585
+ toast.error('Error opening form. Please try again.');
1586
+ }
1587
+ },
1588
+ [config.form, rowsSelected]
1589
+ );
1590
+
1591
+ const closeCustomActionModal = useCallback(() => {
1592
+ setCustomActionModalShow(false);
1593
+ setCustomActionFormData({});
1594
+ setCustomActionConfig(null);
1595
+ }, []);
1596
+
1597
+ const handleCustomActionSubmit = useCallback(() => {
1598
+ // This is called after successful form submission by the Form component
1599
+ // Since we provide closeModal prop, the Form component won't show success message
1600
+ // So we need to show it here, then reload grid and close modal
1601
+
1602
+ console.log('Custom action submitted', customActionConfig);
1603
+ if (customActionConfig) {
1604
+ toast.success(
1605
+ customActionConfig.message?.success ||
1606
+ 'Operation completed successfully.'
1607
+ );
1608
+ }
1609
+ gridRef.current?.reload();
1610
+ closeCustomActionModal();
1611
+ }, [customActionConfig, closeCustomActionModal]);
1612
+
1529
1613
  const handleCustomAction = useCallback(async () => {
1530
1614
  try {
1531
1615
  // Get the custom action configuration using our helper
1532
1616
  const customActionConfig = getFunctionConfig('customAction');
1533
1617
 
1534
1618
  if (customActionConfig) {
1535
- // Implementation will be added when needed
1536
- console.log('Custom action triggered', customActionConfig);
1619
+ // Check if this is a modal type custom action
1620
+ if (customActionConfig.type === 'modal') {
1621
+ // Check if rows are selected for modal type actions
1622
+ if (!Object.keys(rowsSelected).length) {
1623
+ toast.warning(
1624
+ customActionConfig.message?.warning ||
1625
+ 'Please select at least one row.'
1626
+ );
1627
+ return;
1628
+ }
1629
+ handleCustomActionModal(customActionConfig);
1630
+ } else {
1631
+ // Original implementation for non-modal custom actions
1632
+ console.log('Custom action triggered', customActionConfig);
1633
+ }
1537
1634
  }
1538
1635
  } catch (err) {
1539
1636
  console.error(err);
1540
1637
  toast.error('An error occurred while performing the custom action');
1541
1638
  }
1542
- }, [getFunctionConfig]);
1639
+ }, [getFunctionConfig, handleCustomActionModal, rowsSelected]);
1543
1640
 
1544
1641
  const handleCheckboxUpdate = useCallback(async () => {
1545
1642
  try {
@@ -2064,15 +2161,36 @@ function GenericIndex({
2064
2161
  )}
2065
2162
 
2066
2163
  {/* Render Custom Action button if customAction function is configured in any location */}
2067
- {getFunctionConfig('customAction') && (
2068
- <button
2069
- className={styles.btn}
2070
- onClick={handleCustomAction}
2071
- >
2072
- {getFunctionConfig('customAction').label ||
2073
- 'Custom Action'}
2074
- </button>
2075
- )}
2164
+ {getFunctionConfig('customAction') &&
2165
+ (() => {
2166
+ const customActionConfig =
2167
+ getFunctionConfig('customAction');
2168
+ const isModalType =
2169
+ customActionConfig.type === 'modal';
2170
+ const hasSelectedRows =
2171
+ Object.keys(rowsSelected).length > 0;
2172
+ const isDisabled =
2173
+ isModalType && !hasSelectedRows;
2174
+
2175
+ return (
2176
+ <button
2177
+ className={styles.btn}
2178
+ onClick={handleCustomAction}
2179
+ disabled={isDisabled}
2180
+ {...(isDisabled && {
2181
+ 'data-tooltip-id':
2182
+ 'system-tooltip',
2183
+ 'data-tooltip-content':
2184
+ customActionConfig.message
2185
+ ?.warning ||
2186
+ 'Please select at least one row to perform this action',
2187
+ })}
2188
+ >
2189
+ {customActionConfig.label ||
2190
+ 'Custom Action'}
2191
+ </button>
2192
+ );
2193
+ })()}
2076
2194
 
2077
2195
  {setting.export?.label && (
2078
2196
  <button
@@ -2086,6 +2204,43 @@ function GenericIndex({
2086
2204
  );
2087
2205
  })()}
2088
2206
  </div>
2207
+
2208
+ {/* Custom Action Modal */}
2209
+ <Popup
2210
+ open={customActionModalShow}
2211
+ onClose={closeCustomActionModal}
2212
+ closeOnDocumentClick={false}
2213
+ contentStyle={{
2214
+ width: '80vw',
2215
+ maxWidth: '1200px',
2216
+ ...(customActionFormData.verticalAlign === 'top' && {
2217
+ position: 'fixed',
2218
+ top: '5vh',
2219
+ left: '50%',
2220
+ transform: 'translateX(-50%)',
2221
+ margin: '0',
2222
+ }),
2223
+ }}
2224
+ >
2225
+ <div
2226
+ className={`modalwrap top--modal modalWide ${
2227
+ customActionFormData.verticalAlign === 'top'
2228
+ ? 'modal-top-aligned'
2229
+ : ''
2230
+ }`}
2231
+ >
2232
+ <div className="modal">
2233
+ <Form
2234
+ closeModal={closeCustomActionModal}
2235
+ formSettings={customActionFormData}
2236
+ formType="create"
2237
+ fetchTable={handleCustomActionSubmit}
2238
+ style={userProfile?.settings?.style || {}}
2239
+ userProfile={userProfile}
2240
+ />
2241
+ </div>
2242
+ </div>
2243
+ </Popup>
2089
2244
  </>
2090
2245
  );
2091
2246
  }
@@ -214,12 +214,20 @@
214
214
  font-size: 1.25em;
215
215
  }
216
216
 
217
- .btn:hover {
217
+ .btn:hover:not(:disabled) {
218
218
  color: var(--primary-color);
219
219
  background: var(--highlight-color);
220
220
  border: 1px solid rgba(var(--highlight-color-rgb), 1.05);
221
221
  }
222
222
 
223
+ .btn:disabled {
224
+ opacity: 0.5;
225
+ cursor: not-allowed;
226
+ background: var(--primary-color);
227
+ color: var(--tertiary-color);
228
+ border: 1px solid rgba(var(--primary-color--rgb), 0.5);
229
+ }
230
+
223
231
  .polActions {
224
232
  position: fixed;
225
233
  bottom: 15px;