@visns-studio/visns-components 5.10.10 → 5.11.1

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
@@ -93,6 +93,91 @@ const App = () => {
93
93
  export default App;
94
94
  ```
95
95
 
96
+ ## New Modular Architecture (v5.10.11+)
97
+
98
+ ### GenericIndex Component Refactoring
99
+
100
+ Starting from version 5.10.11, the GenericIndex component has been refactored for better maintainability and reusability:
101
+
102
+ **Before**: Single monolithic component (2,281 lines)
103
+ **After**: Modular architecture with separated concerns
104
+
105
+ #### New Components
106
+
107
+ The refactoring introduced two new standalone components that can be used independently:
108
+
109
+ **GenericGrid** - Standalone grid component with full data management capabilities:
110
+
111
+ ```jsx
112
+ import { GenericGrid } from 'visns-components';
113
+
114
+ <GenericGrid
115
+ config={{
116
+ settings: {
117
+ fetch: {
118
+ url: '/api/data',
119
+ method: 'POST',
120
+ params: { filter: 'active' }
121
+ }
122
+ },
123
+ columns: [
124
+ { name: 'id', header: 'ID', type: 'number' },
125
+ { name: 'name', header: 'Name', type: 'text' },
126
+ { name: 'date', header: 'Date', type: 'date' }
127
+ ]
128
+ }}
129
+ userProfile={userProfile}
130
+ onDataChange={(data) => console.log('Data updated:', data)}
131
+ onRowClick={(row) => console.log('Row clicked:', row)}
132
+ />
133
+ ```
134
+
135
+ **GenericReportForm** - Standalone export form component:
136
+
137
+ ```jsx
138
+ import { GenericReportForm } from 'visns-components';
139
+
140
+ <GenericReportForm
141
+ config={{
142
+ form: {
143
+ title: 'Export Data',
144
+ description: 'Select your export criteria below',
145
+ filters: [
146
+ { id: 'startDate', type: 'date', label: 'Start Date', required: true },
147
+ { id: 'endDate', type: 'date', label: 'End Date', required: true },
148
+ { id: 'format', type: 'dropdown', label: 'Format', options: [
149
+ { value: 'csv', label: 'CSV' },
150
+ { value: 'xlsx', label: 'Excel' }
151
+ ]}
152
+ ],
153
+ url: '/api/export',
154
+ method: 'POST',
155
+ filename: 'export.csv'
156
+ }
157
+ }}
158
+ userProfile={userProfile}
159
+ onExport={(formData) => customExportHandler(formData)}
160
+ onFormChange={(formData) => console.log('Form changed:', formData)}
161
+ />
162
+ ```
163
+
164
+ #### Benefits of the Refactoring
165
+
166
+ - **Reduced complexity**: GenericIndex reduced from 2,281 to 1,031 lines (55% reduction)
167
+ - **Enhanced reusability**: Grid and form components can be used independently
168
+ - **Better maintainability**: Focused components are easier to debug and modify
169
+ - **Improved testing**: Components can be unit tested separately
170
+ - **Backward compatibility**: All existing GenericIndex usage remains unchanged
171
+
172
+ #### Shared Utilities
173
+
174
+ The refactoring also introduced shared utility functions:
175
+
176
+ - **`shared/formatters.js`**: Cell content formatting and styling utilities
177
+ - **`shared/gridUtils.js`**: Data operations (sorting, filtering, transformations)
178
+
179
+ These utilities are available for use in custom components and provide consistent functionality across the library.
180
+
96
181
  ## Component Documentation
97
182
 
98
183
  ### Authentication Components
package/package.json CHANGED
@@ -24,17 +24,17 @@
24
24
  "dayjs": "^1.11.13",
25
25
  "fabric": "^6.7.0",
26
26
  "file-saver": "^2.0.5",
27
- "framer-motion": "^12.17.0",
27
+ "framer-motion": "^12.18.1",
28
28
  "html-react-parser": "^5.2.5",
29
29
  "lodash": "^4.17.21",
30
30
  "lodash.debounce": "^4.0.8",
31
31
  "moment": "^2.30.1",
32
- "motion": "^12.17.0",
32
+ "motion": "^12.18.1",
33
33
  "numeral": "^2.0.6",
34
34
  "pluralize": "^8.0.0",
35
35
  "qrcode.react": "^4.2.0",
36
36
  "quill-image-uploader": "^1.3.0",
37
- "react-big-calendar": "^1.19.2",
37
+ "react-big-calendar": "^1.19.3",
38
38
  "react-color": "^2.19.3",
39
39
  "react-copy-to-clipboard": "^5.1.0",
40
40
  "react-datepicker": "^8.4.0",
@@ -53,7 +53,7 @@
53
53
  "react-to-print": "^3.1.0",
54
54
  "react-toastify": "^11.0.5",
55
55
  "react-toggle": "^4.1.3",
56
- "react-tooltip": "^5.29.0",
56
+ "react-tooltip": "^5.29.1",
57
57
  "react-window": "^1.8.11",
58
58
  "reactjs-popup": "^2.0.6",
59
59
  "style-loader": "^4.0.0",
@@ -65,7 +65,7 @@
65
65
  "validator": "^13.15.15",
66
66
  "vite": "^6.3.5",
67
67
  "yarn": "^1.22.22",
68
- "yet-another-react-lightbox": "^3.23.2"
68
+ "yet-another-react-lightbox": "^3.23.3"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@babel/core": "^7.27.4",
@@ -87,7 +87,7 @@
87
87
  "react-dom": "^17.0.0 || ^18.0.0"
88
88
  },
89
89
  "name": "@visns-studio/visns-components",
90
- "version": "5.10.10",
90
+ "version": "5.11.1",
91
91
  "description": "Various packages to assist in the development of our Custom Applications.",
92
92
  "main": "src/index.js",
93
93
  "files": [
@@ -763,9 +763,89 @@ const DataGrid = ({
763
763
  data[column.id] &&
764
764
  data[column.id][column.jsonData]
765
765
  ) {
766
- data = data[column.id][column.jsonData];
767
-
768
- return <span>{data}</span>;
766
+ const jsonValue = data[column.id][column.jsonData];
767
+
768
+ if (!jsonValue) return null;
769
+
770
+ try {
771
+ const jsonData = typeof jsonValue === 'string' ? JSON.parse(jsonValue) : jsonValue;
772
+
773
+ // Check if this looks like a question/answer format
774
+ const isQuestionAnswerFormat = Object.values(jsonData).every(item =>
775
+ item && typeof item === 'object' &&
776
+ (item.hasOwnProperty('question') || item.hasOwnProperty('answer'))
777
+ );
778
+
779
+ if (isQuestionAnswerFormat) {
780
+ return (
781
+ <div style={{ fontSize: '0.8rem' }}>
782
+ {Object.entries(jsonData).map(([key, item]) => {
783
+ if (!item || typeof item !== 'object') return null;
784
+
785
+ const question = item.question || key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
786
+ const answer = item.answer !== undefined ?
787
+ (typeof item.answer === 'boolean' ? (item.answer ? 'Yes' : 'No') : String(item.answer)) :
788
+ 'N/A';
789
+
790
+ return (
791
+ <div key={key} style={{ marginBottom: '0.3rem' }}>
792
+ <div style={{ fontWeight: '600', color: 'var(--primary-color)', marginBottom: '0.1rem', fontSize: '0.75rem' }}>
793
+ {question.length > 60 ? question.substring(0, 60) + '...' : question}
794
+ </div>
795
+ <div style={{ marginLeft: '0.5rem', color: 'var(--paragraph-color)', fontSize: '0.75rem' }}>
796
+ {answer}
797
+ </div>
798
+ </div>
799
+ );
800
+ })}
801
+ </div>
802
+ );
803
+ }
804
+
805
+ // Check if this is a simple key-value object
806
+ const isSimpleKeyValue = Object.values(jsonData).every(val =>
807
+ typeof val !== 'object' || val === null
808
+ );
809
+
810
+ if (isSimpleKeyValue) {
811
+ return (
812
+ <div style={{ fontSize: '0.8rem' }}>
813
+ {Object.entries(jsonData).slice(0, 3).map(([key, val]) => (
814
+ <div key={key} style={{ marginBottom: '0.2rem', display: 'flex', gap: '0.3rem' }}>
815
+ <strong style={{ color: 'var(--primary-color)', fontSize: '0.7rem' }}>
816
+ {key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}:
817
+ </strong>
818
+ <span style={{ color: 'var(--paragraph-color)', fontSize: '0.7rem' }}>
819
+ {val !== null && val !== undefined ?
820
+ (typeof val === 'boolean' ? (val ? 'Yes' : 'No') : String(val).substring(0, 20)) :
821
+ 'N/A'}
822
+ </span>
823
+ </div>
824
+ ))}
825
+ {Object.keys(jsonData).length > 3 && (
826
+ <div style={{ fontSize: '0.65rem', color: 'var(--secondary-color)', fontStyle: 'italic' }}>
827
+ +{Object.keys(jsonData).length - 3} more...
828
+ </div>
829
+ )}
830
+ </div>
831
+ );
832
+ }
833
+
834
+ // For complex nested objects, show a summary
835
+ const keyCount = Object.keys(jsonData).length;
836
+ return (
837
+ <div style={{ fontSize: '0.75rem', color: 'var(--paragraph-color)', fontStyle: 'italic' }}>
838
+ JSON ({keyCount} {keyCount === 1 ? 'property' : 'properties'})
839
+ </div>
840
+ );
841
+
842
+ } catch (error) {
843
+ // If parsing fails, show raw value truncated
844
+ const displayValue = typeof jsonValue === 'string' && jsonValue.length > 30
845
+ ? jsonValue.substring(0, 30) + '...'
846
+ : jsonValue;
847
+ return <span style={{ fontSize: '0.75rem', fontFamily: 'monospace', color: 'var(--paragraph-color)' }}>{displayValue}</span>;
848
+ }
769
849
  } else {
770
850
  return null;
771
851
  }
@@ -376,13 +376,108 @@ export const renderJsonColumn = ({ column, commonProps }) => {
376
376
  data[column.id][column.jsonData]
377
377
  ) {
378
378
  const jsonValue = data[column.id][column.jsonData];
379
+
380
+ if (!jsonValue) return null;
381
+
382
+ let displayContent;
383
+ let tooltipValue = jsonValue;
384
+
385
+ try {
386
+ const jsonData = typeof jsonValue === 'string' ? JSON.parse(jsonValue) : jsonValue;
387
+
388
+ // Check if this looks like a question/answer format
389
+ const isQuestionAnswerFormat = Object.values(jsonData).every(item =>
390
+ item && typeof item === 'object' &&
391
+ (item.hasOwnProperty('question') || item.hasOwnProperty('answer'))
392
+ );
393
+
394
+ if (isQuestionAnswerFormat) {
395
+ const entries = Object.entries(jsonData);
396
+ if (entries.length === 1) {
397
+ const [key, item] = entries[0];
398
+ const question = item.question || key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
399
+ const answer = item.answer !== undefined ?
400
+ (typeof item.answer === 'boolean' ? (item.answer ? 'Yes' : 'No') : String(item.answer)) :
401
+ 'N/A';
402
+
403
+ displayContent = (
404
+ <div style={{ fontSize: '0.8rem' }}>
405
+ <div style={{ fontWeight: '600', color: 'var(--primary-color)', marginBottom: '0.1rem', fontSize: '0.75rem' }}>
406
+ {question.length > 50 ? question.substring(0, 50) + '...' : question}
407
+ </div>
408
+ <div style={{ color: 'var(--paragraph-color)', fontSize: '0.75rem' }}>
409
+ {answer}
410
+ </div>
411
+ </div>
412
+ );
413
+ } else {
414
+ displayContent = (
415
+ <div style={{ fontSize: '0.75rem', color: 'var(--paragraph-color)' }}>
416
+ Q&A ({entries.length} items)
417
+ </div>
418
+ );
419
+ }
420
+ tooltipValue = JSON.stringify(jsonData, null, 2);
421
+ }
422
+ // Check if this is a simple key-value object
423
+ else if (Object.values(jsonData).every(val => typeof val !== 'object' || val === null)) {
424
+ const entries = Object.entries(jsonData);
425
+ if (entries.length <= 2) {
426
+ displayContent = (
427
+ <div style={{ fontSize: '0.8rem' }}>
428
+ {entries.map(([key, val]) => (
429
+ <div key={key} style={{ marginBottom: '0.1rem', display: 'flex', gap: '0.3rem' }}>
430
+ <strong style={{ color: 'var(--primary-color)', fontSize: '0.7rem' }}>
431
+ {key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}:
432
+ </strong>
433
+ <span style={{ color: 'var(--paragraph-color)', fontSize: '0.7rem' }}>
434
+ {val !== null && val !== undefined ?
435
+ (typeof val === 'boolean' ? (val ? 'Yes' : 'No') : String(val).substring(0, 15)) :
436
+ 'N/A'}
437
+ </span>
438
+ </div>
439
+ ))}
440
+ </div>
441
+ );
442
+ } else {
443
+ displayContent = (
444
+ <div style={{ fontSize: '0.75rem', color: 'var(--paragraph-color)' }}>
445
+ Data ({entries.length} properties)
446
+ </div>
447
+ );
448
+ }
449
+ tooltipValue = JSON.stringify(jsonData, null, 2);
450
+ }
451
+ // For complex nested objects
452
+ else {
453
+ const keyCount = Object.keys(jsonData).length;
454
+ displayContent = (
455
+ <div style={{ fontSize: '0.75rem', color: 'var(--paragraph-color)', fontStyle: 'italic' }}>
456
+ JSON ({keyCount} {keyCount === 1 ? 'property' : 'properties'})
457
+ </div>
458
+ );
459
+ tooltipValue = JSON.stringify(jsonData, null, 2);
460
+ }
461
+
462
+ } catch (error) {
463
+ // If parsing fails, show truncated raw value
464
+ const displayValue = typeof jsonValue === 'string' && jsonValue.length > 30
465
+ ? jsonValue.substring(0, 30) + '...'
466
+ : jsonValue;
467
+ displayContent = (
468
+ <span style={{ fontSize: '0.75rem', fontFamily: 'monospace', color: 'var(--paragraph-color)' }}>
469
+ {displayValue}
470
+ </span>
471
+ );
472
+ tooltipValue = jsonValue;
473
+ }
379
474
 
380
475
  return (
381
476
  <CellWithTooltip
382
- value={jsonValue}
477
+ value={tooltipValue}
383
478
  columnType="json"
384
479
  >
385
- <span>{jsonValue}</span>
480
+ {displayContent}
386
481
  </CellWithTooltip>
387
482
  );
388
483
  } else {