@visns-studio/visns-components 5.9.4 → 5.9.6

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.
@@ -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;
@@ -1,7 +1,6 @@
1
1
  /* Notification Container */
2
2
  .notificationContainer {
3
3
  position: relative;
4
- display: inline-flex;
5
4
  align-items: center;
6
5
  }
7
6
 
@@ -433,4 +433,4 @@
433
433
 
434
434
  .InovuaReactDataGrid__group-toolbar {
435
435
  display: none !important;
436
- }
436
+ }
@@ -0,0 +1,104 @@
1
+ import React from 'react';
2
+ import SystemTooltip from '../utils/SystemTooltip';
3
+
4
+ const TooltipTest = () => {
5
+ return (
6
+ <div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
7
+ <h1>SystemTooltip Test Component</h1>
8
+
9
+ <p>This component tests the SystemTooltip functionality. Hover over the elements below:</p>
10
+
11
+ <div style={{ margin: '20px 0' }}>
12
+ <button
13
+ style={{
14
+ background: '#007bff',
15
+ color: 'white',
16
+ border: 'none',
17
+ padding: '10px 20px',
18
+ borderRadius: '4px',
19
+ cursor: 'pointer',
20
+ margin: '10px'
21
+ }}
22
+ data-tooltip-content="This is a test button tooltip"
23
+ >
24
+ Test Button
25
+ </button>
26
+
27
+ <div
28
+ style={{
29
+ width: '30px',
30
+ height: '30px',
31
+ background: '#28a745',
32
+ borderRadius: '50%',
33
+ display: 'inline-block',
34
+ margin: '10px',
35
+ cursor: 'pointer'
36
+ }}
37
+ data-tooltip-content="This is a test icon tooltip"
38
+ ></div>
39
+
40
+ <span
41
+ style={{
42
+ background: '#f8f9fa',
43
+ padding: '10px',
44
+ border: '1px solid #dee2e6',
45
+ borderRadius: '4px',
46
+ margin: '10px',
47
+ display: 'inline-block',
48
+ cursor: 'pointer'
49
+ }}
50
+ data-tooltip-content="This is a test text tooltip with some longer content to see how it wraps"
51
+ >
52
+ Test Text Element
53
+ </span>
54
+ </div>
55
+
56
+ <div style={{ margin: '20px 0' }}>
57
+ <p data-tooltip-content="This paragraph has a tooltip">
58
+ Paragraph with tooltip
59
+ </p>
60
+
61
+ <div data-tooltip-content="Nested element test">
62
+ <span>Nested content</span>
63
+ </div>
64
+ </div>
65
+
66
+ <div style={{ margin: '20px 0' }}>
67
+ <input
68
+ type="text"
69
+ placeholder="Input field"
70
+ data-tooltip-content="This is an input field tooltip"
71
+ style={{ margin: '10px' }}
72
+ />
73
+
74
+ <select
75
+ data-tooltip-content="This is a select dropdown tooltip"
76
+ style={{ margin: '10px' }}
77
+ >
78
+ <option>Option 1</option>
79
+ <option>Option 2</option>
80
+ </select>
81
+ </div>
82
+
83
+ <div style={{ margin: '20px 0' }}>
84
+ <div
85
+ data-tooltip-content="HTML content test: <strong>Bold text</strong><br/>Line break<br/>Another line"
86
+ style={{
87
+ background: '#ffc107',
88
+ padding: '10px',
89
+ borderRadius: '4px',
90
+ cursor: 'pointer',
91
+ display: 'inline-block'
92
+ }}
93
+ >
94
+ Element with HTML content tooltip
95
+ </div>
96
+ </div>
97
+
98
+ {/* Include SystemTooltip component */}
99
+ <SystemTooltip />
100
+ </div>
101
+ );
102
+ };
103
+
104
+ export default TooltipTest;