@dashadmin/dash-components 1.3.25 → 1.3.26

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.
Files changed (111) hide show
  1. package/dist/components/AuditLog/AuditLog.js +345 -1
  2. package/dist/components/AuditLog/index.js +4 -1
  3. package/dist/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.js +630 -3
  4. package/dist/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.js +53 -1
  5. package/dist/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.js +647 -3
  6. package/dist/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.js +53 -1
  7. package/dist/components/Json/Json.js +235 -1
  8. package/dist/components/JsonColorSelector/JsonColorSelector.js +1172 -1
  9. package/dist/components/JsonColorSelector/JsonColorSelectorEnhanced.js +863 -2
  10. package/dist/components/JsonColorSelector/JsonCssVarValues.js +738 -1
  11. package/dist/components/JsonColorSelector/components/ColorEditDialog.js +426 -1
  12. package/dist/components/JsonColorSelector/components/ColorPaletteItem.js +75 -1
  13. package/dist/components/JsonColorSelector/components/ImageColorExtractor.js +283 -1
  14. package/dist/components/JsonColorSelector/helpers/functions.js +101 -1
  15. package/dist/components/JsonColorSelector/useAsyncColorThief.js +105 -1
  16. package/dist/components/ListActive/ListActive.js +126 -1
  17. package/dist/components/ListActive/ListBoolean.js +124 -1
  18. package/dist/components/NotificationPreferences/NotificationPreferences.js +317 -1
  19. package/dist/components/NotificationPreferences/index.js +7 -1
  20. package/dist/components/SearchableSelects/RASearchableSelect.js +236 -1
  21. package/dist/components/SearchableSelects/RASearchableSelectCheckboxes.js +479 -1
  22. package/dist/components/SearchableSelects/RASearchableSelectChips.js +465 -1
  23. package/dist/components/SearchableSelects/RASearchableSelectChipsRecordContext.js +295 -1
  24. package/dist/components/SearchableSelects/SearchableSelect.js +202 -1
  25. package/dist/components/SearchableSelects/SearchableSelectCheckboxes.js +290 -1
  26. package/dist/components/SortableDataGrid/SortableDataGrid.js +72 -1
  27. package/dist/components/SortableDataGrid/SortableDataGridBody.js +167 -1
  28. package/dist/components/SortableDataGrid/SortableDataGridHeader.js +109 -1
  29. package/dist/components/SortableDataGrid/SortableDataGridRow.js +283 -1
  30. package/dist/components/Upload/SingleImageUploader.js +40 -1
  31. package/dist/components/custom/PackageCopyMethod.js +78 -1
  32. package/dist/components/custom/PackageRadioButtonGroup.js +45 -1
  33. package/dist/components/dialog/Basic.js +8 -1
  34. package/dist/components/dialog/Correct.js +14 -1
  35. package/dist/components/dialog/Error.js +14 -1
  36. package/dist/components/dialog/Info.js +14 -1
  37. package/dist/components/dialog/InfoV2.js +21 -1
  38. package/dist/components/dialog/NotFound.js +14 -1
  39. package/dist/components/dialog/QuickSearch.js +117 -1
  40. package/dist/components/notifications/NotificationsCenter.js +597 -1
  41. package/dist/components/notifications/index.js +24 -1
  42. package/dist/components/tables/PaginationComponent.js +36 -1
  43. package/dist/components/theme/AppLoadingFallback.js +38 -2
  44. package/dist/components/theme/ErrorBoundary.js +52 -1
  45. package/dist/components/theme/InitialLoader.js +13 -1
  46. package/dist/components/theme/NotFound.js +136 -1
  47. package/dist/components/theme/NotResults.js +38 -1
  48. package/dist/hooks/useDraggable.js +6 -1
  49. package/dist/hooks/useNotifications.js +8 -1
  50. package/dist/hooks/useQuickSearch.js +26 -1
  51. package/dist/index.js +66 -5567
  52. package/dist/utils/setNativeValue.js +17 -1
  53. package/package.json +149 -132
  54. package/src/components/AuditLog/AuditLog.tsx +476 -0
  55. package/src/components/AuditLog/index.ts +2 -0
  56. package/src/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.tsx +917 -0
  57. package/src/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.tsx +39 -0
  58. package/src/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.tsx +916 -0
  59. package/src/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.tsx +44 -0
  60. package/src/components/Json/Json.tsx +312 -0
  61. package/src/components/JsonColorSelector/JsonColorSelector.tsx +1435 -0
  62. package/src/components/JsonColorSelector/JsonColorSelectorEnhanced.tsx +1029 -0
  63. package/src/components/JsonColorSelector/JsonCssVarValues.tsx +863 -0
  64. package/src/components/JsonColorSelector/components/ColorEditDialog.tsx +491 -0
  65. package/src/components/JsonColorSelector/components/ColorPaletteItem.tsx +80 -0
  66. package/src/components/JsonColorSelector/components/ImageColorExtractor.tsx +328 -0
  67. package/src/components/JsonColorSelector/helpers/functions.tsx +123 -0
  68. package/src/components/JsonColorSelector/interfaces/interfaces.tsx +22 -0
  69. package/src/components/JsonColorSelector/useAsyncColorThief.tsx +97 -0
  70. package/src/components/ListActive/ListActive.tsx +106 -0
  71. package/src/components/ListActive/ListBoolean.tsx +96 -0
  72. package/src/components/NotificationPreferences/NotificationPreferences.tsx +463 -0
  73. package/src/components/NotificationPreferences/index.tsx +2 -0
  74. package/src/components/SearchableSelects/RASearchableSelect.tsx +300 -0
  75. package/src/components/SearchableSelects/RASearchableSelectCheckboxes.tsx +604 -0
  76. package/src/components/SearchableSelects/RASearchableSelectChips.tsx +558 -0
  77. package/src/components/SearchableSelects/RASearchableSelectChipsRecordContext.tsx +362 -0
  78. package/src/components/SearchableSelects/SearchableSelect.tsx +321 -0
  79. package/src/components/SearchableSelects/SearchableSelectCheckboxes.tsx +355 -0
  80. package/src/components/SortableDataGrid/SortableDataGrid.tsx +56 -0
  81. package/src/components/SortableDataGrid/SortableDataGridBody.tsx +175 -0
  82. package/src/components/SortableDataGrid/SortableDataGridHeader.tsx +171 -0
  83. package/src/components/SortableDataGrid/SortableDataGridRow.tsx +247 -0
  84. package/src/components/Upload/SingleImageUploader.tsx +55 -0
  85. package/src/components/custom/PackageCopyMethod.tsx +109 -0
  86. package/src/components/custom/PackageRadioButtonGroup.tsx +47 -0
  87. package/src/components/dialog/Basic.tsx +11 -0
  88. package/src/components/dialog/Correct.tsx +16 -0
  89. package/src/components/dialog/Error.tsx +15 -0
  90. package/src/components/dialog/Info.tsx +15 -0
  91. package/src/components/dialog/InfoV2.tsx +35 -0
  92. package/src/components/dialog/NotFound.tsx +18 -0
  93. package/src/components/dialog/QuickSearch.tsx +115 -0
  94. package/src/components/notifications/NotificationsCenter.tsx +792 -0
  95. package/src/components/notifications/index.ts +42 -0
  96. package/src/components/tables/PaginationComponent.tsx +17 -0
  97. package/src/components/theme/AppLoadingFallback.tsx +42 -0
  98. package/src/components/theme/ErrorBoundary.tsx +57 -0
  99. package/src/components/theme/InitialLoader.tsx +16 -0
  100. package/src/components/theme/NotFound.tsx +124 -0
  101. package/src/components/theme/NotResults.tsx +54 -0
  102. package/src/hooks/useDraggable.ts +11 -0
  103. package/src/hooks/useNotifications.ts +14 -0
  104. package/src/hooks/useQuickSearch.tsx +29 -0
  105. package/src/index.tsx +77 -0
  106. package/src/utils/setNativeValue.tsx +23 -0
  107. package/dist/color-thief-CTwGFhOB.js +0 -272
  108. /package/{dist → src}/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayAutoSearch.tsx.wip +0 -0
  109. /package/{dist → src}/components/JsonColorSelector/JsonColorSelector.tsx.bkup +0 -0
  110. /package/{dist → src}/components/SortableDataGrid/README.md +0 -0
  111. /package/{dist → src}/styles/index.less +0 -0
