@blaze-cms/plugin-data-ui 0.147.0-rc-eagle.2 → 0.147.0-rc-eagle.6
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 +9 -2
- package/lib/components/EntityManager/Entity/Entity.js.map +1 -1
- package/lib/components/ListingTable/SearchFilter/RelationMultiSelectFilter.js +181 -0
- package/lib/components/ListingTable/SearchFilter/RelationMultiSelectFilter.js.map +1 -0
- package/lib/components/ListingTable/SearchFilter/SearchContainer.js +338 -49
- package/lib/components/ListingTable/SearchFilter/SearchContainer.js.map +1 -1
- package/lib/components/ListingTable/SearchFilter/SearchFilter.js +6 -2
- package/lib/components/ListingTable/SearchFilter/SearchFilter.js.map +1 -1
- package/lib/components/ListingTable/SearchFilter/helpers.js +41 -8
- package/lib/components/ListingTable/SearchFilter/helpers.js.map +1 -1
- package/lib/index.js +7 -0
- package/lib/index.js.map +1 -1
- package/lib/utils/get-default-query-params.js +17 -5
- package/lib/utils/get-default-query-params.js.map +1 -1
- package/lib-es/components/EntityManager/Entity/Entity.js.map +1 -1
- package/lib-es/components/ListingTable/SearchFilter/RelationMultiSelectFilter.js +173 -0
- package/lib-es/components/ListingTable/SearchFilter/RelationMultiSelectFilter.js.map +1 -0
- package/lib-es/components/ListingTable/SearchFilter/SearchContainer.js +340 -51
- package/lib-es/components/ListingTable/SearchFilter/SearchContainer.js.map +1 -1
- package/lib-es/components/ListingTable/SearchFilter/SearchFilter.js +7 -3
- package/lib-es/components/ListingTable/SearchFilter/SearchFilter.js.map +1 -1
- package/lib-es/components/ListingTable/SearchFilter/helpers.js +39 -8
- package/lib-es/components/ListingTable/SearchFilter/helpers.js.map +1 -1
- package/lib-es/index.js +1 -0
- package/lib-es/index.js.map +1 -1
- package/lib-es/utils/get-default-query-params.js +17 -5
- package/lib-es/utils/get-default-query-params.js.map +1 -1
- package/package.json +11 -12
- package/src/components/EntityManager/Entity/Entity.js +2 -3
- package/src/components/ListingTable/SearchFilter/RelationMultiSelectFilter.js +160 -0
- package/src/components/ListingTable/SearchFilter/SearchContainer.js +396 -71
- package/src/components/ListingTable/SearchFilter/SearchFilter.js +5 -2
- package/src/components/ListingTable/SearchFilter/helpers.js +55 -8
- package/src/index.js +2 -0
- package/src/utils/get-default-query-params.js +21 -9
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import React, { useState, useMemo, useEffect, useCallback } from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import Select from '@blaze-react/select';
|
|
4
|
+
import MultiSelect from '@blaze-react/multiselect';
|
|
5
|
+
import debounce from 'lodash.debounce';
|
|
6
|
+
import unionBy from 'lodash.unionby';
|
|
7
|
+
|
|
4
8
|
import { useQuery, useApolloClient } from '@apollo/client';
|
|
5
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
getDynamicQuery,
|
|
11
|
+
getQuery,
|
|
12
|
+
fetchFilterByDataFromElastic,
|
|
13
|
+
fetchAllEntities
|
|
14
|
+
} from '@blaze-cms/admin-ui-utils';
|
|
15
|
+
import { getStringTypeProps } from '@blaze-cms/utils';
|
|
16
|
+
|
|
6
17
|
import { BsArrowCounterclockwise, BsSearch } from 'react-icons/bs';
|
|
7
|
-
import { updateListFilters, buildQueryFields } from './helpers';
|
|
18
|
+
import { updateListFilters, buildQueryFields, getTidyLabel } from './helpers';
|
|
19
|
+
import RelationMultiSelectFilter from './RelationMultiSelectFilter';
|
|
8
20
|
import { NOOP_QUERY } from './querys';
|
|
9
21
|
import { ENTER_KEY, DEFAULT_FILTER_OPTION_LABEL } from '../../../constants';
|
|
10
22
|
|
|
@@ -12,16 +24,49 @@ const SearchContainer = ({
|
|
|
12
24
|
setListFilters,
|
|
13
25
|
keywordFiltersToUse = [],
|
|
14
26
|
selectFiltersToDisplay = [],
|
|
27
|
+
multiSelectFiltersToDisplay = [],
|
|
15
28
|
schema,
|
|
16
|
-
index
|
|
29
|
+
index,
|
|
30
|
+
values
|
|
17
31
|
}) => {
|
|
18
|
-
const
|
|
32
|
+
const client = useApolloClient();
|
|
33
|
+
|
|
19
34
|
const [searchTerm, setSearchTerm] = useState('');
|
|
20
35
|
const [selectedFilters, setSelectedFilters] = useState({});
|
|
21
36
|
const [selectOptions, setSelectOptions] = useState([]);
|
|
37
|
+
const [multiSelectOptions, setMultiSelectOptions] = useState([]);
|
|
38
|
+
const [allEntitySchemas, setAllEntitySchemas] = useState([]);
|
|
22
39
|
|
|
23
40
|
const displaySearchFilter = keywordFiltersToUse.length > 0;
|
|
24
41
|
const displaySelectFilters = selectFiltersToDisplay.length > 0;
|
|
42
|
+
const displayMultiSelectFilters = multiSelectFiltersToDisplay.length > 0;
|
|
43
|
+
const displayAggregations = displaySelectFilters || displayMultiSelectFilters;
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
(async () => {
|
|
47
|
+
try {
|
|
48
|
+
const all = await fetchAllEntities({ client });
|
|
49
|
+
setAllEntitySchemas(all || []);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
setAllEntitySchemas([]);
|
|
52
|
+
}
|
|
53
|
+
})();
|
|
54
|
+
}, [client]);
|
|
55
|
+
|
|
56
|
+
const multiSelectPropertySet = useMemo(
|
|
57
|
+
() => new Set(multiSelectFiltersToDisplay.map(([property]) => property)),
|
|
58
|
+
[multiSelectFiltersToDisplay]
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const relationMultiSelectFilters = useMemo(
|
|
62
|
+
() => multiSelectFiltersToDisplay.filter(([, config]) => config && config.relation),
|
|
63
|
+
[multiSelectFiltersToDisplay]
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const nonRelationMultiSelectFilters = useMemo(
|
|
67
|
+
() => multiSelectFiltersToDisplay.filter(([, config]) => !config || !config.relation),
|
|
68
|
+
[multiSelectFiltersToDisplay]
|
|
69
|
+
);
|
|
25
70
|
|
|
26
71
|
const { keywordSearchProperties, keywordSearchLabels } = useMemo(() => {
|
|
27
72
|
const labels = [];
|
|
@@ -36,86 +81,141 @@ const SearchContainer = ({
|
|
|
36
81
|
}, [keywordFiltersToUse]);
|
|
37
82
|
|
|
38
83
|
const { gqlFields, rawQuery } = useMemo(
|
|
39
|
-
() =>
|
|
40
|
-
|
|
84
|
+
() =>
|
|
85
|
+
buildQueryFields([...selectFiltersToDisplay, ...nonRelationMultiSelectFilters], schema.id),
|
|
86
|
+
[selectFiltersToDisplay, nonRelationMultiSelectFilters, schema.id]
|
|
41
87
|
);
|
|
42
|
-
|
|
88
|
+
|
|
89
|
+
const action = displayAggregations
|
|
43
90
|
? getDynamicQuery('ADMIN_SEARCH')([schema], gqlFields, false, index)
|
|
44
91
|
: NOOP_QUERY;
|
|
45
92
|
|
|
46
93
|
const { data } = useQuery(action, {
|
|
47
|
-
skip: !
|
|
94
|
+
skip: !displayAggregations,
|
|
48
95
|
variables: { where: rawQuery, limit: 0 },
|
|
49
96
|
fetchPolicy: 'cache-and-network'
|
|
50
97
|
});
|
|
51
98
|
|
|
52
|
-
const
|
|
99
|
+
const buildRelationsIndex = useCallback(schemaObj => {
|
|
100
|
+
const items = [];
|
|
101
|
+
|
|
102
|
+
if (schemaObj.properties) {
|
|
103
|
+
Object.entries(schemaObj.properties).forEach(([propName, definition]) => {
|
|
104
|
+
const { relation } = definition || {};
|
|
105
|
+
if (!relation) return;
|
|
106
|
+
|
|
107
|
+
const { localField: relationLocalField, entityIdentifier } = relation;
|
|
108
|
+
|
|
109
|
+
const localField = relationLocalField || propName;
|
|
110
|
+
|
|
111
|
+
if (localField) items.push({ localField, entityIdentifier });
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (Array.isArray(schemaObj.relations)) {
|
|
116
|
+
schemaObj.relations.forEach(relation => {
|
|
117
|
+
if (!relation.localField) return;
|
|
118
|
+
|
|
119
|
+
const { localField, entityIdentifier } = relation;
|
|
120
|
+
|
|
121
|
+
items.push({ localField, entityIdentifier });
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const byKey = new Map();
|
|
126
|
+
items.forEach(item => {
|
|
127
|
+
const key = `${item.localField}|${item.entityIdentifier || ''}`;
|
|
128
|
+
if (!byKey.has(key)) byKey.set(key, item);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return Array.from(byKey.values());
|
|
132
|
+
}, []);
|
|
133
|
+
|
|
134
|
+
const relationsIndex = useMemo(() => buildRelationsIndex(schema), [schema, buildRelationsIndex]);
|
|
135
|
+
|
|
136
|
+
const stringTypeProps = useMemo(
|
|
137
|
+
() => getStringTypeProps(schema.id, relationsIndex, allEntitySchemas),
|
|
138
|
+
[schema.id, relationsIndex, allEntitySchemas]
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const aggregationsData = useMemo(() => {
|
|
53
142
|
const { searchResults: { rawResults: { aggregations = {} } = {} } = {} } = data || {};
|
|
54
|
-
const
|
|
143
|
+
const processed = [];
|
|
55
144
|
Object.entries(aggregations).forEach(([key, { buckets }]) => {
|
|
56
|
-
|
|
57
|
-
Array.isArray(buckets) ? [key, buckets.map(
|
|
145
|
+
processed.push(
|
|
146
|
+
Array.isArray(buckets) ? [key, buckets.map(bucket => bucket.key).filter(Boolean)] : []
|
|
58
147
|
);
|
|
59
148
|
});
|
|
60
|
-
|
|
61
|
-
return proccessedData;
|
|
149
|
+
return processed;
|
|
62
150
|
}, [data]);
|
|
63
151
|
|
|
152
|
+
const findFilterConfig = useCallback(
|
|
153
|
+
property =>
|
|
154
|
+
selectFiltersToDisplay.find(([prop]) => prop === property)?.[1] ||
|
|
155
|
+
multiSelectFiltersToDisplay.find(([prop]) => prop === property)?.[1],
|
|
156
|
+
[selectFiltersToDisplay, multiSelectFiltersToDisplay]
|
|
157
|
+
);
|
|
158
|
+
|
|
64
159
|
useEffect(() => {
|
|
65
|
-
if (!
|
|
160
|
+
if (!aggregationsData.length) {
|
|
66
161
|
setSelectOptions([]);
|
|
162
|
+
setMultiSelectOptions([]);
|
|
67
163
|
return;
|
|
68
164
|
}
|
|
69
165
|
|
|
70
166
|
(async () => {
|
|
71
167
|
const results = await Promise.all(
|
|
72
|
-
|
|
168
|
+
aggregationsData.map(async ([property, ids]) => {
|
|
73
169
|
try {
|
|
74
|
-
if (!ids || !ids.length) return
|
|
170
|
+
if (!ids || !ids.length) return null;
|
|
75
171
|
|
|
76
|
-
const
|
|
77
|
-
|
|
172
|
+
const config = findFilterConfig(property);
|
|
173
|
+
if (!config) return null;
|
|
78
174
|
|
|
79
|
-
|
|
175
|
+
const { adminListingOptions: { tidyLabel } = {} } = config;
|
|
80
176
|
|
|
81
|
-
if (!
|
|
177
|
+
if (!config.relation) {
|
|
178
|
+
const baseOptions = ids.map(value => [value, getTidyLabel(value, tidyLabel)]);
|
|
82
179
|
return {
|
|
83
|
-
|
|
84
|
-
label:
|
|
85
|
-
|
|
180
|
+
property,
|
|
181
|
+
label: config.label,
|
|
182
|
+
type: multiSelectPropertySet.has(property) ? 'multi' : 'single',
|
|
183
|
+
options: baseOptions,
|
|
184
|
+
selected: values && values[property]
|
|
86
185
|
};
|
|
87
186
|
}
|
|
88
|
-
|
|
187
|
+
|
|
188
|
+
const schemaQuery = getQuery('GET_ENTITY_SCHEMA');
|
|
89
189
|
const {
|
|
90
190
|
data: {
|
|
91
191
|
getEntitySchemas: [relationSchema]
|
|
92
192
|
}
|
|
93
|
-
} = await
|
|
94
|
-
query,
|
|
95
|
-
variables: { identifier:
|
|
193
|
+
} = await client.query({
|
|
194
|
+
query: schemaQuery,
|
|
195
|
+
variables: { identifier: config.relation.entityIdentifier }
|
|
96
196
|
});
|
|
97
197
|
|
|
98
198
|
const {
|
|
99
199
|
data: { result: selected = [] }
|
|
100
|
-
} = await
|
|
200
|
+
} = await client
|
|
101
201
|
.query({
|
|
102
202
|
query: getDynamicQuery('GET_ALL_ENTITIES')(relationSchema),
|
|
103
203
|
variables: {
|
|
104
|
-
where: {
|
|
105
|
-
id: {
|
|
106
|
-
_in: ids
|
|
107
|
-
}
|
|
108
|
-
},
|
|
204
|
+
where: { id: { _in: ids } },
|
|
109
205
|
sort: [{ property: 'name', direction: 'asc' }]
|
|
110
206
|
}
|
|
111
207
|
})
|
|
112
208
|
// eslint-disable-next-line no-console
|
|
113
|
-
.catch(
|
|
209
|
+
.catch(error => console.error(error));
|
|
210
|
+
|
|
211
|
+
const baseOptions = selected.map(item => [item.id, getTidyLabel(item.name, tidyLabel)]);
|
|
114
212
|
|
|
115
213
|
return {
|
|
116
|
-
|
|
117
|
-
label:
|
|
118
|
-
|
|
214
|
+
property,
|
|
215
|
+
label: config.label || config.relation.label,
|
|
216
|
+
type: multiSelectPropertySet.has(property) ? 'multi' : 'single',
|
|
217
|
+
options: baseOptions,
|
|
218
|
+
selected: values && values[property]
|
|
119
219
|
};
|
|
120
220
|
} catch {
|
|
121
221
|
return null;
|
|
@@ -123,50 +223,215 @@ const SearchContainer = ({
|
|
|
123
223
|
})
|
|
124
224
|
);
|
|
125
225
|
|
|
126
|
-
|
|
226
|
+
const valid = results.filter(Boolean);
|
|
227
|
+
|
|
228
|
+
setSelectOptions(
|
|
229
|
+
valid
|
|
230
|
+
.filter(option => option.type === 'single')
|
|
231
|
+
.map(({ property, label, options, selected }) => ({
|
|
232
|
+
id: property,
|
|
233
|
+
label,
|
|
234
|
+
options,
|
|
235
|
+
selected
|
|
236
|
+
}))
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
setMultiSelectOptions(
|
|
240
|
+
valid
|
|
241
|
+
.filter(option => option.type === 'multi')
|
|
242
|
+
.map(({ property, label, options, selected }) => ({
|
|
243
|
+
id: property,
|
|
244
|
+
label,
|
|
245
|
+
items: options.map(([id, name]) => ({ id, name })),
|
|
246
|
+
selected
|
|
247
|
+
}))
|
|
248
|
+
);
|
|
127
249
|
})();
|
|
128
|
-
}, [
|
|
250
|
+
}, [client, aggregationsData, findFilterConfig, multiSelectPropertySet, values]);
|
|
129
251
|
|
|
130
252
|
const updateFilters = useCallback(
|
|
131
253
|
(term, filters) => updateListFilters(term, filters, setListFilters, keywordSearchProperties),
|
|
132
254
|
[setListFilters, keywordSearchProperties]
|
|
133
255
|
);
|
|
134
256
|
|
|
257
|
+
const debouncedUpdateFilters = useMemo(
|
|
258
|
+
() => debounce((term, filters) => updateFilters(term, filters), 300),
|
|
259
|
+
[updateFilters]
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
useEffect(() => () => debouncedUpdateFilters.cancel(), [debouncedUpdateFilters]);
|
|
263
|
+
|
|
135
264
|
const handleSearch = useCallback(
|
|
136
265
|
() => updateFilters(searchTerm, selectedFilters),
|
|
137
266
|
[searchTerm, selectedFilters, updateFilters]
|
|
138
267
|
);
|
|
139
|
-
|
|
268
|
+
|
|
269
|
+
const handleSearchChange = useCallback(({ target: { value } }) => setSearchTerm(value), []);
|
|
270
|
+
|
|
140
271
|
const handleEnterKey = useCallback(
|
|
141
|
-
|
|
142
|
-
if (
|
|
272
|
+
({ key }) => {
|
|
273
|
+
if (key === ENTER_KEY) handleSearch();
|
|
143
274
|
},
|
|
144
275
|
[handleSearch]
|
|
145
276
|
);
|
|
277
|
+
|
|
146
278
|
const handleReset = useCallback(() => {
|
|
147
279
|
setSearchTerm('');
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
280
|
+
debouncedUpdateFilters.cancel();
|
|
281
|
+
setSelectedFilters(prev =>
|
|
282
|
+
Object.keys(prev).reduce((acc, key) => {
|
|
283
|
+
acc[key] = multiSelectPropertySet.has(key) ? [] : DEFAULT_FILTER_OPTION_LABEL;
|
|
284
|
+
return acc;
|
|
285
|
+
}, {})
|
|
153
286
|
);
|
|
154
287
|
updateFilters('', {});
|
|
155
|
-
}, [
|
|
288
|
+
}, [multiSelectPropertySet, updateFilters, debouncedUpdateFilters]);
|
|
289
|
+
|
|
156
290
|
const handleSelect = useCallback(
|
|
157
|
-
(property,
|
|
158
|
-
const { value } = event;
|
|
291
|
+
(property, { value }) => {
|
|
159
292
|
const next = { ...selectedFilters, [property]: value };
|
|
160
293
|
setSelectedFilters(next);
|
|
161
|
-
|
|
294
|
+
debouncedUpdateFilters(searchTerm, next);
|
|
162
295
|
},
|
|
163
|
-
[searchTerm, selectedFilters,
|
|
296
|
+
[searchTerm, selectedFilters, debouncedUpdateFilters]
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
const handleMultiSelect = useCallback(
|
|
300
|
+
(property, ids) => {
|
|
301
|
+
const next = { ...selectedFilters, [property]: ids };
|
|
302
|
+
setSelectedFilters(next);
|
|
303
|
+
debouncedUpdateFilters(searchTerm, next);
|
|
304
|
+
},
|
|
305
|
+
[searchTerm, selectedFilters, debouncedUpdateFilters]
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const findRelationByLocalField = useCallback(
|
|
309
|
+
localField => relationsIndex.find(relation => relation.localField === localField),
|
|
310
|
+
[relationsIndex]
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
const resolveAggProp = useCallback((property, config) => {
|
|
314
|
+
if (!config.relation) return property;
|
|
315
|
+
if (property.includes('.')) return property;
|
|
316
|
+
const localField = config.relation.localField || property;
|
|
317
|
+
return `${localField}.name`;
|
|
318
|
+
}, []);
|
|
319
|
+
|
|
320
|
+
const resolveEntityIdentifier = useCallback(
|
|
321
|
+
(property, config) => {
|
|
322
|
+
if (!config.relation) return undefined;
|
|
323
|
+
if (config.relation.entityIdentifier) return config.relation.entityIdentifier;
|
|
324
|
+
|
|
325
|
+
const localField = property.includes('.')
|
|
326
|
+
? property.split('.')[0]
|
|
327
|
+
: config.relation.localField || property;
|
|
328
|
+
|
|
329
|
+
return findRelationByLocalField(localField).entityIdentifier;
|
|
330
|
+
},
|
|
331
|
+
[findRelationByLocalField]
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
const addEntitySuffixIfNeeded = useCallback((aggProp, entityIdentifier, config) => {
|
|
335
|
+
if (!config.relation || !entityIdentifier) return aggProp;
|
|
336
|
+
return `${aggProp}/${entityIdentifier}`;
|
|
337
|
+
}, []);
|
|
338
|
+
|
|
339
|
+
const makeOnSearchChange = useCallback(
|
|
340
|
+
property =>
|
|
341
|
+
async ({ event: { value }, currentOffset }) => {
|
|
342
|
+
try {
|
|
343
|
+
if (!value || currentOffset) return [];
|
|
344
|
+
|
|
345
|
+
const config = findFilterConfig(property);
|
|
346
|
+
if (!config) return [];
|
|
347
|
+
|
|
348
|
+
let aggProp = resolveAggProp(property, config);
|
|
349
|
+
|
|
350
|
+
if (config.relation && !config.relation.localField && !property.includes('.')) {
|
|
351
|
+
const matchingRelation = findRelationByLocalField(property);
|
|
352
|
+
if (matchingRelation.localField) aggProp = `${matchingRelation.localField}.name`;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const entityIdentifier =
|
|
356
|
+
resolveEntityIdentifier(property, config) || config.relation?.entityIdentifier;
|
|
357
|
+
|
|
358
|
+
const filterByEntry = addEntitySuffixIfNeeded(aggProp, entityIdentifier, config);
|
|
359
|
+
|
|
360
|
+
const currentValues = {
|
|
361
|
+
entity: schema.id,
|
|
362
|
+
filterByProperty: [filterByEntry]
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const aggregations = await fetchFilterByDataFromElastic(
|
|
366
|
+
client,
|
|
367
|
+
currentValues,
|
|
368
|
+
relationsIndex,
|
|
369
|
+
stringTypeProps,
|
|
370
|
+
value
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
const key = aggProp;
|
|
374
|
+
const buckets = aggregations?.[key]?.buckets || [];
|
|
375
|
+
|
|
376
|
+
let items = [];
|
|
377
|
+
if (config.relation) {
|
|
378
|
+
const names = buckets.map(bucket => bucket.key).filter(Boolean);
|
|
379
|
+
if (names.length) {
|
|
380
|
+
const schemaQuery = getQuery('GET_ENTITY_SCHEMA');
|
|
381
|
+
const {
|
|
382
|
+
data: {
|
|
383
|
+
getEntitySchemas: [relationSchema]
|
|
384
|
+
}
|
|
385
|
+
} = await client.query({
|
|
386
|
+
query: schemaQuery,
|
|
387
|
+
variables: { identifier: entityIdentifier }
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
const {
|
|
391
|
+
data: { result: selected = [] }
|
|
392
|
+
} = await client
|
|
393
|
+
.query({
|
|
394
|
+
query: getDynamicQuery('GET_ALL_ENTITIES')(relationSchema),
|
|
395
|
+
variables: {
|
|
396
|
+
where: { name: { _in: names } },
|
|
397
|
+
sort: [{ property: 'name', direction: 'asc' }]
|
|
398
|
+
}
|
|
399
|
+
})
|
|
400
|
+
// eslint-disable-next-line no-console
|
|
401
|
+
.catch(error => console.error(error));
|
|
402
|
+
|
|
403
|
+
items = selected.map(item => ({ id: item.id, name: item.name }));
|
|
404
|
+
}
|
|
405
|
+
} else {
|
|
406
|
+
items = buckets
|
|
407
|
+
.map(bucket => ({ id: bucket.key, name: bucket.key }))
|
|
408
|
+
.filter(option => option.id != null);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const existingItems = multiSelectOptions.find(opt => opt.id === property)?.items || [];
|
|
412
|
+
return unionBy(items, existingItems, 'id');
|
|
413
|
+
} catch (error) {
|
|
414
|
+
return [];
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
[
|
|
418
|
+
client,
|
|
419
|
+
schema.id,
|
|
420
|
+
relationsIndex,
|
|
421
|
+
stringTypeProps,
|
|
422
|
+
multiSelectOptions,
|
|
423
|
+
findFilterConfig,
|
|
424
|
+
resolveAggProp,
|
|
425
|
+
resolveEntityIdentifier,
|
|
426
|
+
addEntitySuffixIfNeeded,
|
|
427
|
+
findRelationByLocalField
|
|
428
|
+
]
|
|
164
429
|
);
|
|
165
430
|
|
|
166
431
|
const placeholderText = `Search ${keywordSearchLabels.join(', ')}`;
|
|
167
432
|
|
|
168
433
|
return (
|
|
169
|
-
<div className="search-container">
|
|
434
|
+
<div className="search-container ms-filter">
|
|
170
435
|
{displaySearchFilter && (
|
|
171
436
|
<div className="search-container__search-input-container">
|
|
172
437
|
<input
|
|
@@ -184,23 +449,81 @@ const SearchContainer = ({
|
|
|
184
449
|
)}
|
|
185
450
|
|
|
186
451
|
{displaySelectFilters &&
|
|
187
|
-
selectOptions.map(({ id, options, label }) =>
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
452
|
+
selectOptions.map(({ id, options, label, selected }) =>
|
|
453
|
+
options && options.length ? (
|
|
454
|
+
<div className="search-container__select-wrapper" key={`select-${id}`}>
|
|
455
|
+
<Select
|
|
456
|
+
label={label}
|
|
457
|
+
id={id}
|
|
458
|
+
name={id}
|
|
459
|
+
onChange={({ value }) => handleSelect(id, { value })}
|
|
460
|
+
options={options}
|
|
461
|
+
value={selectedFilters[id] !== undefined ? selectedFilters[id] : selected}
|
|
462
|
+
defaultTextValue={DEFAULT_FILTER_OPTION_LABEL}
|
|
463
|
+
/>
|
|
464
|
+
</div>
|
|
465
|
+
) : null
|
|
466
|
+
)}
|
|
467
|
+
|
|
468
|
+
{displayMultiSelectFilters &&
|
|
469
|
+
multiSelectOptions.map(({ id, label, items }) => {
|
|
470
|
+
const selectedIds = selectedFilters[id] ? selectedFilters[id] : [];
|
|
471
|
+
const multiSelectData = {
|
|
472
|
+
filterBy: ['name'],
|
|
473
|
+
identification: 'id',
|
|
474
|
+
keyValue: 'name',
|
|
475
|
+
data: items.map(({ id: itemId, name }) => ({
|
|
476
|
+
id: itemId,
|
|
477
|
+
name,
|
|
478
|
+
description: '',
|
|
479
|
+
checked: selectedIds.includes(itemId),
|
|
480
|
+
show: true
|
|
481
|
+
}))
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
return (
|
|
485
|
+
<div className="search-container__select-wrapper" key={`multiselect-${id}`}>
|
|
486
|
+
<MultiSelect
|
|
487
|
+
onChange={({ value, currentOffset }) =>
|
|
488
|
+
makeOnSearchChange(id)({ event: { value }, currentOffset })
|
|
489
|
+
}
|
|
490
|
+
name={id}
|
|
491
|
+
label={label}
|
|
492
|
+
data={multiSelectData}
|
|
493
|
+
limit={10}
|
|
494
|
+
getSelected={({
|
|
495
|
+
event: {
|
|
496
|
+
target: { value }
|
|
497
|
+
}
|
|
498
|
+
}) => handleMultiSelect(id, value)}
|
|
499
|
+
required={false}
|
|
500
|
+
checkedPreviewCount={1}
|
|
501
|
+
formatMoreLabel={n => `${n} more`}
|
|
502
|
+
/>
|
|
503
|
+
</div>
|
|
504
|
+
);
|
|
505
|
+
})}
|
|
506
|
+
|
|
507
|
+
{relationMultiSelectFilters.map(([property, config]) => {
|
|
508
|
+
const entityIdentifier =
|
|
509
|
+
resolveEntityIdentifier(property, config) || config.relation?.entityIdentifier;
|
|
510
|
+
|
|
511
|
+
if (!entityIdentifier) return null;
|
|
512
|
+
|
|
513
|
+
return (
|
|
514
|
+
<div
|
|
515
|
+
className="search-container__select-wrapper"
|
|
516
|
+
key={`relation-multiselect-${property}`}>
|
|
517
|
+
<RelationMultiSelectFilter
|
|
518
|
+
id={property}
|
|
519
|
+
label={config.label || config.relation.label}
|
|
520
|
+
entityIdentifier={entityIdentifier}
|
|
521
|
+
selectedIds={selectedFilters[property] || []}
|
|
522
|
+
onChange={handleMultiSelect}
|
|
523
|
+
/>
|
|
524
|
+
</div>
|
|
525
|
+
);
|
|
526
|
+
})}
|
|
204
527
|
|
|
205
528
|
<div className="search-container__reset-button">
|
|
206
529
|
<span role="button" onClick={handleReset}>
|
|
@@ -215,6 +538,7 @@ SearchContainer.propTypes = {
|
|
|
215
538
|
setListFilters: PropTypes.func.isRequired,
|
|
216
539
|
keywordFiltersToUse: PropTypes.array,
|
|
217
540
|
selectFiltersToDisplay: PropTypes.array,
|
|
541
|
+
multiSelectFiltersToDisplay: PropTypes.array,
|
|
218
542
|
schema: PropTypes.object.isRequired,
|
|
219
543
|
index: PropTypes.string
|
|
220
544
|
};
|
|
@@ -222,6 +546,7 @@ SearchContainer.propTypes = {
|
|
|
222
546
|
SearchContainer.defaultProps = {
|
|
223
547
|
keywordFiltersToUse: [],
|
|
224
548
|
selectFiltersToDisplay: [],
|
|
549
|
+
multiSelectFiltersToDisplay: [],
|
|
225
550
|
index: 'admin'
|
|
226
551
|
};
|
|
227
552
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import { getKeywordSearchFilters, findSelectFilters } from './helpers';
|
|
3
|
+
import { getKeywordSearchFilters, findSelectFilters, findMultiSelectFilters } from './helpers';
|
|
4
4
|
import SearchContainer from './SearchContainer';
|
|
5
5
|
|
|
6
|
-
const SearchFilter = ({ schema, setListFilters }) => {
|
|
6
|
+
const SearchFilter = ({ schema, setListFilters, values }) => {
|
|
7
7
|
const { displayProperties = {} } = schema || {};
|
|
8
8
|
const {
|
|
9
9
|
adminListings: { dataSource: { source, index } = {}, disableListingFilter = false } = {}
|
|
@@ -11,6 +11,7 @@ const SearchFilter = ({ schema, setListFilters }) => {
|
|
|
11
11
|
|
|
12
12
|
const keywordFiltersToUse = useMemo(() => getKeywordSearchFilters(schema), [schema]);
|
|
13
13
|
const selectFiltersToDisplay = useMemo(() => findSelectFilters(schema), [schema]);
|
|
14
|
+
const multiSelectFiltersToDisplay = useMemo(() => findMultiSelectFilters(schema), [schema]);
|
|
14
15
|
|
|
15
16
|
if (disableListingFilter) return null;
|
|
16
17
|
|
|
@@ -25,7 +26,9 @@ const SearchFilter = ({ schema, setListFilters }) => {
|
|
|
25
26
|
setListFilters={setListFilters}
|
|
26
27
|
keywordFiltersToUse={keywordFiltersToUse}
|
|
27
28
|
selectFiltersToDisplay={selectFiltersToDisplay}
|
|
29
|
+
multiSelectFiltersToDisplay={multiSelectFiltersToDisplay}
|
|
28
30
|
schema={schema}
|
|
31
|
+
values={values}
|
|
29
32
|
/>
|
|
30
33
|
);
|
|
31
34
|
};
|