@truedat/qx 5.17.2 → 5.18.0

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 (43) hide show
  1. package/package.json +3 -3
  2. package/src/api.js +24 -1
  3. package/src/components/QxRoutes.js +12 -5
  4. package/src/components/common/ClauseViewer.js +129 -0
  5. package/src/components/common/ResourceSelector.js +7 -2
  6. package/src/components/common/expressions/Clauses.js +15 -3
  7. package/src/components/common/expressions/Condition.js +8 -6
  8. package/src/components/common/expressions/ShapeSelector.js +18 -8
  9. package/src/components/common/expressions/__tests__/ShapeSelector.spec.js +2 -2
  10. package/src/components/dataViews/DataViewEditor.js +4 -3
  11. package/src/components/dataViews/__tests__/__snapshots__/DataViewEditor.spec.js.snap +2 -2
  12. package/src/components/dataViews/queryableFunctions.js +15 -9
  13. package/src/components/functions/FunctionEditor.js +3 -2
  14. package/src/components/functions/__tests__/__snapshots__/FunctionEditor.spec.js.snap +4 -4
  15. package/src/components/qualityControls/EditQualityControl.js +73 -0
  16. package/src/components/qualityControls/NewDraftQualityControl.js +77 -0
  17. package/src/components/qualityControls/NewQualityControl.js +81 -0
  18. package/src/components/qualityControls/QualityControl.js +93 -0
  19. package/src/components/qualityControls/QualityControlActions.js +67 -0
  20. package/src/components/qualityControls/QualityControlCrumbs.js +23 -0
  21. package/src/components/qualityControls/QualityControlEditor.js +271 -0
  22. package/src/components/qualityControls/QualityControlHeader.js +64 -0
  23. package/src/components/qualityControls/QualityControlHistory.js +81 -0
  24. package/src/components/qualityControls/QualityControlRoutes.js +84 -0
  25. package/src/components/qualityControls/QualityControlRow.js +24 -0
  26. package/src/components/qualityControls/QualityControlTabs.js +34 -0
  27. package/src/components/qualityControls/QualityControls.js +66 -0
  28. package/src/components/qualityControls/QualityControlsTable.js +139 -0
  29. package/src/components/qualityControls/ResultCriteria.js +120 -0
  30. package/src/components/qualityControls/ResultType.js +57 -0
  31. package/src/components/qualityControls/resultCriterias/Deviation.js +89 -0
  32. package/src/components/qualityControls/resultCriterias/ErrorsNumber.js +88 -0
  33. package/src/components/qualityControls/resultCriterias/Percentage.js +89 -0
  34. package/src/components/search/FilterDropdown.js +76 -0
  35. package/src/components/search/FilterItem.js +49 -0
  36. package/src/components/search/FilterMultilevelDropdown.js +200 -0
  37. package/src/components/search/HierarchyFilterDropdown.js +116 -0
  38. package/src/components/search/QualityControlFilters.js +60 -0
  39. package/src/components/search/QualityControlSelectedFilters.js +56 -0
  40. package/src/components/search/QualityControlsSearch.js +30 -0
  41. package/src/components/search/SearchContext.js +180 -0
  42. package/src/hooks/useQualityControls.js +74 -0
  43. package/src/styles/Expression.less +39 -4
