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

Agenda quick filters pills #609

Merged
merged 7 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
16 changes: 8 additions & 8 deletions assets/agenda/components/AgendaCoverageExistsFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {get} from 'lodash';
import {gettext} from 'utils';
import {AgendaDropdown} from './AgendaDropdown';

const filter = {
label: gettext('Coverage status'),
export const agendaCoverageStatusFilter = {
label: gettext('Any coverage status'),
field: 'coverage_status',
nestedField: 'coverage_status',
};
Expand All @@ -18,7 +18,7 @@ const FILTER_VALUES = {
COMPLETED: 'completed'
};

function getActiveFilterLabel(filter: any, activeFilter: any) {
export function getActiveFilterLabel(filter: any, activeFilter: any) {
const filterValue = get(activeFilter, `${filter.field}[0]`);

switch (filterValue) {
Expand All @@ -38,7 +38,7 @@ function getActiveFilterLabel(filter: any, activeFilter: any) {
function AgendaCoverageExistsFilter ({toggleFilter, activeFilter}: any) {
return (
<AgendaDropdown
filter={filter}
filter={agendaCoverageStatusFilter}
activeFilter={activeFilter}
toggleFilter={toggleFilter}
getFilterLabel={getActiveFilterLabel}
Expand All @@ -50,25 +50,25 @@ function AgendaCoverageExistsFilter ({toggleFilter, activeFilter}: any) {
<button
key='coverage-planned'
className='dropdown-item'
onClick={() => toggleFilter(filter.field, FILTER_VALUES.PLANNED)}
onClick={() => toggleFilter(agendaCoverageStatusFilter.field, FILTER_VALUES.PLANNED)}
>{gettext('Coverage is planned')}
</button>
<button
key='coverage-not-planned'
className='dropdown-item'
onClick={() => toggleFilter(filter.field, FILTER_VALUES.NOT_PLANNED)}
onClick={() => toggleFilter(agendaCoverageStatusFilter.field, FILTER_VALUES.NOT_PLANNED)}
>{gettext('Coverage not planned')}
</button>
<button
key='coverage-not-decided'
className='dropdown-item'
onClick={() => toggleFilter(filter.field, FILTER_VALUES.MAY_BE)}
onClick={() => toggleFilter(agendaCoverageStatusFilter.field, FILTER_VALUES.MAY_BE)}
>{gettext('Coverage not decided')}
</button>
<button
key='coverage-completed'
className='dropdown-item'
onClick={() => toggleFilter(filter.field, FILTER_VALUES.COMPLETED)}
onClick={() => toggleFilter(agendaCoverageStatusFilter.field, FILTER_VALUES.COMPLETED)}
>{gettext('Coverage completed')}
</button>
</AgendaDropdown>
Expand Down
2 changes: 1 addition & 1 deletion assets/agenda/components/AgendaDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function AgendaDropdown({
return (
<Dropdown
borderless={borderless}
isActive={isActive}
isActive={(isActive?.length ?? 0) > 0}
icon={filter.icon}
optionLabel={optionLabel}
label={getActiveFilterLabel(filter, activeFilter, isActive)}
Expand Down
6 changes: 6 additions & 0 deletions assets/search/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ export function resetFilter(filter?: any) {
};
}

export const CLEAR_QUICK_FILTER = 'CLEAR_QUICK_FILTER';
export function clearQuickFilter(filter?: string) {
return {type: CLEAR_QUICK_FILTER, filter};
}


export const SET_VIEW = 'SET_VIEW';
export function setView(view: any) {
localStorage.setItem('view', view);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as React from 'react';
import {SearchResultsTopicRow} from './SearchResultsTopicRow';
import {SearchResultsQueryRow} from './SearchResultsQueryRow';
import {SearchResultsAdvancedSearchRow} from './SearchResultsAdvancedSearchRow';
import {SearchResultsFiltersRow} from './SearchResultsFiltersRow';
import SearchResultsFiltersRow from './SearchResultsFiltersRow';
import {IFilterGroup, INavigation, ISearchFields, ISearchParams, ITopic, IUser} from 'interfaces';
import {SearchResultTagList} from './SearchResultTagList';
import {gettext} from 'utils';
Expand Down Expand Up @@ -34,6 +34,8 @@ export interface IProps {

saveMyTopic?: (params: ISearchParams) => void;
deselectMyTopic?: (topicId: ITopic['_id']) => void;
clearQuickFilter: (filter: string) => void;

}

export function SearchResultTagsList({
Expand All @@ -58,6 +60,7 @@ export function SearchResultTagsList({
deselectMyTopic,
resetFilter,
refresh,
clearQuickFilter,
}: IProps) {
return (
<ul
Expand Down Expand Up @@ -93,6 +96,7 @@ export function SearchResultTagsList({
readonly={readonly}
/>
<SearchResultsFiltersRow
clearQuickFilter={clearQuickFilter}
searchParams={searchParams}
filterGroups={filterGroups}
toggleFilter={toggleFilter}
Expand Down
113 changes: 101 additions & 12 deletions assets/search/components/SearchResultsBar/SearchResultsFiltersRow.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import * as React from 'react';
import PropTypes from 'prop-types';

import {gettext, getCreatedSearchParamLabel} from 'utils';

import {SearchResultTagList} from './SearchResultTagList';
import {Tag} from 'components/Tag';

import {IProps as IParentProps} from './SearchResultTagsList';
import {setItemTypeFilter} from 'agenda/actions';
import {searchFilterSelector} from 'search/selectors';
import {connect} from 'react-redux';
import {agendaCoverageStatusFilter, getActiveFilterLabel} from 'agenda/components/AgendaCoverageExistsFilter';

const IS_AGENDA = location.pathname.includes('/agenda');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've already talked that this is a low quality implementation that we'd normally not approve, but are going with it anyway because of effort that would be required to do a proper one. In such cases it'd be very useful to add a comment describing the situation and guidelines on how should we fix it when we have more time.


type IProps = Pick<IParentProps,
'readonly' |
Expand All @@ -15,11 +19,86 @@ type IProps = Pick<IParentProps,
'toggleFilter' |
'setCreatedFilter' |
'resetFilter'
>;
> & {clearQuickFilter: (filter: string) => void;};

type IActiveFilter = {
calendar?: any;
location?: any;
region?: any;
coverage_type?: any;
coverage_status?: any;
};

type IActiveFilterUnionType = keyof IActiveFilter;

interface IReduxStateProps {
itemTypeFilter?: string;
activeFilter?: IActiveFilter;
}

interface IReduxDispatchProps {
clearItemTypeFilter: () => void;
}

type IPropsAgendaExtended = IReduxDispatchProps & IReduxStateProps & IProps;

export function SearchResultsFiltersRow({readonly, searchParams, filterGroups, toggleFilter, setCreatedFilter, resetFilter}: IProps) {
function SearchResultsFiltersRow({
readonly,
searchParams,
filterGroups,
toggleFilter,
setCreatedFilter,
resetFilter,
itemTypeFilter,
clearItemTypeFilter,
activeFilter,
clearQuickFilter,
}: IPropsAgendaExtended) {
const tags = [];

/**
* FIXME: This is a bad implementation, but the proper fix would be too time consuming at this moment.
* Ideally we would want to unify the searchParameters so they are stored in the same variable both from
* agenda and wire. Another solution would be to not reuse the same component in wire and agenda filters
* so that wire has its own filter component and agenda has a separate one. The first solution is the better
* one since from a UI stand point the filters component is identical and should be reused ideally.
*/
if (IS_AGENDA) {
if (itemTypeFilter != null) {
tags.push(
<Tag
key={`tags-filters--from-${itemTypeFilter}`}
testId="tags-filters--agenda-quick-filters"
text={itemTypeFilter === 'events' ? gettext('Events Only') : gettext('Planning Only')}
readOnly={readonly}
onClick={(event) => {
event.preventDefault();
clearItemTypeFilter();
}}
/>
);
}

Object.keys(activeFilter ?? {}).filter((filter) => activeFilter?.[filter as IActiveFilterUnionType] != null)
.forEach((filter) => {
tags.push(
<Tag
key={`tags-filters--${filter}`}
testId={`tags-filters--agenda-quick-filters-${filter}`}
text={filter === 'coverage_status'
? getActiveFilterLabel(agendaCoverageStatusFilter, activeFilter)
: activeFilter?.[filter as IActiveFilterUnionType]
}
readOnly={readonly}
onClick={(event) => {
event.preventDefault();
clearQuickFilter(filter);
}}
/>
);
});
}

if (searchParams.created) {
const created = getCreatedSearchParamLabel(searchParams.created);

Expand Down Expand Up @@ -81,7 +160,7 @@ export function SearchResultsFiltersRow({readonly, searchParams, filterGroups, t
}
}

if (searchParams.filter != null) {
if (searchParams.filter != null && IS_AGENDA !== true) {
for (const field in searchParams.filter) {
const group = filterGroups[field];

Expand Down Expand Up @@ -129,6 +208,7 @@ export function SearchResultsFiltersRow({readonly, searchParams, filterGroups, t
onClick={(event) => {
event.preventDefault();
resetFilter();
clearItemTypeFilter?.();
}}
>
{gettext('Clear filters')}
Expand All @@ -145,10 +225,19 @@ export function SearchResultsFiltersRow({readonly, searchParams, filterGroups, t
);
}

SearchResultsFiltersRow.propTypes = {
searchParams: PropTypes.object,
filterGroups: PropTypes.object,
toggleFilter: PropTypes.func.isRequired,
setCreatedFilter: PropTypes.func.isRequired,
resetFilter: PropTypes.func.isRequired,
};
const mapStateToProps = (state: any) => ({
itemTypeFilter: state.agenda.itemType,
activeFilter: searchFilterSelector(state),
});

const mapDispatchToProps = (dispatch: any) => ({
clearItemTypeFilter: () => dispatch(setItemTypeFilter(null)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this one too

});

let component: React.ComponentType<IProps> = SearchResultsFiltersRow as React.ComponentType<IProps>;

if (IS_AGENDA) {
component = connect<IReduxStateProps, IReduxDispatchProps, IProps>(mapStateToProps, mapDispatchToProps)(SearchResultsFiltersRow);
}

export default component;
3 changes: 3 additions & 0 deletions assets/search/components/SearchResultsBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
clearAdvancedSearchParams,
resetFilter,
deselectMyTopic,
clearQuickFilter,
} from '../../actions';

import {Dropdown} from './../../../components/Dropdown';
Expand Down Expand Up @@ -196,6 +197,7 @@ class SearchResultsBarComponent extends React.Component<any, any> {
)}
{!isTagSectionShown ? null : (
<SearchResultTagsList
clearQuickFilter={this.props.clearQuickFilter}
refresh={this.props.refresh}
user={this.props.user}
showSaveTopic={this.props.showSaveTopic}
Expand Down Expand Up @@ -299,6 +301,7 @@ const mapDispatchToProps = {
clearAdvancedSearchParams,
deselectMyTopic,
resetFilter,
clearQuickFilter,
};

export const SearchResultsBar: React.ComponentType<any> = connect(mapStateToProps, mapDispatchToProps)(SearchResultsBarComponent);
18 changes: 18 additions & 0 deletions assets/search/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
CLEAR_ADVANCED_SEARCH_PARAMS,
SET_ADVANCED_SEARCH_PARAMS,
SET_SEARCH_SORT_QUERY,
CLEAR_QUICK_FILTER,
} from './actions';

import {EXTENDED_VIEW} from 'wire/defaults';
Expand Down Expand Up @@ -120,6 +121,23 @@ export function searchReducer(state=INITIAL_STATE, action?: any, context?: any)
};
}

case CLEAR_QUICK_FILTER:
if (action.filter == null) {
return {
...state,
activeFilter: {},
};
}

// eslint-disable-next-line no-case-declarations
tomaskikutis marked this conversation as resolved.
Show resolved Hide resolved
const updatedQuickFilters = {...state.activeFilter};
delete updatedQuickFilters[action.filter];

return {
...state,
activeFilter: updatedQuickFilters,
};

case RESET_FILTER:
return {
...state,
Expand Down
Loading