@truedat/core 6.3.3 → 6.3.5
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/package.json +3 -3
- package/src/api.js +3 -0
- package/src/components/FilterItem.js +5 -0
- package/src/components/ModalSaveFilter.js +1 -1
- package/src/components/SearchFilterDropdown.js +1 -1
- package/src/components/UserFilter.js +103 -0
- package/src/hooks/useUserFilters.js +39 -0
- package/src/search/FilterDropdown.js +0 -1
- package/src/search/FilterMultilevelDropdown.js +0 -1
- package/src/search/HierarchyFilterDropdown.js +4 -2
- package/src/search/SearchContext.js +49 -6
- package/src/search/SearchFilters.js +32 -16
- package/src/search/SearchSelectedFilters.js +44 -2
- package/src/search/SearchWidget.js +3 -4
- package/src/search/UserFilter.js +103 -0
- package/src/search/UserFilters.js +136 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/core",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.5",
|
|
4
4
|
"description": "Truedat Web Core",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"jsnext:main": "src/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@testing-library/react": "^12.0.0",
|
|
37
37
|
"@testing-library/react-hooks": "^8.0.1",
|
|
38
38
|
"@testing-library/user-event": "^13.2.1",
|
|
39
|
-
"@truedat/test": "6.3.
|
|
39
|
+
"@truedat/test": "6.3.4",
|
|
40
40
|
"babel-jest": "^28.1.0",
|
|
41
41
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
|
42
42
|
"babel-plugin-lodash": "^3.3.4",
|
|
@@ -118,5 +118,5 @@
|
|
|
118
118
|
"react-dom": ">= 16.8.6 < 17",
|
|
119
119
|
"semantic-ui-react": ">= 2.0.3 < 2.2"
|
|
120
120
|
},
|
|
121
|
-
"gitHead": "
|
|
121
|
+
"gitHead": "2cacdf1fc99746cb4f8dca7d770662c0d96c5bb7"
|
|
122
122
|
}
|
package/src/api.js
CHANGED
|
@@ -6,5 +6,8 @@ export const API_MESSAGE = "/api/messages/:id";
|
|
|
6
6
|
export const API_MESSAGES = "/api/messages";
|
|
7
7
|
export const API_REINDEX_GRANTS = "/api/grants/search/reindex_all";
|
|
8
8
|
export const API_REINDEX_STRUCTURES = "/api/data_structures/search/reindex_all";
|
|
9
|
+
export const API_USER_FILTERS = "/api/:type";
|
|
10
|
+
export const API_USER_FILTER = "/api/:type/:id";
|
|
11
|
+
export const API_GET_USER_FILTERS = "/api/:type/user/me";
|
|
9
12
|
export const API_ACL_ENTRY = "/api/acl_entries/:id";
|
|
10
13
|
export const API_ACL_RESOURCE_ENTRIES = "/api/acl_entries/:type/:id";
|
|
@@ -16,6 +16,11 @@ const FilterItemText = ({ filterName, text }) =>
|
|
|
16
16
|
</i>
|
|
17
17
|
);
|
|
18
18
|
|
|
19
|
+
FilterItemText.propTypes = {
|
|
20
|
+
filterName: PropTypes.string,
|
|
21
|
+
text: PropTypes.string,
|
|
22
|
+
};
|
|
23
|
+
|
|
19
24
|
const preventDefault = (e, callback) => {
|
|
20
25
|
e && e.preventDefault();
|
|
21
26
|
e && e.stopPropagation();
|
|
@@ -145,7 +145,7 @@ SearchFilterDropdown.propTypes = {
|
|
|
145
145
|
filter: PropTypes.string,
|
|
146
146
|
FilterDataLoader: PropTypes.node,
|
|
147
147
|
loaderProps: PropTypes.object,
|
|
148
|
-
FilterItem: PropTypes.
|
|
148
|
+
FilterItem: PropTypes.func,
|
|
149
149
|
openFilter: PropTypes.func,
|
|
150
150
|
removeFilter: PropTypes.func,
|
|
151
151
|
toggleFilterValue: PropTypes.func,
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import PropTypes from "prop-types";
|
|
4
|
+
import { Label, Icon } from "semantic-ui-react";
|
|
5
|
+
import { FormattedMessage } from "react-intl";
|
|
6
|
+
import { useAuthorized } from "@truedat/core/hooks";
|
|
7
|
+
import { useSearchContext } from "@truedat/core/search/SearchContext";
|
|
8
|
+
import { useUserFiltersDelete } from "../hooks/useUserFilters";
|
|
9
|
+
import { ConfirmModal } from "./ConfirmModal";
|
|
10
|
+
|
|
11
|
+
export const UserFilter = ({
|
|
12
|
+
userFilter,
|
|
13
|
+
disabled,
|
|
14
|
+
selectedUserFilter,
|
|
15
|
+
mutate,
|
|
16
|
+
setSelectedUserFilter,
|
|
17
|
+
}) => {
|
|
18
|
+
const authorized = useAuthorized();
|
|
19
|
+
const { userFiltersType, resetFilters, setAllActiveFilters } =
|
|
20
|
+
useSearchContext();
|
|
21
|
+
|
|
22
|
+
const isGlobal = _.prop("is_global")(userFilter);
|
|
23
|
+
|
|
24
|
+
const { trigger: deleteUserFilter } = useUserFiltersDelete(
|
|
25
|
+
userFilter.id,
|
|
26
|
+
userFiltersType
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const handleDelete = () => {
|
|
30
|
+
deleteUserFilter().then(() => {
|
|
31
|
+
mutate();
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Label
|
|
37
|
+
basic={selectedUserFilter == userFilter.name ? false : true}
|
|
38
|
+
circular
|
|
39
|
+
className={isGlobal ? "global" : null}
|
|
40
|
+
>
|
|
41
|
+
<span
|
|
42
|
+
onClick={
|
|
43
|
+
disabled
|
|
44
|
+
? null
|
|
45
|
+
: (e) => {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
e.stopPropagation();
|
|
48
|
+
if (selectedUserFilter == userFilter.name) {
|
|
49
|
+
resetFilters();
|
|
50
|
+
setSelectedUserFilter("");
|
|
51
|
+
} else {
|
|
52
|
+
setAllActiveFilters(userFilter.filters);
|
|
53
|
+
setSelectedUserFilter(userFilter.name);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
className="userFilter"
|
|
58
|
+
>
|
|
59
|
+
{userFilter.name}
|
|
60
|
+
</span>
|
|
61
|
+
|
|
62
|
+
{!isGlobal || authorized ? (
|
|
63
|
+
<ConfirmModal
|
|
64
|
+
icon="trash"
|
|
65
|
+
trigger={
|
|
66
|
+
<Icon
|
|
67
|
+
className="selectable"
|
|
68
|
+
color="grey"
|
|
69
|
+
size="small"
|
|
70
|
+
name="trash alternate outline"
|
|
71
|
+
/>
|
|
72
|
+
}
|
|
73
|
+
header={
|
|
74
|
+
<FormattedMessage id="search.filters.actions.delete.confirmation.header" />
|
|
75
|
+
}
|
|
76
|
+
content={
|
|
77
|
+
<FormattedMessage
|
|
78
|
+
id="search.filters.actions.delete.confirmation.content"
|
|
79
|
+
values={{
|
|
80
|
+
name: (
|
|
81
|
+
<b>
|
|
82
|
+
<i>{userFilter.name}</i>
|
|
83
|
+
</b>
|
|
84
|
+
),
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
}
|
|
88
|
+
onConfirm={() => handleDelete()}
|
|
89
|
+
/>
|
|
90
|
+
) : null}
|
|
91
|
+
</Label>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
UserFilter.propTypes = {
|
|
96
|
+
userFilter: PropTypes.object,
|
|
97
|
+
disabled: PropTypes.bool,
|
|
98
|
+
mutate: PropTypes.func,
|
|
99
|
+
selectedUserFilter: PropTypes.func,
|
|
100
|
+
resetFilters: PropTypes.func,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export default UserFilter;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import { compile } from "path-to-regexp";
|
|
3
|
+
import useSWR from "swr";
|
|
4
|
+
import useSWRMutations from "swr/mutation";
|
|
5
|
+
import { accentInsensitivePathOrder } from "@truedat/core/services/sort";
|
|
6
|
+
import {
|
|
7
|
+
apiJson,
|
|
8
|
+
apiJsonDelete,
|
|
9
|
+
apiJsonPost,
|
|
10
|
+
} from "@truedat/core/services/api";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
API_USER_FILTERS,
|
|
14
|
+
API_USER_FILTER,
|
|
15
|
+
API_GET_USER_FILTERS,
|
|
16
|
+
} from "../api";
|
|
17
|
+
|
|
18
|
+
export const useUserFilters = (type, scope) => {
|
|
19
|
+
const url = _.flow(
|
|
20
|
+
() => compile(API_GET_USER_FILTERS)({ type }),
|
|
21
|
+
_.concat(_.isUndefined(scope) ? "" : `?scope=${scope}`),
|
|
22
|
+
_.compact,
|
|
23
|
+
_.join("")
|
|
24
|
+
)("");
|
|
25
|
+
const { data, error, mutate } = useSWR(url, apiJson);
|
|
26
|
+
const userFilters = data?.data?.data;
|
|
27
|
+
_.sortBy(accentInsensitivePathOrder("name"))(userFilters);
|
|
28
|
+
return { userFilters, error, loading: !error && !data, mutate };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const useUserFiltersCreate = (type) => {
|
|
32
|
+
const url = compile(API_USER_FILTERS)({ type });
|
|
33
|
+
return useSWRMutations(url, (url, { arg }) => apiJsonPost(url, arg));
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const useUserFiltersDelete = (id, type) => {
|
|
37
|
+
const url = compile(API_USER_FILTER)({ id, type });
|
|
38
|
+
return useSWRMutations(url, (url, { arg }) => apiJsonDelete(url, arg));
|
|
39
|
+
};
|
|
@@ -13,6 +13,7 @@ const PopulatedHierarchyFilterDropdown = () => {
|
|
|
13
13
|
|
|
14
14
|
const { filter, options, toggleFilterValue, activeFilterSelectedValues } =
|
|
15
15
|
context;
|
|
16
|
+
|
|
16
17
|
const hierarchyId = _.flow(
|
|
17
18
|
_.reject((item) => _.values(item).includes(undefined)),
|
|
18
19
|
_.first,
|
|
@@ -57,11 +58,13 @@ const PopulatedHierarchyFilterDropdown = () => {
|
|
|
57
58
|
const descendentKeys = _.map("key")(descendents);
|
|
58
59
|
return [key, ...descendentKeys];
|
|
59
60
|
};
|
|
61
|
+
|
|
60
62
|
const newValue = _.flow(
|
|
61
63
|
_.flatMap(getChildrenKeys),
|
|
62
64
|
_.uniq,
|
|
63
65
|
_.reject(_.isNil)
|
|
64
66
|
)(value);
|
|
67
|
+
|
|
65
68
|
toggleFilterValue({ filter, value: newValue });
|
|
66
69
|
};
|
|
67
70
|
|
|
@@ -72,8 +75,8 @@ const PopulatedHierarchyFilterDropdown = () => {
|
|
|
72
75
|
name: "hierarchyFilterDropdown",
|
|
73
76
|
filter,
|
|
74
77
|
options: filteredOptions,
|
|
75
|
-
activeFilterSelectedValues: idActiveValues,
|
|
76
78
|
toggleFilterValue: handleToggleFilterValue,
|
|
79
|
+
activeFilterSelectedValues: idActiveValues,
|
|
77
80
|
}}
|
|
78
81
|
key={filter}
|
|
79
82
|
>
|
|
@@ -84,7 +87,6 @@ const PopulatedHierarchyFilterDropdown = () => {
|
|
|
84
87
|
|
|
85
88
|
const HierarchyFilterDropdown = () => {
|
|
86
89
|
const { options } = useSearchContext();
|
|
87
|
-
|
|
88
90
|
return _.isEmpty(options) ? (
|
|
89
91
|
<FilterMultilevelDropdown />
|
|
90
92
|
) : (
|
|
@@ -24,6 +24,11 @@ export const SearchContextProvider = (props) => {
|
|
|
24
24
|
const useSearch = _.prop("useSearch")(props);
|
|
25
25
|
const useFilters = _.prop("useFilters")(props);
|
|
26
26
|
const pageSize = _.propOr(20, "pageSize")(props);
|
|
27
|
+
const userFiltersType = _.prop("userFiltersType")(props);
|
|
28
|
+
const userFilterScope = _.prop("userFilterScope")(props);
|
|
29
|
+
const omitFilters = _.propOr([], "omitFilters")(props);
|
|
30
|
+
const translations = _.propOr(() => ({}), "translations")(props);
|
|
31
|
+
const filtersGroup = _.propOr([], "filtersGroup")(props);
|
|
27
32
|
|
|
28
33
|
const { formatMessage } = useIntl();
|
|
29
34
|
|
|
@@ -37,6 +42,7 @@ export const SearchContextProvider = (props) => {
|
|
|
37
42
|
const [allActiveFilters, setAllActiveFilters] = useState({});
|
|
38
43
|
const [hiddenFilters, setHiddenFilters] = useState({});
|
|
39
44
|
|
|
45
|
+
const [filterParams, setFilterParams] = useState({});
|
|
40
46
|
const [sortColumn, setSortColumn] = useState(initialSortColumn);
|
|
41
47
|
const [sortDirection, setSortDirection] = useState(initialSortDirection);
|
|
42
48
|
const [page, setPage] = useState(1);
|
|
@@ -95,16 +101,13 @@ export const SearchContextProvider = (props) => {
|
|
|
95
101
|
|
|
96
102
|
const filters = _.flow(
|
|
97
103
|
_.propOr({}, "data"),
|
|
104
|
+
_.omit(omitFilters),
|
|
98
105
|
_.omitBy(_.flow(_.propOr([], "values"), (values) => _.size(values) < 2))
|
|
99
106
|
)(filtersPayload);
|
|
100
107
|
|
|
101
108
|
const availableFilters = _.flow(_.keys, _.without(selectedFilters))(filters);
|
|
102
109
|
const filterTypes = _.mapValues("type")(filters);
|
|
103
110
|
|
|
104
|
-
const translations = (formatMessage) => ({
|
|
105
|
-
"status.raw": (v) => formatMessage({ id: v, defaultMessage: v }),
|
|
106
|
-
});
|
|
107
|
-
|
|
108
111
|
const activeFilterValues = _.flow(
|
|
109
112
|
_.propOr({ values: [] }, activeFilterName),
|
|
110
113
|
({ values, type }) => ({
|
|
@@ -152,7 +155,9 @@ export const SearchContextProvider = (props) => {
|
|
|
152
155
|
: null,
|
|
153
156
|
[sortColumn, sortDirection]
|
|
154
157
|
);
|
|
158
|
+
|
|
155
159
|
const { trigger: triggerFilters } = useFilters();
|
|
160
|
+
|
|
156
161
|
useEffect(() => {
|
|
157
162
|
setLoadingFilters(true);
|
|
158
163
|
|
|
@@ -164,9 +169,10 @@ export const SearchContextProvider = (props) => {
|
|
|
164
169
|
setFiltersPayload(data);
|
|
165
170
|
setLoadingFilters(false);
|
|
166
171
|
});
|
|
167
|
-
}, [query, filterMust
|
|
172
|
+
}, [query, filterMust]);
|
|
168
173
|
|
|
169
174
|
const { trigger: triggerSearch } = useSearch();
|
|
175
|
+
|
|
170
176
|
useEffect(() => {
|
|
171
177
|
setLoading(true);
|
|
172
178
|
|
|
@@ -177,18 +183,47 @@ export const SearchContextProvider = (props) => {
|
|
|
177
183
|
page: page - 1,
|
|
178
184
|
size,
|
|
179
185
|
};
|
|
186
|
+
setFilterParams(filterParam);
|
|
187
|
+
|
|
180
188
|
triggerSearch(filterParam).then(({ data, headers }) => {
|
|
181
189
|
setSearchData(data);
|
|
182
190
|
setCountData(headers);
|
|
183
191
|
setLoading(false);
|
|
184
192
|
});
|
|
185
|
-
}, [query, searchMust, sort,
|
|
193
|
+
}, [query, searchMust, sort, defaultFilters, page, size]);
|
|
194
|
+
|
|
195
|
+
const makeFiltersGroup = (filters, groups) =>
|
|
196
|
+
_.groupBy((filter) =>
|
|
197
|
+
_.flow(
|
|
198
|
+
_.find(([_group, fields]) => _.contains(filter)(fields)),
|
|
199
|
+
_.prop("[0]")
|
|
200
|
+
)(groups)
|
|
201
|
+
)(filters);
|
|
202
|
+
|
|
203
|
+
const filtersByGroup = makeFiltersGroup(availableFilters, filtersGroup);
|
|
204
|
+
|
|
205
|
+
const groupWithoutFilters = (groups) =>
|
|
206
|
+
_.flow(
|
|
207
|
+
_.prop("[0]"),
|
|
208
|
+
(groupName) => _.prop(groupName)(filtersByGroup),
|
|
209
|
+
_.isEmpty
|
|
210
|
+
)(groups);
|
|
211
|
+
|
|
212
|
+
const availableGroupedFilters = _.flow(
|
|
213
|
+
(groups) => _.concat(groups, [[undefined, []]]),
|
|
214
|
+
_.reject(groupWithoutFilters),
|
|
215
|
+
_.map(([groupName, _filters]) => [
|
|
216
|
+
groupName,
|
|
217
|
+
_.prop(groupName)(filtersByGroup),
|
|
218
|
+
])
|
|
219
|
+
)(filtersGroup);
|
|
186
220
|
|
|
187
221
|
const context = {
|
|
188
222
|
disabled: false,
|
|
189
223
|
loadingFilters,
|
|
190
224
|
|
|
191
225
|
availableFilters,
|
|
226
|
+
availableGroupedFilters,
|
|
192
227
|
selectedFilters,
|
|
193
228
|
filterTypes,
|
|
194
229
|
|
|
@@ -209,6 +244,9 @@ export const SearchContextProvider = (props) => {
|
|
|
209
244
|
toggleHiddenFilterValue,
|
|
210
245
|
searchMust,
|
|
211
246
|
setQuery,
|
|
247
|
+
setAllActiveFilters,
|
|
248
|
+
allActiveFilters,
|
|
249
|
+
filters,
|
|
212
250
|
|
|
213
251
|
searchData,
|
|
214
252
|
loading,
|
|
@@ -222,6 +260,11 @@ export const SearchContextProvider = (props) => {
|
|
|
222
260
|
count,
|
|
223
261
|
page,
|
|
224
262
|
size,
|
|
263
|
+
sort,
|
|
264
|
+
filterParams,
|
|
265
|
+
|
|
266
|
+
userFiltersType,
|
|
267
|
+
userFilterScope,
|
|
225
268
|
};
|
|
226
269
|
|
|
227
270
|
return (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
|
-
import React from "react";
|
|
2
|
+
import React, { Fragment } from "react";
|
|
3
3
|
import { Dropdown } from "semantic-ui-react";
|
|
4
4
|
import { FormattedMessage, useIntl } from "react-intl";
|
|
5
5
|
import { i18nOrder } from "@truedat/core/services/sort";
|
|
@@ -10,7 +10,7 @@ const removePrefix = _.replace(/^.*\./, "");
|
|
|
10
10
|
export default function SearchFilters() {
|
|
11
11
|
const {
|
|
12
12
|
disabled,
|
|
13
|
-
|
|
13
|
+
availableGroupedFilters,
|
|
14
14
|
addFilter,
|
|
15
15
|
resetFilters,
|
|
16
16
|
loadingFilters: loading,
|
|
@@ -40,20 +40,36 @@ export default function SearchFilters() {
|
|
|
40
40
|
/>
|
|
41
41
|
</em>
|
|
42
42
|
</Dropdown.Item>
|
|
43
|
-
{_.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
43
|
+
{_.map.convert({ cap: false })(([groupName, filters], idx) => (
|
|
44
|
+
<Fragment key={idx}>
|
|
45
|
+
{idx == 0 ? null : <Dropdown.Divider />}
|
|
46
|
+
{groupName ? (
|
|
47
|
+
<Dropdown.Header key={groupName}>
|
|
48
|
+
<b>
|
|
49
|
+
<FormattedMessage
|
|
50
|
+
id={`filters.group.header.${groupName}`}
|
|
51
|
+
defaultMessage={groupName}
|
|
52
|
+
/>
|
|
53
|
+
</b>
|
|
54
|
+
</Dropdown.Header>
|
|
55
|
+
) : null}
|
|
56
|
+
|
|
57
|
+
{_.flow(
|
|
58
|
+
_.defaultTo([]),
|
|
59
|
+
_.sortBy(i18nOrder(formatMessage, "filters")),
|
|
60
|
+
_.map((filter) => (
|
|
61
|
+
<Dropdown.Item
|
|
62
|
+
key={filter}
|
|
63
|
+
text={formatMessage({
|
|
64
|
+
id: `filters.${filter}`,
|
|
65
|
+
defaultMessage: removePrefix(filter),
|
|
66
|
+
})}
|
|
67
|
+
onClick={() => addFilter({ filter })}
|
|
68
|
+
/>
|
|
69
|
+
))
|
|
70
|
+
)(filters)}
|
|
71
|
+
</Fragment>
|
|
72
|
+
))(availableGroupedFilters)}
|
|
57
73
|
</Dropdown.Menu>
|
|
58
74
|
</Dropdown>
|
|
59
75
|
);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
|
-
import React from "react";
|
|
2
|
+
import React, { useState } from "react";
|
|
3
3
|
import { FormattedMessage } from "react-intl";
|
|
4
|
+
import { useUserFilters, useUserFiltersCreate } from "../hooks/useUserFilters";
|
|
5
|
+
import ModalSaveFilter from "../components/ModalSaveFilter";
|
|
6
|
+
import UserFilters from "./UserFilters";
|
|
4
7
|
import FilterDropdown from "./FilterDropdown";
|
|
5
8
|
import FilterMultilevelDropdown from "./FilterMultilevelDropdown";
|
|
6
9
|
import HierarchyFilterDropdown from "./HierarchyFilterDropdown";
|
|
@@ -14,10 +17,37 @@ export default function SearchSelectedFilters() {
|
|
|
14
17
|
filterTypes,
|
|
15
18
|
activeFilterName,
|
|
16
19
|
activeFilterValues,
|
|
20
|
+
allActiveFilters,
|
|
21
|
+
userFiltersType,
|
|
22
|
+
userFilterScope,
|
|
17
23
|
} = context;
|
|
24
|
+
const [selectedUserFilter, setSelectedUserFilter] = useState();
|
|
25
|
+
const { trigger: saveFilters } = useUserFiltersCreate(userFiltersType);
|
|
26
|
+
const { userFilters, loading, mutate } = useUserFilters(
|
|
27
|
+
userFiltersType,
|
|
28
|
+
userFilterScope
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const handleSubmit = ({ filterName, filters, isGlobal }) => {
|
|
32
|
+
const requestData = {
|
|
33
|
+
user_search_filter: { name: filterName, filters, is_global: isGlobal },
|
|
34
|
+
};
|
|
35
|
+
saveFilters(requestData).then(() => {
|
|
36
|
+
mutate();
|
|
37
|
+
});
|
|
38
|
+
};
|
|
18
39
|
|
|
19
40
|
return (
|
|
20
41
|
<>
|
|
42
|
+
{!_.isEmpty(userFilters) ? (
|
|
43
|
+
<UserFilters
|
|
44
|
+
mutate={mutate}
|
|
45
|
+
userFilters={userFilters}
|
|
46
|
+
disabled={loading}
|
|
47
|
+
setSelectedUserFilter={setSelectedUserFilter}
|
|
48
|
+
selectedUserFilter={selectedUserFilter}
|
|
49
|
+
/>
|
|
50
|
+
) : null}
|
|
21
51
|
<div className="selectedFilters">
|
|
22
52
|
{_.isEmpty(selectedFilters) ? null : (
|
|
23
53
|
<>
|
|
@@ -29,6 +59,7 @@ export default function SearchSelectedFilters() {
|
|
|
29
59
|
const options = _.isEqual(filter, activeFilterName)
|
|
30
60
|
? activeFilterValues
|
|
31
61
|
: null;
|
|
62
|
+
|
|
32
63
|
return (
|
|
33
64
|
<SearchContext.Provider
|
|
34
65
|
value={{ ...context, filter, options }}
|
|
@@ -44,9 +75,20 @@ export default function SearchSelectedFilters() {
|
|
|
44
75
|
</SearchContext.Provider>
|
|
45
76
|
);
|
|
46
77
|
})}
|
|
47
|
-
<a
|
|
78
|
+
<a
|
|
79
|
+
className="resetFilters"
|
|
80
|
+
onClick={() => {
|
|
81
|
+
resetFilters();
|
|
82
|
+
setSelectedUserFilter("");
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
48
85
|
<FormattedMessage id="search.clear_filters" />
|
|
49
86
|
</a>
|
|
87
|
+
<ModalSaveFilter
|
|
88
|
+
saveFilters={handleSubmit}
|
|
89
|
+
activeFilters={allActiveFilters}
|
|
90
|
+
scope={userFilterScope}
|
|
91
|
+
/>
|
|
50
92
|
</>
|
|
51
93
|
)}
|
|
52
94
|
</div>
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Input } from "semantic-ui-react";
|
|
3
3
|
import { useIntl } from "react-intl";
|
|
4
|
-
import SearchFilters from "
|
|
5
|
-
import SearchSelectedFilters from "
|
|
6
|
-
|
|
7
|
-
import { useSearchContext } from "@truedat/core/search/SearchContext";
|
|
4
|
+
import SearchFilters from "./SearchFilters";
|
|
5
|
+
import SearchSelectedFilters from "./SearchSelectedFilters";
|
|
6
|
+
import { useSearchContext } from "./SearchContext";
|
|
8
7
|
|
|
9
8
|
export default function SearchWidget() {
|
|
10
9
|
const { formatMessage } = useIntl();
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import PropTypes from "prop-types";
|
|
4
|
+
import { Label, Icon } from "semantic-ui-react";
|
|
5
|
+
import { FormattedMessage } from "react-intl";
|
|
6
|
+
import { useAuthorized } from "@truedat/core/hooks";
|
|
7
|
+
import { useSearchContext } from "@truedat/core/search/SearchContext";
|
|
8
|
+
import { useUserFiltersDelete } from "../hooks/useUserFilters";
|
|
9
|
+
import { ConfirmModal } from "./ConfirmModal";
|
|
10
|
+
|
|
11
|
+
export const UserFilter = ({
|
|
12
|
+
userFilter,
|
|
13
|
+
disabled,
|
|
14
|
+
selectedUserFilter,
|
|
15
|
+
mutate,
|
|
16
|
+
setSelectedUserFilter,
|
|
17
|
+
}) => {
|
|
18
|
+
const authorized = useAuthorized();
|
|
19
|
+
const { userFiltersType, resetFilters, setAllActiveFilters } =
|
|
20
|
+
useSearchContext();
|
|
21
|
+
|
|
22
|
+
const isGlobal = _.prop("is_global")(userFilter);
|
|
23
|
+
|
|
24
|
+
const { trigger: deleteUserFilter } = useUserFiltersDelete(
|
|
25
|
+
userFilter.id,
|
|
26
|
+
userFiltersType
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const handleDelete = () => {
|
|
30
|
+
deleteUserFilter().then(() => {
|
|
31
|
+
mutate();
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Label
|
|
37
|
+
basic={selectedUserFilter == userFilter.name ? false : true}
|
|
38
|
+
circular
|
|
39
|
+
className={isGlobal ? "global" : null}
|
|
40
|
+
>
|
|
41
|
+
<span
|
|
42
|
+
onClick={
|
|
43
|
+
disabled
|
|
44
|
+
? null
|
|
45
|
+
: (e) => {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
e.stopPropagation();
|
|
48
|
+
if (selectedUserFilter == userFilter.name) {
|
|
49
|
+
resetFilters();
|
|
50
|
+
setSelectedUserFilter("");
|
|
51
|
+
} else {
|
|
52
|
+
setAllActiveFilters(userFilter.filters);
|
|
53
|
+
setSelectedUserFilter(userFilter.name);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
className="userFilter"
|
|
58
|
+
>
|
|
59
|
+
{userFilter.name}
|
|
60
|
+
</span>
|
|
61
|
+
|
|
62
|
+
{!isGlobal || authorized ? (
|
|
63
|
+
<ConfirmModal
|
|
64
|
+
icon="trash"
|
|
65
|
+
trigger={
|
|
66
|
+
<Icon
|
|
67
|
+
className="selectable"
|
|
68
|
+
color="grey"
|
|
69
|
+
size="small"
|
|
70
|
+
name="trash alternate outline"
|
|
71
|
+
/>
|
|
72
|
+
}
|
|
73
|
+
header={
|
|
74
|
+
<FormattedMessage id="search.filters.actions.delete.confirmation.header" />
|
|
75
|
+
}
|
|
76
|
+
content={
|
|
77
|
+
<FormattedMessage
|
|
78
|
+
id="search.filters.actions.delete.confirmation.content"
|
|
79
|
+
values={{
|
|
80
|
+
name: (
|
|
81
|
+
<b>
|
|
82
|
+
<i>{userFilter.name}</i>
|
|
83
|
+
</b>
|
|
84
|
+
),
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
}
|
|
88
|
+
onConfirm={() => handleDelete()}
|
|
89
|
+
/>
|
|
90
|
+
) : null}
|
|
91
|
+
</Label>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
UserFilter.propTypes = {
|
|
96
|
+
userFilter: PropTypes.object,
|
|
97
|
+
disabled: PropTypes.bool,
|
|
98
|
+
mutate: PropTypes.func,
|
|
99
|
+
selectedUserFilter: PropTypes.func,
|
|
100
|
+
resetFilters: PropTypes.func,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export default UserFilter;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import React, { useState } from "react";
|
|
3
|
+
import PropTypes from "prop-types";
|
|
4
|
+
import { Label, Icon } from "semantic-ui-react";
|
|
5
|
+
import { FormattedMessage } from "react-intl";
|
|
6
|
+
import { useAuthorized } from "@truedat/core/hooks";
|
|
7
|
+
import { useSearchContext } from "@truedat/core/search/SearchContext";
|
|
8
|
+
import { useUserFiltersDelete } from "../hooks/useUserFilters";
|
|
9
|
+
import { ConfirmModal } from "../components/ConfirmModal";
|
|
10
|
+
|
|
11
|
+
export const DeleteModal = ({ userFilter, userFiltersType, mutate }) => {
|
|
12
|
+
const { trigger: deleteUserFilter } = useUserFiltersDelete(
|
|
13
|
+
userFilter.id,
|
|
14
|
+
userFiltersType
|
|
15
|
+
);
|
|
16
|
+
const handleDelete = () => {
|
|
17
|
+
deleteUserFilter().then(() => {
|
|
18
|
+
mutate();
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
return (
|
|
22
|
+
<ConfirmModal
|
|
23
|
+
icon="trash"
|
|
24
|
+
trigger={
|
|
25
|
+
<Icon
|
|
26
|
+
className="selectable"
|
|
27
|
+
color="grey"
|
|
28
|
+
size="small"
|
|
29
|
+
name="trash alternate outline"
|
|
30
|
+
/>
|
|
31
|
+
}
|
|
32
|
+
header={
|
|
33
|
+
<FormattedMessage id="search.filters.actions.delete.confirmation.header" />
|
|
34
|
+
}
|
|
35
|
+
content={
|
|
36
|
+
<FormattedMessage
|
|
37
|
+
id="search.filters.actions.delete.confirmation.content"
|
|
38
|
+
values={{
|
|
39
|
+
name: (
|
|
40
|
+
<b>
|
|
41
|
+
<i>{userFilter.name}</i>
|
|
42
|
+
</b>
|
|
43
|
+
),
|
|
44
|
+
}}
|
|
45
|
+
/>
|
|
46
|
+
}
|
|
47
|
+
onConfirm={() => {
|
|
48
|
+
handleDelete();
|
|
49
|
+
}}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
DeleteModal.propTypes = {
|
|
55
|
+
userFilter: PropTypes.object,
|
|
56
|
+
userFiltersType: PropTypes.string,
|
|
57
|
+
mutate: PropTypes.func,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const UserFilters = ({
|
|
61
|
+
mutate,
|
|
62
|
+
userFilters,
|
|
63
|
+
disabled,
|
|
64
|
+
setSelectedUserFilter,
|
|
65
|
+
selectedUserFilter,
|
|
66
|
+
}) => {
|
|
67
|
+
const authorized = useAuthorized();
|
|
68
|
+
const { userFiltersType, resetFilters, setAllActiveFilters } =
|
|
69
|
+
useSearchContext();
|
|
70
|
+
|
|
71
|
+
const sortedUserFilters = _.orderBy(
|
|
72
|
+
["is_global", "id"],
|
|
73
|
+
["desc", "asc"]
|
|
74
|
+
)(userFilters);
|
|
75
|
+
|
|
76
|
+
return _.isEmpty(userFilters) ? null : (
|
|
77
|
+
<div className="selectedFilters">
|
|
78
|
+
{sortedUserFilters.map((userFilter, key) => {
|
|
79
|
+
const isGlobal = _.prop("is_global")(userFilter);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<Label
|
|
83
|
+
basic={selectedUserFilter == userFilter.name ? false : true}
|
|
84
|
+
circular
|
|
85
|
+
className={isGlobal ? "global" : null}
|
|
86
|
+
key={key}
|
|
87
|
+
>
|
|
88
|
+
<span
|
|
89
|
+
onClick={
|
|
90
|
+
disabled
|
|
91
|
+
? null
|
|
92
|
+
: (e) => {
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
e.stopPropagation();
|
|
95
|
+
if (selectedUserFilter == userFilter.name) {
|
|
96
|
+
resetFilters();
|
|
97
|
+
setSelectedUserFilter(null);
|
|
98
|
+
} else {
|
|
99
|
+
setAllActiveFilters(
|
|
100
|
+
_.propOr({}, "filters")(userFilter)
|
|
101
|
+
);
|
|
102
|
+
setSelectedUserFilter(userFilter.name);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
className="userFilter"
|
|
107
|
+
>
|
|
108
|
+
{userFilter.name}
|
|
109
|
+
</span>
|
|
110
|
+
|
|
111
|
+
{!isGlobal || authorized ? (
|
|
112
|
+
<DeleteModal
|
|
113
|
+
userFilter={userFilter}
|
|
114
|
+
userFiltersType={userFiltersType}
|
|
115
|
+
mutate={mutate}
|
|
116
|
+
/>
|
|
117
|
+
) : null}
|
|
118
|
+
</Label>
|
|
119
|
+
);
|
|
120
|
+
})}
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
UserFilters.propTypes = {
|
|
126
|
+
selectedUserFilter: PropTypes.string,
|
|
127
|
+
userFilters: PropTypes.array,
|
|
128
|
+
disabled: PropTypes.bool,
|
|
129
|
+
userFilterScope: PropTypes.string,
|
|
130
|
+
mutate: PropTypes.func,
|
|
131
|
+
applyUserFilter: PropTypes.func,
|
|
132
|
+
deleteUserFilter: PropTypes.func,
|
|
133
|
+
resetFilters: PropTypes.func,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export default UserFilters;
|