@truedat/core 6.0.5 → 6.1.1
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/components/CatalogMenu.js +0 -3
- package/src/components/Pagination.js +1 -1
- package/src/components/__tests__/CatalogMenu.spec.js +56 -32
- package/src/components/__tests__/SideMenu.spec.js +37 -2
- package/src/components/__tests__/__snapshots__/CatalogMenu.spec.js.snap +62 -112
- package/src/components/__tests__/__snapshots__/SideMenu.spec.js.snap +465 -33
- package/src/search/FilterDropdown.js +76 -0
- package/src/search/FilterItem.js +49 -0
- package/src/search/FilterMultilevelDropdown.js +202 -0
- package/src/search/HierarchyFilterDropdown.js +95 -0
- package/src/search/SearchContext.js +234 -0
- package/src/search/SearchFilters.js +60 -0
- package/src/search/SearchSelectedFilters.js +55 -0
- package/src/search/SearchWidget.js +30 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import React, { useState, useEffect } from "react";
|
|
3
|
+
import { FormattedMessage } from "react-intl";
|
|
4
|
+
import {
|
|
5
|
+
Label,
|
|
6
|
+
Icon,
|
|
7
|
+
Input,
|
|
8
|
+
Dropdown,
|
|
9
|
+
Dimmer,
|
|
10
|
+
Loader,
|
|
11
|
+
} from "semantic-ui-react";
|
|
12
|
+
import { lowerDeburr } from "@truedat/core/services/sort";
|
|
13
|
+
import DropdownMenuItem from "@truedat/core/components/DropdownMenuItem";
|
|
14
|
+
|
|
15
|
+
import { useSearchContext } from "./SearchContext";
|
|
16
|
+
|
|
17
|
+
export default function FilterMultilevelDropdown() {
|
|
18
|
+
const {
|
|
19
|
+
name,
|
|
20
|
+
loadingFilters: loading,
|
|
21
|
+
filter,
|
|
22
|
+
options,
|
|
23
|
+
activeFilterSelectedValues,
|
|
24
|
+
|
|
25
|
+
openFilter,
|
|
26
|
+
closeFilter,
|
|
27
|
+
removeFilter,
|
|
28
|
+
toggleFilterValue,
|
|
29
|
+
} = useSearchContext();
|
|
30
|
+
|
|
31
|
+
const [selected, setSelected] = useState();
|
|
32
|
+
const [query, setQuery] = useState();
|
|
33
|
+
const [open, setOpen] = useState([]);
|
|
34
|
+
const [displayed, setDisplayed] = useState([]);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const activeOptions = _.filter((option) =>
|
|
38
|
+
_.includes(option.id)(activeFilterSelectedValues)
|
|
39
|
+
)(options);
|
|
40
|
+
|
|
41
|
+
_.flow(
|
|
42
|
+
_.reduce(
|
|
43
|
+
(acc, option) => [
|
|
44
|
+
...acc,
|
|
45
|
+
option.id,
|
|
46
|
+
..._.map("id")(option.descendents),
|
|
47
|
+
],
|
|
48
|
+
[]
|
|
49
|
+
),
|
|
50
|
+
_.uniq,
|
|
51
|
+
setSelected
|
|
52
|
+
)(activeOptions);
|
|
53
|
+
if (_.isEmpty(open) && _.isEmpty(displayed)) {
|
|
54
|
+
const withAncestors = _.flow(
|
|
55
|
+
_.reduce(
|
|
56
|
+
(acc, option) => [...acc, ..._.map("id")(option.ancestors)],
|
|
57
|
+
[]
|
|
58
|
+
),
|
|
59
|
+
_.uniq
|
|
60
|
+
)(activeOptions);
|
|
61
|
+
const newDisplayed = [..._.map("id")(activeOptions), ...withAncestors];
|
|
62
|
+
!_.isEqual(open, withAncestors) && setOpen(withAncestors);
|
|
63
|
+
!_.isEqual(displayed, newDisplayed) && setDisplayed(newDisplayed);
|
|
64
|
+
}
|
|
65
|
+
}, [activeFilterSelectedValues, options, open, displayed]);
|
|
66
|
+
|
|
67
|
+
const handleOpen = (selection) => {
|
|
68
|
+
const option = _.find({ id: selection })(options);
|
|
69
|
+
const isOpen = _.contains(selection)(open);
|
|
70
|
+
const children = _.map("id")(option.children);
|
|
71
|
+
const descendents = _.map("id")(option.descendents);
|
|
72
|
+
|
|
73
|
+
if (isOpen) {
|
|
74
|
+
setOpen(_.without([selection, ...descendents])(open));
|
|
75
|
+
setDisplayed(_.without(descendents)(displayed));
|
|
76
|
+
} else {
|
|
77
|
+
setOpen(_.union([selection])(open));
|
|
78
|
+
setDisplayed(_.union(children)(displayed));
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const handleClick = (e, selection) => {
|
|
83
|
+
const option = _.find({ id: selection })(options);
|
|
84
|
+
const ancestorsToDelete = _.intersection(_.map("id")(option.ancestors))(
|
|
85
|
+
activeFilterSelectedValues
|
|
86
|
+
);
|
|
87
|
+
if (!_.isEmpty(ancestorsToDelete)) {
|
|
88
|
+
const active = _.union([selection])(activeFilterSelectedValues);
|
|
89
|
+
const value = _.without(ancestorsToDelete)(active);
|
|
90
|
+
toggleFilterValue({ filter, value });
|
|
91
|
+
} else if (_.includes(selection)(activeFilterSelectedValues)) {
|
|
92
|
+
const descendents = _.map("id")(option.descendents);
|
|
93
|
+
const value = _.without([selection, ...descendents])(
|
|
94
|
+
activeFilterSelectedValues
|
|
95
|
+
);
|
|
96
|
+
toggleFilterValue({ filter, value });
|
|
97
|
+
} else {
|
|
98
|
+
const descendents = _.map("id")(option.descendents);
|
|
99
|
+
const active = _.without(descendents)(activeFilterSelectedValues);
|
|
100
|
+
const value = _.union([selection])(active);
|
|
101
|
+
toggleFilterValue({ filter, value });
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const displayAll = () => {
|
|
106
|
+
const ids = _.map("id")(options);
|
|
107
|
+
setOpen(ids);
|
|
108
|
+
setDisplayed(ids);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const handleSearch = (e, { value }) => {
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
setQuery(lowerDeburr(value));
|
|
114
|
+
if (!_.isEmpty(value)) {
|
|
115
|
+
displayAll();
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const match = (name, query) => _.contains(query)(lowerDeburr(name));
|
|
119
|
+
const filterSearch = (all) => {
|
|
120
|
+
if (query) {
|
|
121
|
+
return _.filter(
|
|
122
|
+
(domain) =>
|
|
123
|
+
match(domain.name, query) ||
|
|
124
|
+
_.some((descendent) => match(descendent.name, query))(
|
|
125
|
+
domain.descendents
|
|
126
|
+
)
|
|
127
|
+
)(all);
|
|
128
|
+
}
|
|
129
|
+
return all;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const filterDisplayed = (all) =>
|
|
133
|
+
_.filter((domain) => domain.level == 0 || _.contains(domain.id)(displayed))(
|
|
134
|
+
all
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const filteredOptions = _.flow(filterSearch, filterDisplayed)(options);
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<Dropdown
|
|
141
|
+
name={name || "filterMultilevelDropdown"}
|
|
142
|
+
item
|
|
143
|
+
floating
|
|
144
|
+
icon={false}
|
|
145
|
+
upward={false}
|
|
146
|
+
onOpen={() => openFilter({ filter })}
|
|
147
|
+
onClose={() => closeFilter({ filter })}
|
|
148
|
+
trigger={
|
|
149
|
+
<Label key={filter}>
|
|
150
|
+
<FormattedMessage id={`filters.${filter}`} defaultMessage={filter} />
|
|
151
|
+
<Icon
|
|
152
|
+
name="delete"
|
|
153
|
+
onClick={(e) => {
|
|
154
|
+
e.preventDefault();
|
|
155
|
+
e.stopPropagation();
|
|
156
|
+
removeFilter({ filter });
|
|
157
|
+
}}
|
|
158
|
+
/>
|
|
159
|
+
</Label>
|
|
160
|
+
}
|
|
161
|
+
open={!_.isEmpty(options)}
|
|
162
|
+
>
|
|
163
|
+
<Dimmer.Dimmable dimmed={loading} as={Dropdown.Menu}>
|
|
164
|
+
<>
|
|
165
|
+
<Input
|
|
166
|
+
icon="search"
|
|
167
|
+
iconPosition="left"
|
|
168
|
+
className="search"
|
|
169
|
+
onKeyDown={(e) => {
|
|
170
|
+
if (e.key === " ") {
|
|
171
|
+
e.stopPropagation();
|
|
172
|
+
}
|
|
173
|
+
}}
|
|
174
|
+
onChange={handleSearch}
|
|
175
|
+
onClick={(e) => {
|
|
176
|
+
e.preventDefault();
|
|
177
|
+
e.stopPropagation();
|
|
178
|
+
}}
|
|
179
|
+
/>
|
|
180
|
+
<Dropdown.Menu scrolling>
|
|
181
|
+
{_.map.convert({ cap: false })((option, i) => (
|
|
182
|
+
<DropdownMenuItem
|
|
183
|
+
key={i}
|
|
184
|
+
onOpen={handleOpen}
|
|
185
|
+
onClick={handleClick}
|
|
186
|
+
open={_.contains(option.id)(open)}
|
|
187
|
+
canOpen={_.negate(_.isEmpty)(option.children)}
|
|
188
|
+
selected={_.contains(option.id)(selected)}
|
|
189
|
+
{...option}
|
|
190
|
+
/>
|
|
191
|
+
))(filteredOptions)}
|
|
192
|
+
</Dropdown.Menu>
|
|
193
|
+
</>
|
|
194
|
+
{loading && (
|
|
195
|
+
<Dimmer active inverted>
|
|
196
|
+
<Loader size="tiny" />
|
|
197
|
+
</Dimmer>
|
|
198
|
+
)}
|
|
199
|
+
</Dimmer.Dimmable>
|
|
200
|
+
</Dropdown>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { useHierarchy } from "@truedat/df/hooks/useHierarchies";
|
|
4
|
+
import {
|
|
5
|
+
getHierarchyOptions,
|
|
6
|
+
getKeyAndParents,
|
|
7
|
+
} from "@truedat/core/services/getHierarchyOptions";
|
|
8
|
+
import FilterMultilevelDropdown from "./FilterMultilevelDropdown";
|
|
9
|
+
import SearchContext, { useSearchContext } from "./SearchContext";
|
|
10
|
+
|
|
11
|
+
const PopulatedHierarchyFilterDropdown = () => {
|
|
12
|
+
const context = useSearchContext();
|
|
13
|
+
|
|
14
|
+
const { filter, options, toggleFilterValue, activeFilterSelectedValues } =
|
|
15
|
+
context;
|
|
16
|
+
const hierarchyId = _.flow(
|
|
17
|
+
_.reject((item) => _.values(item).includes(undefined)),
|
|
18
|
+
_.first,
|
|
19
|
+
_.prop("value"),
|
|
20
|
+
(value) => value.split("_"),
|
|
21
|
+
_.first
|
|
22
|
+
)(options);
|
|
23
|
+
|
|
24
|
+
const { data, error, loading: hierarchyLoading } = useHierarchy(hierarchyId);
|
|
25
|
+
if (error) return null;
|
|
26
|
+
if (hierarchyLoading) return null;
|
|
27
|
+
|
|
28
|
+
const hierarchyOptions = getHierarchyOptions(data?.nodes);
|
|
29
|
+
|
|
30
|
+
const includedKeys = _.flow(
|
|
31
|
+
_.map("value"),
|
|
32
|
+
_.flatMap(getKeyAndParents(hierarchyOptions)),
|
|
33
|
+
_.uniq
|
|
34
|
+
)(options);
|
|
35
|
+
|
|
36
|
+
const filterIncludedKeys = _.filter(({ key }) =>
|
|
37
|
+
_.includes(key)(includedKeys)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const filteredChildren = _.flow(_.prop("children"), filterIncludedKeys);
|
|
41
|
+
|
|
42
|
+
const filteredOptions = _.flow(
|
|
43
|
+
filterIncludedKeys,
|
|
44
|
+
_.map((node) => ({ ...node, children: filteredChildren(node) }))
|
|
45
|
+
)(hierarchyOptions);
|
|
46
|
+
|
|
47
|
+
const idActiveValues = _.map((key) =>
|
|
48
|
+
_.flow(_.find({ key }), _.prop("id"))(filteredOptions)
|
|
49
|
+
)(activeFilterSelectedValues);
|
|
50
|
+
|
|
51
|
+
const handleToggleFilterValue = ({ filter, value }) => {
|
|
52
|
+
const getChildrenKeys = (id) => {
|
|
53
|
+
const { key, descendents } = _.flow(
|
|
54
|
+
_.find({ id }),
|
|
55
|
+
_.pick(["key", "descendents"])
|
|
56
|
+
)(filteredOptions);
|
|
57
|
+
const descendentKeys = _.map("key")(descendents);
|
|
58
|
+
return [key, ...descendentKeys];
|
|
59
|
+
};
|
|
60
|
+
const newValue = _.flow(
|
|
61
|
+
_.flatMap(getChildrenKeys),
|
|
62
|
+
_.uniq,
|
|
63
|
+
_.reject(_.isNil)
|
|
64
|
+
)(value);
|
|
65
|
+
toggleFilterValue({ filter, value: newValue });
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<SearchContext.Provider
|
|
70
|
+
value={{
|
|
71
|
+
...context,
|
|
72
|
+
name: "hierarchyFilterDropdown",
|
|
73
|
+
filter,
|
|
74
|
+
options: filteredOptions,
|
|
75
|
+
activeFilterSelectedValues: idActiveValues,
|
|
76
|
+
toggleFilterValue: handleToggleFilterValue,
|
|
77
|
+
}}
|
|
78
|
+
key={filter}
|
|
79
|
+
>
|
|
80
|
+
<FilterMultilevelDropdown />
|
|
81
|
+
</SearchContext.Provider>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const HierarchyFilterDropdown = () => {
|
|
86
|
+
const { options } = useSearchContext();
|
|
87
|
+
|
|
88
|
+
return _.isEmpty(options) ? (
|
|
89
|
+
<FilterMultilevelDropdown />
|
|
90
|
+
) : (
|
|
91
|
+
<PopulatedHierarchyFilterDropdown />
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export default HierarchyFilterDropdown;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import React, {
|
|
3
|
+
useState,
|
|
4
|
+
useEffect,
|
|
5
|
+
useContext,
|
|
6
|
+
createContext,
|
|
7
|
+
useMemo,
|
|
8
|
+
} from "react";
|
|
9
|
+
import { useIntl } from "react-intl";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
toFilterValues,
|
|
13
|
+
formatFilterValues,
|
|
14
|
+
} from "@truedat/core/services/filters";
|
|
15
|
+
import { makeOption } from "@truedat/core/services/i18n";
|
|
16
|
+
|
|
17
|
+
const SearchContext = createContext();
|
|
18
|
+
|
|
19
|
+
export const SearchContextProvider = (props) => {
|
|
20
|
+
const children = _.prop("children")(props);
|
|
21
|
+
const initialSortColumn = _.prop("initialSortColumn")(props);
|
|
22
|
+
const initialSortDirection = _.prop("initialSortDirection")(props);
|
|
23
|
+
const defaultFilters = _.prop("defaultFilters")(props);
|
|
24
|
+
const useSearch = _.prop("useSearch")(props);
|
|
25
|
+
const useFilters = _.prop("useFilters")(props);
|
|
26
|
+
const pageSize = _.propOr(20, "pageSize")(props);
|
|
27
|
+
|
|
28
|
+
const { formatMessage } = useIntl();
|
|
29
|
+
|
|
30
|
+
const [loading, setLoading] = useState(true);
|
|
31
|
+
const [searchData, setSearchData] = useState([]);
|
|
32
|
+
|
|
33
|
+
const [filtersPayload, setFiltersPayload] = useState([]);
|
|
34
|
+
const [loadingFilters, setLoadingFilters] = useState(true);
|
|
35
|
+
const [query, setQuery] = useState("");
|
|
36
|
+
const [activeFilterName, setActiveFilterName] = useState([]);
|
|
37
|
+
const [allActiveFilters, setAllActiveFilters] = useState({});
|
|
38
|
+
const [hiddenFilters, setHiddenFilters] = useState({});
|
|
39
|
+
|
|
40
|
+
const [sortColumn, setSortColumn] = useState(initialSortColumn);
|
|
41
|
+
const [sortDirection, setSortDirection] = useState(initialSortDirection);
|
|
42
|
+
const [page, setPage] = useState(1);
|
|
43
|
+
const [size, setSize] = useState(pageSize);
|
|
44
|
+
const [count, setCount] = useState(0);
|
|
45
|
+
|
|
46
|
+
//STATE FUNCTIONS
|
|
47
|
+
const addFilter = ({ filter }) => {
|
|
48
|
+
setAllActiveFilters({ ...allActiveFilters, [filter]: [] });
|
|
49
|
+
setActiveFilterName(filter);
|
|
50
|
+
};
|
|
51
|
+
const resetFilters = () => setAllActiveFilters({});
|
|
52
|
+
|
|
53
|
+
const openFilter = ({ filter }) => setActiveFilterName(filter);
|
|
54
|
+
const closeFilter = () => {
|
|
55
|
+
setActiveFilterName(null);
|
|
56
|
+
setAllActiveFilters(_.pickBy(_.negate(_.isEmpty))(allActiveFilters));
|
|
57
|
+
};
|
|
58
|
+
const removeFilter = ({ filter }) => {
|
|
59
|
+
setAllActiveFilters(_.omit(filter)(allActiveFilters));
|
|
60
|
+
setActiveFilterName(activeFilterName == filter ? null : activeFilterName);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const toggleFilterValue = ({ filter, value }) => {
|
|
64
|
+
const values = _.propOr([], filter)(allActiveFilters);
|
|
65
|
+
const newValue = _.isArray(value)
|
|
66
|
+
? value
|
|
67
|
+
: _.includes(value)(values)
|
|
68
|
+
? _.without([value])(values)
|
|
69
|
+
: _.union([value])(values);
|
|
70
|
+
|
|
71
|
+
setAllActiveFilters({ ...allActiveFilters, [filter]: newValue });
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const toggleHiddenFilterValue = ({ filter, value }) => {
|
|
75
|
+
const values = _.propOr([], filter)(hiddenFilters);
|
|
76
|
+
const newValue = _.isArray(value)
|
|
77
|
+
? value
|
|
78
|
+
: _.includes(value)(values)
|
|
79
|
+
? _.without([value])(values)
|
|
80
|
+
: _.union([value])(values);
|
|
81
|
+
|
|
82
|
+
console.log("toggleHiddenFilterValue", values, newValue);
|
|
83
|
+
setHiddenFilters({ ...hiddenFilters, [filter]: newValue });
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
//CALCULATIONS ON STATE
|
|
87
|
+
|
|
88
|
+
const selectPage = (props) => {
|
|
89
|
+
setPage(props.activePage);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const setCountData = (headers) => {
|
|
93
|
+
setCount(parseInt(_.propOr("0", "x-total-count")(headers)));
|
|
94
|
+
};
|
|
95
|
+
const selectedFilters = _.keys(allActiveFilters);
|
|
96
|
+
|
|
97
|
+
const filters = _.flow(
|
|
98
|
+
_.propOr({}, "data"),
|
|
99
|
+
_.omitBy(_.flow(_.propOr([], "values"), (values) => _.size(values) < 2))
|
|
100
|
+
)(filtersPayload);
|
|
101
|
+
|
|
102
|
+
const availableFilters = _.flow(_.keys, _.without(selectedFilters))(filters);
|
|
103
|
+
const filterTypes = _.mapValues("type")(filters);
|
|
104
|
+
|
|
105
|
+
const translations = (formatMessage) => ({
|
|
106
|
+
"status.raw": (v) => formatMessage({ id: v, defaultMessage: v }),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const activeFilterValues = _.flow(
|
|
110
|
+
_.propOr({ values: [] }, activeFilterName),
|
|
111
|
+
({ values, type }) => ({
|
|
112
|
+
values: _.flow(
|
|
113
|
+
_.concat(_.prop(activeFilterName)(allActiveFilters)),
|
|
114
|
+
_.uniq
|
|
115
|
+
)(values),
|
|
116
|
+
type,
|
|
117
|
+
}),
|
|
118
|
+
formatFilterValues,
|
|
119
|
+
_.map(makeOption(translations(formatMessage), activeFilterName))
|
|
120
|
+
)(filters);
|
|
121
|
+
|
|
122
|
+
const activeFilterSelectedValues = _.flow(
|
|
123
|
+
_.propOr([], activeFilterName),
|
|
124
|
+
toFilterValues
|
|
125
|
+
)(allActiveFilters);
|
|
126
|
+
|
|
127
|
+
const searchMust = useMemo(
|
|
128
|
+
() => ({
|
|
129
|
+
...defaultFilters,
|
|
130
|
+
..._.pickBy(_.negate(_.isEmpty))(allActiveFilters),
|
|
131
|
+
}),
|
|
132
|
+
[allActiveFilters, defaultFilters]
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const filterMust = useMemo(
|
|
136
|
+
() => ({
|
|
137
|
+
...defaultFilters,
|
|
138
|
+
..._.flow(
|
|
139
|
+
_.pickBy(_.negate(_.isEmpty)),
|
|
140
|
+
_.omit(activeFilterName)
|
|
141
|
+
)(allActiveFilters),
|
|
142
|
+
}),
|
|
143
|
+
|
|
144
|
+
[allActiveFilters, activeFilterName, defaultFilters]
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const sort = useMemo(
|
|
148
|
+
() =>
|
|
149
|
+
sortColumn
|
|
150
|
+
? {
|
|
151
|
+
[sortColumn]: sortDirection === "ascending" ? "asc" : "desc",
|
|
152
|
+
}
|
|
153
|
+
: null,
|
|
154
|
+
[sortColumn, sortDirection]
|
|
155
|
+
);
|
|
156
|
+
const { trigger: triggerFilters } = useFilters();
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
setLoadingFilters(true);
|
|
159
|
+
|
|
160
|
+
const filterParam = {
|
|
161
|
+
...(!_.isEmpty(query) && { query }),
|
|
162
|
+
must: filterMust,
|
|
163
|
+
};
|
|
164
|
+
triggerFilters(filterParam).then(({ data }) => {
|
|
165
|
+
setFiltersPayload(data);
|
|
166
|
+
setLoadingFilters(false);
|
|
167
|
+
});
|
|
168
|
+
}, [query, filterMust, triggerFilters]);
|
|
169
|
+
|
|
170
|
+
const { trigger: triggerSearch } = useSearch();
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
setLoading(true);
|
|
173
|
+
|
|
174
|
+
const filterParam = {
|
|
175
|
+
...(!_.isEmpty(query) && { query }),
|
|
176
|
+
must: searchMust,
|
|
177
|
+
sort,
|
|
178
|
+
page: page - 1,
|
|
179
|
+
size,
|
|
180
|
+
};
|
|
181
|
+
triggerSearch(filterParam).then(({ data, headers }) => {
|
|
182
|
+
setSearchData(data);
|
|
183
|
+
setCountData(headers);
|
|
184
|
+
setLoading(false);
|
|
185
|
+
});
|
|
186
|
+
}, [query, searchMust, sort, triggerSearch, defaultFilters, page]);
|
|
187
|
+
|
|
188
|
+
const context = {
|
|
189
|
+
disabled: false,
|
|
190
|
+
loadingFilters,
|
|
191
|
+
|
|
192
|
+
availableFilters,
|
|
193
|
+
selectedFilters,
|
|
194
|
+
filterTypes,
|
|
195
|
+
|
|
196
|
+
activeFilterName,
|
|
197
|
+
activeFilterSelectedValues,
|
|
198
|
+
activeFilterValues,
|
|
199
|
+
defaultFilters,
|
|
200
|
+
hiddenFilters,
|
|
201
|
+
query,
|
|
202
|
+
filterMust,
|
|
203
|
+
|
|
204
|
+
addFilter,
|
|
205
|
+
resetFilters,
|
|
206
|
+
openFilter,
|
|
207
|
+
closeFilter,
|
|
208
|
+
removeFilter,
|
|
209
|
+
toggleFilterValue,
|
|
210
|
+
toggleHiddenFilterValue,
|
|
211
|
+
searchMust,
|
|
212
|
+
setQuery,
|
|
213
|
+
|
|
214
|
+
searchData,
|
|
215
|
+
loading,
|
|
216
|
+
|
|
217
|
+
sortColumn,
|
|
218
|
+
sortDirection,
|
|
219
|
+
setSortColumn,
|
|
220
|
+
setSortDirection,
|
|
221
|
+
selectPage,
|
|
222
|
+
setSize,
|
|
223
|
+
count,
|
|
224
|
+
page,
|
|
225
|
+
size,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<SearchContext.Provider value={context}>{children}</SearchContext.Provider>
|
|
230
|
+
);
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export const useSearchContext = () => useContext(SearchContext);
|
|
234
|
+
export default SearchContext;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Dropdown } from "semantic-ui-react";
|
|
4
|
+
import { FormattedMessage, useIntl } from "react-intl";
|
|
5
|
+
import { i18nOrder } from "@truedat/core/services/sort";
|
|
6
|
+
import { useSearchContext } from "./SearchContext";
|
|
7
|
+
|
|
8
|
+
const removePrefix = _.replace(/^.*\./, "");
|
|
9
|
+
|
|
10
|
+
export default function SearchFilters() {
|
|
11
|
+
const {
|
|
12
|
+
disabled,
|
|
13
|
+
availableFilters,
|
|
14
|
+
addFilter,
|
|
15
|
+
resetFilters,
|
|
16
|
+
loadingFilters: loading,
|
|
17
|
+
} = useSearchContext();
|
|
18
|
+
|
|
19
|
+
const { formatMessage } = useIntl();
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Dropdown
|
|
23
|
+
button
|
|
24
|
+
className="icon"
|
|
25
|
+
disabled={disabled}
|
|
26
|
+
floating
|
|
27
|
+
icon="filter"
|
|
28
|
+
labeled
|
|
29
|
+
loading={loading}
|
|
30
|
+
scrolling
|
|
31
|
+
text={formatMessage({ id: "filters", defaultMessage: "Filters" })}
|
|
32
|
+
upward={false}
|
|
33
|
+
>
|
|
34
|
+
<Dropdown.Menu>
|
|
35
|
+
<Dropdown.Item onClick={resetFilters}>
|
|
36
|
+
<em>
|
|
37
|
+
<FormattedMessage
|
|
38
|
+
id="filters.reset"
|
|
39
|
+
defaultMessage="(reset filters)"
|
|
40
|
+
/>
|
|
41
|
+
</em>
|
|
42
|
+
</Dropdown.Item>
|
|
43
|
+
{_.flow(
|
|
44
|
+
_.defaultTo([]),
|
|
45
|
+
_.sortBy(i18nOrder(formatMessage, "filters")),
|
|
46
|
+
_.map((filter) => (
|
|
47
|
+
<Dropdown.Item
|
|
48
|
+
key={filter}
|
|
49
|
+
text={formatMessage({
|
|
50
|
+
id: `filters.${filter}`,
|
|
51
|
+
defaultMessage: removePrefix(filter),
|
|
52
|
+
})}
|
|
53
|
+
onClick={() => addFilter({ filter })}
|
|
54
|
+
/>
|
|
55
|
+
))
|
|
56
|
+
)(availableFilters)}
|
|
57
|
+
</Dropdown.Menu>
|
|
58
|
+
</Dropdown>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { FormattedMessage } from "react-intl";
|
|
4
|
+
import FilterDropdown from "./FilterDropdown";
|
|
5
|
+
import FilterMultilevelDropdown from "./FilterMultilevelDropdown";
|
|
6
|
+
import HierarchyFilterDropdown from "./HierarchyFilterDropdown";
|
|
7
|
+
import SearchContext, { useSearchContext } from "./SearchContext";
|
|
8
|
+
|
|
9
|
+
export default function SearchSelectedFilters() {
|
|
10
|
+
const context = useSearchContext();
|
|
11
|
+
const {
|
|
12
|
+
selectedFilters,
|
|
13
|
+
resetFilters,
|
|
14
|
+
filterTypes,
|
|
15
|
+
activeFilterName,
|
|
16
|
+
activeFilterValues,
|
|
17
|
+
} = context;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
<div className="selectedFilters">
|
|
22
|
+
{_.isEmpty(selectedFilters) ? null : (
|
|
23
|
+
<>
|
|
24
|
+
<div className="appliedFilters">
|
|
25
|
+
<FormattedMessage id="search.applied_filters" />
|
|
26
|
+
</div>
|
|
27
|
+
{selectedFilters.map((filter) => {
|
|
28
|
+
const filterType = _.prop(filter)(filterTypes);
|
|
29
|
+
const options = _.isEqual(filter, activeFilterName)
|
|
30
|
+
? activeFilterValues
|
|
31
|
+
: null;
|
|
32
|
+
return (
|
|
33
|
+
<SearchContext.Provider
|
|
34
|
+
value={{ ...context, filter, options }}
|
|
35
|
+
key={filter}
|
|
36
|
+
>
|
|
37
|
+
{filterType === "domain" ? (
|
|
38
|
+
<FilterMultilevelDropdown />
|
|
39
|
+
) : filterType === "hierarchy" ? (
|
|
40
|
+
<HierarchyFilterDropdown />
|
|
41
|
+
) : (
|
|
42
|
+
<FilterDropdown />
|
|
43
|
+
)}
|
|
44
|
+
</SearchContext.Provider>
|
|
45
|
+
);
|
|
46
|
+
})}
|
|
47
|
+
<a className="resetFilters" onClick={() => resetFilters()}>
|
|
48
|
+
<FormattedMessage id="search.clear_filters" />
|
|
49
|
+
</a>
|
|
50
|
+
</>
|
|
51
|
+
)}
|
|
52
|
+
</div>
|
|
53
|
+
</>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Input } from "semantic-ui-react";
|
|
3
|
+
import { useIntl } from "react-intl";
|
|
4
|
+
import SearchFilters from "@truedat/core/search/SearchFilters";
|
|
5
|
+
import SearchSelectedFilters from "@truedat/core/search/SearchSelectedFilters";
|
|
6
|
+
|
|
7
|
+
import { useSearchContext } from "@truedat/core/search/SearchContext";
|
|
8
|
+
|
|
9
|
+
export default function SearchWidget() {
|
|
10
|
+
const { formatMessage } = useIntl();
|
|
11
|
+
|
|
12
|
+
const { query, setQuery, loadingFilters: loading } = useSearchContext();
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<>
|
|
16
|
+
<Input
|
|
17
|
+
value={query}
|
|
18
|
+
onChange={(_e, data) => setQuery(data.value)}
|
|
19
|
+
icon={{ name: "search", link: true }}
|
|
20
|
+
iconPosition="left"
|
|
21
|
+
action={<SearchFilters />}
|
|
22
|
+
placeholder={formatMessage({
|
|
23
|
+
id: "search.placeholder",
|
|
24
|
+
})}
|
|
25
|
+
loading={loading}
|
|
26
|
+
/>
|
|
27
|
+
<SearchSelectedFilters />
|
|
28
|
+
</>
|
|
29
|
+
);
|
|
30
|
+
}
|