@blaze-cms/plugin-data-ui 0.147.0-rc-eagle.4 → 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 +7 -6
- 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 +18 -1
- package/lib/components/ListingTable/SearchFilter/SearchContainer.js.map +1 -1
- package/lib/components/ListingTable/SearchFilter/helpers.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 +7 -6
- 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 +18 -1
- package/lib-es/components/ListingTable/SearchFilter/SearchContainer.js.map +1 -1
- package/lib-es/components/ListingTable/SearchFilter/helpers.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 +7 -8
- package/src/components/EntityManager/Entity/Entity.js +8 -9
- package/src/components/ListingTable/SearchFilter/RelationMultiSelectFilter.js +160 -0
- package/src/components/ListingTable/SearchFilter/SearchContainer.js +35 -2
- package/src/components/ListingTable/SearchFilter/helpers.js +2 -3
- package/src/utils/get-default-query-params.js +21 -9
|
@@ -10,7 +10,6 @@ import { DeleteAction, CopyDataFromAction } from '@blaze-cms/admin';
|
|
|
10
10
|
import { useToasts } from '@blaze-react/toaster';
|
|
11
11
|
import cloneDeep from 'lodash.clonedeep';
|
|
12
12
|
import { getPageName } from '../utils/data-mappers';
|
|
13
|
-
import isFormEmpty from '../utils/is-form-empty';
|
|
14
13
|
import availableActions from './actions-handlers';
|
|
15
14
|
import SideBarRelations from './SideBarRelations';
|
|
16
15
|
import useCallbackDebounce from '../../hooks/useCallbackDebounce';
|
|
@@ -70,6 +69,7 @@ const Entity = ({
|
|
|
70
69
|
const [newUpdatedDate, setNewUpdatedDate] = useState(null);
|
|
71
70
|
const [saveButtonText, setSaveButtonText] = useState(SAVE_BUTTON_TEXTS.save);
|
|
72
71
|
const [externalUpdateTime, setExternalUpdateTime] = useState(null);
|
|
72
|
+
const [suppressPrompt, setSuppressPrompt] = useState(false);
|
|
73
73
|
const [isRightSidebarOpen, setIsRightSidebarOpen] = useState(true);
|
|
74
74
|
const handleToggleRightSidebar = () => setIsRightSidebarOpen(!isRightSidebarOpen);
|
|
75
75
|
|
|
@@ -249,13 +249,12 @@ const Entity = ({
|
|
|
249
249
|
const [updatedEntity = null] = fields.filter(({ id }) => id === 'name');
|
|
250
250
|
updatedEntity && setPageTitle(updatedEntity.value);
|
|
251
251
|
|
|
252
|
-
const isValidAndNewValueSet = isValid && isNewValueSet;
|
|
253
252
|
const isNotValidOrNotNewValueSet = !isValid || !isNewValueSet;
|
|
254
253
|
if (componentIsMounted.current) {
|
|
255
254
|
setIsFormValid(isValid);
|
|
256
255
|
}
|
|
257
256
|
|
|
258
|
-
if (
|
|
257
|
+
if (isValid && isNewValueSet && componentIsMounted.current) {
|
|
259
258
|
setIsSaveButtonAvailable(true);
|
|
260
259
|
setSaveButtonText(SAVE_BUTTON_TEXTS.save);
|
|
261
260
|
}
|
|
@@ -271,10 +270,12 @@ const Entity = ({
|
|
|
271
270
|
|
|
272
271
|
const onSubmit = event => {
|
|
273
272
|
event && event.preventDefault();
|
|
274
|
-
|
|
275
273
|
if (!isSaveButtonAvailable) return null;
|
|
276
|
-
const parsedFormValues = parseFormValues(formValues);
|
|
277
274
|
|
|
275
|
+
if (isCreatePage) {
|
|
276
|
+
setSuppressPrompt(true);
|
|
277
|
+
}
|
|
278
|
+
const parsedFormValues = parseFormValues(formValues);
|
|
278
279
|
const updatedFormValues = newUpdatedDate
|
|
279
280
|
? { ...formData.values, update: newUpdatedDate }
|
|
280
281
|
: { ...formData.values };
|
|
@@ -412,8 +413,7 @@ const Entity = ({
|
|
|
412
413
|
|
|
413
414
|
const pageName = formData && formData.values && formData.values.name;
|
|
414
415
|
|
|
415
|
-
const
|
|
416
|
-
const hasToDisplayPrompt = isSaveButtonAvailable || hasNewFormValues;
|
|
416
|
+
const hasToDisplayPrompt = isSaveButtonAvailable && !suppressPrompt;
|
|
417
417
|
|
|
418
418
|
const showSideBarRelations = checkFulfilConditions(schema, formData);
|
|
419
419
|
|
|
@@ -457,8 +457,7 @@ const Entity = ({
|
|
|
457
457
|
)}
|
|
458
458
|
|
|
459
459
|
<div
|
|
460
|
-
className={`page__content
|
|
461
|
-
}`}>
|
|
460
|
+
className={`page__content${!isRightSidebarOpen ? ' right-sidebar-collapsed' : ''}`}>
|
|
462
461
|
<div className="page__content">
|
|
463
462
|
<div className="content">
|
|
464
463
|
<RenderHook
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import React, { useState, useEffect, useMemo } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import MultiSelect from '@blaze-react/multiselect';
|
|
4
|
+
import debounce from 'lodash.debounce';
|
|
5
|
+
import unionBy from 'lodash.unionby';
|
|
6
|
+
import { useApolloClient } from '@apollo/client';
|
|
7
|
+
import {
|
|
8
|
+
getQuery,
|
|
9
|
+
getDynamicQuery,
|
|
10
|
+
getAdminRecordSearch,
|
|
11
|
+
buildLabel
|
|
12
|
+
} from '@blaze-cms/admin-ui-utils';
|
|
13
|
+
|
|
14
|
+
const DEBOUNCE_TIME = 500;
|
|
15
|
+
const SEARCH_LOAD_OFFSET = 10;
|
|
16
|
+
const SELECTION_LIMIT = 10;
|
|
17
|
+
|
|
18
|
+
const shouldLoadMore = (searchTerm, stopIndex, currentDataLength) =>
|
|
19
|
+
searchTerm &&
|
|
20
|
+
stopIndex > SEARCH_LOAD_OFFSET &&
|
|
21
|
+
stopIndex >= currentDataLength - SEARCH_LOAD_OFFSET;
|
|
22
|
+
|
|
23
|
+
const RelationMultiSelectFilter = ({ id, label, entityIdentifier, selectedIds, onChange }) => {
|
|
24
|
+
const client = useApolloClient();
|
|
25
|
+
const [schema, setSchema] = useState(null);
|
|
26
|
+
const [data, setData] = useState([]);
|
|
27
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
28
|
+
|
|
29
|
+
const { displayProperties } = schema || {};
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
let active = true;
|
|
33
|
+
(async () => {
|
|
34
|
+
try {
|
|
35
|
+
const { data: { getEntitySchemas = [] } = {} } = await client.query({
|
|
36
|
+
query: getQuery('GET_ENTITY_SCHEMA'),
|
|
37
|
+
variables: { identifier: entityIdentifier }
|
|
38
|
+
});
|
|
39
|
+
const [relationSchema] = getEntitySchemas;
|
|
40
|
+
if (active && relationSchema) setSchema(relationSchema);
|
|
41
|
+
} catch {
|
|
42
|
+
if (active) setSchema(null);
|
|
43
|
+
}
|
|
44
|
+
})();
|
|
45
|
+
return () => {
|
|
46
|
+
active = false;
|
|
47
|
+
};
|
|
48
|
+
}, [client, entityIdentifier]);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!schema || !selectedIds.length) return undefined;
|
|
52
|
+
let active = true;
|
|
53
|
+
(async () => {
|
|
54
|
+
try {
|
|
55
|
+
const { data: { result = [] } = {} } = await client.query({
|
|
56
|
+
query: getDynamicQuery('GET_ALL_ENTITIES')(schema),
|
|
57
|
+
variables: {
|
|
58
|
+
where: { id: { _in: selectedIds } },
|
|
59
|
+
sort: [{ property: 'name', direction: 'asc' }]
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
if (active && result.length) setData(prev => unionBy([...prev, ...result], 'id'));
|
|
63
|
+
} catch {
|
|
64
|
+
// ignore - selected chips will fall back to id-only display
|
|
65
|
+
}
|
|
66
|
+
})();
|
|
67
|
+
return () => {
|
|
68
|
+
active = false;
|
|
69
|
+
};
|
|
70
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
71
|
+
}, [schema]);
|
|
72
|
+
|
|
73
|
+
const onSearch = useMemo(
|
|
74
|
+
() => (schema ? getAdminRecordSearch(schema, client) : null),
|
|
75
|
+
[schema, client]
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const currentData = useMemo(
|
|
79
|
+
() =>
|
|
80
|
+
data.map(option => {
|
|
81
|
+
const checked = selectedIds.includes(option.id);
|
|
82
|
+
return {
|
|
83
|
+
...option,
|
|
84
|
+
checked,
|
|
85
|
+
show: true,
|
|
86
|
+
name: buildLabel(displayProperties, option)
|
|
87
|
+
};
|
|
88
|
+
}),
|
|
89
|
+
[data, selectedIds, displayProperties]
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const interceptOnSearchChange = debounce(async ({ value }) => {
|
|
93
|
+
setSearchTerm(value);
|
|
94
|
+
if (!onSearch) return;
|
|
95
|
+
const result = await onSearch({ event: { value }, currentValue: selectedIds });
|
|
96
|
+
if (!result) return;
|
|
97
|
+
setData(prev => {
|
|
98
|
+
const selected = prev.filter(item => selectedIds.includes(item.id));
|
|
99
|
+
const newOnes = result.filter(item => !selectedIds.includes(item.id));
|
|
100
|
+
return unionBy([...selected, ...newOnes], 'id');
|
|
101
|
+
});
|
|
102
|
+
}, DEBOUNCE_TIME);
|
|
103
|
+
|
|
104
|
+
const scrollHook = debounce(async ({ stopIndex }) => {
|
|
105
|
+
if (!onSearch) return;
|
|
106
|
+
const currentDataLength = data.length - selectedIds.length;
|
|
107
|
+
if (!shouldLoadMore(searchTerm, stopIndex, currentDataLength)) return;
|
|
108
|
+
const result = await onSearch({
|
|
109
|
+
event: { value: searchTerm },
|
|
110
|
+
currentValue: selectedIds,
|
|
111
|
+
currentOffset: currentDataLength
|
|
112
|
+
});
|
|
113
|
+
if (!result || !result.length) return;
|
|
114
|
+
setData(prev => unionBy([...prev, ...result], 'id'));
|
|
115
|
+
}, DEBOUNCE_TIME);
|
|
116
|
+
|
|
117
|
+
const onItemsRenderedIntercept = async event => {
|
|
118
|
+
if (!onSearch) return;
|
|
119
|
+
scrollHook(event);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const getSelected = ({
|
|
123
|
+
event: {
|
|
124
|
+
target: { value }
|
|
125
|
+
}
|
|
126
|
+
}) => {
|
|
127
|
+
onChange(id, value);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<MultiSelect
|
|
132
|
+
label={label}
|
|
133
|
+
name={id}
|
|
134
|
+
data={{ data: currentData, filterBy: ['name'], identification: 'id', keyValue: 'name' }}
|
|
135
|
+
limit={SELECTION_LIMIT}
|
|
136
|
+
onChange={interceptOnSearchChange}
|
|
137
|
+
getSelected={getSelected}
|
|
138
|
+
onItemsRendered={onItemsRenderedIntercept}
|
|
139
|
+
required={false}
|
|
140
|
+
checkedPreviewCount={1}
|
|
141
|
+
formatMoreLabel={count => `${count} more`}
|
|
142
|
+
isDynamic
|
|
143
|
+
/>
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
RelationMultiSelectFilter.propTypes = {
|
|
148
|
+
id: PropTypes.string.isRequired,
|
|
149
|
+
label: PropTypes.string,
|
|
150
|
+
entityIdentifier: PropTypes.string.isRequired,
|
|
151
|
+
selectedIds: PropTypes.array,
|
|
152
|
+
onChange: PropTypes.func.isRequired
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
RelationMultiSelectFilter.defaultProps = {
|
|
156
|
+
label: '',
|
|
157
|
+
selectedIds: []
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export default RelationMultiSelectFilter;
|
|
@@ -16,6 +16,7 @@ import { getStringTypeProps } from '@blaze-cms/utils';
|
|
|
16
16
|
|
|
17
17
|
import { BsArrowCounterclockwise, BsSearch } from 'react-icons/bs';
|
|
18
18
|
import { updateListFilters, buildQueryFields, getTidyLabel } from './helpers';
|
|
19
|
+
import RelationMultiSelectFilter from './RelationMultiSelectFilter';
|
|
19
20
|
import { NOOP_QUERY } from './querys';
|
|
20
21
|
import { ENTER_KEY, DEFAULT_FILTER_OPTION_LABEL } from '../../../constants';
|
|
21
22
|
|
|
@@ -57,6 +58,16 @@ const SearchContainer = ({
|
|
|
57
58
|
[multiSelectFiltersToDisplay]
|
|
58
59
|
);
|
|
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
|
+
);
|
|
70
|
+
|
|
60
71
|
const { keywordSearchProperties, keywordSearchLabels } = useMemo(() => {
|
|
61
72
|
const labels = [];
|
|
62
73
|
const properties = [];
|
|
@@ -70,8 +81,9 @@ const SearchContainer = ({
|
|
|
70
81
|
}, [keywordFiltersToUse]);
|
|
71
82
|
|
|
72
83
|
const { gqlFields, rawQuery } = useMemo(
|
|
73
|
-
() =>
|
|
74
|
-
|
|
84
|
+
() =>
|
|
85
|
+
buildQueryFields([...selectFiltersToDisplay, ...nonRelationMultiSelectFilters], schema.id),
|
|
86
|
+
[selectFiltersToDisplay, nonRelationMultiSelectFilters, schema.id]
|
|
75
87
|
);
|
|
76
88
|
|
|
77
89
|
const action = displayAggregations
|
|
@@ -492,6 +504,27 @@ const SearchContainer = ({
|
|
|
492
504
|
);
|
|
493
505
|
})}
|
|
494
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
|
+
})}
|
|
527
|
+
|
|
495
528
|
<div className="search-container__reset-button">
|
|
496
529
|
<span role="button" onClick={handleReset}>
|
|
497
530
|
<BsArrowCounterclockwise />
|
|
@@ -75,9 +75,8 @@ const findMultiSelectFilters = schema => {
|
|
|
75
75
|
|
|
76
76
|
const { properties = {}, dynamicProperties = {} } = schema;
|
|
77
77
|
const propertyMultiSelect = Object.entries(properties).filter(isFilterMultiSelectItems);
|
|
78
|
-
const dynamicPropertyMultiSelect =
|
|
79
|
-
isFilterMultiSelectItems
|
|
80
|
-
);
|
|
78
|
+
const dynamicPropertyMultiSelect =
|
|
79
|
+
Object.entries(dynamicProperties).filter(isFilterMultiSelectItems);
|
|
81
80
|
return [...propertyMultiSelect, ...dynamicPropertyMultiSelect];
|
|
82
81
|
};
|
|
83
82
|
|
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
const getDefaultQueryParams = schema => {
|
|
2
2
|
const updated = schema.properties && schema.properties.updated;
|
|
3
|
-
const { adminDefaultListingFilter: where } =
|
|
3
|
+
const { adminDefaultListingFilter: where, adminListings: { defaultSort } = {} } =
|
|
4
|
+
schema.displayProperties || {};
|
|
5
|
+
|
|
6
|
+
let sort = [];
|
|
7
|
+
|
|
8
|
+
if (defaultSort?.property) {
|
|
9
|
+
sort = [
|
|
10
|
+
{
|
|
11
|
+
property: defaultSort.property,
|
|
12
|
+
direction: defaultSort.direction || 'asc'
|
|
13
|
+
}
|
|
14
|
+
];
|
|
15
|
+
} else if (updated) {
|
|
16
|
+
sort = [
|
|
17
|
+
{
|
|
18
|
+
property: 'updated',
|
|
19
|
+
direction: 'desc'
|
|
20
|
+
}
|
|
21
|
+
];
|
|
22
|
+
}
|
|
4
23
|
|
|
5
24
|
return {
|
|
6
25
|
where,
|
|
7
26
|
limit: 10,
|
|
8
27
|
offset: 0,
|
|
9
|
-
sort
|
|
10
|
-
? [
|
|
11
|
-
{
|
|
12
|
-
property: 'updated',
|
|
13
|
-
direction: 'desc'
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
: []
|
|
28
|
+
sort
|
|
17
29
|
};
|
|
18
30
|
};
|
|
19
31
|
|