@visns-studio/visns-components 5.5.3 → 5.5.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/README.md CHANGED
@@ -14,28 +14,34 @@ VISNS Components is a React-based UI component library that provides a set of re
14
14
  ### Authentication System
15
15
 
16
16
  - **Login** - Email/password authentication
17
- - **Two-Factor Authentication (2FA)** - Additional security layer (optional)
17
+ - **Two-Factor Authentication (2FA)** - Additional security layer with QR code and manual setup
18
18
  - **Password Reset** - Secure password recovery flow
19
19
  - **Profile Management** - User profile editing
20
20
  - **SSO Integration** - Support for Microsoft Azure
21
+ - **Remember Me** - Persistent login option that works with 2FA
21
22
 
22
23
  ### Data Visualization
23
24
 
24
25
  - **DataGrid** - Powerful data table with sorting, filtering, and pagination
25
26
  - **Charts** - Various chart types for data visualization
27
+ - **Report Builder** - Custom report creation with table joins, column selection, and filtering
28
+ - **Sketch Field** - Interactive drawing tools for diagrams and annotations
26
29
 
27
30
  ### Form Components
28
31
 
29
32
  - **Form Builder** - Dynamic form creation and validation
30
33
  - **Field Components** - Text inputs, selects, checkboxes, etc.
31
- - **Multi-select** - Enhanced select with multiple selection support
34
+ - **Multi-select** - Enhanced select with multiple selection support and selection limits
32
35
  - **Autocomplete** - Mapbox integration for location selection
33
36
  - **Dropzone** - File upload with drag and drop support
37
+ - **AsyncSelect** - Asynchronous data loading for select components
38
+ - **Creatable Select** - Select components that allow creating new options
34
39
 
35
40
  ### Navigation
36
41
 
37
42
  - **Header/Navigation** - Application header with navigation
38
43
  - **Sub Navigation** - Secondary navigation options
44
+ - **Breadcrumb** - Path-based navigation component
39
45
 
40
46
  ### Utility Components
41
47
 
@@ -44,6 +50,7 @@ VISNS Components is a React-based UI component library that provides a set of re
44
50
  - **Loader** - Loading indicators
45
51
  - **Notification** - Toast notifications
46
52
  - **Call Popup** - Modal dialogs
53
+ - **QR Code** - QR code generation and printing
47
54
 
48
55
  ### Generic Pages
49
56
 
@@ -51,6 +58,7 @@ VISNS Components is a React-based UI component library that provides a set of re
51
58
  - **Detail Pages** - Item detail templates
52
59
  - **Notification Pages** - User notification center
53
60
  - **Sorting Pages** - Content ordering interfaces
61
+ - **Report Pages** - Custom report building and execution
54
62
 
55
63
  ## Installation
56
64
 
@@ -230,7 +238,7 @@ A flexible form component with validation.
230
238
 
231
239
  #### AsyncSelect
232
240
 
233
- Asynchronous select component with search functionality.
241
+ Asynchronous select component with search functionality and dynamic data loading.
234
242
 
235
243
  ```jsx
236
244
  <AsyncSelect
@@ -239,23 +247,53 @@ Asynchronous select component with search functionality.
239
247
  labelKey="name"
240
248
  placeholder="Select an option"
241
249
  onChange={handleChange}
242
- value={selectedValue}
250
+ inputValue={selectedValue}
251
+ settings={{
252
+ id: 'myAsyncSelect',
253
+ params: { additionalParam: 'value' }, // Optional: additional parameters to send with the request
254
+ }}
255
+ isCreatable={false} // Optional: allow creating new options
256
+ creatableConfig={{
257
+ // Configuration for creatable options
258
+ url: '/api/options/create',
259
+ method: 'POST',
260
+ }}
243
261
  />
244
262
  ```
245
263
 
264
+ The AsyncSelect component supports these features:
265
+
266
+ - **Dynamic Data Loading**: Loads options from an API endpoint as the user types
267
+ - **Debounced Requests**: Prevents excessive API calls during typing
268
+ - **Custom Parameters**: Send additional parameters with the API request
269
+ - **Creatable Options**: Allow users to create new options that don't exist in the API
270
+
246
271
  #### MultiSelect
