@dashadmin/dash-components 1.3.25 → 1.3.28
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 +35 -0
- package/dist/components/AuditLog/AuditLog.js +345 -1
- package/dist/components/AuditLog/index.js +4 -1
- package/dist/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.js +630 -3
- package/dist/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.js +53 -1
- package/dist/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.js +647 -3
- package/dist/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.js +53 -1
- package/dist/components/Json/Json.js +235 -1
- package/dist/components/JsonColorSelector/JsonColorSelector.js +1172 -1
- package/dist/components/JsonColorSelector/JsonColorSelectorEnhanced.js +863 -2
- package/dist/components/JsonColorSelector/JsonCssVarValues.js +738 -1
- package/dist/components/JsonColorSelector/components/ColorEditDialog.js +426 -1
- package/dist/components/JsonColorSelector/components/ColorPaletteItem.js +75 -1
- package/dist/components/JsonColorSelector/components/ImageColorExtractor.js +283 -1
- package/dist/components/JsonColorSelector/helpers/functions.js +101 -1
- package/dist/components/JsonColorSelector/useAsyncColorThief.js +105 -1
- package/dist/components/ListActive/ListActive.js +126 -1
- package/dist/components/ListActive/ListBoolean.js +124 -1
- package/dist/components/NotificationPreferences/NotificationPreferences.js +317 -1
- package/dist/components/NotificationPreferences/index.js +7 -1
- package/dist/components/SearchableSelects/RASearchableSelect.js +236 -1
- package/dist/components/SearchableSelects/RASearchableSelectCheckboxes.js +479 -1
- package/dist/components/SearchableSelects/RASearchableSelectChips.js +465 -1
- package/dist/components/SearchableSelects/RASearchableSelectChipsRecordContext.js +295 -1
- package/dist/components/SearchableSelects/SearchableSelect.js +202 -1
- package/dist/components/SearchableSelects/SearchableSelectCheckboxes.js +290 -1
- package/dist/components/SortableDataGrid/SortableDataGrid.js +72 -1
- package/dist/components/SortableDataGrid/SortableDataGridBody.js +167 -1
- package/dist/components/SortableDataGrid/SortableDataGridHeader.js +109 -1
- package/dist/components/SortableDataGrid/SortableDataGridRow.js +283 -1
- package/dist/components/Upload/SingleImageUploader.js +40 -1
- package/dist/components/custom/PackageCopyMethod.js +78 -1
- package/dist/components/custom/PackageRadioButtonGroup.js +45 -1
- package/dist/components/dialog/Basic.js +8 -1
- package/dist/components/dialog/Correct.js +14 -1
- package/dist/components/dialog/Error.js +14 -1
- package/dist/components/dialog/Info.js +14 -1
- package/dist/components/dialog/InfoV2.js +21 -1
- package/dist/components/dialog/NotFound.js +14 -1
- package/dist/components/dialog/QuickSearch.js +117 -1
- package/dist/components/notifications/NotificationsCenter.js +597 -1
- package/dist/components/notifications/index.js +24 -1
- package/dist/components/tables/PaginationComponent.js +36 -1
- package/dist/components/theme/AppLoadingFallback.js +38 -2
- package/dist/components/theme/ErrorBoundary.js +52 -1
- package/dist/components/theme/InitialLoader.js +13 -1
- package/dist/components/theme/NotFound.js +136 -1
- package/dist/components/theme/NotResults.js +38 -1
- package/dist/hooks/useDraggable.js +6 -1
- package/dist/hooks/useNotifications.js +8 -1
- package/dist/hooks/useQuickSearch.js +26 -1
- package/dist/index.js +66 -5567
- package/dist/utils/setNativeValue.js +17 -1
- package/package.json +148 -132
- package/src/components/AuditLog/AuditLog.tsx +476 -0
- package/src/components/AuditLog/index.ts +2 -0
- package/src/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.tsx +917 -0
- package/src/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.tsx +39 -0
- package/src/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.tsx +916 -0
- package/src/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.tsx +44 -0
- package/src/components/Json/Json.tsx +312 -0
- package/src/components/JsonColorSelector/JsonColorSelector.tsx +1435 -0
- package/src/components/JsonColorSelector/JsonColorSelectorEnhanced.tsx +1029 -0
- package/src/components/JsonColorSelector/JsonCssVarValues.tsx +863 -0
- package/src/components/JsonColorSelector/components/ColorEditDialog.tsx +491 -0
- package/src/components/JsonColorSelector/components/ColorPaletteItem.tsx +80 -0
- package/src/components/JsonColorSelector/components/ImageColorExtractor.tsx +328 -0
- package/src/components/JsonColorSelector/helpers/functions.tsx +123 -0
- package/src/components/JsonColorSelector/interfaces/interfaces.tsx +22 -0
- package/src/components/JsonColorSelector/useAsyncColorThief.tsx +97 -0
- package/src/components/ListActive/ListActive.tsx +106 -0
- package/src/components/ListActive/ListBoolean.tsx +96 -0
- package/src/components/NotificationPreferences/NotificationPreferences.tsx +463 -0
- package/src/components/NotificationPreferences/index.tsx +2 -0
- package/src/components/SearchableSelects/RASearchableSelect.tsx +300 -0
- package/src/components/SearchableSelects/RASearchableSelectCheckboxes.tsx +604 -0
- package/src/components/SearchableSelects/RASearchableSelectChips.tsx +558 -0
- package/src/components/SearchableSelects/RASearchableSelectChipsRecordContext.tsx +362 -0
- package/src/components/SearchableSelects/SearchableSelect.tsx +321 -0
- package/src/components/SearchableSelects/SearchableSelectCheckboxes.tsx +355 -0
- package/src/components/SortableDataGrid/SortableDataGrid.tsx +56 -0
- package/src/components/SortableDataGrid/SortableDataGridBody.tsx +175 -0
- package/src/components/SortableDataGrid/SortableDataGridHeader.tsx +171 -0
- package/src/components/SortableDataGrid/SortableDataGridRow.tsx +247 -0
- package/src/components/Upload/SingleImageUploader.tsx +55 -0
- package/src/components/custom/PackageCopyMethod.tsx +109 -0
- package/src/components/custom/PackageRadioButtonGroup.tsx +47 -0
- package/src/components/dialog/Basic.tsx +11 -0
- package/src/components/dialog/Correct.tsx +16 -0
- package/src/components/dialog/Error.tsx +15 -0
- package/src/components/dialog/Info.tsx +15 -0
- package/src/components/dialog/InfoV2.tsx +35 -0
- package/src/components/dialog/NotFound.tsx +18 -0
- package/src/components/dialog/QuickSearch.tsx +115 -0
- package/src/components/notifications/NotificationsCenter.tsx +792 -0
- package/src/components/notifications/index.ts +42 -0
- package/src/components/tables/PaginationComponent.tsx +17 -0
- package/src/components/theme/AppLoadingFallback.tsx +42 -0
- package/src/components/theme/ErrorBoundary.tsx +57 -0
- package/src/components/theme/InitialLoader.tsx +16 -0
- package/src/components/theme/NotFound.tsx +124 -0
- package/src/components/theme/NotResults.tsx +54 -0
- package/src/hooks/useDraggable.ts +11 -0
- package/src/hooks/useNotifications.ts +14 -0
- package/src/hooks/useQuickSearch.tsx +29 -0
- package/src/index.tsx +77 -0
- package/src/utils/setNativeValue.tsx +23 -0
- package/dist/color-thief-CTwGFhOB.js +0 -272
- /package/{dist → src}/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayAutoSearch.tsx.wip +0 -0
- /package/{dist → src}/components/JsonColorSelector/JsonColorSelector.tsx.bkup +0 -0
- /package/{dist → src}/components/SortableDataGrid/README.md +0 -0
- /package/{dist → src}/styles/index.less +0 -0
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone SearchableSelectChips for filters and other uses
|
|
3
|
+
* @author Francisco Aranda <francisco.aranda@sudo.cl> <faranda@gmail.com>
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Autocomplete,
|
|
8
|
+
Box,
|
|
9
|
+
Chip,
|
|
10
|
+
CircularProgress,
|
|
11
|
+
TextField,
|
|
12
|
+
} from "@mui/material";
|
|
13
|
+
import React, { useEffect, useState } from "react";
|
|
14
|
+
import { useGetList } from "react-admin";
|
|
15
|
+
import { isArray } from "lodash";
|
|
16
|
+
import { LoadingIndicator } from "react-admin";
|
|
17
|
+
|
|
18
|
+
interface ISearchableSelectChips {
|
|
19
|
+
name?: string;
|
|
20
|
+
resource?: string;
|
|
21
|
+
selectLabel?: string;
|
|
22
|
+
title?: string;
|
|
23
|
+
renderText: (option: any, caller: string) => any;
|
|
24
|
+
transformData?: (val: any) => any;
|
|
25
|
+
defaultValues?: any;
|
|
26
|
+
value?: any;
|
|
27
|
+
onChange?: (value: any) => void;
|
|
28
|
+
isMultiple?: boolean;
|
|
29
|
+
minSearch?: number;
|
|
30
|
+
searchResults?: number;
|
|
31
|
+
minOptions?: number; // Minimum options to show
|
|
32
|
+
timerSearch?: number;
|
|
33
|
+
isEmpty?: boolean;
|
|
34
|
+
filter?: { [x: string]: any };
|
|
35
|
+
queryFilter?: string;
|
|
36
|
+
isOptionEqualToValue: (option: any, value: any) => boolean;
|
|
37
|
+
viewAttribute: string;
|
|
38
|
+
valueKeyId?: string;
|
|
39
|
+
transformOption?: (
|
|
40
|
+
option: any,
|
|
41
|
+
parsedOptions: any[],
|
|
42
|
+
caller?: string
|
|
43
|
+
) => boolean;
|
|
44
|
+
disabled?: boolean;
|
|
45
|
+
placeholder?: string;
|
|
46
|
+
emptyText?: string;
|
|
47
|
+
fullWidth?: boolean;
|
|
48
|
+
// 🔥 NEW: Enable mixed results feature
|
|
49
|
+
enableMixedResults?: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const SearchableSelectChips: React.FC<ISearchableSelectChips> = ({
|
|
53
|
+
isMultiple = false,
|
|
54
|
+
searchResults = 50,
|
|
55
|
+
minSearch = 2,
|
|
56
|
+
minOptions = 10,
|
|
57
|
+
timerSearch = 500,
|
|
58
|
+
resource,
|
|
59
|
+
selectLabel,
|
|
60
|
+
renderText,
|
|
61
|
+
transformData = (val: any) => val,
|
|
62
|
+
queryFilter = "q",
|
|
63
|
+
defaultValues = undefined,
|
|
64
|
+
value,
|
|
65
|
+
onChange,
|
|
66
|
+
isEmpty = false,
|
|
67
|
+
filter,
|
|
68
|
+
isOptionEqualToValue,
|
|
69
|
+
viewAttribute = null,
|
|
70
|
+
valueKeyId = "id",
|
|
71
|
+
transformOption,
|
|
72
|
+
disabled = false,
|
|
73
|
+
placeholder = "",
|
|
74
|
+
emptyText = "",
|
|
75
|
+
fullWidth = true,
|
|
76
|
+
// 🔥 NEW: Mixed results props
|
|
77
|
+
enableMixedResults = false,
|
|
78
|
+
}) => {
|
|
79
|
+
// Component state
|
|
80
|
+
const [open, setOpen] = useState(false);
|
|
81
|
+
const [fullyLoaded, setFullyLoaded] = useState(false);
|
|
82
|
+
const [parsedOptions, setParsedOptions] = useState<any[]>([]);
|
|
83
|
+
const [q, setQ] = useState("");
|
|
84
|
+
const [selectedOptions, setSelectedOptions] = useState<any[]>([]);
|
|
85
|
+
const [timeOutSearch, setTimeOutSearch] = useState<any>(undefined);
|
|
86
|
+
|
|
87
|
+
const _transformOption = (option: any, caller: string) => {
|
|
88
|
+
if (typeof transformOption === "function") {
|
|
89
|
+
return transformOption(option, parsedOptions, caller);
|
|
90
|
+
}
|
|
91
|
+
return option;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Get includes for the API call
|
|
95
|
+
const getIncludes = () => {
|
|
96
|
+
const currentValue = value || defaultValues;
|
|
97
|
+
if (!currentValue) return [];
|
|
98
|
+
|
|
99
|
+
if (Array.isArray(currentValue)) {
|
|
100
|
+
return currentValue.map(item =>
|
|
101
|
+
typeof item === 'object' ? item[valueKeyId] : item
|
|
102
|
+
).filter(Boolean);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const id = typeof currentValue === 'object' ? currentValue[valueKeyId] : currentValue;
|
|
106
|
+
return id ? [id] : [];
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const includes = getIncludes();
|
|
110
|
+
|
|
111
|
+
const buildFilterParams = () => {
|
|
112
|
+
const baseFilter = { ...filter };
|
|
113
|
+
|
|
114
|
+
// Add includes if we have selected values
|
|
115
|
+
if (includes.length > 0) {
|
|
116
|
+
baseFilter.includes = includes;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
console.log('🔧 SearchableSelectChips DEBUG - buildFilterParams called:', {
|
|
120
|
+
resource,
|
|
121
|
+
enableMixedResults,
|
|
122
|
+
searchQuery: q,
|
|
123
|
+
hasSearch: q && q.length >= minSearch,
|
|
124
|
+
timestamp: new Date().toISOString()
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// 🔥 NEW: Mixed results logic with query parameter
|
|
128
|
+
if (enableMixedResults) {
|
|
129
|
+
const mixedResultsValue = minOptions;
|
|
130
|
+
|
|
131
|
+
// Always add mixedResults parameter when enabled
|
|
132
|
+
baseFilter.mixedResults = mixedResultsValue;
|
|
133
|
+
|
|
134
|
+
console.log('🔥 SearchableSelectChips DEBUG - Mixed results enabled via query param:', {
|
|
135
|
+
resource,
|
|
136
|
+
mixedResults: mixedResultsValue,
|
|
137
|
+
searchQuery: q,
|
|
138
|
+
hasSearch: q && q.length >= minSearch,
|
|
139
|
+
baseFilter,
|
|
140
|
+
timestamp: new Date().toISOString()
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Add search query if present
|
|
144
|
+
if (q && q.length >= minSearch) {
|
|
145
|
+
baseFilter[queryFilter] = q;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Set limit
|
|
149
|
+
baseFilter.limit = searchResults;
|
|
150
|
+
|
|
151
|
+
} else {
|
|
152
|
+
// 🔥 FALLBACK: Original logic for backward compatibility
|
|
153
|
+
if (q && q.length >= minSearch) {
|
|
154
|
+
baseFilter[queryFilter] = q;
|
|
155
|
+
baseFilter.mixed_results = true;
|
|
156
|
+
baseFilter.min_options = minOptions;
|
|
157
|
+
baseFilter.limit = searchResults;
|
|
158
|
+
|
|
159
|
+
console.log('🔍 SearchableSelectChips DEBUG - Legacy mixed results enabled:', {
|
|
160
|
+
resource,
|
|
161
|
+
searchQuery: q,
|
|
162
|
+
minOptions,
|
|
163
|
+
searchResults,
|
|
164
|
+
mixed_results: true,
|
|
165
|
+
baseFilter,
|
|
166
|
+
timestamp: new Date().toISOString()
|
|
167
|
+
});
|
|
168
|
+
} else if (q === "") {
|
|
169
|
+
baseFilter.limit = Math.max(minOptions, searchResults);
|
|
170
|
+
} else {
|
|
171
|
+
baseFilter.limit = Math.max(minOptions, searchResults);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
console.log('🚀 SearchableSelectChips DEBUG - Final filter params:', {
|
|
176
|
+
resource,
|
|
177
|
+
filterParams: baseFilter,
|
|
178
|
+
enableMixedResults,
|
|
179
|
+
timestamp: new Date().toISOString()
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return baseFilter;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const filterParams = buildFilterParams();
|
|
186
|
+
|
|
187
|
+
// React Admin useGetList with dynamic filter
|
|
188
|
+
const {
|
|
189
|
+
data: resourceSearchResults,
|
|
190
|
+
isLoading: isResourceSearchLoading,
|
|
191
|
+
error: isResourceSearchErrored,
|
|
192
|
+
} = useGetList(resource, {
|
|
193
|
+
meta: { removeSortFilters: true },
|
|
194
|
+
filter: filterParams,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Update parsed options when search results change
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
if (resourceSearchResults) {
|
|
200
|
+
const resultsArray = Array.isArray(resourceSearchResults)
|
|
201
|
+
? resourceSearchResults
|
|
202
|
+
: Object.values(resourceSearchResults);
|
|
203
|
+
|
|
204
|
+
console.log('📊 SearchableSelectChips DEBUG - Processing results:', {
|
|
205
|
+
resource,
|
|
206
|
+
totalResults: resultsArray.length,
|
|
207
|
+
searchQuery: q,
|
|
208
|
+
hasSearch: q && q.length >= minSearch,
|
|
209
|
+
enableMixedResults,
|
|
210
|
+
timestamp: new Date().toISOString()
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
let processedOptions = resultsArray.map((ele, index) => {
|
|
214
|
+
// Determine if this is a search match (client-side check for visual indicators)
|
|
215
|
+
const isSearchMatch = q && q.length >= minSearch ?
|
|
216
|
+
(ele[viewAttribute] && ele[viewAttribute].toLowerCase().includes(q.toLowerCase())) :
|
|
217
|
+
true;
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
...ele,
|
|
221
|
+
key: `option-${ele[valueKeyId] || index}`,
|
|
222
|
+
isSearchMatch,
|
|
223
|
+
priority: isSearchMatch ? 1 : 2,
|
|
224
|
+
};
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Sort by priority (search matches first) and then by name
|
|
228
|
+
processedOptions.sort((a, b) => {
|
|
229
|
+
if (a.priority !== b.priority) {
|
|
230
|
+
return a.priority - b.priority;
|
|
231
|
+
}
|
|
232
|
+
const nameA = a[viewAttribute] || a.name || '';
|
|
233
|
+
const nameB = b[viewAttribute] || b.name || '';
|
|
234
|
+
return nameA.localeCompare(nameB);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Count search matches vs non-matches
|
|
238
|
+
const searchMatches = processedOptions.filter(opt => opt.isSearchMatch);
|
|
239
|
+
const nonMatches = processedOptions.filter(opt => !opt.isSearchMatch);
|
|
240
|
+
|
|
241
|
+
console.log('🎯 SearchableSelectChips DEBUG - Results breakdown:', {
|
|
242
|
+
resource,
|
|
243
|
+
searchQuery: q,
|
|
244
|
+
totalProcessed: processedOptions.length,
|
|
245
|
+
searchMatches: searchMatches.length,
|
|
246
|
+
nonMatches: nonMatches.length,
|
|
247
|
+
enableMixedResults,
|
|
248
|
+
mixedResultsParam: enableMixedResults ? (minOptions) : 'disabled',
|
|
249
|
+
timestamp: new Date().toISOString()
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Limit results
|
|
253
|
+
if (processedOptions.length > searchResults) {
|
|
254
|
+
processedOptions = processedOptions.slice(0, searchResults);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
setParsedOptions(processedOptions);
|
|
258
|
+
}
|
|
259
|
+
}, [resourceSearchResults, valueKeyId, q, minSearch, viewAttribute, searchResults, minOptions, enableMixedResults]);
|
|
260
|
+
|
|
261
|
+
useEffect(() => {
|
|
262
|
+
console.log('🔍 SearchableSelectChips DEBUG - Component initialized with resource:', {
|
|
263
|
+
resource,
|
|
264
|
+
resourceType: typeof resource,
|
|
265
|
+
selectLabel,
|
|
266
|
+
isMultiple,
|
|
267
|
+
minOptions,
|
|
268
|
+
searchResults,
|
|
269
|
+
minSearch,
|
|
270
|
+
timestamp: new Date().toISOString()
|
|
271
|
+
});
|
|
272
|
+
}, []);
|
|
273
|
+
|
|
274
|
+
// Initialize selected options from value or defaultValues
|
|
275
|
+
useEffect(() => {
|
|
276
|
+
const currentValue = value !== undefined ? value : defaultValues;
|
|
277
|
+
|
|
278
|
+
console.log('🎯 SearchableSelectChips DEBUG - Value changed:', {
|
|
279
|
+
resource,
|
|
280
|
+
currentValue,
|
|
281
|
+
valueType: typeof currentValue,
|
|
282
|
+
isArray: Array.isArray(currentValue),
|
|
283
|
+
isMultiple,
|
|
284
|
+
timestamp: new Date().toISOString()
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
if (currentValue === null || currentValue === undefined) {
|
|
288
|
+
setSelectedOptions([]);
|
|
289
|
+
setFullyLoaded(true);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let initialSelectedOptions: any[] = [];
|
|
294
|
+
|
|
295
|
+
if (isMultiple) {
|
|
296
|
+
// For multiple, expect array
|
|
297
|
+
if (Array.isArray(currentValue)) {
|
|
298
|
+
initialSelectedOptions = currentValue;
|
|
299
|
+
} else if (currentValue) {
|
|
300
|
+
// Single value in multiple mode - wrap in array
|
|
301
|
+
initialSelectedOptions = [currentValue];
|
|
302
|
+
}
|
|
303
|
+
} else {
|
|
304
|
+
// For single, wrap in array for internal processing
|
|
305
|
+
if (currentValue) {
|
|
306
|
+
initialSelectedOptions = [currentValue];
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
console.log('🎯 SearchableSelectChips DEBUG - Selected options set:', {
|
|
311
|
+
resource,
|
|
312
|
+
currentValue,
|
|
313
|
+
initialSelectedOptions,
|
|
314
|
+
isMultiple,
|
|
315
|
+
timestamp: new Date().toISOString()
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
setSelectedOptions(initialSelectedOptions);
|
|
319
|
+
setFullyLoaded(true);
|
|
320
|
+
}, [value, defaultValues, isMultiple]);
|
|
321
|
+
|
|
322
|
+
// Update selected options when parsedOptions change and we have IDs but no full objects
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
if (parsedOptions.length > 0 && selectedOptions.length > 0) {
|
|
325
|
+
const updatedOptions = selectedOptions.map(selectedOption => {
|
|
326
|
+
// If it's already a full object, keep it
|
|
327
|
+
if (typeof selectedOption === 'object' && selectedOption[viewAttribute]) {
|
|
328
|
+
return selectedOption;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// If it's just an ID, find the full object
|
|
332
|
+
const id = typeof selectedOption === 'object' ? selectedOption[valueKeyId] : selectedOption;
|
|
333
|
+
return parsedOptions.find(option => option[valueKeyId] === id) || selectedOption;
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
setSelectedOptions(updatedOptions);
|
|
337
|
+
}
|
|
338
|
+
}, [parsedOptions, viewAttribute, valueKeyId]);
|
|
339
|
+
|
|
340
|
+
// Debounced search
|
|
341
|
+
const debounce = (fn: Function, delay = 500) => {
|
|
342
|
+
clearTimeout(timeOutSearch);
|
|
343
|
+
setTimeOutSearch(
|
|
344
|
+
setTimeout(() => {
|
|
345
|
+
fn();
|
|
346
|
+
}, delay)
|
|
347
|
+
);
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
// FIXED: Simplified update field value
|
|
351
|
+
const updateFieldValue = (newSelectedOptions: any[]) => {
|
|
352
|
+
setSelectedOptions(newSelectedOptions || []);
|
|
353
|
+
|
|
354
|
+
if (onChange) {
|
|
355
|
+
if (isMultiple) {
|
|
356
|
+
// For multiple, send the array as-is
|
|
357
|
+
onChange(newSelectedOptions || []);
|
|
358
|
+
} else {
|
|
359
|
+
// For single, send the first item or null
|
|
360
|
+
onChange(newSelectedOptions && newSelectedOptions.length > 0 ? newSelectedOptions[0] : null);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
if (!fullyLoaded) return <LoadingIndicator />;
|
|
366
|
+
|
|
367
|
+
return (
|
|
368
|
+
<Autocomplete
|
|
369
|
+
multiple={isMultiple}
|
|
370
|
+
loading={isResourceSearchLoading}
|
|
371
|
+
fullWidth={fullWidth}
|
|
372
|
+
freeSolo={false}
|
|
373
|
+
options={parsedOptions}
|
|
374
|
+
open={open}
|
|
375
|
+
onOpen={() => setOpen(true)}
|
|
376
|
+
onClose={() => setOpen(false)}
|
|
377
|
+
value={isMultiple ? selectedOptions : (selectedOptions[0] || null)}
|
|
378
|
+
isOptionEqualToValue={isOptionEqualToValue}
|
|
379
|
+
autoHighlight
|
|
380
|
+
disabled={disabled}
|
|
381
|
+
getOptionLabel={(option: any) => {
|
|
382
|
+
if (!option) return '';
|
|
383
|
+
return renderText(_transformOption(option, "optionlabel"), "optionlabel") || '';
|
|
384
|
+
}}
|
|
385
|
+
renderOption={(props, option: any) => {
|
|
386
|
+
const { key, ...newProps } = props as any;
|
|
387
|
+
const isSearchMatch = option.isSearchMatch;
|
|
388
|
+
const hasSearchQuery = q && q.length >= minSearch;
|
|
389
|
+
|
|
390
|
+
return (
|
|
391
|
+
<Box
|
|
392
|
+
key={option.key || `option-${option[valueKeyId]}`}
|
|
393
|
+
component="li"
|
|
394
|
+
sx={{
|
|
395
|
+
"& > img": { mr: 2, flexShrink: 0 },
|
|
396
|
+
// 🔥 ENHANCED: Better visual distinction for mixed results
|
|
397
|
+
fontWeight: hasSearchQuery && isSearchMatch ? 'bold' : 'normal',
|
|
398
|
+
opacity: hasSearchQuery && !isSearchMatch ? 0.7 : 1,
|
|
399
|
+
borderLeft: hasSearchQuery && isSearchMatch ? '3px solid #1976d2' : 'none',
|
|
400
|
+
paddingLeft: hasSearchQuery && isSearchMatch ? 1 : 2,
|
|
401
|
+
backgroundColor: hasSearchQuery && isSearchMatch ? 'rgba(25, 118, 210, 0.04)' : 'transparent',
|
|
402
|
+
// 🔥 NEW: Add subtle separator between search matches and other options
|
|
403
|
+
borderTop: hasSearchQuery && !isSearchMatch && option.priority === 2 ? '1px solid rgba(0,0,0,0.1)' : 'none',
|
|
404
|
+
marginTop: hasSearchQuery && !isSearchMatch && option.priority === 2 ? 0.5 : 0,
|
|
405
|
+
}}
|
|
406
|
+
{...newProps}
|
|
407
|
+
>
|
|
408
|
+
{/* 🔥 ENHANCED: Show section headers for mixed results */}
|
|
409
|
+
{hasSearchQuery && option.priority === 2 && parsedOptions.indexOf(option) === parsedOptions.findIndex(opt => opt.priority === 2) && (
|
|
410
|
+
<Box
|
|
411
|
+
sx={{
|
|
412
|
+
position: 'absolute',
|
|
413
|
+
top: -20,
|
|
414
|
+
left: 0,
|
|
415
|
+
right: 0,
|
|
416
|
+
fontSize: '0.7rem',
|
|
417
|
+
color: 'text.secondary',
|
|
418
|
+
backgroundColor: 'background.paper',
|
|
419
|
+
padding: '2px 8px',
|
|
420
|
+
borderBottom: '1px solid rgba(0,0,0,0.1)',
|
|
421
|
+
fontWeight: 'bold',
|
|
422
|
+
}}
|
|
423
|
+
>
|
|
424
|
+
Otras opciones
|
|
425
|
+
</Box>
|
|
426
|
+
)}
|
|
427
|
+
|
|
428
|
+
{renderText(_transformOption(option, "option"), "option")}
|
|
429
|
+
|
|
430
|
+
{/* 🔥 ENHANCED: Better indicators for search matches */}
|
|
431
|
+
{hasSearchQuery && isSearchMatch && (
|
|
432
|
+
<Box
|
|
433
|
+
component="span"
|
|
434
|
+
sx={{
|
|
435
|
+
ml: 'auto',
|
|
436
|
+
fontSize: '0.75rem',
|
|
437
|
+
color: '#1976d2',
|
|
438
|
+
fontWeight: 'bold',
|
|
439
|
+
display: 'flex',
|
|
440
|
+
alignItems: 'center',
|
|
441
|
+
gap: 0.5
|
|
442
|
+
}}
|
|
443
|
+
>
|
|
444
|
+
<Box
|
|
445
|
+
component="span"
|
|
446
|
+
sx={{
|
|
447
|
+
width: 6,
|
|
448
|
+
height: 6,
|
|
449
|
+
borderRadius: '50%',
|
|
450
|
+
backgroundColor: '#1976d2',
|
|
451
|
+
}}
|
|
452
|
+
/>
|
|
453
|
+
Coincidencia
|
|
454
|
+
</Box>
|
|
455
|
+
)}
|
|
456
|
+
</Box>
|
|
457
|
+
);
|
|
458
|
+
}}
|
|
459
|
+
// FIXED: Use modern renderValue instead of deprecated renderTags
|
|
460
|
+
renderValue={(tagValue, getTagProps) => {
|
|
461
|
+
return tagValue.map((option, index) => {
|
|
462
|
+
const chipProps = getTagProps({ index });
|
|
463
|
+
return (
|
|
464
|
+
<Chip
|
|
465
|
+
key={`chip-${option[valueKeyId] || index}`}
|
|
466
|
+
label={renderText(_transformOption(option, "chip"), "chip")}
|
|
467
|
+
{...chipProps}
|
|
468
|
+
/>
|
|
469
|
+
);
|
|
470
|
+
});
|
|
471
|
+
}}
|
|
472
|
+
onChange={(event: any, rawValue) => {
|
|
473
|
+
console.log('🔄 SearchableSelectChips DEBUG - Autocomplete onChange:', {
|
|
474
|
+
resource,
|
|
475
|
+
rawValue,
|
|
476
|
+
isMultiple,
|
|
477
|
+
timestamp: new Date().toISOString()
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// FIXED: Simple handling - let Autocomplete handle the value format
|
|
481
|
+
if (isMultiple) {
|
|
482
|
+
updateFieldValue(Array.isArray(rawValue) ? rawValue : []);
|
|
483
|
+
} else {
|
|
484
|
+
updateFieldValue(rawValue ? [rawValue] : []);
|
|
485
|
+
}
|
|
486
|
+
}}
|
|
487
|
+
renderInput={(params) => (
|
|
488
|
+
<TextField
|
|
489
|
+
{...params}
|
|
490
|
+
label={selectLabel}
|
|
491
|
+
variant="outlined"
|
|
492
|
+
placeholder={placeholder}
|
|
493
|
+
onChange={(ev) => {
|
|
494
|
+
const searchValue = ev.target.value;
|
|
495
|
+
|
|
496
|
+
console.log('🔍 SearchableSelectChips DEBUG - Search input changed:', {
|
|
497
|
+
resource,
|
|
498
|
+
searchValue,
|
|
499
|
+
searchLength: searchValue.length,
|
|
500
|
+
minSearch,
|
|
501
|
+
willTriggerSearch: searchValue.length >= minSearch,
|
|
502
|
+
timestamp: new Date().toISOString()
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
if (
|
|
506
|
+
searchValue &&
|
|
507
|
+
searchValue !== "" &&
|
|
508
|
+
searchValue.length >= minSearch
|
|
509
|
+
) {
|
|
510
|
+
debounce(() => {
|
|
511
|
+
console.log('🚀 SearchableSelectChips DEBUG - Triggering search:', {
|
|
512
|
+
resource,
|
|
513
|
+
searchValue,
|
|
514
|
+
minOptions,
|
|
515
|
+
searchResults,
|
|
516
|
+
timestamp: new Date().toISOString()
|
|
517
|
+
});
|
|
518
|
+
setQ(searchValue);
|
|
519
|
+
}, timerSearch);
|
|
520
|
+
} else if (searchValue === "") {
|
|
521
|
+
console.log('🔄 SearchableSelectChips DEBUG - Clearing search:', {
|
|
522
|
+
resource,
|
|
523
|
+
timestamp: new Date().toISOString()
|
|
524
|
+
});
|
|
525
|
+
setQ("");
|
|
526
|
+
}
|
|
527
|
+
}}
|
|
528
|
+
// FIXED: Use modern slotProps instead of deprecated InputProps
|
|
529
|
+
slotProps={{
|
|
530
|
+
input: {
|
|
531
|
+
...params.InputProps,
|
|
532
|
+
autoComplete: "new-password",
|
|
533
|
+
endAdornment: (
|
|
534
|
+
<>
|
|
535
|
+
{isResourceSearchLoading && (
|
|
536
|
+
<CircularProgress color="inherit" size={20} />
|
|
537
|
+
)}
|
|
538
|
+
{params.InputProps.endAdornment}
|
|
539
|
+
</>
|
|
540
|
+
),
|
|
541
|
+
},
|
|
542
|
+
}}
|
|
543
|
+
/>
|
|
544
|
+
)}
|
|
545
|
+
// 🔥 NEW: Add custom props for better mixed results UX
|
|
546
|
+
ListboxProps={{
|
|
547
|
+
sx: {
|
|
548
|
+
maxHeight: 400,
|
|
549
|
+
'& .MuiAutocomplete-option': {
|
|
550
|
+
position: 'relative',
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}}
|
|
554
|
+
/>
|
|
555
|
+
);
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
export default SearchableSelectChips;
|