@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.
Files changed (35) hide show
  1. package/README.md +9 -2
  2. package/lib/components/EntityManager/Entity/Entity.js.map +1 -1
  3. package/lib/components/ListingTable/SearchFilter/RelationMultiSelectFilter.js +181 -0
  4. package/lib/components/ListingTable/SearchFilter/RelationMultiSelectFilter.js.map +1 -0
  5. package/lib/components/ListingTable/SearchFilter/SearchContainer.js +338 -49
  6. package/lib/components/ListingTable/SearchFilter/SearchContainer.js.map +1 -1
  7. package/lib/components/ListingTable/SearchFilter/SearchFilter.js +6 -2
  8. package/lib/components/ListingTable/SearchFilter/SearchFilter.js.map +1 -1
  9. package/lib/components/ListingTable/SearchFilter/helpers.js +41 -8
  10. package/lib/components/ListingTable/SearchFilter/helpers.js.map +1 -1
  11. package/lib/index.js +7 -0
  12. package/lib/index.js.map +1 -1
  13. package/lib/utils/get-default-query-params.js +17 -5
  14. package/lib/utils/get-default-query-params.js.map +1 -1
  15. package/lib-es/components/EntityManager/Entity/Entity.js.map +1 -1
  16. package/lib-es/components/ListingTable/SearchFilter/RelationMultiSelectFilter.js +173 -0
  17. package/lib-es/components/ListingTable/SearchFilter/RelationMultiSelectFilter.js.map +1 -0
  18. package/lib-es/components/ListingTable/SearchFilter/SearchContainer.js +340 -51
  19. package/lib-es/components/ListingTable/SearchFilter/SearchContainer.js.map +1 -1
  20. package/lib-es/components/ListingTable/SearchFilter/SearchFilter.js +7 -3
  21. package/lib-es/components/ListingTable/SearchFilter/SearchFilter.js.map +1 -1
  22. package/lib-es/components/ListingTable/SearchFilter/helpers.js +39 -8
  23. package/lib-es/components/ListingTable/SearchFilter/helpers.js.map +1 -1
  24. package/lib-es/index.js +1 -0
  25. package/lib-es/index.js.map +1 -1
  26. package/lib-es/utils/get-default-query-params.js +17 -5
  27. package/lib-es/utils/get-default-query-params.js.map +1 -1
  28. package/package.json +11 -12
  29. package/src/components/EntityManager/Entity/Entity.js +2 -3
  30. package/src/components/ListingTable/SearchFilter/RelationMultiSelectFilter.js +160 -0
  31. package/src/components/ListingTable/SearchFilter/SearchContainer.js +396 -71
  32. package/src/components/ListingTable/SearchFilter/SearchFilter.js +5 -2
  33. package/src/components/ListingTable/SearchFilter/helpers.js +55 -8
  34. package/src/index.js +2 -0
  35. 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 { getDynamicQuery, getQuery } from '@blaze-cms/admin-ui-utils';
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 apolloClient = useApolloClient();
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
- () => buildQueryFields(selectFiltersToDisplay, schema.id),
40
- [selectFiltersToDisplay, schema.id]
84
+ () =>
85
+ buildQueryFields([...selectFiltersToDisplay, ...nonRelationMultiSelectFilters], schema.id),
86
+ [selectFiltersToDisplay, nonRelationMultiSelectFilters, schema.id]
41
87
  );
42
- const action = displaySelectFilters
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: !displaySelectFilters,
94
+ skip: !displayAggregations,
48
95
  variables: { where: rawQuery, limit: 0 },
49
96
  fetchPolicy: 'cache-and-network'
50
97
  });
51
98
 
52
- const filterData = useMemo(() => {
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 proccessedData = [];
143
+ const processed = [];
55
144
  Object.entries(aggregations).forEach(([key, { buckets }]) => {
56
- proccessedData.push(
57
- Array.isArray(buckets) ? [key, buckets.map(b => b.key).filter(Boolean)] : []
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 (!filterData.length) {
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
- filterData.map(async ([property, ids]) => {
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 [, selectionDetails] =
77
- selectFiltersToDisplay.find(([selectProperty]) => selectProperty === property) || [];
172
+ const config = findFilterConfig(property);
173
+ if (!config) return null;
78
174
 
79
- if (!selectionDetails) return null;
175
+ const { adminListingOptions: { tidyLabel } = {} } = config;
80
176
 
81
- if (!selectionDetails.relation) {
177
+ if (!config.relation) {
178
+ const baseOptions = ids.map(value => [value, getTidyLabel(value, tidyLabel)]);
82
179
  return {
83
- id: property,
84
- label: selectionDetails.label,
85
- options: ids.map(value => [value, value])
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
- const query = getQuery('GET_ENTITY_SCHEMA');
187
+
188
+ const schemaQuery = getQuery('GET_ENTITY_SCHEMA');
89
189
  const {
90
190
  data: {
91
191
  getEntitySchemas: [relationSchema]
92
192
  }
93
- } = await apolloClient.query({
94
- query,
95
- variables: { identifier: selectionDetails.relation.entityIdentifier }
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 apolloClient
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(e => console.error(e));
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
- id: property,
117
- label: selectionDetails.label,
118
- options: selected.map(item => [item.id, item.name])
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
- setSelectOptions(results.filter(Boolean));
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
- }, [apolloClient, filterData, schema, selectFiltersToDisplay]);
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
- const handleSearchChange = useCallback(e => setSearchTerm(e.target.value), []);
268
+
269
+ const handleSearchChange = useCallback(({ target: { value } }) => setSearchTerm(value), []);
270
+
140
271
  const handleEnterKey = useCallback(
141
- e => {
142
- if (e.key === ENTER_KEY) handleSearch();
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
- setSelectedFilters(
149
- Object.keys(selectedFilters).reduce(
150
- (acc, key) => ({ ...acc, [key]: DEFAULT_FILTER_OPTION_LABEL }),
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
- }, [selectedFilters, updateFilters]);
288
+ }, [multiSelectPropertySet, updateFilters, debouncedUpdateFilters]);
289
+
156
290
  const handleSelect = useCallback(
157
- (property, event) => {
158
- const { value } = event;
291
+ (property, { value }) => {
159
292
  const next = { ...selectedFilters, [property]: value };
160
293
  setSelectedFilters(next);
161
- updateFilters(searchTerm, next);
294
+ debouncedUpdateFilters(searchTerm, next);
162
295
  },
163
- [searchTerm, selectedFilters, updateFilters]
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
- {options && !!options.length && (
190
- <div className="search-container__select-wrapper">
191
- <Select
192
- label={label}
193
- id={id}
194
- name={id}
195
- onChange={event => handleSelect(id, event)}
196
- options={options}
197
- value={selectedFilters[id]}
198
- defaultTextValue={DEFAULT_FILTER_OPTION_LABEL}
199
- />
200
- </div>
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
  };