@blaze-cms/plugin-data-ui 0.146.0-node18-core-styles-tooltips.56 → 0.146.0-node18-tooltips.44

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