@@ -0,0 +1,362 @@
1
+ /**
2
+ * ISearchableSelect for AutoAdmin for ReactAdmin
3
+ * @author Franisco Aranda <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 { useFormContext } from "react-hook-form";
15
+ import { useGetList } from "react-admin";
16
+
17
+ import { useRecordContext } from "react-admin";
18
+ import { isArray } from "lodash";
19
+ import { LoadingIndicator } from "react-admin";
20
+ import { IDashAutoAdminCustomFieldComponent } from "dash-auto-admin";
21
+
22
+ /* NOTES: use defaultValues = null, to infer record attribute */
23
+ interface ISearchableSelectChipsControlRecordContext {
24
+ name?: string;
25
+ resource?: string;
26
+ selectLabel?: string;
27
+ title?: string;
28
+ renderText: (option: any, caller: string) => any;
29
+ transformData?: (val: any) => any;
30
+ defaultValues?: any;
31
+ isMultiple?: boolean;
32
+ minSearch?: number;
33
+ searchResults?: number;
34
+ timerSearch?: number;
35
+ isEmpty?: boolean;
36
+ filter?: { [x: string]: any };
37
+ queryFilter?: string;
38
+ isOptionEqualToValue: (option: any, value: any) => boolean;
39
+ viewAttribute: string;
40
+ valueKeyId?: string;
41
+ useBaseAttributeName?: boolean;
42
+ useListAttributeForController?: boolean;
43
+ transformOption?: (
44
+ option: any,
45
+ parsedOptions: any[],
46
+ caller?: string
47
+ ) => boolean;
48
+ }
49
+
50
+ export const SearchableSelectChipsControlRecordContextEdit: React.FC<
51
+ ISearchableSelectChipsControlRecordContext & IDashAutoAdminCustomFieldComponent
52
+ > = ({
53
+ method,
54
+ attribute,
55
+ isMultiple = false,
56
+ searchResults = 50,
57
+ minSearch = 3,
58
+ timerSearch = 500,
59
+ resource,
60
+ selectLabel,
61
+ renderText,
62
+ transformData = (val: any) => val,
63
+ queryFilter = "q",
64
+ defaultValues = undefined,
65
+ isEmpty = false,
66
+ filter,
67
+ isOptionEqualToValue = undefined,
68
+ viewAttribute = null,
69
+ valueKeyId = "id",
70
+ useBaseAttributeName = false,
71
+ useListAttributeForController = false,
72
+ transformOption,
73
+ }) => {
74
+ const { setValue, watch } = useFormContext();
75
+ const record = useRecordContext();
76
+
77
+ // Get the correct attribute names
78
+ const saveAttributeName = attribute.attribute; // e.g., 'category_ids' - what gets saved
79
+ const displayAttributeName = attribute.listAttribute || attribute.attribute; // e.g., 'categories' - what gets displayed
80
+
81
+ console.log('Attribute config:', {
82
+ saveAttributeName,
83
+ displayAttributeName,
84
+ record: record ? {
85
+ [saveAttributeName]: record[saveAttributeName],
86
+ [displayAttributeName]: record[displayAttributeName]
87
+ } : null
88
+ });
89
+
90
+ // Component state
91
+ const [open, setOpen] = useState(false);
92
+ const [fullyLoaded, setFullyLoaded] = useState(false);
93
+ const [parsedOptions, setParsedOptions] = useState<any[]>([]);
94
+ const [q, setQ] = useState("");
95
+ const [selectedOptions, setSelectedOptions] = useState<any[]>([]);
96
+ const [timeOutSearch, setTimeOutSearch] = useState<any>(undefined);
97
+
98
+ // Watch current form value (the IDs that get saved)
99
+ const currentFormValue = watch(saveAttributeName);
100
+
101
+ const _transformOption = (option: any, caller: string) => {
102
+ if (typeof transformOption === "function") {
103
+ return transformOption(option, parsedOptions, caller);
104
+ }
105
+ return option;
106
+ };
107
+
108
+ // Get includes for the API call - use the save attribute (IDs)
109
+ const getIncludes = () => {
110
+ let recordValue = record?.[saveAttributeName]; // category_ids
111
+
112
+ if (!recordValue) return [];
113
+
114
+ if (!Array.isArray(recordValue)) {
115
+ recordValue = [recordValue];
116
+ }
117
+
118
+ return recordValue.filter(Boolean);
119
+ };
120
+
121
+ const includes = getIncludes();
122
+
123
+ // React Admin useGetList
124
+ const {
125
+ data: resourceSearchResults,
126
+ total: totalSearchResults,
127
+ isLoading: isResourceSearchLoading,
128
+ error: isResourceSearchErrored,
129
+ } = useGetList(resource, {
130
+ meta: { removeSortFilters: true },
131
+ filter: { [queryFilter]: q, ...filter, includes: includes },
132
+ });
133
+
134
+ // Update parsed options when search results change
135
+ useEffect(() => {
136
+ if (resourceSearchResults) {
137
+ const resultsArray = Array.isArray(resourceSearchResults)
138
+ ? resourceSearchResults
139
+ : Object.values(resourceSearchResults);
140
+
141
+ const _parsedOptions = resultsArray.map((ele, index) => ({
142
+ ...ele,
143
+ key: `option-${ele[valueKeyId] || index}`,
144
+ }));
145
+
146
+ setParsedOptions(_parsedOptions);
147
+ }
148
+ }, [resourceSearchResults, valueKeyId]);
149
+
150
+ // Initialize default values
151
+ useEffect(() => {
152
+ if (fullyLoaded) return;
153
+
154
+ console.log('Initializing default values...');
155
+
156
+ let initialSelectedOptions: any[] = [];
157
+ let initialFormValue: any = isMultiple ? [] : null;
158
+
159
+ if (defaultValues === null && record) {
160
+ // Try to get full objects from listAttribute first (e.g., 'categories')
161
+ if (record[displayAttributeName] && Array.isArray(record[displayAttributeName])) {
162
+ initialSelectedOptions = record[displayAttributeName];
163
+ initialFormValue = record[displayAttributeName].map((item: any) => item[valueKeyId]);
164
+ }
165
+ // Fallback to IDs from saveAttribute (e.g., 'category_ids')
166
+ else if (record[saveAttributeName]) {
167
+ const ids = Array.isArray(record[saveAttributeName])
168
+ ? record[saveAttributeName]
169
+ : [record[saveAttributeName]];
170
+ initialFormValue = isMultiple ? ids : ids[0];
171
+ // selectedOptions will be set when parsedOptions are loaded
172
+ }
173
+ } else if (defaultValues !== undefined && defaultValues !== null) {
174
+ if (Array.isArray(defaultValues)) {
175
+ initialSelectedOptions = defaultValues;
176
+ initialFormValue = defaultValues.map((item: any) =>
177
+ typeof item === 'object' ? item[valueKeyId] : item
178
+ );
179
+ } else {
180
+ initialSelectedOptions = [defaultValues];
181
+ initialFormValue = typeof defaultValues === 'object' ? defaultValues[valueKeyId] : defaultValues;
182
+ }
183
+ }
184
+
185
+ console.log('Initial values:', {
186
+ initialSelectedOptions,
187
+ initialFormValue
188
+ });
189
+
190
+ setSelectedOptions(initialSelectedOptions);
191
+ setValue(saveAttributeName, initialFormValue);
192
+ setFullyLoaded(true);
193
+ }, [record, defaultValues, fullyLoaded, saveAttributeName, displayAttributeName, valueKeyId, isMultiple]);
194
+
195
+ // Update selected options when parsedOptions change and we have form values but no selected options
196
+ useEffect(() => {
197
+ if (parsedOptions.length > 0 && selectedOptions.length === 0 && currentFormValue) {
198
+ const formValueArray = Array.isArray(currentFormValue) ? currentFormValue : [currentFormValue];
199
+ const matchedOptions = formValueArray.map(id =>
200
+ parsedOptions.find(option => option[valueKeyId] === id)
201
+ ).filter(Boolean);
202
+
203
+ if (matchedOptions.length > 0) {
204
+ console.log('Setting selected options from parsed options:', matchedOptions);
205
+ setSelectedOptions(matchedOptions);
206
+ }
207
+ }
208
+ }, [parsedOptions, selectedOptions, currentFormValue, valueKeyId]);
209
+
210
+ // Debounced search
211
+ const debounce = (fn: Function, delay = 500) => {
212
+ clearTimeout(timeOutSearch);
213
+ setTimeOutSearch(
214
+ setTimeout(() => {
215
+ fn();
216
+ }, delay)
217
+ );
218
+ };
219
+
220
+ // Update field value
221
+ const updateFieldValue = (newSelectedOptions: any[]) => {
222
+ console.log('Updating field value:', newSelectedOptions);
223
+
224
+ setSelectedOptions(newSelectedOptions || []);
225
+
226
+ // Save the IDs to the form
227
+ const idsToSave = (newSelectedOptions || []).map(option => option[valueKeyId]);
228
+ const valueToSave = isMultiple ? idsToSave : (idsToSave[0] || null);
229
+
230
+ console.log('Saving to form:', { attribute: saveAttributeName, value: valueToSave });
231
+ setValue(saveAttributeName, valueToSave);
232
+ };
233
+
234
+ if (!fullyLoaded) return <LoadingIndicator />;
235
+
236
+ return (
237
+ <>
238
+ <Autocomplete
239
+ key={`autocomplete-${saveAttributeName}`}
240
+ multiple={isMultiple}
241
+ loading={isResourceSearchLoading}
242
+ fullWidth
243
+ freeSolo={false}
244
+ options={parsedOptions}
245
+ open={open}
246
+ onOpen={() => setOpen(true)}
247
+ onClose={() => setOpen(false)}
248
+ value={isMultiple ? selectedOptions : (selectedOptions[0] || null)}
249
+ isOptionEqualToValue={isOptionEqualToValue}
250
+ autoHighlight
251
+ getOptionLabel={(option: any) => {
252
+ if (!option) return '';
253
+ return renderText(_transformOption(option, "optionlabel"), "optionlabel") || '';
254
+ }}
255
+ renderOption={(props, option: any) => {
256
+ const { key, ...newProps } = props as any;
257
+ return (
258
+ <Box
259
+ key={option.key || `option-${option[valueKeyId]}`}
260
+ component="li"
261
+ sx={{ "& > img": { mr: 2, flexShrink: 0 } }}
262
+ {...newProps}
263
+ >
264
+ {renderText(_transformOption(option, "option"), "option")}
265
+ </Box>
266
+ );
267
+ }}
268
+ renderTags={(tagValue, getTagProps) => {
269
+ return tagValue.map((option, index) => (
270
+ <Chip
271
+ key={`chip-${option[valueKeyId] || index}`}
272
+ label={renderText(_transformOption(option, "chip"), "chip")}
273
+ {...getTagProps({ index })}
274
+ />
275
+ ));
276
+ }}
277
+ onChange={(event: any, rawValue) => {
278
+ const newValue = isMultiple ? rawValue : (rawValue ? [rawValue] : []);
279
+ updateFieldValue(newValue);
280
+ }}
281
+ renderInput={(params) => (
282
+ <TextField
283
+ {...params}
284
+ label={selectLabel}
285
+ variant="outlined"
286
+ onChange={(ev) => {
287
+ const searchValue = ev.target.value;
288
+ if (
289
+ searchValue &&
290
+ searchValue !== "" &&
291
+ searchValue.length >= minSearch
292
+ ) {
293
+ debounce(() => setQ(searchValue), timerSearch);
294
+ }
295
+ }}
296
+ InputProps={{
297
+ ...params.InputProps,
298
+ autoComplete: "new-password",
299
+ endAdornment: (
300
+ <>
301
+ {isResourceSearchLoading && (
302
+ <CircularProgress color="inherit" size={20} />
303
+ )}
304
+ {params.InputProps.endAdornment}
305
+ </>
306
+ ),
307
+ }}
308
+ />
309
+ )}
310
+ />
311
+ </>
312
+ );
313
+ };
314
+
315
+ export const SearchableSelectChipsControlRecordContextView: React.FC<IDashAutoAdminCustomFieldComponent> = ({attribute,
316
+ resourceConfig }) => {
317
+ const record = useRecordContext<any>();
318
+ const values = Array.isArray(record[attribute.listAttribute || attribute.attribute]) ? record[attribute.listAttribute || attribute.attribute] : [record[attribute.listAttribute || attribute.attribute]];
319
+ return (<>
320
+ {record && (
321
+ <Box sx={{ width: "100%" }}>
322
+ {values.map((item: any) => (
323
+ <Chip
324
+ key={item.id}
325
+ label={item.name}
326
+ sx={{ margin: 0.5 }}
327
+ />
328
+ ))}
329
+ </Box>
330
+ )}
331
+ </>);
332
+ };
333
+
334
+
335
+ const SearchableSelectChipsControlRecordContext: React.FC<
336
+ IDashAutoAdminCustomFieldComponent & ISearchableSelectChipsControlRecordContext
337
+ > = ({ method, attribute, resourceConfig,...props }) => {
338
+ switch (method) {
339
+ case "edit":
340
+ case "create":
341
+ return (
342
+ <SearchableSelectChipsControlRecordContextEdit
343
+ resourceConfig={resourceConfig}
344
+ attribute={attribute}
345
+ method={method}
346
+ {...props}
347
+ />
348
+ );
349
+ case "view":
350
+ case "list":
351
+ return <SearchableSelectChipsControlRecordContextView
352
+ resourceConfig={resourceConfig}
353
+ attribute={attribute}
354
+ method={method}
355
+ {...props}
356
+ />
357
+ default:
358
+ return null;
359
+ }
360
+ };
361
+
362
+ export default SearchableSelectChipsControlRecordContext;
@@ -0,0 +1,321 @@
1
+ /**
2
+ * React Auto Admin - SearchableSelect
3
+ *
4
+ * @author Franisco Aranda <francisco.aranda@dash.cl> <faranda@gmail.com>
5
+ * @author Omar Muñoz <omar.munoz@dash.cl>
6
+ */
7
+
8
+ import {
9
+ Autocomplete,
10
+ AutocompleteProps,
11
+ Box,
12
+ CircularProgress,
13
+ TextField,
14
+ } from '@mui/material';
15
+ import React, { JSX, useEffect, useState } from 'react';
16
+ import { useController } from 'react-hook-form';
17
+ import { useGetList } from 'react-admin';
18
+ import { isArray } from 'lodash';
19
+
20
+ export interface ISearchableSelect<
21
+ T,
22
+ Multiple extends boolean | undefined,
23
+ DisableClearable extends boolean | undefined,
24
+ FreeSolo extends boolean | undefined = undefined,
25
+ > extends Omit<AutocompleteProps<T | string, Multiple, DisableClearable, FreeSolo>, 'renderInput' | 'options'> {
26
+ /** Options are ingored because they are calculated within this component */
27
+ /** renderInput is ignored because is implemented privately within this component */
28
+
29
+ name: string;
30
+ /** url path to the resource, a getList or getForSelect e.g: 'system/user' */
31
+ resource: string;
32
+ /** label for the select */
33
+ selectLabel: string;
34
+ /** Optional function to parse the item and return a string or JSX.Element to show in the selector list*/
35
+ renderText?: (object: any) => string | JSX.Element;
36
+ /** Optional function will transform the item at the moment of field.onChange */
37
+ transformData?: (val: any) => any;
38
+ /** Optional defaultValue, or defaultValues array */
39
+ defaultValues?: any;
40
+ /** Specify if the component must handle multiple options */
41
+ isMultiple?: boolean;
42
+ /** @deprecated pagination, must be sent on filters */
43
+ //pagination?: boolean,
44
+ /** Min character input to perform search */
45
+ minSearch?: number;
46
+ /** @deprecated searchResults, must be sent on filters as perPage */
47
+ //searchResults?: number;
48
+ /** Search after x ms */
49
+ timerSearch?: number;
50
+ /** isEmpty - TODO: this flow is not tested. */
51
+ isEmpty?: boolean;
52
+ /** filter - optional parameters for the axios query */
53
+ filter?: { [x: string]: any };
54
+ /** name of the query parameter, usually just 'q' or 'search' */
55
+ queryFilter?: string;
56
+ /**
57
+ * The default function will compare the raw values equality
58
+ * the edge case is when a transformData is implemented, then the isOptionEqualToValue will have to compare to the transformation output.
59
+ * e.g: isOptionEqualToValue={(option, value) => value === option.id }
60
+ transformData={(data) => data?.id || null}
61
+ */
62
+ isOptionEqualToValue?: (option, value) => boolean;
63
+ /**
64
+ * if not specified it will render the raw value for options
65
+ * if the response is not just a string or number array, you will have to specify which attribute from the option object you want to render
66
+ */
67
+ renderAttribute?: string;
68
+ /** elementKeyID, Advanced usage, by default id, specify the attribute from the option object representing the key or id */
69
+ elementKeyId?: string;
70
+ /** onChange */
71
+ onChange?: (value: any) => void;
72
+ /* renderInputTextFieldProps - Partial<TextFieldProps> */
73
+ renderInputTextFieldProps?: any; // It should be: Partial<TextFieldProps>
74
+ }
75
+
76
+ const SearchableSelect = function WrappedAutoComplete<
77
+ T,
78
+ Multiple extends boolean | undefined = undefined,
79
+ DisableClearable extends boolean | undefined = undefined,
80
+ FreeSolo extends boolean | undefined = undefined,
81
+ >({ ...props }: ISearchableSelect<T, Multiple, DisableClearable, FreeSolo>) {
82
+ const {
83
+ isMultiple = false,
84
+ //pagination = true,
85
+ //searchResults = 50,
86
+ minSearch = 3,
87
+ timerSearch = 500,
88
+ resource,
89
+ selectLabel,
90
+ renderText,
91
+ transformData,
92
+ queryFilter = 'q',
93
+ defaultValues = undefined,
94
+ elementKeyId = 'id',
95
+ isEmpty = false,
96
+ filter,
97
+ isOptionEqualToValue = (option, value) => value === option,
98
+ renderAttribute = null,
99
+ name,
100
+ onChange,
101
+ renderInputTextFieldProps,
102
+ //..._rest
103
+ } = props;
104
+ //const element_key = "system_marketplace_category_id";
105
+ const [open, setOpen] = useState(false);
106
+ /* parsedOptions se setea null, esto para que el control no aparezca hasta que se obtenga la data */
107
+ const [parsedOptions, setParsedOptions] = useState(null);
108
+ const [q, setQ] = useState('');
109
+
110
+ const field = useController({
111
+ name: name,
112
+ defaultValue: defaultValues ? defaultValues : null,
113
+ });
114
+ const includes =
115
+ defaultValues && isArray(defaultValues)
116
+ ? defaultValues.map((element) =>
117
+ element[elementKeyId]
118
+ ? element[elementKeyId]
119
+ : element)
120
+ : defaultValues;
121
+
122
+ const {
123
+ data: resourceData,
124
+ isLoading,
125
+ } = useGetList(
126
+ resource,
127
+ {
128
+ filter: { [queryFilter]: q, ...filter, includes: includes },
129
+ },
130
+ { refetchOnWindowFocus: false },
131
+ );
132
+
133
+ useEffect(() => {
134
+ if (resourceData && !isLoading) {
135
+ //Le agrega el key, para evitar el warning de react.
136
+ //setParsedOptions([...parsedOptions,...resourceData.map(ele => { return {...ele,key:"option"+ele.id}})])
137
+ if (parsedOptions)
138
+ setParsedOptions([
139
+ ...parsedOptions,
140
+ ...resourceData.map((ele) => {
141
+ return { ...ele, key: 'option' + ele.id };
142
+ }),
143
+ ]);
144
+ setParsedOptions(
145
+ resourceData.map((ele) => {
146
+ return { ...ele, key: 'option' + ele.id };
147
+ }),
148
+ );
149
+ } /*else {
150
+ // Setea la data a un arreglo vacío, ya que si es null, se ocultará el componente.
151
+ //setParsedOptions([]);
152
+ }*/
153
+ return () => setParsedOptions([]);
154
+ // TODO! reset callback, empty parsedOptions.
155
+ }, [resourceData, isLoading]);
156
+
157
+ const [timeOutSearch, setTimeOutSearch] = useState(undefined);
158
+
159
+ const debounce = (fn: Function, delay = 500) => {
160
+ clearTimeout(timeOutSearch);
161
+ setTimeOutSearch(
162
+ setTimeout(() => {
163
+ fn();
164
+ }, delay),
165
+ );
166
+ };
167
+
168
+ const calculateDefaultValue = () => {
169
+ let value = null;
170
+ if (isMultiple) value = [];
171
+ if (isMultiple && field.field.value && Array.isArray(field.field.value))
172
+ value = field.field.value;
173
+ if (isMultiple && field.field.value && !Array.isArray(field.field.value))
174
+ value = [field.field.value];
175
+ if (!isMultiple && field.field.value) value = field.field.value;
176
+ return value;
177
+ };
178
+
179
+ const defaultValue = calculateDefaultValue();
180
+
181
+ return (
182
+ <>
183
+ {isLoading && !parsedOptions ? (
184
+ <CircularProgress color='inherit' size={20} />
185
+ ) : (
186
+ <></>
187
+ )}
188
+ {parsedOptions && (
189
+ <Autocomplete
190
+ key={'autocomplete-' + name}
191
+ multiple={isMultiple}
192
+ loading={isLoading && open}
193
+ //sx={{ width: 373 }}
194
+ fullWidth
195
+ freeSolo={true}
196
+ //options={!resourceData ? [] : !defaultValues ? resourceData : resourceData}
197
+ //options={resourceData ? resourceData.map(ele => { return {...ele,key:ele.id}} ) : []}
198
+ options={parsedOptions}
199
+ open={open}
200
+ onOpen={() => setOpen(true)}
201
+ onClose={() => setOpen(false)}
202
+ /*defaultValue={field.field?.value && field.field.value.map(item => parsedOptions.find(
203
+ (element) => {
204
+
205
+ return element.id === item.id
206
+ }))}*/
207
+ //value={isOptionEqualToValue && parsedOptions ? parsedOptions.find(ele => isOptionEqualToValue(ele,field.field.value))[renderAttribute] : field.field.value}
208
+
209
+ defaultValue={defaultValue}
210
+ //value={field.field.value}
211
+
212
+ isOptionEqualToValue={isOptionEqualToValue}
213
+ autoHighlight
214
+ //{...(!isEmpty && { value: field.field.value })}
215
+ //{...(isEmpty && { renderTags: (value, getTagProps) => <></> })}
216
+ // 8
217
+
218
+ getOptionLabel={(option: any) => {
219
+ const _option = !Array.isArray(option) // si es un numero como 'id', busca la opción en los valores con isOptionEqualToValue, sino devuelve el objeto original.
220
+ ? parsedOptions &&
221
+ parsedOptions.find((ele) =>
222
+ isOptionEqualToValue(ele, field.field.value))
223
+ : option;
224
+ if (!_option) return q; //es posbile que no se haya encontrado en parsedOptions o que parsedOptions aún no esté definido.
225
+ const r =
226
+ renderAttribute && !renderText
227
+ ? _option[renderAttribute]
228
+ : renderText(_option);
229
+ /*console.log(
230
+ "getOptionLabel",
231
+ parsedOptions.find((ele) =>
232
+ isOptionEqualToValue(ele, field.field.value)
233
+ ),
234
+ r
235
+ );*/
236
+ if (!r) return q;
237
+ return r;
238
+ }}
239
+ //getOptionLabel={(option: any) => option}
240
+
241
+ renderOption={(p, option: any) => {
242
+ const { key, ...newProps } = p as any;
243
+
244
+ return (
245
+ <Box
246
+ key={option.key}
247
+ component='li'
248
+ sx={{ '& > img': { mr: 2, flexShrink: 0 } }}
249
+ {...newProps}
250
+ >
251
+ {renderAttribute && !renderText
252
+ ? option[renderAttribute]
253
+ : renderText(option)}
254
+ </Box>
255
+ );
256
+ }}
257
+ onChange={(event: any, rawValue) => {
258
+ //console.log("onChange", event, rawValue);
259
+ let newValue = rawValue;
260
+
261
+ if (Array.isArray(rawValue)) {
262
+ newValue = isEmpty
263
+ ? [...rawValue, ...field.field.value]
264
+ : [...new Map(newValue.map((v) => [v.id, v])).values()];
265
+ }
266
+ if (transformData) {
267
+ field.field.onChange(transformData(newValue));
268
+ } else {
269
+ field.field.onChange(newValue);
270
+ }
271
+
272
+ if (onChange) { onChange(newValue); }
273
+ }}
274
+ renderInput={(params) => {
275
+ //console.log("renderInput", params.id);
276
+ return (
277
+ <TextField
278
+ key={params.id}
279
+ {...params}
280
+ label={selectLabel}
281
+ variant='outlined'
282
+ onChange={(ev) => {
283
+ // dont fire API if the user delete or not entered anything
284
+ const searchValue = ev.target.value;
285
+ //onSearch={(searchValue) => {}
286
+
287
+ if (
288
+ searchValue &&
289
+ searchValue !== '' &&
290
+ searchValue.length >= minSearch
291
+ )
292
+ debounce(() => setQ(searchValue), timerSearch);
293
+
294
+ /*if (ev.target.value !== "" || ev.target.value !== null) {
295
+ setQ(ev.target.value);
296
+ }*/
297
+ }}
298
+ //onBlur={() => setQ('')}
299
+ InputProps={{
300
+ ...params.InputProps,
301
+ autoComplete: 'new-password',
302
+ endAdornment: (
303
+ <>
304
+ {isLoading ? (
305
+ <CircularProgress color='inherit' size={20} />
306
+ ) : null}
307
+ {params.InputProps.endAdornment}
308
+ </>
309
+ ),
310
+ }}
311
+ {...renderInputTextFieldProps}
312
+ />
313
+ );
314
+ }}
315
+ />
316
+ )}
317
+ </>
318
+ );
319
+ };
320
+
321
+ export default SearchableSelect;