@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.
Files changed (25) hide show
  1. package/README.md +9 -2
  2. package/lib/components/EntityManager/Entity/Entity.js +7 -6
  3. package/lib/components/EntityManager/Entity/Entity.js.map +1 -1
  4. package/lib/components/ListingTable/SearchFilter/RelationMultiSelectFilter.js +181 -0
  5. package/lib/components/ListingTable/SearchFilter/RelationMultiSelectFilter.js.map +1 -0
  6. package/lib/components/ListingTable/SearchFilter/SearchContainer.js +18 -1
  7. package/lib/components/ListingTable/SearchFilter/SearchContainer.js.map +1 -1
  8. package/lib/components/ListingTable/SearchFilter/helpers.js.map +1 -1
  9. package/lib/utils/get-default-query-params.js +17 -5
  10. package/lib/utils/get-default-query-params.js.map +1 -1
  11. package/lib-es/components/EntityManager/Entity/Entity.js +7 -6
  12. package/lib-es/components/EntityManager/Entity/Entity.js.map +1 -1
  13. package/lib-es/components/ListingTable/SearchFilter/RelationMultiSelectFilter.js +173 -0
  14. package/lib-es/components/ListingTable/SearchFilter/RelationMultiSelectFilter.js.map +1 -0
  15. package/lib-es/components/ListingTable/SearchFilter/SearchContainer.js +18 -1
  16. package/lib-es/components/ListingTable/SearchFilter/SearchContainer.js.map +1 -1
  17. package/lib-es/components/ListingTable/SearchFilter/helpers.js.map +1 -1
  18. package/lib-es/utils/get-default-query-params.js +17 -5
  19. package/lib-es/utils/get-default-query-params.js.map +1 -1
  20. package/package.json +7 -8
  21. package/src/components/EntityManager/Entity/Entity.js +8 -9
  22. package/src/components/ListingTable/SearchFilter/RelationMultiSelectFilter.js +160 -0
  23. package/src/components/ListingTable/SearchFilter/SearchContainer.js +35 -2
  24. package/src/components/ListingTable/SearchFilter/helpers.js +2 -3
  25. 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 (isValidAndNewValueSet && componentIsMounted.current) {
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 hasNewFormValues = checkFulfilConditions(isNewEntity, !isFormEmpty(formValues));
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--fixed${!isRightSidebarOpen ? ' right-sidebar-collapsed' : ''
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
- () => buildQueryFields([...selectFiltersToDisplay, ...multiSelectFiltersToDisplay], schema.id),
74
- [selectFiltersToDisplay, multiSelectFiltersToDisplay, schema.id]
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 = Object.entries(dynamicProperties).filter(
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 } = schema.displayProperties || {};
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: updated
10
- ? [
11
- {
12
- property: 'updated',
13
- direction: 'desc'
14
- }
15
- ]
16
- : []
28
+ sort
17
29
  };
18
30
  };
19
31