@visns-studio/visns-components 5.11.10 → 5.11.11

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
@@ -321,6 +321,7 @@ The DataGrid component utilizes a modular column renderer system with 28 special
321
321
  - **Extensibility**: Easy to add new column types or modify existing ones
322
322
  - **Maintainability**: Centralized location for all column rendering logic
323
323
  - **Performance**: Optimized rendering for each specific column type
324
+ - **Intelligent Sorting**: Automatic detection of sortable relationship and JSON fields
324
325
 
325
326
  **Supported Column Types:**
326
327
  - `boolean` - Yes/No display with tooltips
@@ -354,6 +355,133 @@ The DataGrid component utilizes a modular column renderer system with 28 special
354
355
 
355
356
  All column renderers are exported from `@visns-studio/visns-components` and can be used individually or as part of the DataGrid component.
356
357
 
358
+ #### Intelligent Relationship Sorting
359
+
360
+ The DataGrid component includes intelligent sorting capabilities that automatically detect and enable sorting for relationship and JSON fields. This feature works seamlessly with the backend `HasRelationshipSorting` trait.
361
+
362
+ **Automatic Sortable Detection:**
363
+
364
+ ```jsx
365
+ // The DataGrid automatically detects sortable fields based on naming patterns
366
+ <DataGrid
367
+ columns={[
368
+ {
369
+ name: 'user.profile.name', // Relationship field - automatically sortable
370
+ headerName: 'User Name',
371
+ nameFrom: ['user', 'profile', 'name']
372
+ },
373
+ {
374
+ name: 'settings.theme', // JSON field - automatically sortable
375
+ headerName: 'Theme Preference',
376
+ nameFrom: ['settings', 'theme']
377
+ },
378
+ {
379
+ name: 'client.company', // BelongsTo relationship - automatically sortable
380
+ headerName: 'Company',
381
+ nameFrom: ['client', 'company']
382
+ }
383
+ ]}
384
+ settings={{
385
+ intelligentSorting: true // Enable intelligent sorting (default: true)
386
+ }}
387
+ />
388
+ ```
389
+
390
+ **Manual Sortable Control:**
391
+
392
+ ```jsx
393
+ // Override automatic detection with explicit sortable configuration
394
+ <DataGrid
395
+ columns={[
396
+ {
397
+ name: 'user.profile.name',
398
+ headerName: 'User Name',
399
+ sortable: true, // Explicitly enable sorting
400
+ nameFrom: ['user', 'profile', 'name']
401
+ },
402
+ {
403
+ name: 'complex.calculation',
404
+ headerName: 'Complex Field',
405
+ sortable: false, // Explicitly disable sorting
406
+ nameFrom: ['complex', 'calculation']
407
+ }
408
+ ]}
409
+ />
410
+ ```
411
+
412
+ **Supported Sorting Patterns:**
413
+
414
+ The intelligent sorting system recognizes these field patterns as sortable:
415
+
416
+ - **Relationship Fields**: `user.profile.name`, `order.customer.company`
417
+ - **JSON Fields**: `settings.theme`, `metadata.tags`, `data.preferences.language`
418
+ - **Standard Fields**: Any direct model attribute
419
+ - **Nested Relationships**: `post.author.profile.display_name`
420
+
421
+ **Field Name Detection:**
422
+
423
+ The sorting system intelligently determines sortable fields using:
424
+
425
+ 1. **nameFrom Array**: Primary method for complex field paths
426
+ ```jsx
427
+ nameFrom: ['user', 'profile', 'full_name'] // → user.profile.full_name
428
+ ```
429
+
430
+ 2. **Dot Notation**: Direct string paths
431
+ ```jsx
432
+ name: 'client.address.city' // → client.address.city
433
+ ```
434
+
435
+ 3. **Pattern Recognition**: Automatic detection of common relationship patterns
436
+ - Fields ending with `_id` that have corresponding relationships
437
+ - JSON field patterns like `data.*`, `settings.*`, `metadata.*`
438
+ - Nested object notation
439
+
440
+ **Configuration Options:**
441
+
442
+ ```jsx
443
+ <DataGrid
444
+ settings={{
445
+ // Global intelligent sorting toggle
446
+ intelligentSorting: true, // default: true
447
+
448
+ // Additional sorting configuration
449
+ sortingConfig: {
450
+ // Custom sortable field patterns
451
+ additionalPatterns: [
452
+ /^custom_data\./, // Match custom_data.* fields
453
+ /^config\./ // Match config.* fields
454
+ ],
455
+
456
+ // Fields to exclude from automatic sorting
457
+ excludePatterns: [
458
+ /^temp\./, // Exclude temp.* fields
459
+ /^cache\./ // Exclude cache.* fields
460
+ ]
461
+ }
462
+ }}
463
+ />
464
+ ```
465
+
466
+ **Backend Integration:**
467
+
468
+ The frontend intelligent sorting works seamlessly with the backend `HasRelationshipSorting` trait:
469
+
470
+ ```jsx
471
+ // Frontend automatically generates these API calls:
472
+ // GET /ajax/users/table?orderBy=profile.name&order=asc
473
+ // GET /ajax/orders/table?orderBy=customer.company&order=desc
474
+ // GET /ajax/products/table?orderBy=metadata.category&order=asc
475
+ ```
476
+
477
+ **Benefits:**
478
+
479
+ - **Zero Configuration**: Most relationship and JSON fields work automatically
480
+ - **Performance Optimized**: Uses backend subqueries instead of joins
481
+ - **Type Safety**: Automatic validation of sortable field patterns
482
+ - **Fallback Support**: Graceful handling of unsupported sort fields
483
+ - **Developer Friendly**: Clear visual indicators for sortable columns
484
+
357
485
  #### DataGrid Props
