Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LIBSEARCH-801] Implement "clear active filters" designs for advanced search #495

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
53bdcc2
Simplifying `changeAdvancedFilter`.
erinesullivan Aug 16, 2024
047facb
Simplifying functions.
erinesullivan Aug 19, 2024
1e8efd4
Adding remove active filter options and styling. Note: Cannot remove …
erinesullivan Aug 21, 2024
abc9727
Merge branch 'master' into LIBSEARCH-801-implement-clear-active-filte…
erinesullivan Aug 21, 2024
ad255ea
Finessing styling.
erinesullivan Aug 21, 2024
0d83e44
Separating `FilterList` into its own directory.
erinesullivan Aug 21, 2024
d1ce71b
Adding logic for removing `multiple_select` types.
erinesullivan Aug 22, 2024
c7a193a
Removing listing `checkbox` filter types.
erinesullivan Aug 26, 2024
39ed0c2
Merge branch 'master' into LIBSEARCH-801-implement-clear-active-filte…
erinesullivan Aug 29, 2024
48e4094
Merge branch 'master' into LIBSEARCH-801-implement-clear-active-filte…
erinesullivan Aug 29, 2024
dab6012
Merge branch 'master' into LIBSEARCH-801-implement-clear-active-filte…
erinesullivan Sep 5, 2024
205f40f
Merge branch 'master' into LIBSEARCH-801-implement-clear-active-filte…
erinesullivan Sep 5, 2024
205623c
Adding removal logic for `Narrow Search To` filters.
erinesullivan Sep 6, 2024
fa4c6f4
Adding group information for `Narrow Search To` filters.
erinesullivan Sep 9, 2024
c9afaa3
Simplifying logic.
erinesullivan Sep 9, 2024
253dd9b
Simplifying logic.
erinesullivan Sep 9, 2024
52e0f1f
Simplifying logic.
erinesullivan Sep 10, 2024
5db3737
Linting.
erinesullivan Sep 10, 2024
8d2cd8b
Merge branch 'master' into LIBSEARCH-801-implement-clear-active-filte…
erinesullivan Sep 10, 2024
6436147
Simplifying logic and optimizing performance.
erinesullivan Sep 11, 2024
d9ed61d
Merge branch 'master' into LIBSEARCH-801-implement-clear-active-filte…
erinesullivan Oct 14, 2024
f4f4f37
Changing props to just `datastoreUid` to pass in a string and not an …
erinesullivan Oct 14, 2024
b74e284
Resolving `useSelector` warning. Adding `booleanTypes` for `FieldInput`.
erinesullivan Oct 15, 2024
65180e0
Removing `MultiselectOption`, and simplifying code.
erinesullivan Oct 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 32 additions & 77 deletions src/modules/advanced/components/AdvancedFilter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,93 +5,48 @@ import PropTypes from 'prop-types';
import React from 'react';

const getIsCheckboxFilterChecked = ({ advancedFilter }) => {
const hasActiveFilter = advancedFilter.activeFilters?.length > 0;
const { activeFilters, conditions } = advancedFilter;
const hasActiveFilters = activeFilters?.length > 0;

if (!hasActiveFilter && advancedFilter.conditions.default === 'checked') {
return true;
}

if (hasActiveFilter && advancedFilter.activeFilters[0] === advancedFilter.conditions.checked) {
return true;
}

return false;
return (!hasActiveFilters && conditions.default === 'checked')
|| (hasActiveFilters && activeFilters[0] === conditions.checked);
};

const getDateRangeValue = ({ beginDateQuery, endDateQuery, selectedRange }) => {
switch (selectedRange) {
case 'Before':
if (endDateQuery) {
return `before ${endDateQuery}`;
}
return null;
case 'After':
if (beginDateQuery) {
return `after ${beginDateQuery}`;
}
return null;
case 'Between':
if (beginDateQuery && endDateQuery) {
return `${beginDateQuery} to ${endDateQuery}`;
}
return null;
case 'In':
if (beginDateQuery) {
return beginDateQuery;
}
return null;
default:
return null;
}
const dateRanges = {
After: beginDateQuery && `after ${beginDateQuery}`,
Before: endDateQuery && `before ${endDateQuery}`,
Between: (beginDateQuery && endDateQuery) && `${beginDateQuery} to ${endDateQuery}`,
In: beginDateQuery
};

return dateRanges[selectedRange] || null;
};