247
272
 
248
- Select component with multiple selection support.
273
+ Select component with multiple selection support and optional selection limits.
249
274
 
250
275
  ```jsx
251
276
  <MultiSelect
252
277
  options={options}
253
- value={selectedValues}
278
+ inputValue={selectedValues}
254
279
  onChange={handleChange}
255
280
  placeholder="Select options"
281
+ multi={true}
282
+ settings={{
283
+ id: 'myMultiSelect',
284
+ limit: 3, // Optional: limit the number of selections
285
+ }}
286
+ isCreatable={false} // Optional: allow creating new options
287
+ creatableConfig={{}} // Configuration for creatable options
256
288
  />
257
289
  ```
258
290
 
291
+ The MultiSelect component supports these features:
292
+
293
+ - **Selection Limits**: Set a maximum number of items that can be selected
294
+ - **Single Item Mode**: When `limit={1}` with `multi={true}`, it keeps only the most recently selected item
295
+ - **Creatable Options**: Allow users to create new options that don't exist in the list
296
+
259
297
  #### Autocomplete
260
298
 
261
299
  Location autocomplete with Mapbox integration.
@@ -289,6 +327,10 @@ File upload component with drag and drop support.
289
327
  Data fetching utility with error handling.
290
328
 
291
329
  ```jsx
330
+ // Using async/await
331
+ const result = await CustomFetch('/api/data', 'GET', { param1: 'value1' });
332
+
333
+ // Using callbacks
292
334
  CustomFetch(
293
335
  '/api/data',
294
336
  'GET',
@@ -332,6 +374,24 @@ Toast notification component.
332
374
  />
333
375
  ```
334
376
 
377
+ #### QrCode
378
+
379
+ QR code generation and printing component.
380
+
381
+ ```jsx
382
+ <QrCode config={{ url: '/api/qrcode' }} dataId={123} />
383
+ ```
384
+
385
+ The QrCode component fetches QR code data from the specified URL and displays it with a print button. The API response should include:
386
+
387
+ ```json
388
+ {
389
+ "logo": "optional-logo-url",
390
+ "qrCode": "base64-encoded-svg-data",
391
+ "title": "QR Code Title"
392
+ }
393
+ ```
394
+
335
395
  ## Two-Factor Authentication (2FA)
336
396
 
337
397
  ### How 2FA Works
@@ -342,6 +402,15 @@ Toast notification component.
342
402
  4. User enters the verification code
343
403
  5. If the code is valid, the user is authenticated and redirected to the main application
344
404
 
405
+ ### Setting Up 2FA
406
+
407
+ Users can set up 2FA through their profile page. The setup process includes:
408
+
409
+ 1. Generating a QR code that can be scanned with authenticator apps
410
+ 2. Providing a secret key for manual entry
411
+ 3. Verifying the setup with a 6-digit code
412
+ 4. Receiving recovery codes for backup access
413
+
345
414
  ### Enforcing 2FA
346
415
 
347
416
  You can enforce 2FA for all users by setting the `enforce2FA` prop to `true` on the GenericAuth component:
@@ -367,6 +436,14 @@ When 2FA enforcement is disabled (default):
367
436
  1. No alert is shown to users without 2FA
368
437
  2. Users can still enable 2FA voluntarily through their profile page
369
438
 
439
+ ### Remember Me Functionality
440
+
441
+ The login form includes a "Remember Me" checkbox that persists through the 2FA verification process. When enabled:
442
+
443
+ 1. The user's login session will be remembered for a longer period
444
+ 2. The "Remember Me" setting is passed from the login form to the 2FA verification process
445
+ 3. The setting is applied only after successful 2FA verification
446
+
370
447
  ### API Integration for 2FA
371
448
 
372
449
  #### Login Endpoint Response (when 2FA is required)
