diff --git a/src/components/Filters/FilterSearch/FilterSearch.js b/src/components/Filters/FilterSearch/FilterSearch.js new file mode 100644 index 00000000..6cf04c02 --- /dev/null +++ b/src/components/Filters/FilterSearch/FilterSearch.js @@ -0,0 +1,96 @@ +import '../../Typeahead/Typeahead.scss'; +import { useEffect, useRef, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { Typeahead } from 'react-bootstrap-typeahead'; +import { filterAdded } from '../../../actions'; +import PropTypes from 'prop-types'; +import { useGetAggregations } from '../../../api/hooks/useGetAggregations'; +import { SLUG_SEPARATOR } from '../../../constants'; +import { ClearButton } from '../../Typeahead/ClearButton/ClearButton'; +import getIcon from '../../Common/Icon/iconMap'; + +export const FilterSearch = ({ fieldName }) => { + const ref = useRef(); + const dispatch = useDispatch(); + const [inputText, setInputText] = useState(''); + const fieldNameNew = fieldName.replace(/_/g, ' '); + const { data } = useGetAggregations(); + + const aggResults = data[fieldName] || []; + const subaggName = `sub_${fieldName}.raw`.toLowerCase(); + const buckets = []; + aggResults.forEach((option) => { + buckets.push(option); + if (option[subaggName] && option[subaggName].buckets) { + option[subaggName].buckets.forEach((bucket) => { + const item = { + key: option.key + SLUG_SEPARATOR + bucket.key, + value: bucket.key, + }; + buckets.push(item); + }); + } + }); + + const handleClear = () => { + ref.current.clear(); + setInputText(''); + }; + + const handleSelections = (selected) => { + dispatch(filterAdded(fieldName, selected[0].key)); + handleClear(); + }; + + // give the input focus when the component renders the first time + useEffect(() => { + ref.current.focus(); + }, [ref]); + + return ( +
+
+
+ + handleSelections(selected)} + onInputChange={(text) => setInputText(text)} + placeholder={'Enter name of ' + fieldNameNew} + labelKey="key" + options={buckets} + ref={ref} + inputProps={{ + 'aria-label': `${fieldNameNew} Filter Menu Input`, + className: 'a-text-input a-text-input--full', + }} + renderMenuItemChildren={(option) => ( +
  • + {option.key.split(SLUG_SEPARATOR)[0]} + {option.value ? ( +
    + {option.value} +
    + ) : null} +
  • + )} + /> + {!!inputText && } +
    +
    +
    + ); +}; + +FilterSearch.propTypes = { + fieldName: PropTypes.string.isRequired, +}; diff --git a/src/components/Filters/Issue/Issue.js b/src/components/Filters/Issue/Issue.js index 092f1746..c8a62681 100644 --- a/src/components/Filters/Issue/Issue.js +++ b/src/components/Filters/Issue/Issue.js @@ -1,19 +1,15 @@ -import { useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { useSelector } from 'react-redux'; import { sortSelThenCount } from '../../../utils'; import { CollapsibleFilter } from '../CollapsibleFilter/CollapsibleFilter'; -import { filtersReplaced } from '../../../reducers/filters/filtersSlice'; -import { SLUG_SEPARATOR } from '../../../constants'; -import { Typeahead } from '../../Typeahead/Typeahead/Typeahead'; import { selectFiltersIssue } from '../../../reducers/filters/selectors'; import { MoreOrLess } from '../MoreOrLess/MoreOrLess'; import { AggregationBranch } from '../Aggregation/AggregationBranch/AggregationBranch'; import { useGetAggregations } from '../../../api/hooks/useGetAggregations'; +import { FilterSearch } from '../FilterSearch/FilterSearch'; +import { SLUG_SEPARATOR } from '../../../constants'; // eslint-disable-next-line react/prop-types export const Issue = () => { - const dispatch = useDispatch(); - const [dropdownOptions, setDropdownOptions] = useState([]); const { data } = useGetAggregations(); const filters = useSelector(selectFiltersIssue); @@ -40,33 +36,6 @@ export const Issue = () => { }); // Make a cloned, sorted version of the aggs const options = sortSelThenCount(aggsFilters, selections); - // create an array optimized for typeahead - const optionKeys = options.map((opt) => opt.key); - - const onInputChange = (value) => { - const num = value.toLowerCase(); - if (num === '') { - setDropdownOptions([]); - return; - } - const options = optionKeys.map((opt) => ({ - key: opt, - label: opt, - position: opt.toLowerCase().indexOf(num), - value, - })); - setDropdownOptions(options); - }; - - const onSelection = (items) => { - const replacementFilters = filters - // remove child items - .filter((filter) => filter.indexOf(items[0].key + SLUG_SEPARATOR) === -1) - // add parent item - .concat(items[0].key); - dispatch(filtersReplaced('issue', replacementFilters)); - }; - const onBucket = (bucket, props) => { props.subitems = bucket['sub_issue.raw'].buckets; return props; @@ -78,15 +47,7 @@ export const Issue = () => { desc={desc} className="aggregation issue" > - + { }); test('Options appear when user types and dispatches filtersReplaced on selection', async () => { - const filtersReplacedSpy = jest - .spyOn(filterActions, 'filtersReplaced') + const filterAddedSpy = jest + .spyOn(filterActions, 'filterAdded') .mockImplementation(() => jest.fn()); fetchMock.mockResponseOnce(JSON.stringify(aggResponse)); renderComponent(); @@ -43,15 +42,15 @@ describe('Issue', () => { const input = screen.getByPlaceholderText('Enter name of issue'); await user.type(input, 'Improper'); const option = await screen.findByRole('option', { - name: /Improper use of your report/, + name: 'Improper use of your report•Reporting company used your report improperly', }); await user.click(option); await waitFor(() => - expect(filtersReplacedSpy).toBeCalledWith('issue', [ - 'Incorrect information on your report', - listOfIssues[0].key, - ]), + expect(filterAddedSpy).toBeCalledWith( + 'issue', + 'Improper use of your report•Reporting company used your report improperly', + ), ); }); @@ -78,10 +77,10 @@ describe('Issue', () => { const input = screen.getByPlaceholderText('Enter name of issue'); await user.type(input, 'Improper'); const option = await screen.findByRole('option', { - name: /Improper use of your report/, + name: 'Improper use of your report•Reporting company used your report improperly', }); - await user.clear(input); - + await user.clear(screen.getByPlaceholderText('Enter name of issue')); + expect(input).toHaveValue(''); expect(option).not.toBeInTheDocument(); }); }); diff --git a/src/components/Filters/Product/Product.js b/src/components/Filters/Product/Product.js index 6ddac2da..ccef2216 100644 --- a/src/components/Filters/Product/Product.js +++ b/src/components/Filters/Product/Product.js @@ -11,6 +11,7 @@ import { import { selectFiltersProduct } from '../../../reducers/filters/selectors'; import { selectViewTab } from '../../../reducers/view/selectors'; import { useGetAggregations } from '../../../api/hooks/useGetAggregations'; +import { FilterSearch } from '../FilterSearch/FilterSearch'; /** * Helper function generate and sort options @@ -98,6 +99,7 @@ export const Product = () => { desc={desc} className="aggregation product" > +