@truedat/core 6.1.0 → 6.1.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/core",
3
- "version": "6.1.0",
3
+ "version": "6.1.2",
4
4
  "description": "Truedat Web Core",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -35,7 +35,7 @@
35
35
  "@testing-library/jest-dom": "^5.16.5",
36
36
  "@testing-library/react": "^12.0.0",
37
37
  "@testing-library/user-event": "^13.2.1",
38
- "@truedat/test": "6.0.5",
38
+ "@truedat/test": "6.1.2",
39
39
  "babel-jest": "^28.1.0",
40
40
  "babel-plugin-dynamic-import-node": "^2.3.3",
41
41
  "babel-plugin-lodash": "^3.3.4",
@@ -117,5 +117,5 @@
117
117
  "react-dom": ">= 16.8.6 < 17",
118
118
  "semantic-ui-react": ">= 2.0.3 < 2.2"
119
119
  },
120
- "gitHead": "0e2dd260284f95d075f53ec5df1111606b5ecd0d"
120
+ "gitHead": "34f3dad97550142a4a23048fee3214ce04e957bf"
121
121
  }
@@ -28,7 +28,7 @@ Pagination.propTypes = {
28
28
  activePage: PropTypes.number,
29
29
  selectPage: PropTypes.func,
30
30
  size: PropTypes.string,
31
- totalPages: PropTypes.number
31
+ totalPages: PropTypes.number,
32
32
  };
33
33
 
34
34
  export default Pagination;
@@ -0,0 +1,76 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import { FormattedMessage } from "react-intl";
4
+ import { Label, Icon, Dropdown, Dimmer, Loader } from "semantic-ui-react";
5
+ import { accentInsensitivePathOrder } from "@truedat/core/services/sort";
6
+ import FilterItem from "./FilterItem";
7
+
8
+ import { useSearchContext } from "./SearchContext";
9
+
10
+ const removePrefix = _.replace(/^.*\./, "");
11
+
12
+ export default function FilterDropdown() {
13
+ const {
14
+ loadingFilters: loading,
15
+ filter,
16
+ options,
17
+ activeFilterSelectedValues,
18
+
19
+ openFilter,
20
+ closeFilter,
21
+ removeFilter,
22
+ toggleFilterValue,
23
+ } = useSearchContext();
24
+
25
+ return (
26
+ <Dropdown
27
+ item
28
+ floating
29
+ scrolling
30
+ icon={false}
31
+ upward={false}
32
+ trigger={
33
+ <Label key={filter}>
34
+ <FormattedMessage
35
+ id={`filters.${filter}`}
36
+ defaultMessage={removePrefix(filter)}
37
+ />
38
+ <Icon
39
+ name="delete"
40
+ onClick={(e) => {
41
+ e.preventDefault();
42
+ e.stopPropagation();
43
+ removeFilter({ filter });
44
+ }}
45
+ />
46
+ </Label>
47
+ }
48
+ onOpen={() => openFilter({ filter })}
49
+ onClose={() => closeFilter({ filter })}
50
+ open={!_.isEmpty(options)}
51
+ >
52
+ <Dimmer.Dimmable as={Dropdown.Menu} dimmed={loading}>
53
+ {options &&
54
+ _.flow(
55
+ _.sortBy(accentInsensitivePathOrder("text")),
56
+ _.map.convert({ cap: false })((option, i) => (
57
+ <FilterItem
58
+ key={i}
59
+ filter={filter}
60
+ option={option}
61
+ toggleFilterValue={toggleFilterValue}
62
+ active={_.includes(_.prop("value")(option))(
63
+ activeFilterSelectedValues
64
+ )}
65
+ />
66
+ ))
67
+ )(options)}
68
+ {loading && (
69
+ <Dimmer active inverted>
70
+ <Loader size="tiny" />
71
+ </Dimmer>
72
+ )}
73
+ </Dimmer.Dimmable>
74
+ </Dropdown>
75
+ );
76
+ }
@@ -0,0 +1,49 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import PropTypes from "prop-types";
4
+ import { FormattedMessage } from "react-intl";
5
+ import { Icon, Dropdown } from "semantic-ui-react";
6
+
7
+ const FilterItemText = ({ filterName, text }) =>
8
+ _.trim(text) ? (
9
+ <FormattedMessage
10
+ id={`filters.${filterName}.${_.trim(text)}`}
11
+ defaultMessage={_.trim(text)}
12
+ />
13
+ ) : (
14
+ <i>
15
+ <FormattedMessage id="filter.empty" />
16
+ </i>
17
+ );
18
+
19
+ const preventDefault = (e, callback) => {
20
+ e && e.preventDefault();
21
+ e && e.stopPropagation();
22
+ callback();
23
+ };
24
+
25
+ export const FilterItem = ({
26
+ active,
27
+ filter,
28
+ toggleFilterValue,
29
+ option: { text, value },
30
+ }) => (
31
+ <Dropdown.Item
32
+ onClick={(e) =>
33
+ preventDefault(e, () => toggleFilterValue({ filter, value }))
34
+ }
35
+ active={active}
36
+ >
37
+ <Icon name={active ? "check square outline" : "square outline"} />
38
+ <FilterItemText filterName={filter} text={text} />
39
+ </Dropdown.Item>
40
+ );
41
+
42
+ FilterItem.propTypes = {
43
+ filterName: PropTypes.string,
44
+ text: PropTypes.string,
45
+ toggleFilterValue: PropTypes.func,
46
+ option: PropTypes.object,
47
+ };
48
+
49
+ export default FilterItem;
@@ -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
+ }
@@ -0,0 +1,15 @@
1
+ /*
2
+ * Global configuration object written by td-web and read by td-web-modules.
3
+ * Using this instead of a global variable. This is written just once, so
4
+ * no need for propagation of change with Redux Store.
5
+ */
6
+
7
+ // eslint-disable-next-line fp/no-let
8
+ let config = {};
9
+
10
+ function setConfig(newConfig) {
11
+ // eslint-disable-next-line fp/no-mutation
12
+ config = newConfig;
13
+ }
14
+
15
+ export { config, setConfig };