@visns-studio/visns-components 5.11.10 → 5.11.12

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.
@@ -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
+ };