const getStateDateRangeValues = ({ advancedFilter }) => {
if (advancedFilter.activeFilters?.length > 0) {
const [filterValue] = advancedFilter.activeFilters;

// Before
if (filterValue.indexOf('before') !== -1) {
const values = filterValue.split('before');

return {
stateEndQuery: values[1],
stateSelectedRangeOption: 0
};
}

// After
if (filterValue.indexOf('after') !== -1) {
const values = filterValue.split('after');

return {
stateBeginQuery: values[1],
stateSelectedRangeOption: 1
};
}

// Between
if (filterValue.indexOf('to') !== -1) {
const values = filterValue.split('to');

return {
stateBeginQuery: values[0],
stateEndQuery: values[1],
stateSelectedRangeOption: 2
};
}
const rangeValues = {
stateSelectedRangeOption: 0
};
const dates = [null, null];

// In or other
return {
stateBeginQuery: filterValue,
stateSelectedRangeOption: 3
};
if (advancedFilter?.activeFilters?.length) {
const ranges = ['before', 'after', 'to'];
rangeValues.stateSelectedRangeOption = ranges.length;
const [filterValue] = advancedFilter.activeFilters;
dates.unshift(...filterValue.match(/\d+/gu));
ranges.forEach((range, index) => {
if (filterValue.includes(range)) {
rangeValues.stateSelectedRangeOption = index;
if (range === ranges[0]) {
dates.unshift(null);
}
}
});
}

return {
stateBeginQuery: '',
stateEndQuery: '',
stateSelectedRangeOption: 0
};
[rangeValues.stateBeginQuery, rangeValues.stateEndQuery] = dates;

return rangeValues;
};

const AdvancedFilter = ({ advancedFilter, changeAdvancedFilter }) => {
Expand Down
26 changes: 12 additions & 14 deletions src/modules/advanced/components/AdvancedSearchForm/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,24 @@ const AdvancedSearchForm = ({ datastore }) => {
const [errors, setErrors] = useState([]);
const dispatch = useDispatch();

const fields = useSelector((state) => {
return state.advanced[datastore.uid].fields;
const { activeFilters: advancedFilters = {}, fieldedSearches, fields } = useSelector((state) => {
return state.advanced[datastore.uid] || {};
});
const booleanTypes = useSelector((state) => {
return state.advanced.booleanTypes;
});
const fieldedSearches = useSelector((state) => {
return state.advanced[datastore.uid].fieldedSearches;
const { booleanTypes } = useSelector((state) => {
return state.advanced;
});
const institution = useSelector((state) => {
return state.institution;
});
const activeFilters = useSelector((state) => {
const currentFilters = state.advanced[datastore.uid].activeFilters || {};
const activeFilters = () => {
const currentFilters = advancedFilters;
Object.keys(currentFilters).forEach((filter) => {
if (!currentFilters[filter]) {
delete currentFilters[filter];
}
});
return currentFilters;
});
};

// Functions wrapped with useCallback to prevent unnecessary re-creation
const changeFieldedSearch = useCallback(({ booleanType, fieldedSearchIndex, query, selectedFieldUid }) => {
Expand Down Expand Up @@ -87,9 +84,9 @@ const AdvancedSearchForm = ({ datastore }) => {
}, [])
.join(' ');

if (query.length > 0 || (Object.keys(activeFilters).length > 0)) {
if (query.length > 0 || (Object.keys(activeFilters()).length > 0)) {
const search = {
filter: { ...activeFilters },
filter: { ...activeFilters() },
query
};

Expand All @@ -110,7 +107,7 @@ const AdvancedSearchForm = ({ datastore }) => {
]);
window.scrollTo(0, 0);
}
}, [navigate, institution, booleanTypes, fieldedSearches, activeFilters, datastore]);
}, [navigate, institution, booleanTypes, fieldedSearches, activeFilters(), datastore]);

return (
<form className='y-spacing' onSubmit={handleSubmit}>
Expand All @@ -132,6 +129,7 @@ const AdvancedSearchForm = ({ datastore }) => {
return (
<FieldInput
key={index}
booleanTypes={booleanTypes}
fieldedSearchIndex={index}
fieldedSearch={fs}
fields={fields}
Expand All @@ -158,7 +156,7 @@ const AdvancedSearchForm = ({ datastore }) => {
<Icon icon='search' size={24} /> Advanced Search
</button>

<FiltersContainer datastore={datastore} />
<FiltersContainer datastoreUid={datastore.uid} />
</form>
);
};
Expand Down
4 changes: 3 additions & 1 deletion src/modules/advanced/components/FieldInput/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import SearchByOptions from '../../../search/components/SearchByOptions';

const FieldInput = ({
activeDatastore,
booleanTypes,
changeFieldedSearch,
fieldedSearch,
fieldedSearchIndex,
Expand All @@ -19,7 +20,7 @@ const FieldInput = ({
{notFirst && (
<fieldset className='flex__responsive'>
<legend className='visually-hidden'>Boolean operator for field {fieldedSearchIndex} and field {fieldedSearchIndex + 1}</legend>
{['AND', 'OR', 'NOT'].map((option, index) => {
{booleanTypes.map((option, index) => {
return (
<label key={index}>
<input
Expand Down Expand Up @@ -89,6 +90,7 @@ const FieldInput = ({

FieldInput.propTypes = {
activeDatastore: PropTypes.object,
booleanTypes: PropTypes.array,
changeFieldedSearch: PropTypes.func,
fieldedSearch: PropTypes.object,
fieldedSearchIndex: PropTypes.number,
Expand Down
115 changes: 115 additions & 0 deletions src/modules/advanced/components/FilterList/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import './styles.css';
import '../../../filters/components/Filters/styles.css';
import { useDispatch, useSelector } from 'react-redux';
import { Icon } from '../../../reusable';
import PropTypes from 'prop-types';
import React from 'react';
import { setAdvancedFilter } from '../../../advanced';

const FilterList = ({ datastoreUid }) => {
const dispatch = useDispatch();
const { activeFilters = {}, filters: filterGroup } = useSelector((state) => {
return state.advanced[datastoreUid] || {};
});

const filterList = Object.entries(activeFilters).flatMap(([groupUid, filters]) => {
const {
name = groupUid === 'institution' ? 'Library' : groupUid.charAt(0).toUpperCase() + groupUid.slice(1).replaceAll('_', ' '),
type = 'scope_down'
} = filterGroup.find((group) => {
return group.uid === groupUid;
}) || {};
return type === 'checkbox'
? []
: filters.map((value) => {
return { groupUid, name, type, value };
});
});

const handleRemoveFilter = ({ groupUid, type, value }) => {
const createAction = (overrides = {}) => {
dispatch(setAdvancedFilter({
...{
datastoreUid,
filterGroupUid: groupUid,
filterType: type,
filterValue: type === 'multiple_select' ? value : null,
onlyOneFilterValue: type !== 'multiple_select'
},
...overrides
}));
};

if (['institution', 'location'].includes(groupUid)) {
createAction({ filterGroupUid: 'collection' });
}

if (groupUid === 'institution') {
createAction({ filterGroupUid: 'location' });
}

createAction();
};

const handleClearFilters = () => {
Object.entries(activeFilters).forEach(([groupUid, filters]) => {
filters.forEach(() => {
dispatch(setAdvancedFilter({
datastoreUid,
filterGroupUid: groupUid,
filterValue: null,
onlyOneFilterValue: true
}));
});
});
};

if (!filterList.length) {
return null;
}

return (
<section aria-label='active-filters'>
<div className='flex margin-bottom__m active-filters-header'>
<h2 id='active-filters' className='margin__none h4'>
Active filters <span className='text-grey__light'>({filterList.length})</span>
</h2>
{filterList.length > 1 && (
<button
className='button-link-light'
onClick={(event) => {
event.preventDefault();
handleClearFilters();
}}
>
Clear all active filters
</button>
)}
</div>
<ul className='list__unstyled active-filter-list'>
{filterList.map(({ groupUid, name, type, value }) => {
return (
<li key={`${groupUid}-${value}`}>
<button
className='padding-y__xs padding-x__s remove-filter underline__hover'
onClick={(event) => {
event.preventDefault();
handleRemoveFilter({ groupUid, type, value });
}}
>
{`${name}: ${value}`}
<Icon icon='close' />
</button>
</li>
);
})}
</ul>
</section>
);
};

FilterList.propTypes = {
datastoreUid: PropTypes.string
};

export default FilterList;
33 changes: 33 additions & 0 deletions src/modules/advanced/components/FilterList/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.active-filters-header {
align-items: flex-start;
flex-wrap: wrap;
}

.active-filters-header > .text-grey__light {
font-weight: 400;
}

.active-filter-list {
display: grid;
gap: 1rem;
grid-template-columns: repeat(1, 1fr);
}

@media screen and (min-width: 640px) {
.active-filter-list {
grid-template-columns: repeat(2, 1fr);
}
}

@media screen and (min-width: 820px) {
.active-filter-list {
grid-template-columns: repeat(3, 1fr);
}
}


.active-filter-list > li > button {
height: 100%;
text-align: left;
width: 100%;
}
Loading