Skip to content

Commit

Permalink
Add Advanced Search
Browse files Browse the repository at this point in the history
  • Loading branch information
heyrict committed Mar 8, 2019
1 parent 6a68ae9 commit 7a29cd9
Show file tree
Hide file tree
Showing 15 changed files with 304 additions and 41 deletions.
4 changes: 4 additions & 0 deletions react-boilerplate/app/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,10 @@ export function getQueryStr(qs) {
return qObj;
}

export function isAvailParam(p) {
return p !== '' && p !== undefined && p !== null;
}

export function setQueryStr(qObj) {
const query = [];
Object.entries(qObj).forEach(
Expand Down
4 changes: 3 additions & 1 deletion react-boilerplate/app/components/ButtonSelect/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import PropTypes from 'prop-types';
export function ButtonSelect(props) {
const { onChange, buttonProps } = props;
return (
<Flex flexWrap="wrap">
<Flex style={props.style} flexWrap="wrap">
{props.options.map(
(option) =>
option.value === props.value ? (
Expand Down Expand Up @@ -46,6 +46,7 @@ export function ButtonSelect(props) {

ButtonSelect.propTypes = {
value: PropTypes.any,
style: PropTypes.object,
onChange: PropTypes.func,
options: PropTypes.arrayOf(
PropTypes.shape({
Expand All @@ -58,6 +59,7 @@ ButtonSelect.propTypes = {

ButtonSelect.defaultProps = {
buttonProps: {},
style: {},
};

export default ButtonSelect;
119 changes: 119 additions & 0 deletions react-boilerplate/app/components/FilterableList/AdvancedSearchPanel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { Flex, Box } from 'rebass';
import { FormattedMessage } from 'react-intl';
import ButtonSelect from 'components/ButtonSelect';

import { SearchBtn, SearchInput } from './SearchPanel';
import messages from './messages';

const SearchIndicator = styled.div`
min-height: 36px;
margin-top: 5px;
margin-bottom: 5px;
border-radius: 0 10px 10px 0;
border: 1px solid #ccc;
box-shadow: unset;
text-align: center;
font-size: 1.2em;
font-weight: bold;
color: #586e75;
background-color: #eee8d5;
`;

class AdvancedSearchPanel extends React.PureComponent {
constructor(props) {
super(props);
this.init_state = props.currentFilter;
this.props.filterList.forEach((filter) => {
if (typeof filter === 'string') {
if (filter in this.init_state) return;
this.init_state = { ...this.init_state, [filter]: '' };
} else {
if (filter.name in this.init_state) return;
this.init_state = { ...this.init_state, [filter.name]: '' };
}
});

this.state = this.init_state;

this.handleChange = (key, e) => this.setState({ [key]: e.target.value });
this.handleSelectChange = (key, opt) => this.setState({ [key]: opt.value });
}

render() {
return (
<Flex
w={1}
px={1}
flexWrap="wrap"
justifyContent="center"
alignItems="center"
>
{this.props.filterList.map(
(filter) =>
typeof filter === 'string' ? (
<Flex w={1} key={filter}>
<Box w={[1 / 3, 1 / 4]}>
<SearchIndicator style={{ borderRadius: '10px 0 0 10px' }}>
{filter in messages ? (
<FormattedMessage {...messages[filter]} key={filter} />
) : (
filter
)}
</SearchIndicator>
</Box>
<Box w={[2 / 3, 3 / 4]}>
<SearchInput
value={this.state[filter]}
onChange={this.handleChange.bind(this, filter)}
/>
</Box>
</Flex>
) : (
<Flex w={1} key={filter.name}>
<Box w={[1 / 3, 1 / 4]}>
<SearchIndicator style={{ borderRadius: '10px 0 0 10px' }}>
{filter.name in messages ? (
<FormattedMessage
{...messages[filter.name]}
key={filter.name}
/>
) : (
filter.name
)}
</SearchIndicator>
</Box>
<Box w={[2 / 3, 3 / 4]}>
<ButtonSelect
style={{ marginTop: '5px' }}
value={this.state[filter.name]}
options={filter.options}
onChange={this.handleSelectChange.bind(this, filter.name)}
/>
</Box>
</Flex>
),
)}
<Box w={1}>
<SearchBtn
w={1}
onClick={() => this.props.handleSearchButtonClick(this.state)}
style={{ borderRadius: '10px' }}
>
<FormattedMessage {...messages.search} />
</SearchBtn>
</Box>
</Flex>
);
}
}

AdvancedSearchPanel.propTypes = {
filterList: PropTypes.array,
currentFilter: PropTypes.object,
handleSearchButtonClick: PropTypes.func.isRequired,
};

export default AdvancedSearchPanel;
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import React from 'react';
import styled from 'styled-components';
import PropTypes from 'prop-types';
import { RoundedPanel, Button, ButtonOutline } from 'style-store';
import { FormattedMessage } from 'react-intl';
import { Flex, Box } from 'rebass';
import { Flex, Box, ButtonTransparent as RebassBT } from 'rebass';

import FilterButton from './FilterButton';
import SearchPanel from './SearchPanel';
import AdvancedSearchPanel from './AdvancedSearchPanel';
import messages from './messages';

const ButtonTransparent = styled(RebassBT)`
padding: 2px;
overflow: hidden;
&:hover {
background-color: rgba(0, 0, 0, 0.1);
}
`;

const ToggleBtn = (props) => {
const { on, ...others } = props;
return on ? <Button {...others} /> : <ButtonOutline {...others} />;
Expand All @@ -31,6 +41,7 @@ class FilterVarSetPanel extends React.Component {
this.MODE = {
SORT: 'SORT',
SEARCH: 'SEARCH',
ADVSEARCH: 'ADVSEARCH',
};
this.state = {
display: this.MODE.SORT,
Expand Down Expand Up @@ -82,14 +93,17 @@ class FilterVarSetPanel extends React.Component {
<FormattedMessage {...messages.sort} />
</ToggleBtn>
<ToggleBtn
on={this.state.display === this.MODE.SEARCH}
on={
this.state.display === this.MODE.SEARCH ||
this.state.display === this.MODE.ADVSEARCH
}
onClick={() => this.handleToggleButtonClick(this.MODE.SEARCH)}
>
<FormattedMessage {...messages.search} />
</ToggleBtn>
</Flex>
)}
<Flex justifyContent="center">
<Flex justifyContent="center" flexWrap="wrap">
{this.state.display === this.MODE.SORT && (
<Flex
w={1}
Expand All @@ -111,12 +125,39 @@ class FilterVarSetPanel extends React.Component {
))}
</Flex>
)}
{this.state.display === this.MODE.SEARCH && (
<Box w={1} mx={2} style={{ textAlign: 'right' }}>
<ButtonTransparent
onClick={() =>
this.handleToggleButtonClick(this.MODE.ADVSEARCH)
}
>
<FormattedMessage {...messages.goToAdvanced} />
</ButtonTransparent>
</Box>
)}
{this.state.display === this.MODE.ADVSEARCH && (
<Box w={1} mx={2}>
<ButtonTransparent
onClick={() => this.handleToggleButtonClick(this.MODE.SEARCH)}
>
<FormattedMessage {...messages.backToSimple} />
</ButtonTransparent>
</Box>
)}
{this.state.display === this.MODE.SEARCH && (
<SearchPanel
filterList={this.props.filterList}
handleSearchButtonClick={this.props.onFilterChange}
/>
)}
{this.state.display === this.MODE.ADVSEARCH && (
<AdvancedSearchPanel
filterList={this.props.filterList}
currentFilter={this.props.currentFilter}
handleSearchButtonClick={this.props.onFilterChange}
/>
)}
</Flex>
</RoundedPanel>
);
Expand All @@ -127,6 +168,7 @@ FilterVarSetPanel.propTypes = {
filterList: PropTypes.array.isRequired,
orderList: PropTypes.array.isRequired,
order: PropTypes.string.isRequired,
currentFilter: PropTypes.object.isRequired,
onOrderChange: PropTypes.func.isRequired,
onFilterChange: PropTypes.func.isRequired,
};
Expand Down
38 changes: 24 additions & 14 deletions react-boilerplate/app/components/FilterableList/SearchPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { FormattedMessage } from 'react-intl';

import messages from './messages';

const SearchBtn = styled(Button)`
export const SearchBtn = styled(Button)`
border-radius: 10px;
padding: 5px;
min-width: 50px;
Expand All @@ -29,7 +29,7 @@ const SearchSelect = styled(Select)`
}
`;

const SearchInput = styled(Input)`
export const SearchInput = styled(Input)`
min-height: 36px;
margin-top: 5px;
margin-bottom: 5px;
Expand Down Expand Up @@ -57,6 +57,27 @@ class SearchPanel extends React.PureComponent {
this.setState({ filterValue: e.target.value.slice(0, 64) });
}

renderFilterOptions(filter) {
if (typeof filter === 'object' && filter.options) {
return null;
}

const filterKey = typeof filter === 'string' ? filter : filter.name;

if (filterKey in messages) {
return (
<FormattedMessage {...messages[filterKey]} key={filterKey}>
{(msg) => <option value={filterKey}>{msg}</option>}
</FormattedMessage>
);
}
return (
<option value={filterKey} key={filterKey}>
{filterKey}
</option>
);
}

render() {
return (
<Flex
Expand All @@ -72,18 +93,7 @@ class SearchPanel extends React.PureComponent {
value={this.state.filterKey}
onChange={this.handleFilterChange}
>
{this.props.filterList.map(
(f) =>
f in messages ? (
<FormattedMessage {...messages[f]} key={f}>
{(msg) => <option value={f}>{msg}</option>}
</FormattedMessage>
) : (
<option value={f} key={f}>
{f}
</option>
),
)}
{this.props.filterList.map(this.renderFilterOptions)}
</SearchSelect>
</Box>
<Box w={[2 / 3, 3 / 4]}>
Expand Down
27 changes: 23 additions & 4 deletions react-boilerplate/app/components/FilterableList/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,17 @@ import { push } from 'react-router-redux';
import FilterVarSetPanel from './FilterVarSetPanel';
import { makeSelectQuery } from './selectors';

function isAvailParam(p) {
return p !== '' && p !== undefined && p !== null;
}

function filterQuery(filterList, query) {
let filtered = {};
const filtered = {};
filterList.forEach((filter) => {
if (query[filter]) {
if (typeof filter === 'string' && isAvailParam(query[filter])) {
filtered[filter] = query[filter];
} else if (typeof filter === 'object' && isAvailParam(query[filter.name])) {
filtered[filter.name] = query[filter.name];
}
});
return filtered;
Expand Down Expand Up @@ -59,6 +65,7 @@ export const FilterableList = (props) => {
order={curOrder}
onOrderChange={setOrder}
onFilterChange={setFilter}
currentFilter={curFilter || filter}
/>
<QueryList
variables={{
Expand All @@ -74,7 +81,6 @@ export const FilterableList = (props) => {

FilterableList.defaultProps = {
variables: {},
order: '',
orderList: ['id', 'created'],
filter: {},
filterList: [],
Expand All @@ -87,7 +93,20 @@ FilterableList.propTypes = {
order: PropTypes.string.isRequired,
orderList: PropTypes.array,
filter: PropTypes.object,
filterList: PropTypes.array,
filterList: PropTypes.arrayOf(
PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
name: PropTypes.string.isRequired,
options: PropTypes.arrayOf(
PropTypes.shape({
value: PropTypes.any,
label: PropTypes.any,
}),
),
}),
]),
),
query: PropTypes.object,
goto: PropTypes.func.isRequired,
};
Expand Down
Loading

0 comments on commit 7a29cd9

Please sign in to comment.