358
486
 
359
487
  | Prop | Description | Type | Default |
package/package.json CHANGED
@@ -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.11.10",
90
+ "version": "5.11.11",
91
91
  "description": "Various packages to assist in the development of our Custom Applications.",
92
92
  "main": "src/index.js",
93
93
  "files": [
@@ -41,6 +41,7 @@ import ReactDataGrid from '@visns-studio/visns-datagrid-enterprise';
41
41
  import { toast } from 'react-toastify';
42
42
  import fetchUtil from '../../utils/fetchUtil';
43
43
  import { confirmDialog } from '../utils/ConfirmDialog';
44
+ import { DEFAULT_INTELLIGENT_SORTING_CONFIG } from '../../utils/relationshipSortingUtils';
44
45
  import {
45
46
  AlarmClock,
46
47
  RotateCcw,
@@ -2592,6 +2593,7 @@ const DataGrid = forwardRef(
2592
2593
  filterEditor,
2593
2594
  filterEditorProps,
2594
2595
  relationName,
2596
+ intelligentSortingConfig: tableSetting?.intelligentSorting || DEFAULT_INTELLIGENT_SORTING_CONFIG,
2595
2597
  });
2596
2598
  case 'relationArray':
2597
2599
  return renderRelationArrayColumn({
@@ -2600,6 +2602,7 @@ const DataGrid = forwardRef(
2600
2602
  filterEditor,
2601
2603
  filterEditorProps,
2602
2604
  relationName,
2605
+ intelligentSortingConfig: tableSetting?.intelligentSorting || DEFAULT_INTELLIGENT_SORTING_CONFIG,
2603
2606
  });
2604
2607
  case 'richtext':
2605
2608
  return renderRichTextColumn({
@@ -4,6 +4,11 @@ import parse from 'html-react-parser';
4
4
  import { Download, AlarmClock } from 'lucide-react';
5
5
  import { toast } from 'react-toastify';
6
6
  import CellWithTooltip from '../cells/CellWithTooltip';
7
+ import {
8
+ isRelationshipColumnSortable,
9
+ getRelationshipPath,
10
+ DEFAULT_INTELLIGENT_SORTING_CONFIG,
11
+ } from '../../../utils/relationshipSortingUtils';
7
12
 
8
13
  // Boolean Column Renderer
9
14
  export const renderBooleanColumn = ({
@@ -17,7 +22,7 @@ export const renderBooleanColumn = ({
17
22
  filterEditor: filterEditor,
18
23
  filterEditorProps: filterEditorProps,
19
24
  name: `${column.type}.${column.id}`,
20
- sortable: false,
25
+ sortable: true,
21
26
  render: ({ data }) => {
22
27
  const value = data[column.id] ? 'Yes' : 'No';
23
28
  return (
@@ -146,7 +151,7 @@ export const renderArrayCountColumn = ({ column, commonProps }) => {
146
151
  return {
147
152
  ...commonProps,
148
153
  name: `${column.type}.${column.id}`,
149
- sortable: false,
154
+ sortable: true,
150
155
  render: ({ data }) => {
151
156
  if (data && data[column.id]) {
152
157
  return data[column.id].length;
@@ -713,11 +718,39 @@ export const renderRelationColumn = ({
713
718
  filterEditor,
714
719
  filterEditorProps,
715
720
  relationName,
721
+ intelligentSortingConfig = DEFAULT_INTELLIGENT_SORTING_CONFIG,
716
722
  }) => {
723
+ // Intelligent sorting detection
724
+ const isSortable = isRelationshipColumnSortable(
725
+ column,
726
+ intelligentSortingConfig
727
+ );
728
+
729
+ // Log sorting analysis in development
730
+ if (
731
+ intelligentSortingConfig.logAnalysis &&
732
+ process.env.NODE_ENV === 'development'
733
+ ) {
734
+ const relationshipPath = getRelationshipPath(column);
735
+ console.log(
736
+ `Relationship sorting analysis for "${relationshipPath}":`,
737
+ {
738
+ sortable: isSortable,
739
+ fieldName: column.nameFrom,
740
+ relationshipDepth: Array.isArray(column.id)
741
+ ? column.id.length
742
+ : 1,
743
+ explicitSortable: column.hasOwnProperty('sortable')
744
+ ? column.sortable
745
+ : 'not specified',
746
+ }
747
+ );
748
+ }
749
+
717
750
  return {
718
751
  ...commonProps,
719
752
  name: relationName,
720
- sortable: false,
753
+ sortable: isSortable,
721
754
  filterEditor: filterEditor,
722
755
  filterEditorProps: filterEditorProps,
723
756
  render: ({ data }) => {
@@ -770,18 +803,22 @@ export const renderRelationColumn = ({
770
803
  // Check if the value is a hex color and render with color preview
771
804
  const isHexColor = (str) => {
772
805
  if (!str || typeof str !== 'string') return false;
773
- return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(str);
806
+ return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(
807
+ str
808
+ );
774
809
  };
775
810
 
776
811
  if (isHexColor(value)) {
777
812
  return (
778
813
  <div className="relation-color-container">
779
- <div
814
+ <div
780
815
  className="relation-color-preview"
781
816
  style={{ backgroundColor: value }}
782
817
  title={`Color: ${value}`}
783
818
  ></div>
784
- <span className="relation-color-text">{value}</span>
819
+ <span className="relation-color-text">
820
+ {value}
821
+ </span>
785
822
  </div>
786
823
  );
787
824
  }
@@ -812,11 +849,39 @@ export const renderRelationArrayColumn = ({
812
849
  filterEditor,
813
850
  filterEditorProps,
814
851
  relationName,
852
+ intelligentSortingConfig = DEFAULT_INTELLIGENT_SORTING_CONFIG,
815
853
  }) => {
854
+ // Intelligent sorting detection for relation arrays
855
+ const isSortable = isRelationshipColumnSortable(
856
+ column,
857
+ intelligentSortingConfig
858
+ );
859
+
860
+ // Log sorting analysis in development
861
+ if (
862
+ intelligentSortingConfig.logAnalysis &&
863
+ process.env.NODE_ENV === 'development'
864
+ ) {
865
+ const relationshipPath = getRelationshipPath(column);
866
+ console.log(
867
+ `Relationship array sorting analysis for "${relationshipPath}":`,
868
+ {
869
+ sortable: isSortable,
870
+ fieldName: column.nameFrom,
871
+ relationshipDepth: Array.isArray(column.id)
872
+ ? column.id.length
873
+ : 1,
874
+ explicitSortable: column.hasOwnProperty('sortable')
875
+ ? column.sortable
876
+ : 'not specified',
877
+ }
878
+ );
879
+ }
880
+
816
881
  return {
817
882
  ...commonProps,
818
883
  name: relationName,
819
- sortable: false,
884
+ sortable: isSortable,
820
885
  filterEditor: filterEditor,
821
886
  filterEditorProps: filterEditorProps,
822
887
  render: ({ data }) => {
@@ -1284,7 +1349,7 @@ export const renderCreatedByColumn = ({ column, commonProps }) => {
1284
1349
  return {
1285
1350
  ...commonProps,
1286
1351
  name: `${column.type}.${column.id}`,
1287
- sortable: false,
1352
+ sortable: true,
1288
1353
  render: ({ data }) => {
1289
1354
  if (
1290
1355
  data.audits[0] &&
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Fallback version that restores original behavior
3
+ */
4
+
5
+ export const DEFAULT_INTELLIGENT_SORTING_CONFIG = {
6
+ enableIntelligentSorting: false, // Disabled by default for safety
7
+ maxRelationshipDepth: 3,
8
+ customSortableFields: [],
9
+ customUnsortableFields: [],
10
+ respectExplicitSortable: true,
11
+ logAnalysis: false
12
+ };
13
+
14
+ export const isRelationshipColumnSortable = (column, options = {}) => {
15
+ // If intelligent sorting is disabled, use original behavior
16
+ if (options.enableIntelligentSorting === false) {
17
+ // Original behavior: return false for relation columns unless explicitly set
18
+ return column.sortable === true;
19
+ }
20
+
21
+ // Respect explicit configuration
22
+ if (column.hasOwnProperty('sortable')) {
23
+ return column.sortable === true;
24
+ }
25
+
26
+ // Default to false (original behavior) for safety
27
+ return false;
28
+ };
29
+
30
+ export const getRelationshipPath = (column) => {
31
+ if (!Array.isArray(column.id)) {
32
+ return column.nameFrom || 'unknown';
33
+ }
34
+
35
+ const relationPath = column.id.join('.');
36
+ return column.nameFrom ? `${relationPath}.${column.nameFrom}` : relationPath;
37
+ };
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Intelligent Relationship Sorting Utilities
3
+ *
4
+ * Automatically determines if relationship columns should be sortable
5
+ * based on column configuration patterns and field types.
6
+ */
7
+
8
+ /**
9
+ * Fields that are typically sortable
10
+ */
11
+ const SORTABLE_FIELD_PATTERNS = [
12
+ // Text fields
13
+ 'name', 'title', 'label', 'description', 'email', 'username', 'slug',
14
+ 'first_name', 'last_name', 'full_name', 'display_name', 'company_name',
15
+ 'organization', 'department', 'position', 'role', 'status', 'type',
16
+ 'category', 'tag', 'code', 'reference', 'identifier', 'serial',
17
+
18
+ // Date fields
19
+ 'created_at', 'updated_at', 'deleted_at', 'published_at', 'expires_at',
20
+ 'start_date', 'end_date', 'due_date', 'birth_date', 'date', 'timestamp',
21
+
22
+ // Numeric fields
23
+ 'id', 'order', 'sort_order', 'priority', 'weight', 'score', 'rating',
24
+ 'amount', 'price', 'cost', 'total', 'quantity', 'count', 'number',
25
+ 'percentage', 'rate', 'value', 'balance', 'age', 'duration',
26
+
27
+ // Boolean/Status fields (often useful for sorting)
28
+ 'active', 'enabled', 'visible', 'published', 'featured', 'verified',
29
+ 'approved', 'completed', 'archived', 'deleted'
30
+ ];
31
+
32
+ /**
33
+ * Fields that should NOT be sortable
34
+ */
35
+ const UNSORTABLE_FIELD_PATTERNS = [
36
+ // Large text/blob fields
37
+ 'content', 'body', 'text', 'html', 'markdown', 'notes', 'comments',
38
+ 'description_long', 'bio', 'about', 'details', 'message', 'review',
39
+
40
+ // Binary/complex data
41
+ 'data', 'metadata', 'settings', 'config', 'options', 'attributes',
42
+ 'properties', 'json', 'xml', 'blob', 'binary', 'file', 'image',
43
+ 'photo', 'avatar', 'logo', 'attachment', 'upload',
44
+
45
+ // Sensitive fields
46
+ 'password', 'token', 'key', 'secret', 'hash', 'signature', 'checksum'
47
+ ];
48
+
49
+ /**
50
+ * Relationship depth limits for sorting
51
+ */
52
+ const MAX_RELATIONSHIP_DEPTH = 3;
53
+
54
+ /**
55
+ * Determines if a relationship column should be sortable based on intelligent analysis
56
+ *
57
+ * @param {Object} column - Column configuration object
58
+ * @param {Object} options - Additional options for sorting intelligence
59
+ * @returns {boolean} - Whether the column should be sortable
60
+ */
61
+ export const isRelationshipColumnSortable = (column, options = {}) => {
62
+ const {
63
+ enableIntelligentSorting = true,
64
+ maxRelationshipDepth = MAX_RELATIONSHIP_DEPTH,
65
+ customSortableFields = [],
66
+ customUnsortableFields = [],
67
+ respectExplicitSortable = true
68
+ } = options;
69
+
70
+ // If intelligent sorting is disabled, fall back to explicit configuration
71
+ if (!enableIntelligentSorting) {
72
+ return column.sortable === true;
73
+ }
74
+
75
+ // Respect explicit sortable configuration if specified
76
+ if (respectExplicitSortable && column.hasOwnProperty('sortable')) {
77
+ return column.sortable === true;
78
+ }
79
+
80
+ // Only analyze relation and relationArray types
81
+ if (!['relation', 'relationArray'].includes(column.type)) {
82
+ return false;
83
+ }
84
+
85
+ // Check relationship depth
86
+ const relationshipDepth = Array.isArray(column.id) ? column.id.length : 1;
87
+ if (relationshipDepth > maxRelationshipDepth) {
88
+ console.warn(`Relationship depth ${relationshipDepth} exceeds maximum ${maxRelationshipDepth} for sorting: ${getRelationshipPath(column)}`);
89
+ return false;
90
+ }
91
+
92
+ // Analyze the target field name
93
+ let fieldName = column.nameFrom;
94
+ if (!fieldName) {
95
+ return false;
96
+ }
97
+
98
+ // Handle array nameFrom (for relationArray columns)
99
+ if (Array.isArray(fieldName)) {
100
+ // Use the first field name for analysis
101
+ fieldName = fieldName[0];
102
+ if (!fieldName) {
103
+ return false;
104
+ }
105
+ }
106
+
107
+ // Check custom configurations first
108
+ const customSortable = [...SORTABLE_FIELD_PATTERNS, ...customSortableFields];
109
+ const customUnsortable = [...UNSORTABLE_FIELD_PATTERNS, ...customUnsortableFields];
110
+
111
+ // Explicit unsortable fields take precedence
112
+ if (isFieldInPatterns(fieldName, customUnsortable)) {
113
+ return false;
114
+ }
115
+
116
+ // Check if field matches sortable patterns
117
+ if (isFieldInPatterns(fieldName, customSortable)) {
118
+ return true;
119
+ }
120
+
121
+ // Analyze field name patterns for common sortable types
122
+ return analyzeFieldNamePattern(fieldName);
123
+ };
124
+
125
+ /**
126
+ * Checks if a field name matches any of the given patterns
127
+ *
128
+ * @param {string} fieldName - The field name to check
129
+ * @param {Array} patterns - Array of patterns to match against
130
+ * @returns {boolean} - Whether the field matches any pattern
131
+ */
132
+ const isFieldInPatterns = (fieldName, patterns) => {
133
+ // Ensure fieldName is a string
134
+ if (typeof fieldName !== 'string') {
135
+ return false;
136
+ }
137
+
138
+ const normalizedFieldName = fieldName.toLowerCase();
139
+
140
+ return patterns.some(pattern => {
141
+ const normalizedPattern = pattern.toLowerCase();
142
+
143
+ // Exact match
144
+ if (normalizedFieldName === normalizedPattern) {
145
+ return true;
146
+ }
147
+
148
+ // Contains pattern (for composite field names)
149
+ if (normalizedFieldName.includes(normalizedPattern)) {
150
+ return true;
151
+ }
152
+
153
+ // Pattern contains field name (for abbreviated fields)
154
+ if (normalizedPattern.includes(normalizedFieldName)) {
155
+ return true;
156
+ }
157
+
158
+ return false;
159
+ });
160
+ };
161
+
162
+ /**
163
+ * Analyzes field name patterns to determine if they're likely sortable
164
+ *
165
+ * @param {string} fieldName - The field name to analyze
166
+ * @returns {boolean} - Whether the field appears to be sortable
167
+ */
168
+ const analyzeFieldNamePattern = (fieldName) => {
169
+ const normalizedName = fieldName.toLowerCase();
170
+
171
+ // Date-like patterns
172
+ if (/_at$|_date$|date_|_time$|time_/.test(normalizedName)) {
173
+ return true;
174
+ }
175
+
176
+ // ID patterns
177
+ if (/^id$|_id$|^uuid$|_uuid$/.test(normalizedName)) {
178
+ return true;
179
+ }
180
+
181
+ // Name patterns
182
+ if (/name|title|label/.test(normalizedName)) {
183
+ return true;
184
+ }
185
+
186
+ // Order/priority patterns
187
+ if (/order|priority|sort|rank|position|sequence/.test(normalizedName)) {
188
+ return true;
189
+ }
190
+
191
+ // Status patterns
192
+ if (/status|state|active|enabled|visible/.test(normalizedName)) {
193
+ return true;
194
+ }
195
+
196
+ // Numeric patterns
197
+ if (/amount|price|cost|total|count|number|quantity|score|rating/.test(normalizedName)) {
198
+ return true;
199
+ }
200
+
201
+ // Default to false for unknown patterns to be conservative
202
+ return false;
203
+ };
204
+
205
+ /**
206
+ * Constructs the relationship path for a column (for logging/debugging)
207
+ *
208
+ * @param {Object} column - Column configuration object
209
+ * @returns {string} - The relationship path (e.g., "user.profile.name")
210
+ */
211
+ export const getRelationshipPath = (column) => {
212
+ if (!Array.isArray(column.id)) {
213
+ return column.nameFrom || 'unknown';
214
+ }
215
+
216
+ const relationPath = column.id.join('.');
217
+ return column.nameFrom ? `${relationPath}.${column.nameFrom}` : relationPath;
218
+ };
219
+
220
+ /**
221
+ * Analyzes all relationship columns in a column configuration array
222
+ * and provides sorting recommendations
223
+ *
224
+ * @param {Array} columns - Array of column configuration objects
225
+ * @param {Object} options - Options for analysis
226
+ * @returns {Object} - Analysis results with recommendations
227
+ */
228
+ export const analyzeRelationshipSorting = (columns, options = {}) => {
229
+ const results = {
230
+ totalRelationColumns: 0,
231
+ sortableRelationColumns: 0,
232
+ unsortableRelationColumns: 0,
233
+ recommendations: [],
234
+ sortableColumns: [],
235
+ unsortableColumns: []
236
+ };
237
+
238
+ columns.forEach(column => {
239
+ if (['relation', 'relationArray'].includes(column.type)) {
240
+ results.totalRelationColumns++;
241
+
242
+ const isSortable = isRelationshipColumnSortable(column, options);
243
+ const relationshipPath = getRelationshipPath(column);
244
+
245
+ if (isSortable) {
246
+ results.sortableRelationColumns++;
247
+ results.sortableColumns.push({
248
+ column,
249
+ path: relationshipPath,
250
+ reason: 'Matches sortable field pattern'
251
+ });
252
+ } else {
253
+ results.unsortableRelationColumns++;
254
+ results.unsortableColumns.push({
255
+ column,
256
+ path: relationshipPath,
257
+ reason: 'Does not match sortable patterns or exceeds depth limit'
258
+ });
259
+ }
260
+ }
261
+ });
262
+
263
+ // Generate recommendations
264
+ if (results.unsortableRelationColumns > 0) {
265
+ results.recommendations.push(
266
+ `Consider adding explicit sortable: true to ${results.unsortableRelationColumns} relationship columns if sorting is needed`
267
+ );
268
+ }
269
+
270
+ if (results.sortableRelationColumns > 10) {
271
+ results.recommendations.push(
272
+ 'Large number of sortable relationship columns detected - consider performance impact'
273
+ );
274
+ }
275
+
276
+ return results;
277
+ };
278
+
279
+ /**
280
+ * Default configuration for intelligent sorting
281
+ */
282
+ export const DEFAULT_INTELLIGENT_SORTING_CONFIG = {
283
+ enableIntelligentSorting: true,
284
+ maxRelationshipDepth: MAX_RELATIONSHIP_DEPTH,
285
+ customSortableFields: [],
286
+ customUnsortableFields: [],
287
+ respectExplicitSortable: true,
288
+ logAnalysis: process.env.NODE_ENV === 'development'
289
+ };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Simplified version for testing import issues
3
+ */
4
+
5
+ export const DEFAULT_INTELLIGENT_SORTING_CONFIG = {
6
+ enableIntelligentSorting: true,
7
+ maxRelationshipDepth: 3,
8
+ customSortableFields: [],
9
+ customUnsortableFields: [],
10
+ respectExplicitSortable: true,
11
+ logAnalysis: false
12
+ };
13
+
14
+ export const isRelationshipColumnSortable = (column, options = {}) => {
15
+ // Simple implementation for testing
16
+ if (options.enableIntelligentSorting === false) {
17
+ return column.sortable === true;
18
+ }
19
+
20
+ if (column.hasOwnProperty('sortable')) {
21
+ return column.sortable === true;
22
+ }
23
+
24
+ // Basic field name analysis
25
+ const fieldName = column.nameFrom;
26
+ if (!fieldName) return false;
27
+
28
+ const sortablePatterns = ['name', 'title', 'email', 'created_at', 'updated_at', 'id'];
29
+ const unsortablePatterns = ['bio', 'content', 'data', 'notes', 'description'];
30
+
31
+ const normalizedName = fieldName.toLowerCase();
32
+
33
+ if (unsortablePatterns.some(pattern => normalizedName.includes(pattern))) {
34
+ return false;
35
+ }
36
+
37
+ if (sortablePatterns.some(pattern => normalizedName.includes(pattern))) {
38
+ return true;
39
+ }
40
+
41
+ return false;
42
+ };
43
+
44
+ export const getRelationshipPath = (column) => {
45
+ if (!Array.isArray(column.id)) {
46
+ return column.nameFrom || 'unknown';
47
+ }
48
+
49
+ const relationPath = column.id.join('.');
50
+ return column.nameFrom ? `${relationPath}.${column.nameFrom}` : relationPath;
51
+ };