Skip to content

Commit

Permalink
Merge pull request #1277 from sharetribe/sort-search-results
Browse files Browse the repository at this point in the history
Sort search results
  • Loading branch information
kpuputti authored Mar 25, 2020
2 parents 2ea7ffa + eba47bd commit 6979891
Show file tree
Hide file tree
Showing 14 changed files with 611 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ way to update this template, but currently, we follow a pattern:

## Upcoming version 2020-XX-XX

- [add] Search result sorting [#1277](https://github.com/sharetribe/ftw-daily/pull/1277)
- [change] Move category and amenities search filters from primary filters to secondary filters. [#1275](https://github.com/sharetribe/ftw-daily/pull/1275)

## [v4.3.0] 2020-03-16
Expand Down
9 changes: 8 additions & 1 deletion src/components/SearchFilters/SearchFilters.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@

.root {
display: flex;
justify-content: space-between;
flex-direction: column;
background-color: var(--matterColorBright);
}

.filterWrapper {
display: flex;
justify-content: space-between;
position: relative;
}

Expand All @@ -24,6 +29,8 @@
line-height: 20px;
margin-top: 9px;
margin-bottom: 11px;
margin-left: auto;
margin-right: 8px;

/* parent uses flexbox: this defines minimum width for text "6 results" */
flex-basis: 55px;
Expand Down
56 changes: 41 additions & 15 deletions src/components/SearchFilters/SearchFilters.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import classNames from 'classnames';
import { withRouter } from 'react-router-dom';
import omit from 'lodash/omit';

import { BookingDateRangeFilter, PriceFilter, KeywordFilter } from '../../components';
import config from '../../config';
import { BookingDateRangeFilter, PriceFilter, KeywordFilter, SortBy } from '../../components';
import routeConfiguration from '../../routeConfiguration';
import { parseDateFromISO8601, stringifyDateToISO8601 } from '../../util/dates';
import { createResourceLocatorString } from '../../util/routes';
Expand Down Expand Up @@ -53,6 +54,7 @@ const SearchFiltersComponent = props => {
rootClassName,
className,
urlQueryParams,
sort,
listingsAreLoaded,
resultsCount,
searchInProgress,
Expand All @@ -67,7 +69,7 @@ const SearchFiltersComponent = props => {
} = props;

const hasNoResult = listingsAreLoaded && resultsCount === 0;
const classes = classNames(rootClassName || css.root, { [css.longInfo]: hasNoResult }, className);
const classes = classNames(rootClassName || css.root, className);

const keywordLabel = intl.formatMessage({
id: 'SearchFilters.keywordLabel',
Expand All @@ -85,6 +87,8 @@ const SearchFiltersComponent = props => {
? initialValue(urlQueryParams, keywordFilter.paramName)
: null;

const isKeywordFilterActive = !!initialKeyword;

const handlePrice = (urlParam, range) => {
const { minPrice, maxPrice } = range || {};
const queryParams =
Expand Down Expand Up @@ -172,22 +176,44 @@ const SearchFiltersComponent = props => {
/>
</button>
) : null;

const handleSortBy = (urlParam, values) => {
const queryParams = values
? { ...urlQueryParams, [urlParam]: values }
: omit(urlQueryParams, urlParam);

history.push(createResourceLocatorString('SearchPage', routeConfiguration(), {}, queryParams));
};

const sortBy = config.custom.sortConfig.active ? (
<SortBy
sort={sort}
showAsPopup
isKeywordFilterActive={isKeywordFilterActive}
onSelect={handleSortBy}
contentPlacementOffset={FILTER_DROPDOWN_OFFSET}
/>
) : null;

return (
<div className={classes}>
<div className={css.filters}>
{priceFilterElement}
{dateRangeFilterElement}
{keywordFilterElement}
{toggleSearchFiltersPanelButton}
</div>

{listingsAreLoaded && resultsCount > 0 ? (
<div className={css.searchResultSummary}>
<span className={css.resultsFound}>
<FormattedMessage id="SearchFilters.foundResults" values={{ count: resultsCount }} />
</span>
<div className={css.filterWrapper}>
<div className={css.filters}>
{priceFilterElement}
{dateRangeFilterElement}
{keywordFilterElement}
{toggleSearchFiltersPanelButton}
</div>
) : null}

{listingsAreLoaded && resultsCount > 0 ? (
<div className={css.searchResultSummary}>
<span className={css.resultsFound}>
<FormattedMessage id="SearchFilters.foundResults" values={{ count: resultsCount }} />
</span>
</div>
) : null}
{sortBy}
</div>

{hasNoResult ? (
<div className={css.noSearchResults}>
Expand Down
27 changes: 27 additions & 0 deletions src/components/SearchFiltersMobile/SearchFiltersMobile.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { FormattedMessage, injectIntl, intlShape } from '../../util/reactIntl';
import { withRouter } from 'react-router-dom';
import omit from 'lodash/omit';

import config from '../../config';

import routeConfiguration from '../../routeConfiguration';
import { parseDateFromISO8601, stringifyDateToISO8601 } from '../../util/dates';
import { createResourceLocatorString } from '../../util/routes';
Expand All @@ -15,6 +17,7 @@ import {
PriceFilter,
SelectSingleFilter,
SelectMultipleFilter,
SortBy,
BookingDateRangeFilter,
} from '../../components';
import { propTypes } from '../../util/types';
Expand All @@ -36,6 +39,7 @@ class SearchFiltersMobileComponent extends Component {
this.handlePrice = this.handlePrice.bind(this);
this.handleDateRange = this.handleDateRange.bind(this);
this.handleKeyword = this.handleKeyword.bind(this);
this.handleSortBy = this.handleSortBy.bind(this);
this.initialValue = this.initialValue.bind(this);
this.initialValues = this.initialValues.bind(this);
this.initialPriceRangeValue = this.initialPriceRangeValue.bind(this);
Expand Down Expand Up @@ -129,6 +133,15 @@ class SearchFiltersMobileComponent extends Component {
history.push(createResourceLocatorString('SearchPage', routeConfiguration(), {}, queryParams));
}

handleSortBy(urlParam, sort) {
const { urlQueryParams, history } = this.props;
const queryParams = urlParam
? { ...urlQueryParams, [urlParam]: sort }
: omit(urlQueryParams, urlParam);

history.push(createResourceLocatorString('SearchPage', routeConfiguration(), {}, queryParams));
}

// Reset all filter query parameters
resetAll(e) {
const { urlQueryParams, history, filterParamNames } = this.props;
Expand Down Expand Up @@ -185,6 +198,7 @@ class SearchFiltersMobileComponent extends Component {
const {
rootClassName,
className,
sort,
listingsAreLoaded,
resultsCount,
searchInProgress,
Expand Down Expand Up @@ -297,6 +311,16 @@ class SearchFiltersMobileComponent extends Component {
/>
) : null;

const isKeywordFilterActive = !!initialKeyword;

const sortBy = config.custom.sortConfig.active ? (
<SortBy
sort={sort}
isKeywordFilterActive={isKeywordFilterActive}
onSelect={this.handleSortBy}
/>
) : null;

return (
<div className={classes}>
<div className={css.searchResultSummary}>
Expand Down Expand Up @@ -334,6 +358,7 @@ class SearchFiltersMobileComponent extends Component {
{amenitiesFilterElement}
{priceFilterElement}
{dateRangeFilterElement}
{sortBy}
</div>
) : null}

Expand All @@ -351,6 +376,7 @@ class SearchFiltersMobileComponent extends Component {
SearchFiltersMobileComponent.defaultProps = {
rootClassName: null,
className: null,
sort: null,
resultsCount: null,
searchingInProgress: false,
selectedFiltersCount: 0,
Expand All @@ -365,6 +391,7 @@ SearchFiltersMobileComponent.propTypes = {
rootClassName: string,
className: string,
urlQueryParams: object.isRequired,
sort: string,
listingsAreLoaded: bool.isRequired,
resultsCount: number,
searchingInProgress: bool,
Expand Down
45 changes: 45 additions & 0 deletions src/components/SortBy/SortBy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import { string, bool } from 'prop-types';
import { intlShape, injectIntl } from '../../util/reactIntl';

import config from '../../config';

import SortByPlain from './SortByPlain';
import SortByPopup from './SortByPopup';

const SortBy = props => {
const { sort, showAsPopup, isKeywordFilterActive, intl, ...rest } = props;

const { relevanceKey } = config.custom.sortConfig;

const options = config.custom.sortConfig.options.map(option => {
const isRelevance = option.key === relevanceKey;
return {
...option,
disabled: (isRelevance && !isKeywordFilterActive) || (!isRelevance && isKeywordFilterActive),
};
});
const defaultValue = 'createdAt';
const componentProps = {
urlParam: 'sort',
label: intl.formatMessage({ id: 'SortBy.heading' }),
options,
initialValue: isKeywordFilterActive ? relevanceKey : sort || defaultValue,
...rest,
};
return showAsPopup ? <SortByPopup {...componentProps} /> : <SortByPlain {...componentProps} />;
};

SortBy.defaultProps = {
sort: null,
showAsPopup: false,
};

SortBy.propTypes = {
sort: string,
showAsPopup: bool,
isKeywordFilterActive: bool.isRequired,
intl: intlShape.isRequired,
};

export default injectIntl(SortBy);
102 changes: 102 additions & 0 deletions src/components/SortBy/SortByPlain.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
@import '../../marketplace.css';

.root {
padding-top: 24px;
padding-bottom: 17px;
border-bottom: 1px solid var(--matterColorNegative);
}

.filterLabel,
.filterLabelSelected {
@apply --marketplaceH3FontStyles;

/* Baseline adjustment for label text */
margin-top: 0;
margin-bottom: 12px;
padding: 4px 0 2px 0;
}

.filterLabel {
color: var(--matterColorDark);
}

.filterLabelSelected {
color: var(--marketplaceColor);
}

.labelButton {
/* Override button styles */
outline: none;
text-align: left;
border: none;
padding: 0;
cursor: pointer;
}

.optionsContainerOpen {
height: auto;
padding-bottom: 30px;
}

.optionsContainerClosed {
height: 0;
overflow: hidden;
}

.optionBorder,
.optionBorderSelected {
position: absolute;
height: calc(100% - 12px);
top: 4px;
left: -24px;
transition: width var(--transitionStyleButton);
}

/* left animated "border" like hover element */
.optionBorder {
width: 0;
background-color: var(--matterColorDark);
}

/* left static border for selected element */
.optionBorderSelected {
width: 8px;
background-color: var(--matterColorDark);
}

.option {
@apply --marketplaceH4FontStyles;
font-weight: var(--fontWeightMedium);
font-size: 18px;
color: var(--matterColor);

/* Layout */
display: block;
position: relative;
margin: 0;
padding: 4px 0 8px 20px;

/* Override button styles */
outline: none;
border: none;
cursor: pointer;

&:focus,
&:hover {
color: var(--matterColorDark);
}

&:hover .menuItemBorder {
width: 6px;
}

&:disabled {
color: var(--matterColorAnti);
cursor: default;
}
}

.optionSelected {
composes: option;
color: var(--matterColorDark);
}
Loading

0 comments on commit 6979891

Please sign in to comment.