@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.
- package/README.md +128 -0
- package/package.json +4 -4
- package/src/components/cms/DataGrid.jsx +22 -4
- package/src/components/crm/Autocomplete.jsx +3 -3
- package/src/components/crm/DataGrid.jsx +26 -4
- package/src/components/crm/Field.jsx +66 -21
- package/src/components/crm/columns/ColumnRenderers.jsx +73 -8
- package/src/components/crm/generic/GenericGrid.jsx +52 -60
- package/src/components/crm/generic/styles/GenericIndex.module.scss +52 -0
- package/src/utils/columnsMetadataUtils.js +277 -0
- package/src/utils/relationshipSortingFallback.js +37 -0
- package/src/utils/relationshipSortingUtils.js +289 -0
- package/src/utils/relationshipSortingUtilsSimple.js +51 -0
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
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
"dayjs": "^1.11.13",
|
|
25
25
|
"fabric": "^6.7.0",
|
|
26
26
|
"file-saver": "^2.0.5",
|
|
27
|
-
"framer-motion": "^12.
|
|
27
|
+
"framer-motion": "^12.19.1",
|
|
28
28
|
"html-react-parser": "^5.2.5",
|
|
29
29
|
"lodash": "^4.17.21",
|
|
30
30
|
"lodash.debounce": "^4.0.8",
|
|
31
31
|
"lucide-react": "^0.518.0",
|
|
32
32
|
"moment": "^2.30.1",
|
|
33
|
-
"motion": "^12.
|
|
33
|
+
"motion": "^12.19.1",
|
|
34
34
|
"numeral": "^2.0.6",
|
|
35
35
|
"pluralize": "^8.0.0",
|
|
36
36
|
"qrcode.react": "^4.2.0",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"reactjs-popup": "^2.0.6",
|
|
59
59
|
"style-loader": "^4.0.0",
|
|
60
60
|
"swapy": "^1.0.5",
|
|
61
|
-
"sweetalert2": "^11.22.
|
|
61
|
+
"sweetalert2": "^11.22.1",
|
|
62
62
|
"tesseract.js": "^6.0.1",
|
|
63
63
|
"truncate": "^3.0.0",
|
|
64
64
|
"uuid": "^11.1.0",
|
|
@@ -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.
|
|
90
|
+
"version": "5.11.12",
|
|
91
91
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
92
92
|
"main": "src/index.js",
|
|
93
93
|
"files": [
|
|
@@ -36,6 +36,7 @@ import 'react-toggle/style.css';
|
|
|
36
36
|
|
|
37
37
|
import CustomFetch from '../crm/Fetch';
|
|
38
38
|
import Form from './Form';
|
|
39
|
+
import { extractColumnsMetadata, isColumnSortable } from '../../utils/columnsMetadataUtils';
|
|
39
40
|
|
|
40
41
|
const loadData = async (
|
|
41
42
|
{ skip, limit, sortInfo, currentData, filterValue, groupBy },
|
|
@@ -83,10 +84,14 @@ const loadData = async (
|
|
|
83
84
|
try {
|
|
84
85
|
const response = await fetchUtil.post(url, params);
|
|
85
86
|
const { data, total } = response.data;
|
|
86
|
-
|
|
87
|
+
|
|
88
|
+
// Extract columns metadata if available
|
|
89
|
+
const metadata = extractColumnsMetadata(response);
|
|
90
|
+
|
|
91
|
+
return { data, count: total, metadata };
|
|
87
92
|
} catch (error) {
|
|
88
93
|
console.error('Error fetching data:', error);
|
|
89
|
-
return { data: [], count: 0 };
|
|
94
|
+
return { data: [], count: 0, metadata: {} };
|
|
90
95
|
}
|
|
91
96
|
};
|
|
92
97
|
|
|
@@ -146,9 +151,16 @@ const DataGrid = ({
|
|
|
146
151
|
|
|
147
152
|
sqlResult.then((res) => {
|
|
148
153
|
setDataCount(res.count);
|
|
154
|
+
// Update metadata when data is loaded
|
|
155
|
+
if (res.metadata) {
|
|
156
|
+
setColumnsMetadata(res.metadata);
|
|
157
|
+
}
|
|
149
158
|
});
|
|
150
159
|
|
|
151
|
-
return sqlResult
|
|
160
|
+
return sqlResult.then(res => ({
|
|
161
|
+
data: res.data,
|
|
162
|
+
count: res.count
|
|
163
|
+
}));
|
|
152
164
|
},
|
|
153
165
|
[ajaxSetting, dataReload, dSearch]
|
|
154
166
|
);
|
|
@@ -157,6 +169,7 @@ const DataGrid = ({
|
|
|
157
169
|
const [gridColumns, setGridColumns] = useState([]);
|
|
158
170
|
const [limit, setLimit] = useState(ajaxSetting.take || 10);
|
|
159
171
|
const [search, setSearch] = useState('');
|
|
172
|
+
const [columnsMetadata, setColumnsMetadata] = useState({});
|
|
160
173
|
|
|
161
174
|
/** Table Functions */
|
|
162
175
|
const handleChangeSearch = (e) => {
|
|
@@ -455,9 +468,14 @@ const DataGrid = ({
|
|
|
455
468
|
let style;
|
|
456
469
|
let value;
|
|
457
470
|
|
|
471
|
+
// Check if column is sortable based on metadata
|
|
472
|
+
const columnKey = column.id;
|
|
473
|
+
const sortable = isColumnSortable(columnKey, columnsMetadata);
|
|
474
|
+
|
|
458
475
|
const commonProps = {
|
|
459
476
|
header: column.label,
|
|
460
477
|
defaultFlex: 1,
|
|
478
|
+
sortable: sortable,
|
|
461
479
|
link: column.link ? column.link : {},
|
|
462
480
|
minWidth:
|
|
463
481
|
column.minWidth && column.minWidth > 0
|
|
@@ -1384,7 +1402,7 @@ const DataGrid = ({
|
|
|
1384
1402
|
});
|
|
1385
1403
|
|
|
1386
1404
|
setFilterValue(newFilterValue);
|
|
1387
|
-
}, [columns, filterDataSource]);
|
|
1405
|
+
}, [columns, filterDataSource, columnsMetadata]);
|
|
1388
1406
|
|
|
1389
1407
|
useEffect(() => {
|
|
1390
1408
|
if (setFilterData) {
|
|
@@ -178,11 +178,11 @@ const VisnsAutocomplete = memo((props) => {
|
|
|
178
178
|
|
|
179
179
|
// Effect to handle overwrite toggle
|
|
180
180
|
useEffect(() => {
|
|
181
|
-
if (!overwrite && search) {
|
|
181
|
+
if (!overwrite && search && search.length > 1 && isFocused) {
|
|
182
182
|
setIsLoading(true);
|
|
183
183
|
performSearch(1, search);
|
|
184
184
|
}
|
|
185
|
-
}, [overwrite, search, performSearch]);
|
|
185
|
+
}, [overwrite, search, performSearch, isFocused]);
|
|
186
186
|
|
|
187
187
|
// Effect to sync with inputValue prop
|
|
188
188
|
useEffect(() => {
|
|
@@ -274,7 +274,7 @@ const VisnsAutocomplete = memo((props) => {
|
|
|
274
274
|
)}
|
|
275
275
|
</div>
|
|
276
276
|
|
|
277
|
-
{isFocused && (search.length > 1 || isLoading) && (
|
|
277
|
+
{isFocused && !overwrite && (search.length > 1 || isLoading) && (
|
|
278
278
|
<ul
|
|
279
279
|
ref={resultsRef}
|
|
280
280
|
className={styles['AutocompletePlace-results']}
|
|
@@ -41,6 +41,8 @@ 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';
|
|
45
|
+
import { extractColumnsMetadata, isColumnSortable } from '../../utils/columnsMetadataUtils';
|
|
44
46
|
import {
|
|
45
47
|
AlarmClock,
|
|
46
48
|
RotateCcw,
|
|
@@ -204,10 +206,14 @@ const loadData = async (
|
|
|
204
206
|
try {
|
|
205
207
|
const response = await fetchUtil.post(url, params);
|
|
206
208
|
const { data, total } = response.data;
|
|
207
|
-
|
|
209
|
+
|
|
210
|
+
// Extract columns metadata if available
|
|
211
|
+
const metadata = extractColumnsMetadata(response);
|
|
212
|
+
|
|
213
|
+
return { data, count: total, metadata };
|
|
208
214
|
} catch (error) {
|
|
209
215
|
console.error('Error fetching data:', error);
|
|
210
|
-
return { data: [], count: 0 };
|
|
216
|
+
return { data: [], count: 0, metadata: {} };
|
|
211
217
|
}
|
|
212
218
|
};
|
|
213
219
|
|
|
@@ -376,6 +382,11 @@ const DataGrid = forwardRef(
|
|
|
376
382
|
}
|
|
377
383
|
|
|
378
384
|
sqlResult.then((res) => {
|
|
385
|
+
// Update metadata when data is loaded
|
|
386
|
+
if (res.metadata) {
|
|
387
|
+
setColumnsMetadata(res.metadata);
|
|
388
|
+
}
|
|
389
|
+
|
|
379
390
|
// Sort data by grouping field if grouping is enabled
|
|
380
391
|
if (ajaxSetting?.groupBy?.length > 0) {
|
|
381
392
|
const groupField = ajaxSetting.groupBy[0]; // groupBy is array of strings
|
|
@@ -433,13 +444,17 @@ const DataGrid = forwardRef(
|
|
|
433
444
|
}, 10);
|
|
434
445
|
});
|
|
435
446
|
|
|
436
|
-
return sqlResult
|
|
447
|
+
return sqlResult.then(res => ({
|
|
448
|
+
data: res.data,
|
|
449
|
+
count: res.count
|
|
450
|
+
}));
|
|
437
451
|
},
|
|
438
452
|
[ajaxSetting, dSearch, collapsedGroups]
|
|
439
453
|
);
|
|
440
454
|
const [filterDataSource, setFilterDataSource] = useState([]);
|
|
441
455
|
const [filterValue, setFilterValue] = useState([]);
|
|
442
456
|
const [gridColumns, setGridColumns] = useState([]);
|
|
457
|
+
const [columnsMetadata, setColumnsMetadata] = useState({});
|
|
443
458
|
const gridRef = useRef(null);
|
|
444
459
|
const [limit, setLimit] = useState(0);
|
|
445
460
|
const [search, setSearch] = useState('');
|
|
@@ -2353,9 +2368,14 @@ const DataGrid = forwardRef(
|
|
|
2353
2368
|
let columnStyle;
|
|
2354
2369
|
let value;
|
|
2355
2370
|
|
|
2371
|
+
// Check if column is sortable based on metadata
|
|
2372
|
+
const columnKey = column.id;
|
|
2373
|
+
const sortable = isColumnSortable(columnKey, columnsMetadata);
|
|
2374
|
+
|
|
2356
2375
|
const commonProps = {
|
|
2357
2376
|
header: column.label,
|
|
2358
2377
|
defaultFlex: 1,
|
|
2378
|
+
sortable: sortable,
|
|
2359
2379
|
link: column.link ?? {},
|
|
2360
2380
|
minWidth: column.minWidth ?? undefined,
|
|
2361
2381
|
maxWidth: column.maxWidth ?? undefined,
|
|
@@ -2592,6 +2612,7 @@ const DataGrid = forwardRef(
|
|
|
2592
2612
|
filterEditor,
|
|
2593
2613
|
filterEditorProps,
|
|
2594
2614
|
relationName,
|
|
2615
|
+
intelligentSortingConfig: tableSetting?.intelligentSorting || DEFAULT_INTELLIGENT_SORTING_CONFIG,
|
|
2595
2616
|
});
|
|
2596
2617
|
case 'relationArray':
|
|
2597
2618
|
return renderRelationArrayColumn({
|
|
@@ -2600,6 +2621,7 @@ const DataGrid = forwardRef(
|
|
|
2600
2621
|
filterEditor,
|
|
2601
2622
|
filterEditorProps,
|
|
2602
2623
|
relationName,
|
|
2624
|
+
intelligentSortingConfig: tableSetting?.intelligentSorting || DEFAULT_INTELLIGENT_SORTING_CONFIG,
|
|
2603
2625
|
});
|
|
2604
2626
|
case 'richtext':
|
|
2605
2627
|
return renderRichTextColumn({
|
|
@@ -2833,7 +2855,7 @@ const DataGrid = forwardRef(
|
|
|
2833
2855
|
.catch((error) => {
|
|
2834
2856
|
console.error('Error in fetching dropdown data: ', error);
|
|
2835
2857
|
});
|
|
2836
|
-
}, [columns, filterDataSource]);
|
|
2858
|
+
}, [columns, filterDataSource, columnsMetadata]);
|
|
2837
2859
|
|
|
2838
2860
|
useEffect(() => {
|
|
2839
2861
|
if (setFilterData) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import '../styles/global.css';
|
|
2
2
|
|
|
3
|
-
import React, { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import React, { useEffect, useRef, useState, useMemo } from 'react';
|
|
4
4
|
import DatePicker from 'react-datepicker';
|
|
5
5
|
import _ from 'lodash';
|
|
6
6
|
import Toggle from 'react-toggle';
|
|
@@ -189,22 +189,6 @@ function Field({
|
|
|
189
189
|
const fetchOptions = () => {
|
|
190
190
|
let filter = {};
|
|
191
191
|
|
|
192
|
-
if (settings.where?.length > 0) {
|
|
193
|
-
const processedWhere = settings.where.map((whereItem) => {
|
|
194
|
-
if (whereItem.getKey && formData[whereItem.getKey]) {
|
|
195
|
-
return {
|
|
196
|
-
...whereItem,
|
|
197
|
-
value: formData[whereItem.getKey],
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
return whereItem;
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
filter = {
|
|
204
|
-
where: processedWhere,
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
192
|
if (settings.fields?.length > 0) {
|
|
209
193
|
filter.fields = settings.fields;
|
|
210
194
|
}
|
|
@@ -261,7 +245,10 @@ function Field({
|
|
|
261
245
|
case 'dropdown-ajax':
|
|
262
246
|
case 'multi-dropdown-ajax':
|
|
263
247
|
case 'radio-ajax':
|
|
264
|
-
|
|
248
|
+
// Only fetch if there are no dependent fields (no 'where' conditions)
|
|
249
|
+
if (!settings.where || settings.where.length === 0) {
|
|
250
|
+
fetchOptions();
|
|
251
|
+
}
|
|
265
252
|
break;
|
|
266
253
|
default:
|
|
267
254
|
break;
|
|
@@ -285,7 +272,66 @@ function Field({
|
|
|
285
272
|
);
|
|
286
273
|
setCanvasUrl(canvasType.url);
|
|
287
274
|
}
|
|
288
|
-
}, [settings, counter
|
|
275
|
+
}, [settings, counter]);
|
|
276
|
+
|
|
277
|
+
// Get dependency field keys once
|
|
278
|
+
const dependencyKeys = useMemo(() => {
|
|
279
|
+
return settings.where?.map(whereItem => whereItem.getKey).filter(Boolean) || [];
|
|
280
|
+
}, [settings.where]);
|
|
281
|
+
|
|
282
|
+
// Separate useEffect for handling dependent form data changes for dropdown refetch
|
|
283
|
+
useEffect(() => {
|
|
284
|
+
if (['dropdown-ajax', 'multi-dropdown-ajax', 'radio-ajax'].includes(settings.type) && settings.where?.length > 0) {
|
|
285
|
+
let filter = {};
|
|
286
|
+
|
|
287
|
+
const processedWhere = settings.where.map((whereItem) => {
|
|
288
|
+
if (whereItem.getKey && formData[whereItem.getKey]) {
|
|
289
|
+
return {
|
|
290
|
+
...whereItem,
|
|
291
|
+
value: formData[whereItem.getKey],
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
return whereItem;
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
filter = { where: processedWhere };
|
|
298
|
+
|
|
299
|
+
if (settings.fields?.length > 0) {
|
|
300
|
+
filter.fields = settings.fields;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (settings.order && settings.order.sortBy && settings.order.sort) {
|
|
304
|
+
filter.sortBy = settings.order.sortBy;
|
|
305
|
+
filter.sort = settings.order.sort;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (settings?.customParams) {
|
|
309
|
+
filter = { ...filter, ...settings.customParams };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
CustomFetch(settings.url, 'POST', filter, function (result) {
|
|
313
|
+
setOptions(result.data);
|
|
314
|
+
|
|
315
|
+
let dataArray = [];
|
|
316
|
+
result.data.forEach((a) => {
|
|
317
|
+
let _tempObject = a;
|
|
318
|
+
if (!_.isEmpty(_tempObject)) {
|
|
319
|
+
let modifiedObject = {};
|
|
320
|
+
Object.keys(_tempObject).forEach(function (c) {
|
|
321
|
+
if (c !== 'id' && c !== 'label') {
|
|
322
|
+
modifiedObject['data-' + c] = _tempObject[c];
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
dataArray.push(modifiedObject);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
if (dataArray.length > 0) {
|
|
330
|
+
setDataOptions(dataArray);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}, [...dependencyKeys.map(key => formData[key])]);
|
|
289
335
|
|
|
290
336
|
const fetchChildDropdownData = (s, v) => {
|
|
291
337
|
let filter = {};
|
|
@@ -2126,7 +2172,6 @@ function Field({
|
|
|
2126
2172
|
</span>
|
|
2127
2173
|
);
|
|
2128
2174
|
}
|
|
2129
|
-
break;
|
|
2130
2175
|
case 'plaintextheading':
|
|
2131
2176
|
return <h2>{settings.label ? settings.label : ''}</h2>;
|
|
2132
2177
|
case 'section':
|
|
@@ -2143,7 +2188,7 @@ function Field({
|
|
|
2143
2188
|
onEditorChange={(value, e) => {
|
|
2144
2189
|
onChangeRicheditor(e, value, settings.id);
|
|
2145
2190
|
}}
|
|
2146
|
-
onInit={(
|
|
2191
|
+
onInit={(_, editor) => (editorRef.current = editor)}
|
|
2147
2192
|
value={inputValue || ''}
|
|
2148
2193
|
init={{
|
|
2149
2194
|
branding: false,
|
|
@@ -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:
|
|
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:
|
|
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:
|
|
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(
|
|
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">
|
|
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:
|
|
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:
|
|
1352
|
+
sortable: true,
|
|
1288
1353
|
render: ({ data }) => {
|
|
1289
1354
|
if (
|
|
1290
1355
|
data.audits[0] &&
|