Search: move selected from state to props
This commit is contained in:
parent
e38284eb96
commit
3e7bed27cc
|
@ -28,7 +28,7 @@ export const advancedFilterConfig = {
|
|||
addLabel: __( 'Order Status', 'wc-admin' ),
|
||||
rules: [
|
||||
{ value: 'is', label: __( 'Is', 'wc-admin' ) },
|
||||
{ value: 'is-not', label: __( 'Is Not', 'wc-admin' ) },
|
||||
{ value: 'is_not', label: __( 'Is Not', 'wc-admin' ) },
|
||||
],
|
||||
input: {
|
||||
component: 'SelectControl',
|
||||
|
@ -43,28 +43,28 @@ export const advancedFilterConfig = {
|
|||
],
|
||||
},
|
||||
},
|
||||
product: {
|
||||
product_id: {
|
||||
label: __( 'Product', 'wc-admin' ),
|
||||
addLabel: __( 'Products', 'wc-admin' ),
|
||||
rules: [
|
||||
{ value: 'includes', label: __( 'Includes', 'wc-admin' ) },
|
||||
{ value: 'excludes', label: __( 'Excludes', 'wc-admin' ) },
|
||||
{ value: 'is', label: __( 'Is', 'wc-admin' ) },
|
||||
{ value: 'is-not', label: __( 'Is Not', 'wc-admin' ) },
|
||||
{ value: 'is_not', label: __( 'Is Not', 'wc-admin' ) },
|
||||
],
|
||||
input: {
|
||||
component: 'Search',
|
||||
type: 'products',
|
||||
},
|
||||
},
|
||||
coupon: {
|
||||
code: {
|
||||
label: __( 'Coupon Code', 'wc-admin' ),
|
||||
addLabel: __( 'Coupon Codes', 'wc-admin' ),
|
||||
rules: [
|
||||
{ value: 'includes', label: __( 'Includes', 'wc-admin' ) },
|
||||
{ value: 'excludes', label: __( 'Excludes', 'wc-admin' ) },
|
||||
{ value: 'is', label: __( 'Is', 'wc-admin' ) },
|
||||
{ value: 'is-not', label: __( 'Is Not', 'wc-admin' ) },
|
||||
{ value: 'is_not', label: __( 'Is Not', 'wc-admin' ) },
|
||||
],
|
||||
input: {
|
||||
component: 'Search',
|
||||
|
@ -74,9 +74,12 @@ export const advancedFilterConfig = {
|
|||
customer: {
|
||||
label: __( 'Customer is', 'wc-admin' ),
|
||||
addLabel: __( 'Customer Type', 'wc-admin' ),
|
||||
rules: [
|
||||
{ value: 'new', label: __( 'New', 'wc-admin' ) },
|
||||
{ value: 'returning', label: __( 'Returning', 'wc-admin' ) },
|
||||
],
|
||||
input: {
|
||||
component: 'SelectControl',
|
||||
options: [
|
||||
{ value: 'new', label: __( 'New', 'wc-admin' ) },
|
||||
{ value: 'returning', label: __( 'Returning', 'wc-admin' ) },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -13,7 +13,11 @@ import Gridicon from 'gridicons';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import Card from 'components/card';
|
||||
import Search from 'components/search';
|
||||
import Link from 'components/link';
|
||||
import SelectFilter from './select-filter';
|
||||
import SearchFilter from './search-filter';
|
||||
import { getActiveFiltersFromQuery, getQueryFromActiveFilters } from './utils';
|
||||
import { getNewPath } from 'lib/nav-utils';
|
||||
import './style.scss';
|
||||
|
||||
const matches = [
|
||||
|
@ -27,25 +31,22 @@ const matches = [
|
|||
class AdvancedFilters extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
const activeFiltersFromQuery = getActiveFiltersFromQuery( props.query, props.config );
|
||||
this.state = {
|
||||
match: matches[ 0 ],
|
||||
activeFilters: [
|
||||
/**
|
||||
* Example activeFilter
|
||||
* { key: ‘product’, rule: ‘includes’, value: [ ‘one’, ‘two’ ] }
|
||||
*/
|
||||
],
|
||||
activeFilters: activeFiltersFromQuery,
|
||||
previousFilters: activeFiltersFromQuery,
|
||||
};
|
||||
|
||||
this.filterListRef = createRef();
|
||||
|
||||
this.onMatchChange = this.onMatchChange.bind( this );
|
||||
this.onFilterChange = this.onFilterChange.bind( this );
|
||||
this.getSelector = this.getSelector.bind( this );
|
||||
this.getAvailableFilterKeys = this.getAvailableFilterKeys.bind( this );
|
||||
this.addFilter = this.addFilter.bind( this );
|
||||
this.removeFilter = this.removeFilter.bind( this );
|
||||
this.clearAllFilters = this.clearAllFilters.bind( this );
|
||||
this.clearFilters = this.clearFilters.bind( this );
|
||||
this.getUpdateHref = this.getUpdateHref.bind( this );
|
||||
}
|
||||
|
||||
onMatchChange( value ) {
|
||||
|
@ -90,35 +91,6 @@ class AdvancedFilters extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
getSelector( filter ) {
|
||||
const filterConfig = this.props.config[ filter.key ];
|
||||
const { input } = filterConfig;
|
||||
if ( ! input ) {
|
||||
return null;
|
||||
}
|
||||
if ( 'SelectControl' === input.component ) {
|
||||
return (
|
||||
<SelectControl
|
||||
className="woocommerce-filters-advanced__list-select"
|
||||
options={ input.options }
|
||||
value={ filter.value }
|
||||
onChange={ partial( this.onFilterChange, filter.key, 'value' ) }
|
||||
aria-label={ sprintf( __( 'Select %s', 'wc-admin' ), filterConfig.label ) }
|
||||
/>
|
||||
);
|
||||
}
|
||||
if ( 'Search' === input.component ) {
|
||||
return (
|
||||
<Search
|
||||
onChange={ partial( this.onFilterChange, filter.key, 'value' ) }
|
||||
type={ input.type }
|
||||
selected={ filter.value }
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getAvailableFilterKeys() {
|
||||
const { config } = this.props;
|
||||
const activeFilterKeys = this.state.activeFilters.map( f => f.key );
|
||||
|
@ -127,9 +99,15 @@ class AdvancedFilters extends Component {
|
|||
|
||||
addFilter( key, onClose ) {
|
||||
const filterConfig = this.props.config[ key ];
|
||||
const newFilter = { key, rule: filterConfig.rules[ 0 ] };
|
||||
const newFilter = { key };
|
||||
if ( Array.isArray( filterConfig.rules ) && filterConfig.rules.length ) {
|
||||
newFilter.rule = filterConfig.rules[ 0 ].value;
|
||||
}
|
||||
if ( filterConfig.input && filterConfig.input.options ) {
|
||||
newFilter.value = filterConfig.input.options[ 0 ];
|
||||
newFilter.value = filterConfig.input.options[ 0 ].value;
|
||||
}
|
||||
if ( filterConfig.input && 'Search' === filterConfig.input.component ) {
|
||||
newFilter.value = [];
|
||||
}
|
||||
this.setState( state => {
|
||||
return {
|
||||
|
@ -144,20 +122,29 @@ class AdvancedFilters extends Component {
|
|||
} );
|
||||
}
|
||||
|
||||
clearAllFilters() {
|
||||
clearFilters() {
|
||||
this.setState( {
|
||||
activeFilters: [],
|
||||
} );
|
||||
}
|
||||
|
||||
getUpdateHref( activeFilters ) {
|
||||
const { previousFilters } = this.state;
|
||||
const { path, query } = this.props;
|
||||
const updatedQuery = getQueryFromActiveFilters( activeFilters, previousFilters );
|
||||
return getNewPath( updatedQuery, path, query );
|
||||
}
|
||||
|
||||
render() {
|
||||
const { config } = this.props;
|
||||
const { activeFilters } = this.state;
|
||||
const availableFilterKeys = this.getAvailableFilterKeys();
|
||||
const updateHref = this.getUpdateHref( activeFilters );
|
||||
return (
|
||||
<Card className="woocommerce-filters-advanced" title={ this.getTitle() }>
|
||||
<ul className="woocommerce-filters-advanced__list" ref={ this.filterListRef }>
|
||||
{ this.state.activeFilters.map( filter => {
|
||||
const { key, rule } = filter;
|
||||
{ activeFilters.map( filter => {
|
||||
const { key } = filter;
|
||||
const filterConfig = config[ key ];
|
||||
return (
|
||||
<li className="woocommerce-filters-advanced__list-item" key={ key }>
|
||||
|
@ -166,22 +153,20 @@ class AdvancedFilters extends Component {
|
|||
{ /*eslint-enable-next-line jsx-a11y/no-noninteractive-tabindex*/ }
|
||||
<legend className="screen-reader-text">{ filterConfig.label }</legend>
|
||||
<div className="woocommerce-filters-advanced__fieldset">
|
||||
<div className="woocommerce-filters-advanced__fieldset-legend">
|
||||
{ filterConfig.label }
|
||||
</div>
|
||||
<SelectControl
|
||||
className="woocommerce-filters-advanced__list-specifier"
|
||||
options={ filterConfig.rules }
|
||||
value={ rule }
|
||||
onChange={ partial( this.onFilterChange, key, 'rule' ) }
|
||||
aria-label={ sprintf(
|
||||
__( 'Select a %s filter match', 'wc-admin' ),
|
||||
filterConfig.addLabel
|
||||
) }
|
||||
/>
|
||||
<div className="woocommerce-filters-advanced__list-selector">
|
||||
{ this.getSelector( filter ) }
|
||||
</div>
|
||||
{ 'SelectControl' === filterConfig.input.component && (
|
||||
<SelectFilter
|
||||
filter={ filter }
|
||||
config={ filterConfig }
|
||||
onFilterChange={ this.onFilterChange }
|
||||
/>
|
||||
) }
|
||||
{ 'Search' === filterConfig.input.component && (
|
||||
<SearchFilter
|
||||
filter={ filter }
|
||||
config={ filterConfig }
|
||||
onFilterChange={ this.onFilterChange }
|
||||
/>
|
||||
) }
|
||||
</div>
|
||||
</fieldset>
|
||||
<IconButton
|
||||
|
@ -224,10 +209,17 @@ class AdvancedFilters extends Component {
|
|||
) }
|
||||
|
||||
<div className="woocommerce-filters-advanced__controls">
|
||||
<Button isPrimary>{ __( 'Filter', 'wc-admin' ) }</Button>
|
||||
<Button isLink onClick={ this.clearAllFilters }>
|
||||
<Link
|
||||
className="components-button is-primary is-button"
|
||||
type="wc-admin"
|
||||
disabled={ window.location.hash.substr( 1 ) === updateHref }
|
||||
href={ updateHref }
|
||||
>
|
||||
{ __( 'Filter', 'wc-admin' ) }
|
||||
</Link>
|
||||
<Link type="wc-admin" href={ this.getUpdateHref( [] ) } onClick={ this.clearFilters }>
|
||||
{ __( 'Clear all filters', 'wc-admin' ) }
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import { SelectControl } from '@wordpress/components';
|
||||
import { partial } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Search from 'components/search';
|
||||
|
||||
class SearchFilter extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.onSearchChange = this.onSearchChange.bind( this );
|
||||
}
|
||||
|
||||
onSearchChange( values ) {
|
||||
const { filter, onFilterChange } = this.props;
|
||||
const nextValues = values.map( value => value.id );
|
||||
onFilterChange( filter.key, 'value', nextValues );
|
||||
}
|
||||
|
||||
render() {
|
||||
const { filter, config, onFilterChange } = this.props;
|
||||
const { key, rule, value } = filter;
|
||||
const selected = value.map( id => {
|
||||
// For now
|
||||
return {
|
||||
id: parseInt( id, 10 ),
|
||||
label: id.toString(),
|
||||
};
|
||||
} );
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="woocommerce-filters-advanced__fieldset-legend">{ config.label }</div>
|
||||
{ rule && (
|
||||
<SelectControl
|
||||
className="woocommerce-filters-advanced__list-specifier"
|
||||
options={ config.rules }
|
||||
value={ rule }
|
||||
onChange={ partial( onFilterChange, key, 'rule' ) }
|
||||
aria-label={ sprintf( __( 'Select a %s filter match', 'wc-admin' ), config.addLabel ) }
|
||||
/>
|
||||
) }
|
||||
<div className="woocommerce-filters-advanced__list-selector">
|
||||
<Search
|
||||
onChange={ this.onSearchChange }
|
||||
type={ config.input.type }
|
||||
selected={ selected }
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SearchFilter.propTypes = {
|
||||
/**
|
||||
* The configuration object for the single filter to be rendered.
|
||||
*/
|
||||
config: PropTypes.shape( {
|
||||
label: PropTypes.string,
|
||||
addLabel: PropTypes.string,
|
||||
rules: PropTypes.arrayOf( PropTypes.object ),
|
||||
input: PropTypes.object,
|
||||
} ).isRequired,
|
||||
/**
|
||||
* The activeFilter handed down by AdvancedFilters.
|
||||
*/
|
||||
filter: PropTypes.shape( {
|
||||
key: PropTypes.string,
|
||||
rule: PropTypes.string,
|
||||
value: PropTypes.array,
|
||||
} ).isRequired,
|
||||
/**
|
||||
* Function to be called on update.
|
||||
*/
|
||||
onFilterChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default SearchFilter;
|
|
@ -0,0 +1,62 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { Fragment } from '@wordpress/element';
|
||||
import { SelectControl } from '@wordpress/components';
|
||||
import { partial } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const SelectFilter = ( { filter, config, onFilterChange } ) => {
|
||||
const { key, rule, value } = filter;
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="woocommerce-filters-advanced__fieldset-legend">{ config.label }</div>
|
||||
{ rule && (
|
||||
<SelectControl
|
||||
className="woocommerce-filters-advanced__list-specifier"
|
||||
options={ config.rules }
|
||||
value={ rule }
|
||||
onChange={ partial( onFilterChange, key, 'rule' ) }
|
||||
aria-label={ sprintf( __( 'Select a %s filter match', 'wc-admin' ), config.addLabel ) }
|
||||
/>
|
||||
) }
|
||||
<div className="woocommerce-filters-advanced__list-selector">
|
||||
<SelectControl
|
||||
className="woocommerce-filters-advanced__list-select"
|
||||
options={ config.input.options }
|
||||
value={ value }
|
||||
onChange={ partial( onFilterChange, filter.key, 'value' ) }
|
||||
aria-label={ sprintf( __( 'Select %s', 'wc-admin' ), config.label ) }
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
SelectFilter.propTypes = {
|
||||
/**
|
||||
* The configuration object for the single filter to be rendered.
|
||||
*/
|
||||
config: PropTypes.shape( {
|
||||
label: PropTypes.string,
|
||||
addLabel: PropTypes.string,
|
||||
rules: PropTypes.arrayOf( PropTypes.object ),
|
||||
input: PropTypes.object,
|
||||
} ).isRequired,
|
||||
/**
|
||||
* The activeFilter handed down by AdvancedFilters.
|
||||
*/
|
||||
filter: PropTypes.shape( {
|
||||
key: PropTypes.string,
|
||||
rule: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
} ).isRequired,
|
||||
/**
|
||||
* Function to be called on update.
|
||||
*/
|
||||
onFilterChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default SelectFilter;
|
|
@ -29,13 +29,17 @@
|
|||
}
|
||||
|
||||
.woocommerce-filters-advanced__list-item {
|
||||
padding: $gap-smaller $gap;
|
||||
padding: 0 $gap 0 0;
|
||||
margin: 0;
|
||||
display: grid;
|
||||
grid-template-columns: auto 40px;
|
||||
background-color: $core-grey-light-100;
|
||||
border-bottom: 1px solid $core-grey-light-700;
|
||||
|
||||
fieldset {
|
||||
padding: $gap-smaller $gap;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $core-grey-light-200;
|
||||
}
|
||||
|
@ -116,7 +120,7 @@
|
|||
.woocommerce-filters-advanced__controls {
|
||||
padding: $gap-smaller $gap;
|
||||
|
||||
& > button {
|
||||
.components-button {
|
||||
margin-right: $gap;
|
||||
}
|
||||
}
|
||||
|
@ -144,8 +148,6 @@
|
|||
}
|
||||
|
||||
.woocommerce-filters-advanced__list-selector {
|
||||
padding: 0 0 0 $gap-smaller;
|
||||
|
||||
@include breakpoint( '<782px' ) {
|
||||
padding: $gap-smallest 0;
|
||||
}
|
||||
|
@ -155,4 +157,12 @@
|
|||
@include breakpoint( '<782px' ) {
|
||||
padding: $gap-smallest 0;
|
||||
}
|
||||
|
||||
& + .woocommerce-filters-advanced__list-selector {
|
||||
padding: 0 0 0 $gap-smaller;
|
||||
|
||||
@include breakpoint( '<782px' ) {
|
||||
padding: $gap-smallest 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
/**
|
||||
* @format
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
getUrlKey,
|
||||
getSearchFilterValue,
|
||||
getActiveFiltersFromQuery,
|
||||
getUrlValue,
|
||||
getQueryFromActiveFilters,
|
||||
} from '../utils';
|
||||
|
||||
describe( 'getUrlKey', () => {
|
||||
it( 'should return a correctly formatted string', () => {
|
||||
const key = getUrlKey( 'key', 'rule' );
|
||||
expect( key ).toBe( 'key_rule' );
|
||||
} );
|
||||
|
||||
it( 'should return a correctly formatted string with no rule', () => {
|
||||
const key = getUrlKey( 'key' );
|
||||
expect( key ).toBe( 'key' );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'getSearchFilterValue', () => {
|
||||
it( 'should convert url query param into value readable by Search component', () => {
|
||||
const str = '1,2,3';
|
||||
const values = getSearchFilterValue( str );
|
||||
expect( Array.isArray( values ) ).toBeTruthy();
|
||||
expect( values[ 0 ] ).toBe( '1' );
|
||||
expect( values[ 1 ] ).toBe( '2' );
|
||||
expect( values[ 2 ] ).toBe( '3' );
|
||||
} );
|
||||
|
||||
it( 'should convert an empty string into an empty array', () => {
|
||||
const str = '';
|
||||
const values = getSearchFilterValue( str );
|
||||
expect( Array.isArray( values ) ).toBeTruthy();
|
||||
expect( values.length ).toBe( 0 );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'getActiveFiltersFromQuery', () => {
|
||||
const config = {
|
||||
with_select: {
|
||||
rules: [ { value: 'is' } ],
|
||||
input: {
|
||||
component: 'SelectControl',
|
||||
options: [ { value: 'pending' } ],
|
||||
},
|
||||
},
|
||||
with_search: {
|
||||
rules: [ { value: 'includes' } ],
|
||||
input: {
|
||||
component: 'Search',
|
||||
},
|
||||
},
|
||||
with_no_rules: {
|
||||
input: {
|
||||
component: 'SelectControl',
|
||||
options: [ { value: 'pending' } ],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
it( 'should return activeFilters from a query', () => {
|
||||
const query = {
|
||||
with_select_is: 'pending',
|
||||
with_search_includes: '',
|
||||
with_no_rules: 'pending',
|
||||
};
|
||||
|
||||
const activeFilters = getActiveFiltersFromQuery( query, config );
|
||||
expect( Array.isArray( activeFilters ) ).toBeTruthy();
|
||||
expect( activeFilters.length ).toBe( 3 );
|
||||
|
||||
// with_select
|
||||
const with_select = activeFilters[ 0 ];
|
||||
expect( with_select.key ).toBe( 'with_select' );
|
||||
expect( with_select.rule ).toBe( 'is' );
|
||||
expect( with_select.value ).toBe( 'pending' );
|
||||
|
||||
// with_search
|
||||
const with_search = activeFilters[ 1 ];
|
||||
expect( with_search.key ).toBe( 'with_search' );
|
||||
expect( with_search.rule ).toBe( 'includes' );
|
||||
expect( with_search.value ).toEqual( [] );
|
||||
|
||||
// with_search
|
||||
const with_no_rules = activeFilters[ 2 ];
|
||||
expect( with_no_rules.key ).toBe( 'with_no_rules' );
|
||||
expect( with_no_rules.rule ).toBeUndefined();
|
||||
expect( with_no_rules.value ).toEqual( 'pending' );
|
||||
} );
|
||||
|
||||
it( 'should ignore irrelevant query parameters', () => {
|
||||
const query = {
|
||||
with_select: 'pending', // no rule associated
|
||||
status: 45,
|
||||
};
|
||||
|
||||
const activeFilters = getActiveFiltersFromQuery( query, config );
|
||||
expect( activeFilters.length ).toBe( 0 );
|
||||
} );
|
||||
|
||||
it( 'should return an empty array with no relevant parameters', () => {
|
||||
const query = {};
|
||||
|
||||
const activeFilters = getActiveFiltersFromQuery( query, config );
|
||||
expect( Array.isArray( activeFilters ) ).toBe( true );
|
||||
expect( activeFilters.length ).toBe( 0 );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'getUrlValue', () => {
|
||||
it( 'should pass through a string', () => {
|
||||
const value = getUrlValue( 'my string' );
|
||||
expect( value ).toBe( 'my string' );
|
||||
} );
|
||||
|
||||
it( 'should return null for a non-string value', () => {
|
||||
const value = getUrlValue( {} );
|
||||
expect( value ).toBeNull();
|
||||
} );
|
||||
|
||||
it( 'should return null for an empty array', () => {
|
||||
const value = getUrlValue( [] );
|
||||
expect( value ).toBeNull();
|
||||
} );
|
||||
|
||||
it( 'should return comma separated values when given an array', () => {
|
||||
const value = getUrlValue( [ 1, 2, 3 ] );
|
||||
expect( value ).toBe( '1,2,3' );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'getQueryFromActiveFilters', () => {
|
||||
it( 'should return a query object from activeFilters', () => {
|
||||
const activeFilters = [
|
||||
{ key: 'status', rule: 'is', value: 'open' },
|
||||
{
|
||||
key: 'things',
|
||||
rule: 'includes',
|
||||
value: [ 1, 2, 3 ],
|
||||
},
|
||||
{ key: 'customer', value: 'new' },
|
||||
];
|
||||
|
||||
const query = getQueryFromActiveFilters( activeFilters );
|
||||
expect( query.status_is ).toBe( 'open' );
|
||||
expect( query.things_includes ).toBe( '1,2,3' );
|
||||
expect( query.customer ).toBe( 'new' );
|
||||
} );
|
||||
|
||||
it( 'should remove parameters from the previous filters', () => {
|
||||
const nextFilters = [];
|
||||
const previousFilters = [
|
||||
{ key: 'status', rule: 'is', value: 'open' },
|
||||
{
|
||||
key: 'things',
|
||||
rule: 'includes',
|
||||
value: [ 1, 2, 3 ],
|
||||
},
|
||||
{ key: 'customer', value: 'new' },
|
||||
];
|
||||
|
||||
const query = getQueryFromActiveFilters( nextFilters, previousFilters );
|
||||
expect( query.status_is ).toBeUndefined();
|
||||
expect( query.things_includes ).toBeUndefined();
|
||||
expect( query.customer ).toBeUndefined();
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,115 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { find, compact } from 'lodash';
|
||||
|
||||
/**
|
||||
* Get the url query key from the filter key and rule.
|
||||
*
|
||||
* @param {string} key - filter key.
|
||||
* @param {string} rule - filter rule.
|
||||
* @return {string} - url query key.
|
||||
*/
|
||||
export const getUrlKey = ( key, rule ) => {
|
||||
if ( rule && rule.length ) {
|
||||
return `${ key }_${ rule }`;
|
||||
}
|
||||
return key;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert url values to array of objects for <Search /> component
|
||||
*
|
||||
* @param {string} str - url query parameter value
|
||||
* @return {array} - array of Search values
|
||||
*/
|
||||
export const getSearchFilterValue = str => {
|
||||
return str.length ? str.trim().split( ',' ) : [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Describe activeFilter object.
|
||||
*
|
||||
* @typedef {Object} activeFilter
|
||||
* @property {string} key - filter key.
|
||||
* @property {string} [rule] - a modifying rule for a filter, eg 'includes' or 'is_not'.
|
||||
* @property {string|array} value - filter value(s).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Given a query object, return an array of activeFilters, if any.
|
||||
*
|
||||
* @param {object} query - query oject
|
||||
* @param {object} config - config object
|
||||
* @return {activeFilters[]} - array of activeFilters
|
||||
*/
|
||||
export const getActiveFiltersFromQuery = ( query, config ) => {
|
||||
return compact(
|
||||
Object.keys( config ).map( configKey => {
|
||||
const filter = config[ configKey ];
|
||||
if ( filter.rules ) {
|
||||
const match = find( filter.rules, rule => {
|
||||
return query.hasOwnProperty( getUrlKey( configKey, rule.value ) );
|
||||
} );
|
||||
|
||||
if ( match ) {
|
||||
const rawValue = query[ getUrlKey( configKey, match.value ) ];
|
||||
const value =
|
||||
'Search' === filter.input.component ? getSearchFilterValue( rawValue ) : rawValue;
|
||||
return {
|
||||
key: configKey,
|
||||
rule: match.value,
|
||||
value,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if ( query[ configKey ] ) {
|
||||
return {
|
||||
key: configKey,
|
||||
value: query[ configKey ],
|
||||
};
|
||||
}
|
||||
return null;
|
||||
} )
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a string value for url. Return a string directly or concatenate ids if supplied
|
||||
* an array of objects.
|
||||
*
|
||||
* @param {string|array} value - value of an activeFilter
|
||||
* @return {string|null} - url query param value
|
||||
*/
|
||||
export const getUrlValue = value => {
|
||||
if ( Array.isArray( value ) ) {
|
||||
return value.length ? value.join( ',' ) : null;
|
||||
}
|
||||
return 'string' === typeof value ? value : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given activeFilters, create a new query object to update the url. Use previousFilters to
|
||||
* Remove unused params.
|
||||
*
|
||||
* @param {activeFilters[]} nextFilters - activeFilters shown in the UI
|
||||
* @param {activeFilters[]} previousFilters - filters represented by the current url
|
||||
* @return {object} - query object representing the new parameters
|
||||
*/
|
||||
export const getQueryFromActiveFilters = ( nextFilters, previousFilters = [] ) => {
|
||||
const previousData = previousFilters.reduce( ( query, filter ) => {
|
||||
query[ getUrlKey( filter.key, filter.rule ) ] = undefined;
|
||||
return query;
|
||||
}, {} );
|
||||
const data = nextFilters.reduce( ( query, filter ) => {
|
||||
const urlValue = getUrlValue( filter.value );
|
||||
if ( urlValue ) {
|
||||
query[ getUrlKey( filter.key, filter.rule ) ] = urlValue;
|
||||
}
|
||||
return query;
|
||||
}, {} );
|
||||
|
||||
return { ...previousData, ...data };
|
||||
};
|
|
@ -46,6 +46,7 @@ class ReportFilters extends Component {
|
|||
return (
|
||||
<div className="woocommerce-filters__advanced-filters">
|
||||
<AdvancedFilters
|
||||
key={ JSON.stringify( query ) }
|
||||
config={ advancedConfig }
|
||||
filterTitle={ __( 'Orders', 'wc-admin' ) }
|
||||
path={ path }
|
||||
|
|
|
@ -123,7 +123,7 @@ Search.propTypes = {
|
|||
*/
|
||||
type: PropTypes.oneOf( [ 'products', 'product_cats', 'orders', 'customers' ] ).isRequired,
|
||||
/**
|
||||
* An array of objects describing selected values
|
||||
* An array of objects describing selected values.
|
||||
*/
|
||||
selected: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
|
|
|
@ -38,6 +38,16 @@ export const getAdminLink = path => {
|
|||
return wcSettings.adminUrl + path;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a query object to a query string.
|
||||
*
|
||||
* @param {Object} query parameters to be converted.
|
||||
* @return {String} Query string.
|
||||
*/
|
||||
export const stringifyQuery = query => {
|
||||
return query ? '?' + stringify( query ) : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a URL with set query parameters.
|
||||
*
|
||||
|
@ -47,8 +57,8 @@ export const getAdminLink = path => {
|
|||
* @return {String} Updated URL merging query params into existing params.
|
||||
*/
|
||||
export const getNewPath = ( query, path = getPath(), currentQuery = getQuery() ) => {
|
||||
const queryString = stringify( { ...currentQuery, ...query } );
|
||||
return `${ path }?${ queryString }`;
|
||||
const queryString = stringifyQuery( { ...currentQuery, ...query } );
|
||||
return `${ path }${ queryString }`;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -62,13 +72,3 @@ export const updateQueryString = ( query, path = getPath(), currentQuery = getQu
|
|||
const newPath = getNewPath( query, path, currentQuery );
|
||||
history.push( newPath );
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a query object to a query string.
|
||||
*
|
||||
* @param {Object} query parameters to be converted.
|
||||
* @return {String} Query string.
|
||||
*/
|
||||
export const stringifyQuery = query => {
|
||||
return query ? '?' + stringify( query ) : '';
|
||||
};
|
||||
|
|
|
@ -37,6 +37,10 @@
|
|||
color: $woocommerce-500;
|
||||
}
|
||||
|
||||
a.components-button.is-button {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:active,
|
||||
a:focus,
|
||||
|
|
Loading…
Reference in New Issue