@@ -0,0 +1,88 @@
1
+ import React, { useContext } from "react";
2
+ import { useIntl } from "react-intl";
3
+ import { Controller, useFormContext } from "react-hook-form";
4
+ import { Form } from "semantic-ui-react";
5
+ import QxContext from "@truedat/qx/components/QxContext";
6
+ import { FieldLabel } from "@truedat/core/components";
7
+ import { numberRules } from "@truedat/core/services/formRules";
8
+
9
+ export default function ErrorsNumber() {
10
+ const { formatMessage } = useIntl();
11
+ const { field } = useContext(QxContext);
12
+ const { control, watch } = useFormContext();
13
+
14
+ const maximumField = `${field}.maximum`;
15
+ const goalField = `${field}.goal`;
16
+ const maximum = watch(maximumField);
17
+ const goal = watch(goalField);
18
+
19
+ return (
20
+ <>
21
+ <Controller
22
+ name={goalField}
23
+ control={control}
24
+ rules={numberRules({
25
+ formatMessage,
26
+ minValue: 0,
27
+ maxValue: maximum,
28
+ required: true,
29
+ })}
30
+ render={({
31
+ field: { onBlur, onChange, value },
32
+ fieldState: { error },
33
+ }) => (
34
+ <FieldLabel
35
+ label={formatMessage({
36
+ id: "quality_control.result_criteria.errors_number.goal",
37
+ })}
38
+ required
39
+ error={error?.message}
40
+ >
41
+ <Form.Input
42
+ autoComplete="off"
43
+ placeholder={formatMessage({
44
+ id: "quality_control.result_criteria.errors_number.goal",
45
+ })}
46
+ error={!!error}
47
+ onBlur={onBlur}
48
+ onChange={(_e, { value }) => onChange(value)}
49
+ value={value}
50
+ />
51
+ </FieldLabel>
52
+ )}
53
+ />
54
+ <Controller
55
+ name={maximumField}
56
+ control={control}
57
+ rules={numberRules({
58
+ formatMessage,
59
+ minValue: goal,
60
+ required: true,
61
+ })}
62
+ render={({
63
+ field: { onBlur, onChange, value },
64
+ fieldState: { error },
65
+ }) => (
66
+ <FieldLabel
67
+ label={formatMessage({
68
+ id: "quality_control.result_criteria.errors_number.maximum",
69
+ })}
70
+ required
71
+ error={error?.message}
72
+ >
73
+ <Form.Input
74
+ autoComplete="off"
75
+ placeholder={formatMessage({
76
+ id: "quality_control.result_criteria.errors_number.maximum",
77
+ })}
78
+ error={!!error}
79
+ onBlur={onBlur}
80
+ onChange={(_e, { value }) => onChange(value)}
81
+ value={value}
82
+ />
83
+ </FieldLabel>
84
+ )}
85
+ />
86
+ </>
87
+ );
88
+ }
@@ -0,0 +1,89 @@
1
+ import React, { useContext } from "react";
2
+ import { useIntl } from "react-intl";
3
+ import { Controller, useFormContext } from "react-hook-form";
4
+ import { Form } from "semantic-ui-react";
5
+ import QxContext from "@truedat/qx/components/QxContext";
6
+ import { FieldLabel } from "@truedat/core/components";
7
+ import { numberRules } from "@truedat/core/services/formRules";
8
+
9
+ export default function Percentage() {
10
+ const { formatMessage } = useIntl();
11
+ const { field } = useContext(QxContext);
12
+ const { control, watch } = useFormContext();
13
+
14
+ const minimumField = `${field}.minimum`;
15
+ const goalField = `${field}.goal`;
16
+ const minimum = watch(minimumField);
17
+ const goal = watch(goalField);
18
+
19
+ return (
20
+ <>
21
+ <Controller
22
+ name={minimumField}
23
+ control={control}
24
+ rules={numberRules({
25
+ formatMessage,
26
+ minValue: 0,
27
+ maxValue: goal,
28
+ required: true,
29
+ })}
30
+ render={({
31
+ field: { onBlur, onChange, value },
32
+ fieldState: { error },
33
+ }) => (
34
+ <FieldLabel
35
+ label={formatMessage({
36
+ id: "quality_control.result_criteria.percentage.minimum",
37
+ })}
38
+ required
39
+ error={error?.message}
40
+ >
41
+ <Form.Input
42
+ autoComplete="off"
43
+ placeholder={formatMessage({
44
+ id: "quality_control.result_criteria.percentage.minimum",
45
+ })}
46
+ error={!!error}
47
+ onBlur={onBlur}
48
+ onChange={(_e, { value }) => onChange(value)}
49
+ value={value}
50
+ />
51
+ </FieldLabel>
52
+ )}
53
+ />
54
+ <Controller
55
+ name={goalField}
56
+ control={control}
57
+ rules={numberRules({
58
+ formatMessage,
59
+ minValue: minimum,
60
+ maxValue: 100,
61
+ required: true,
62
+ })}
63
+ render={({
64
+ field: { onBlur, onChange, value },
65
+ fieldState: { error },
66
+ }) => (
67
+ <FieldLabel
68
+ label={formatMessage({
69
+ id: "quality_control.result_criteria.percentage.goal",
70
+ })}
71
+ required
72
+ error={error?.message}
73
+ >
74
+ <Form.Input
75
+ autoComplete="off"
76
+ placeholder={formatMessage({
77
+ id: "quality_control.result_criteria.percentage.goal",
78
+ })}
79
+ error={!!error}
80
+ onBlur={onBlur}
81
+ onChange={(_e, { value }) => onChange(value)}
82
+ value={value}
83
+ />
84
+ </FieldLabel>
85
+ )}
86
+ />
87
+ </>
88
+ );
89
+ }
@@ -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,200 @@
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
+ loadingFilters: loading,
20
+ filter,
21
+ options,
22
+ activeFilterSelectedValues,
23
+
24
+ openFilter,
25
+ closeFilter,
26
+ removeFilter,
27
+ toggleFilterValue,
28
+ } = useSearchContext();
29
+
30
+ const [selected, setSelected] = useState();
31
+ const [query, setQuery] = useState();
32
+ const [open, setOpen] = useState([]);
33
+ const [displayed, setDisplayed] = useState([]);
34
+
35
+ useEffect(() => {
36
+ const activeOptions = _.filter((option) =>
37
+ _.includes(option.id)(activeFilterSelectedValues)
38
+ )(options);
39
+
40
+ _.flow(
41
+ _.reduce(
42
+ (acc, option) => [
43
+ ...acc,
44
+ option.id,
45
+ ..._.map("id")(option.descendents),
46
+ ],
47
+ []
48
+ ),
49
+ _.uniq,
50
+ setSelected
51
+ )(activeOptions);
52
+ if (_.isEmpty(open) && _.isEmpty(displayed)) {
53
+ const withAncestors = _.flow(
54
+ _.reduce(
55
+ (acc, option) => [...acc, ..._.map("id")(option.ancestors)],
56
+ []
57
+ ),
58
+ _.uniq
59
+ )(activeOptions);
60
+ const newDisplayed = [..._.map("id")(activeOptions), ...withAncestors];
61
+ !_.isEqual(open, withAncestors) && setOpen(withAncestors);
62
+ !_.isEqual(displayed, newDisplayed) && setDisplayed(newDisplayed);
63
+ }
64
+ }, [activeFilterSelectedValues, options, open, displayed]);
65
+
66
+ const handleOpen = (selection) => {
67
+ const option = _.find({ id: selection })(options);
68
+ const isOpen = _.contains(selection)(open);
69
+ const children = _.map("id")(option.children);
70
+ const descendents = _.map("id")(option.descendents);
71
+
72
+ if (isOpen) {
73
+ setOpen(_.without([selection, ...descendents])(open));
74
+ setDisplayed(_.without(descendents)(displayed));
75
+ } else {
76
+ setOpen(_.union([selection])(open));
77
+ setDisplayed(_.union(children)(displayed));
78
+ }
79
+ };
80
+
81
+ const handleClick = (e, selection) => {
82
+ const option = _.find({ id: selection })(options);
83
+ const ancestorsToDelete = _.intersection(_.map("id")(option.ancestors))(
84
+ activeFilterSelectedValues
85
+ );
86
+ if (!_.isEmpty(ancestorsToDelete)) {
87
+ const active = _.union([selection])(activeFilterSelectedValues);
88
+ const value = _.without(ancestorsToDelete)(active);
89
+ toggleFilterValue({ filter, value });
90
+ } else if (_.includes(selection)(activeFilterSelectedValues)) {
91
+ const descendents = _.map("id")(option.descendents);
92
+ const value = _.without([selection, ...descendents])(
93
+ activeFilterSelectedValues
94
+ );
95
+ toggleFilterValue({ filter, value });
96
+ } else {
97
+ const descendents = _.map("id")(option.descendents);
98
+ const active = _.without(descendents)(activeFilterSelectedValues);
99
+ const value = _.union([selection])(active);
100
+ toggleFilterValue({ filter, value });
101
+ }
102
+ };
103
+
104
+ const displayAll = () => {
105
+ const ids = _.map("id")(options);
106
+ setOpen(ids);
107
+ setDisplayed(ids);
108
+ };
109
+
110
+ const handleSearch = (e, { value }) => {
111
+ e.preventDefault();
112
+ setQuery(lowerDeburr(value));
113
+ if (!_.isEmpty(value)) {
114
+ displayAll();
115
+ }
116
+ };
117
+ const match = (name, query) => _.contains(query)(lowerDeburr(name));
118
+ const filterSearch = (all) => {
119
+ if (query) {
120
+ return _.filter(
121
+ (domain) =>
122
+ match(domain.name, query) ||
123
+ _.some((descendent) => match(descendent.name, query))(
124
+ domain.descendents
125
+ )
126
+ )(all);
127
+ }
128
+ return all;
129
+ };
130
+
131
+ const filterDisplayed = (all) =>
132
+ _.filter((domain) => domain.level == 0 || _.contains(domain.id)(displayed))(
133
+ all
134
+ );
135
+
136
+ const filteredOptions = _.flow(filterSearch, filterDisplayed)(options);
137
+
138
+ return (
139
+ <Dropdown
140
+ item
141
+ floating
142
+ icon={false}
143
+ upward={false}
144
+ onOpen={() => openFilter({ filter })}
145
+ onClose={() => closeFilter({ filter })}
146
+ trigger={
147
+ <Label key={filter}>
148
+ <FormattedMessage id={`filters.${filter}`} defaultMessage={filter} />
149
+ <Icon
150
+ name="delete"
151
+ onClick={(e) => {
152
+ e.preventDefault();
153
+ e.stopPropagation();
154
+ removeFilter({ filter });
155
+ }}
156
+ />
157
+ </Label>
158
+ }
159
+ open={!_.isEmpty(options)}
160
+ >
161
+ <Dimmer.Dimmable dimmed={loading} as={Dropdown.Menu}>
162
+ <>
163
+ <Input
164
+ icon="search"
165
+ iconPosition="left"
166
+ className="search"
167
+ onKeyDown={(e) => {
168
+ if (e.key === " ") {
169
+ e.stopPropagation();
170
+ }
171
+ }}
172
+ onChange={handleSearch}
173
+ onClick={(e) => {
174
+ e.preventDefault();
175
+ e.stopPropagation();
176
+ }}
177
+ />
178
+ <Dropdown.Menu scrolling>
179
+ {_.map.convert({ cap: false })((option, i) => (
180
+ <DropdownMenuItem
181
+ key={i}
182
+ onOpen={handleOpen}
183
+ onClick={handleClick}
184
+ open={_.contains(option.id)(open)}
185
+ canOpen={_.negate(_.isEmpty)(option.children)}
186
+ selected={_.contains(option.id)(selected)}
187
+ {...option}
188
+ />
189
+ ))(filteredOptions)}
190
+ </Dropdown.Menu>
191
+ </>
192
+ {loading && (
193
+ <Dimmer active inverted>
194
+ <Loader size="tiny" />
195
+ </Dimmer>
196
+ )}
197
+ </Dimmer.Dimmable>
198
+ </Dropdown>
199
+ );
200
+ }
@@ -0,0 +1,116 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import PropTypes from "prop-types";
4
+ import { useHierarchy } from "@truedat/df/hooks/useHierarchies";
5
+ import {
6
+ getHierarchyOptions,
7
+ getKeyAndParents,
8
+ } from "@truedat/core/services/getHierarchyOptions";
9
+ import FilterMultilevelDropdown from "./FilterMultilevelDropdown";
10
+
11
+ const PopulatedHierarchyFilterDropdown = (props) => {
12
+ const {
13
+ activeValues,
14
+ closeFilter,
15
+ filter,
16
+ loading,
17
+ openFilter,
18
+ options,
19
+ removeFilter,
20
+ toggleFilterValue,
21
+ } = props;
22
+ const hierarchyId = _.flow(
23
+ _.first,
24
+ _.prop("value"),
25
+ (value) => value.split("_"),
26
+ _.first
27
+ )(options);
28
+
29
+ const { data, error, loading: hierarchyLoading } = useHierarchy(hierarchyId);
30
+ if (error) return null;
31
+ if (hierarchyLoading) return null;
32
+
33
+ const hierarchyOptions = getHierarchyOptions(data?.nodes);
34
+
35
+ const includedKeys = _.flow(
36
+ _.map("value"),
37
+ _.flatMap(getKeyAndParents(hierarchyOptions)),
38
+ _.uniq
39
+ )(options);
40
+
41
+ const filterIncludedKeys = _.filter(({ key }) =>
42
+ _.includes(key)(includedKeys)
43
+ );
44
+
45
+ const filteredChildren = _.flow(_.prop("children"), filterIncludedKeys);
46
+
47
+ const filteredOptions = _.flow(
48
+ filterIncludedKeys,
49
+ _.map((node) => ({ ...node, children: filteredChildren(node) }))
50
+ )(hierarchyOptions);
51
+
52
+ const idActiveValues = _.map((key) =>
53
+ _.flow(_.find({ key }), _.prop("id"))(filteredOptions)
54
+ )(activeValues);
55
+
56
+ const handleToggleFilterValue = ({ filter, value }) => {
57
+ const getChildrenKeys = (id) => {
58
+ const { key, descendents } = _.flow(
59
+ _.find({ id }),
60
+ _.pick(["key", "descendents"])
61
+ )(filteredOptions);
62
+ const descendentKeys = _.map("key")(descendents);
63
+ return [key, ...descendentKeys];
64
+ };
65
+ const newValue = _.flow(
66
+ _.flatMap(getChildrenKeys),
67
+ _.uniq,
68
+ _.reject(_.isNil)
69
+ )(value);
70
+ toggleFilterValue({ filter, value: newValue });
71
+ };
72
+
73
+ return (
74
+ <FilterMultilevelDropdown
75
+ activeValues={idActiveValues}
76
+ closeFilter={closeFilter}
77
+ filter={filter}
78
+ loading={loading}
79
+ openFilter={openFilter}
80
+ options={filteredOptions}
81
+ removeFilter={removeFilter}
82
+ toggleFilterValue={handleToggleFilterValue}
83
+ />
84
+ );
85
+ };
86
+
87
+ PopulatedHierarchyFilterDropdown.propTypes = {
88
+ activeValues: PropTypes.array,
89
+ closeFilter: PropTypes.func,
90
+ filter: PropTypes.string,
91
+ loading: PropTypes.bool,
92
+ openFilter: PropTypes.func,
93
+ options: PropTypes.array,
94
+ removeFilter: PropTypes.func,
95
+ toggleFilterValue: PropTypes.func,
96
+ };
97
+
98
+ const HierarchyFilterDropdown = (props) =>
99
+ _.isEmpty(props.options) ? (
100
+ <FilterMultilevelDropdown {...props} />
101
+ ) : (
102
+ <PopulatedHierarchyFilterDropdown {...props} />
103
+ );
104
+
105
+ HierarchyFilterDropdown.propTypes = {
106
+ activeValues: PropTypes.array,
107
+ closeFilter: PropTypes.func,
108
+ filter: PropTypes.string,
109
+ loading: PropTypes.bool,
110
+ openFilter: PropTypes.func,
111
+ options: PropTypes.array,
112
+ removeFilter: PropTypes.func,
113
+ toggleFilterValue: PropTypes.func,
114
+ };
115
+
116
+ export default HierarchyFilterDropdown;
@@ -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 QualityControlFilters() {
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
+ }