@blaze-cms/plugin-data-ui 0.147.0-rc-eagle.2 → 0.147.0-rc-eagle.4
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/lib/components/EntityManager/Entity/Entity.js +6 -7
- package/lib/components/EntityManager/Entity/Entity.js.map +1 -1
- package/lib/components/ListingTable/SearchFilter/SearchContainer.js +321 -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-es/components/EntityManager/Entity/Entity.js +6 -7
- package/lib-es/components/EntityManager/Entity/Entity.js.map +1 -1
- package/lib-es/components/ListingTable/SearchFilter/SearchContainer.js +323 -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/package.json +11 -11
- package/src/components/EntityManager/Entity/Entity.js +11 -11
- package/src/components/ListingTable/SearchFilter/SearchContainer.js +363 -71
- package/src/components/ListingTable/SearchFilter/SearchFilter.js +5 -2
- package/src/components/ListingTable/SearchFilter/helpers.js +56 -8
- package/src/index.js +2 -0
|
@@ -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 {
|
|
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,16 +23,39 @@ 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
|
|
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;
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
(async () => {
|
|
46
|
+
try {
|
|
47
|
+
const all = await fetchAllEntities({ client });
|
|
48
|
+
setAllEntitySchemas(all || []);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
setAllEntitySchemas([]);
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
53
|
+
}, [client]);
|
|
54
|
+
|
|
55
|
+
const multiSelectPropertySet = useMemo(
|
|
56
|
+
() => new Set(multiSelectFiltersToDisplay.map(([property]) => property)),
|
|
57
|
+
[multiSelectFiltersToDisplay]
|
|
58
|
+
);
|
|
25
59
|
|
|
26
60
|
const { keywordSearchProperties, keywordSearchLabels } = useMemo(() => {
|
|
27
61
|
const labels = [];
|
|
@@ -36,86 +70,140 @@ const SearchContainer = ({
|
|
|
36
70
|
}, [keywordFiltersToUse]);
|
|
37
71
|
|
|
38
72
|
const { gqlFields, rawQuery } = useMemo(
|
|
39
|
-
() => buildQueryFields(selectFiltersToDisplay, schema.id),
|
|
40
|
-
[selectFiltersToDisplay, schema.id]
|
|
73
|
+
() => buildQueryFields([...selectFiltersToDisplay, ...multiSelectFiltersToDisplay], schema.id),
|
|
74
|
+
[selectFiltersToDisplay, multiSelectFiltersToDisplay, schema.id]
|
|
41
75
|
);
|
|
42
|
-
|
|
76
|
+
|
|
77
|
+
const action = displayAggregations
|
|
43
78
|
? getDynamicQuery('ADMIN_SEARCH')([schema], gqlFields, false, index)
|
|
44
79
|
: NOOP_QUERY;
|
|
45
80
|
|
|
46
81
|
const { data } = useQuery(action, {
|
|
47
|
-
skip: !
|
|
82
|
+
skip: !displayAggregations,
|
|
48
83
|
variables: { where: rawQuery, limit: 0 },
|
|
49
84
|
fetchPolicy: 'cache-and-network'
|
|
50
85
|
});
|
|
51
86
|
|
|
52
|
-
const
|
|
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 });
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
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]
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const aggregationsData = useMemo(() => {
|
|
53
130
|
const { searchResults: { rawResults: { aggregations = {} } = {} } = {} } = data || {};
|
|
54
|
-
const
|
|
131
|
+
const processed = [];
|
|
55
132
|
Object.entries(aggregations).forEach(([key, { buckets }]) => {
|
|
56
|
-
|
|
57
|
-
Array.isArray(buckets) ? [key, buckets.map(
|
|
133
|
+
processed.push(
|
|
134
|
+
Array.isArray(buckets) ? [key, buckets.map(bucket => bucket.key).filter(Boolean)] : []
|
|
58
135
|
);
|
|
59
136
|
});
|
|
60
|
-
|
|
61
|
-
return proccessedData;
|
|
137
|
+
return processed;
|
|
62
138
|
}, [data]);
|
|
63
139
|
|
|
140
|
+
const findFilterConfig = useCallback(
|
|
141
|
+
property =>
|
|
142
|
+
selectFiltersToDisplay.find(([prop]) => prop === property)?.[1] ||
|
|
143
|
+
multiSelectFiltersToDisplay.find(([prop]) => prop === property)?.[1],
|
|
144
|
+
[selectFiltersToDisplay, multiSelectFiltersToDisplay]
|
|
145
|
+
);
|
|
146
|
+
|
|
64
147
|
useEffect(() => {
|
|
65
|
-
if (!
|
|
148
|
+
if (!aggregationsData.length) {
|
|
66
149
|
setSelectOptions([]);
|
|
150
|
+
setMultiSelectOptions([]);
|
|
67
151
|
return;
|
|
68
152
|
}
|
|
69
153
|
|
|
70
154
|
(async () => {
|
|
71
155
|
const results = await Promise.all(
|
|
72
|
-
|
|
156
|
+
aggregationsData.map(async ([property, ids]) => {
|
|
73
157
|
try {
|
|
74
|
-
if (!ids || !ids.length) return
|
|
158
|
+
if (!ids || !ids.length) return null;
|
|
75
159
|
|
|
76
|
-
const
|
|
77
|
-
|
|
160
|
+
const config = findFilterConfig(property);
|
|
161
|
+
if (!config) return null;
|
|
78
162
|
|
|
79
|
-
|
|
163
|
+
const { adminListingOptions: { tidyLabel } = {} } = config;
|
|
80
164
|
|
|
81
|
-
if (!
|
|
165
|
+
if (!config.relation) {
|
|
166
|
+
const baseOptions = ids.map(value => [value, getTidyLabel(value, tidyLabel)]);
|
|
82
167
|
return {
|
|
83
|
-
|
|
84
|
-
label:
|
|
85
|
-
|
|
168
|
+
property,
|
|
169
|
+
label: config.label,
|
|
170
|
+
type: multiSelectPropertySet.has(property) ? 'multi' : 'single',
|
|
171
|
+
options: baseOptions,
|
|
172
|
+
selected: values && values[property]
|
|
86
173
|
};
|
|
87
174
|
}
|
|
88
|
-
|
|
175
|
+
|
|
176
|
+
const schemaQuery = getQuery('GET_ENTITY_SCHEMA');
|
|
89
177
|
const {
|
|
90
178
|
data: {
|
|
91
179
|
getEntitySchemas: [relationSchema]
|
|
92
180
|
}
|
|
93
|
-
} = await
|
|
94
|
-
query,
|
|
95
|
-
variables: { identifier:
|
|
181
|
+
} = await client.query({
|
|
182
|
+
query: schemaQuery,
|
|
183
|
+
variables: { identifier: config.relation.entityIdentifier }
|
|
96
184
|
});
|
|
97
185
|
|
|
98
186
|
const {
|
|
99
187
|
data: { result: selected = [] }
|
|
100
|
-
} = await
|
|
188
|
+
} = await client
|
|
101
189
|
.query({
|
|
102
190
|
query: getDynamicQuery('GET_ALL_ENTITIES')(relationSchema),
|
|
103
191
|
variables: {
|
|
104
|
-
where: {
|
|
105
|
-
id: {
|
|
106
|
-
_in: ids
|
|
107
|
-
}
|
|
108
|
-
},
|
|
192
|
+
where: { id: { _in: ids } },
|
|
109
193
|
sort: [{ property: 'name', direction: 'asc' }]
|
|
110
194
|
}
|
|
111
195
|
})
|
|
112
196
|
// eslint-disable-next-line no-console
|
|
113
|
-
.catch(
|
|
197
|
+
.catch(error => console.error(error));
|
|
198
|
+
|
|
199
|
+
const baseOptions = selected.map(item => [item.id, getTidyLabel(item.name, tidyLabel)]);
|
|
114
200
|
|
|
115
201
|
return {
|
|
116
|
-
|
|
117
|
-
label:
|
|
118
|
-
|
|
202
|
+
property,
|
|
203
|
+
label: config.label || config.relation.label,
|
|
204
|
+
type: multiSelectPropertySet.has(property) ? 'multi' : 'single',
|
|
205
|
+
options: baseOptions,
|
|
206
|
+
selected: values && values[property]
|
|
119
207
|
};
|
|
120
208
|
} catch {
|
|
121
209
|
return null;
|
|
@@ -123,50 +211,215 @@ const SearchContainer = ({
|
|
|
123
211
|
})
|
|
124
212
|
);
|
|
125
213
|
|
|
126
|
-
|
|
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
|
+
);
|
|
127
237
|
})();
|
|
128
|
-
}, [
|
|
238
|
+
}, [client, aggregationsData, findFilterConfig, multiSelectPropertySet, values]);
|
|
129
239
|
|
|
130
240
|
const updateFilters = useCallback(
|
|
131
241
|
(term, filters) => updateListFilters(term, filters, setListFilters, keywordSearchProperties),
|
|
132
242
|
[setListFilters, keywordSearchProperties]
|
|
133
243
|
);
|
|
134
244
|
|
|
245
|
+
const debouncedUpdateFilters = useMemo(
|
|
246
|
+
() => debounce((term, filters) => updateFilters(term, filters), 300),
|
|
247
|
+
[updateFilters]
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
useEffect(() => () => debouncedUpdateFilters.cancel(), [debouncedUpdateFilters]);
|
|
251
|
+
|
|
135
252
|
const handleSearch = useCallback(
|
|
136
253
|
() => updateFilters(searchTerm, selectedFilters),
|
|
137
254
|
[searchTerm, selectedFilters, updateFilters]
|
|
138
255
|
);
|
|
139
|
-
|
|
256
|
+
|
|
257
|
+
const handleSearchChange = useCallback(({ target: { value } }) => setSearchTerm(value), []);
|
|
258
|
+
|
|
140
259
|
const handleEnterKey = useCallback(
|
|
141
|
-
|
|
142
|
-
if (
|
|
260
|
+
({ key }) => {
|
|
261
|
+
if (key === ENTER_KEY) handleSearch();
|
|
143
262
|
},
|
|
144
263
|
[handleSearch]
|
|
145
264
|
);
|
|
265
|
+
|
|
146
266
|
const handleReset = useCallback(() => {
|
|
147
267
|
setSearchTerm('');
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
+
}, {})
|
|
153
274
|
);
|
|
154
275
|
updateFilters('', {});
|
|
155
|
-
}, [
|
|
276
|
+
}, [multiSelectPropertySet, updateFilters, debouncedUpdateFilters]);
|
|
277
|
+
|
|
156
278
|
const handleSelect = useCallback(
|
|
157
|
-
(property,
|
|
158
|
-
const { value } = event;
|
|
279
|
+
(property, { value }) => {
|
|
159
280
|
const next = { ...selectedFilters, [property]: value };
|
|
160
281
|
setSelectedFilters(next);
|
|
161
|
-
|
|
282
|
+
debouncedUpdateFilters(searchTerm, next);
|
|
162
283
|
},
|
|
163
|
-
[searchTerm, selectedFilters,
|
|
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
|
+
]
|
|
164
417
|
);
|
|
165
418
|
|
|
166
419
|
const placeholderText = `Search ${keywordSearchLabels.join(', ')}`;
|
|
167
420
|
|
|
168
421
|
return (
|
|
169
|
-
<div className="search-container">
|
|
422
|
+
<div className="search-container ms-filter">
|
|
170
423
|
{displaySearchFilter && (
|
|
171
424
|
<div className="search-container__search-input-container">
|
|
172
425
|
<input
|
|
@@ -184,23 +437,60 @@ const SearchContainer = ({
|
|
|
184
437
|
)}
|
|
185
438
|
|
|
186
439
|
{displaySelectFilters &&
|
|
187
|
-
selectOptions.map(({ id, options, label }) =>
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
+
})}
|
|
204
494
|
|
|
205
495
|
<div className="search-container__reset-button">
|
|
206
496
|
<span role="button" onClick={handleReset}>
|
|
@@ -215,6 +505,7 @@ SearchContainer.propTypes = {
|
|
|
215
505
|
setListFilters: PropTypes.func.isRequired,
|
|
216
506
|
keywordFiltersToUse: PropTypes.array,
|
|
217
507
|
selectFiltersToDisplay: PropTypes.array,
|
|
508
|
+
multiSelectFiltersToDisplay: PropTypes.array,
|
|
218
509
|
schema: PropTypes.object.isRequired,
|
|
219
510
|
index: PropTypes.string
|
|
220
511
|
};
|
|
@@ -222,6 +513,7 @@ SearchContainer.propTypes = {
|
|
|
222
513
|
SearchContainer.defaultProps = {
|
|
223
514
|
keywordFiltersToUse: [],
|
|
224
515
|
selectFiltersToDisplay: [],
|
|
516
|
+
multiSelectFiltersToDisplay: [],
|
|
225
517
|
index: 'admin'
|
|
226
518
|
};
|
|
227
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
|
};
|
|
@@ -14,14 +14,32 @@ const updateListFilters = (searchTerm, selectedFilters, setListFilters, fields =
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
Object.entries(selectedFilters).forEach(([filterKey, filterValue]) => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
const isEmptyArray = Array.isArray(filterValue) && filterValue.length === 0;
|
|
18
|
+
if (
|
|
19
|
+
filterValue == null ||
|
|
20
|
+
filterValue === '' ||
|
|
21
|
+
filterValue === DEFAULT_FILTER_OPTION_LABEL ||
|
|
22
|
+
isEmptyArray
|
|
23
|
+
)
|
|
24
|
+
return;
|
|
25
|
+
|
|
26
|
+
const field = `${filterKey}.keyword`;
|
|
27
|
+
|
|
28
|
+
if (Array.isArray(filterValue)) {
|
|
29
|
+
if (filterValue.length > 1) {
|
|
30
|
+
filters.push({
|
|
31
|
+
terms: { [field]: filterValue }
|
|
32
|
+
});
|
|
33
|
+
} else {
|
|
34
|
+
filters.push({
|
|
35
|
+
term: { [field]: filterValue[0] }
|
|
36
|
+
});
|
|
23
37
|
}
|
|
24
|
-
}
|
|
38
|
+
} else {
|
|
39
|
+
filters.push({
|
|
40
|
+
term: { [field]: filterValue }
|
|
41
|
+
});
|
|
42
|
+
}
|
|
25
43
|
});
|
|
26
44
|
|
|
27
45
|
setListFilters(filters);
|
|
@@ -52,9 +70,23 @@ const findSelectFilters = schema => {
|
|
|
52
70
|
return [...propertySelects, ...dynamicPropertySelects];
|
|
53
71
|
};
|
|
54
72
|
|
|
73
|
+
const findMultiSelectFilters = schema => {
|
|
74
|
+
if (!schema || !schema.properties) return [];
|
|
75
|
+
|
|
76
|
+
const { properties = {}, dynamicProperties = {} } = schema;
|
|
77
|
+
const propertyMultiSelect = Object.entries(properties).filter(isFilterMultiSelectItems);
|
|
78
|
+
const dynamicPropertyMultiSelect = Object.entries(dynamicProperties).filter(
|
|
79
|
+
isFilterMultiSelectItems
|
|
80
|
+
);
|
|
81
|
+
return [...propertyMultiSelect, ...dynamicPropertyMultiSelect];
|
|
82
|
+
};
|
|
83
|
+
|
|
55
84
|
const isFilterSelectItem = ([, item]) =>
|
|
56
85
|
item.adminListingOptions && item.adminListingOptions.filterType === 'select';
|
|
57
86
|
|
|
87
|
+
const isFilterMultiSelectItems = ([, item]) =>
|
|
88
|
+
item.adminListingOptions && item.adminListingOptions.filterType === 'multiSelect';
|
|
89
|
+
|
|
58
90
|
const buildQueryFields = (selectFilters, entityId) => {
|
|
59
91
|
if (!Array.isArray(selectFilters) || selectFilters.length === 0) {
|
|
60
92
|
return { gqlFields: '', rawQuery: '{}' };
|
|
@@ -96,4 +128,20 @@ const buildQueryFields = (selectFilters, entityId) => {
|
|
|
96
128
|
return { gqlFields, rawQuery };
|
|
97
129
|
};
|
|
98
130
|
|
|
99
|
-
|
|
131
|
+
const getTidyLabel = (value, tidy) => {
|
|
132
|
+
if (!tidy) return value;
|
|
133
|
+
|
|
134
|
+
return value
|
|
135
|
+
.split(/[\s_-]+/)
|
|
136
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
137
|
+
.join(' ');
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export {
|
|
141
|
+
buildQueryFields,
|
|
142
|
+
updateListFilters,
|
|
143
|
+
getKeywordSearchFilters,
|
|
144
|
+
findSelectFilters,
|
|
145
|
+
findMultiSelectFilters,
|
|
146
|
+
getTidyLabel
|
|
147
|
+
};
|
package/src/index.js
CHANGED
|
@@ -14,6 +14,8 @@ import EntityManager from './components/EntityManager';
|
|
|
14
14
|
// () => import(/* webpackChunkName: 'EntityManager' */ './components/EntityManager')
|
|
15
15
|
// );
|
|
16
16
|
|
|
17
|
+
export { default as SearchFilter } from './components/ListingTable/SearchFilter/SearchFilter';
|
|
18
|
+
|
|
17
19
|
export default async function load(app) {
|
|
18
20
|
app.events.once('admin:menu:config:load', getAddContentMenuItems(app));
|
|
19
21
|
|