@@ -391,7 +468,8 @@ Request:
391
468
  {
392
469
  "user_id": "user_id",
393
470
  "verification_code": "123456",
394
- "previous_url": "/optional-redirect-url"
471
+ "previous_url": "/optional-redirect-url",
472
+ "remember": true
395
473
  }
396
474
  ```
397
475
 
@@ -410,6 +488,74 @@ Response:
410
488
  }
411
489
  ```
412
490
 
491
+ #### 2FA Setup Endpoint Response
492
+
493
+ ```json
494
+ {
495
+ "two_factor_enabled": true,
496
+ "two_factor_confirmed": false,
497
+ "qr_code": "<SVG data for QR code>",
498
+ "secret_key": "ABCDEFGHIJKLMNOP",
499
+ "recovery_codes": ["code1", "code2", "code3", ...]
500
+ }
501
+ ```
502
+
503
+ ### Report Builder Component
504
+
505
+ #### GenericReport
506
+
507
+ A powerful report building component that allows users to create custom reports by selecting tables, columns, and configuring joins, filters, and sorting.
508
+
509
+ ```jsx
510
+ <GenericReport
511
+ setting={{
512
+ tableUrl: '/ajax/reportBuilder/getTables',
513
+ columnUrl: '/ajax/reportBuilder/getTableColumns',
514
+ reportsUrl: '/ajax/reportBuilder/reports',
515
+ executeUrl: '/ajax/reportBuilder/execute',
516
+ suggestedJoinsUrl: '/ajax/reportBuilder/getSuggestedJoins',
517
+ }}
518
+ />
519
+ ```
520
+
521
+ The GenericReport component provides these features:
522
+
523
+ - **Table Selection**: Choose a main table for the report
524
+ - **Column Selection**: Select columns to include in the report
525
+ - **Table Joins**: Add related tables with automatic relationship detection
526
+ - **Filtering**: Apply filters to the report data
527
+ - **Sorting**: Sort the report results
528
+ - **Report Preview**: View a preview of the report data
529
+ - **Save/Load Reports**: Save reports for future use and load saved reports
530
+ - **Export**: Export reports in various formats
531
+
532
+ ### Sketch Field Component
533
+
534
+ #### SketchField
535
+
536
+ An interactive drawing component based on Fabric.js that provides various drawing tools.
537
+
538
+ ```jsx
539
+ <SketchField
540
+ width={800}
541
+ height={600}
542
+ tool={Tools.Pencil} // Available tools: Select, Pencil, Line, Arrow, Rectangle, Circle, Pan
543
+ lineColor="black"
544
+ lineWidth={3}
545
+ backgroundColor="transparent"
546
+ backgroundImage="/path/to/background-image.jpg" // Optional
547
+ onChange={handleChange}
548
+ />
549
+ ```
550
+
551
+ The SketchField component supports these features:
552
+
553
+ - **Multiple Drawing Tools**: Pencil, Line, Arrow, Rectangle, Circle, etc.
554
+ - **Background Images**: Add background images for annotation
555
+ - **Undo/Redo**: History management for drawing actions
556
+ - **Save/Load**: Save drawings as JSON and load them later
557
+ - **Customization**: Configure colors, line widths, and other properties
558
+
413
559
  ## Customization
414
560
 
415
561
  ### Theming
package/package.json CHANGED
@@ -82,7 +82,7 @@
82
82
  "react-dom": "^17.0.0 || ^18.0.0"
83
83
  },
84
84
  "name": "@visns-studio/visns-components",
85
- "version": "5.5.3",
85
+ "version": "5.5.5",
86
86
  "description": "Various packages to assist in the development of our Custom Applications.",
87
87
  "main": "src/index.js",
88
88
  "files": [
@@ -756,6 +756,17 @@ const DataGrid = forwardRef(
756
756
  // Extracting nested value using predefined keys from 's.relation'
757
757
  const nestedValue = getNestedValue(d, s.relation);
758
758
 
759
+ console.info(d, s.relation);
760
+
761
+ // Check if nestedValue is defined and is an array before mapping
762
+ if (!nestedValue || !Array.isArray(nestedValue)) {
763
+ console.warn(
764
+ 'Gallery data is undefined or not an array'
765
+ );
766
+ toast.warning('Gallery data is unavailable');
767
+ return;
768
+ }
769
+
759
770
  // Mapping nested values to create gallery array with source, captions, and dimensions
760
771
  const galleryArray = nestedValue.map((item) => ({
761
772
  src: item[s.key],
@@ -1337,7 +1348,13 @@ const DataGrid = forwardRef(
1337
1348
  const handleIconClick = (e) => {
1338
1349
  e.preventDefault();
1339
1350
  e.stopPropagation();
1340
- handleSettingClick(s, d);
1351
+
1352
+ // Ensure both s and d are defined before calling handleSettingClick
1353
+ if (s && d) {
1354
+ handleSettingClick(s, d);
1355
+ } else {
1356
+ console.warn('Missing settings or data for icon click');
1357
+ }
1341
1358
  };
1342
1359
 
1343
1360
  const tooltipContent =
@@ -882,7 +882,9 @@ function Form({
882
882
  closeModal();
883
883
  if (type === 'saveAnother') {
884
884
  const { saveAnother } =
885
- formSettings?.update || {};
885
+ formSettings?.update ||
886
+ formSettings?.create ||
887
+ {};
886
888
 
887
889
  if (
888
890
  saveAnother?.url &&
@@ -919,6 +921,16 @@ function Form({
919
921
  fetchData();
920
922
  modalOpen('create', 0);
921
923
  }
924
+ } else if (type === 'saveExit') {
925
+ const { saveExit } =
926
+ formSettings?.update ||
927
+ formSettings?.create ||
928
+ {};
929
+
930
+ if (saveExit?.redirectUrl) {
931
+ console.info('aa');
932
+ navigate(saveExit.redirectUrl);
933
+ }
922
934
  }
923
935
  } else {
924
936
  toast.success(
@@ -1505,11 +1517,25 @@ function Form({
1505
1517
  </button>
1506
1518
  ) : null}
1507
1519
 
1508
- <button className={styles.saveBtn} onClick={handleSubmit}>
1520
+ <button
1521
+ className={styles.saveBtn}
1522
+ onClick={handleSubmit}
1523
+ data-type="save"
1524
+ >
1509
1525
  {formSettings?.save?.button?.label
1510
1526
  ? formSettings.save.button.label
1511
1527
  : 'Save'}
1512
1528
  </button>
1529
+
1530
+ {formSettings.hasOwnProperty('saveExit') && (
1531
+ <button
1532
+ className={styles.btn}
1533
+ onClick={handleSubmit}
1534
+ data-type="saveExit"
1535
+ >
1536
+ {formSettings.saveExit?.label || 'Save & Exit'}
1537
+ </button>
1538
+ )}
1513
1539
  </div>
1514
1540
  </div>
1515
1541
  );
@@ -1621,65 +1647,102 @@ function Form({
1621
1647
  }}
1622
1648
  />
1623
1649
  </div>
1624
- {formType === 'create' &&
1625
- formSettings.create?.hasOwnProperty(
1626
- 'saveAnother'
1627
- ) ? (
1628
- <div
1629
- className={`${styles.formItem} ${styles.fwItem} ${styles.buttonContainer}`}
1630
- >
1631
- <button
1632
- className={styles.btn}
1633
- onClick={handleSubmit}
1634
- data-type="save"
1635
- >
1636
- Save & Close
1637
- </button>
1638
- <button
1639
- className={styles.btn}
1640
- onClick={handleSubmit}
1641
- data-type="saveAnother"
1642
- >
1643
- Save & Create Another
1644
- </button>
1645
- </div>
1646
- ) : formType === 'update' &&
1647
- formSettings.update?.hasOwnProperty(
1648
- 'saveAnother'
1649
- ) ? (
1650
- <div
1651
- className={`${styles.formItem} ${styles.fwItem} ${styles.buttonContainer}`}
1652
- >
1653
- <button
1654
- className={styles.btn}
1655
- onClick={handleSubmit}
1656
- data-type="save"
1657
- >
1658
- Save & Close
1659
- </button>
1660
- <button
1661
- className={styles.btn}
1662
- onClick={handleSubmit}
1663
- data-type="saveAnother"
1664
- >
1665
- {formSettings.update.saveAnother
1666
- .label ||
1667
- 'Save & Create Another'}
1668
- </button>
1669
- </div>
1670
- ) : (
1671
- <div
1672
- className={`${styles.formItem} ${styles.fwItem} ${styles.lastItem}`}
1673
- >
1674
- <button
1675
- className={styles.btn}
1676
- onClick={handleSubmit}
1677
- data-type="save"
1678
- >
1679
- Save
1680
- </button>
1681
- </div>
1682
- )}
1650
+ {(() => {
1651
+ if (formType === 'create') {
1652
+ return (
1653
+ <div
1654
+ className={`${styles.formItem} ${styles.fwItem} ${styles.buttonContainer}`}
1655
+ >
1656
+ <button
1657
+ className={styles.btn}
1658
+ onClick={handleSubmit}
1659
+ data-type="save"
1660
+ >
1661
+ Save & Close
1662
+ </button>
1663
+ {formSettings.create?.hasOwnProperty(
1664
+ 'saveAnother'
1665
+ ) && (
1666
+ <button
1667
+ className={styles.btn}
1668
+ onClick={handleSubmit}
1669
+ data-type="saveAnother"
1670
+ >
1671
+ Save & Create Another
1672
+ </button>
1673
+ )}
1674
+ {formSettings.create?.hasOwnProperty(
1675
+ 'saveExit'
1676
+ ) && (
1677
+ <button
1678
+ className={styles.btn}
1679
+ onClick={handleSubmit}
1680
+ data-type="saveExit"
1681
+ >
1682
+ {formSettings.create
1683
+ .saveExit?.label ||
1684
+ 'Save & Exit'}
1685
+ </button>
1686
+ )}
1687
+ </div>
1688
+ );
1689
+ } else if (formType === 'update') {
1690
+ return (
1691
+ <div
1692
+ className={`${styles.formItem} ${styles.fwItem} ${styles.buttonContainer}`}
1693
+ >
1694
+ <button
1695
+ className={styles.btn}
1696
+ onClick={handleSubmit}
1697
+ data-type="save"
1698
+ >
1699
+ Save & Close
1700
+ </button>
1701
+ {formSettings.update?.hasOwnProperty(
1702
+ 'saveAnother'
1703
+ ) && (
1704
+ <button
1705
+ className={styles.btn}
1706
+ onClick={handleSubmit}
1707
+ data-type="saveAnother"
1708
+ >
1709
+ {formSettings.update
1710
+ .saveAnother
1711
+ ?.label ||
1712
+ 'Save & Create Another'}
1713
+ </button>
1714
+ )}
1715
+ {formSettings.update?.hasOwnProperty(
1716
+ 'saveExit'
1717
+ ) && (
1718
+ <button
1719
+ className={styles.btn}
1720
+ onClick={handleSubmit}
1721
+ data-type="saveExit"
1722
+ >
1723
+ {formSettings.update
1724
+ .saveExit?.label ||
1725
+ 'Save & Exit'}
1726
+ </button>
1727
+ )}
1728
+ </div>
1729
+ );
1730
+ } else {
1731
+ return (
1732
+ <div
1733
+ className={`${styles.formItem} ${styles.fwItem} ${styles.lastItem}`}
1734
+ >
1735
+ <button
1736
+ className={styles.btn}
1737
+ onClick={handleSubmit}
1738
+ data-type="save"
1739
+ >
1740
+ Save
1741
+ </button>
1742
+ </div>
1743
+ );
1744
+ }
1745
+ })()}
1683
1746
  </form>
1684
1747
  </div>
1685
1748
  </div>