* Move labels loading logic into a lib

* Move lib/labels into lib/async-requests

* Implement tabular data filtering

* Allow searching for string in report tables

* Add table filtering to customers table

* Get ids from searched string to populate the table

* Fix autocompleter keyboard interactions

* Improve props naming

* Cleanup report customers data store

* Prevent an edge case issue that might not update the selectedOptions when directily modifying the URL

* Fix wrong selected autocompleter option

* Add missing translation domain

* Move searchItemsByString to wc-api/items/utils.js

* Avoid autocompleter results appearing when there was no search string

* Alphabetically order 'allowFreeTextSearch' prop

* Reset selected table rows when directly modifying the URL

* Simplify props destructuring

* Undo customers data store change

* Simplify isProductDetailsView expression

* Improve order

* Filter tax code parts before modifying them
This commit is contained in:
Albert Juhé Lluveras 2019-02-01 10:55:19 +01:00 committed by GitHub
parent 1069034ca2
commit d8ed3b6614
26 changed files with 380 additions and 171 deletions

View File

@ -153,6 +153,7 @@ class CategoriesReportTable extends Component {
getSummary={ this.getSummary }
itemIdField="category_id"
query={ query }
searchBy="categories"
labels={ labels }
tableQuery={ {
orderby: query.orderby || 'items_sold',
@ -168,14 +169,14 @@ class CategoriesReportTable extends Component {
export default compose(
withSelect( select => {
const { getCategories, getCategoriesError, isGetCategoriesRequesting } = select( 'wc-api' );
const { getItems, getItemsError, isGetItemsRequesting } = select( 'wc-api' );
const tableQuery = {
per_page: -1,
};
const categories = getCategories( tableQuery );
const isError = Boolean( getCategoriesError( tableQuery ) );
const isRequesting = isGetCategoriesRequesting( tableQuery );
const categories = getItems( 'categories', tableQuery );
const isError = Boolean( getItemsError( 'categories', tableQuery ) );
const isRequesting = isGetItemsRequesting( 'categories', tableQuery );
return { categories, isError, isRequesting };
} )

View File

@ -169,6 +169,7 @@ export default class CouponsReportTable extends Component {
getSummary={ this.getSummary }
itemIdField="coupon_id"
query={ query }
searchBy="coupons"
tableQuery={ {
orderby: query.orderby || 'orders_count',
order: query.order || 'desc',

View File

@ -227,7 +227,6 @@ export default class CustomersReportTable extends Component {
query={ query }
labels={ { placeholder: __( 'Search by customer name', 'wc-admin' ) } }
searchBy="customers"
searchParam="name_includes"
title={ __( 'Customers', 'wc-admin' ) }
columnPrefsKey="customers_report_columns"
/>

View File

@ -5,6 +5,7 @@
import { __ } from '@wordpress/i18n';
import { applyFilters } from '@wordpress/hooks';
import { Component, Fragment } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import PropTypes from 'prop-types';
import { find } from 'lodash';
@ -12,6 +13,7 @@ import { find } from 'lodash';
* WooCommerce dependencies
*/
import { useFilters } from '@woocommerce/components';
import { getQuery } from '@woocommerce/navigation';
/**
* Internal dependencies
@ -27,6 +29,8 @@ import TaxesReport from './taxes';
import DownloadsReport from './downloads';
import StockReport from './stock';
import CustomersReport from './customers';
import { searchItemsByString } from 'wc-api/items/utils';
import withSelect from 'wc-api/with-select';
const REPORTS_FILTER = 'woocommerce-reports-list';
@ -131,4 +135,27 @@ Report.propTypes = {
params: PropTypes.object.isRequired,
};
export default useFilters( REPORTS_FILTER )( Report );
export default compose(
useFilters( REPORTS_FILTER ),
withSelect( ( select, props ) => {
const { search } = getQuery();
if ( ! search ) {
return {};
}
const { report } = props.params;
const items = searchItemsByString( select, report, search );
const ids = Object.keys( items );
if ( ! ids.length ) {
return {}; // @TODO if no results were found, we should avoid making a server request.
}
return {
query: {
...props.query,
[ report ]: ids.join( ',' ),
},
};
} )
)( Report );

View File

@ -24,7 +24,7 @@ import VariationsReportTable from './table-variations';
export default class ProductsReport extends Component {
render() {
const { path, query } = this.props;
const isProductDetailsView = query.products && 1 === query.products.split( ',' ).length;
const isProductDetailsView = query.filter === 'single_product';
const itemsLabel = isProductDetailsView
? __( '%s variations', 'wc-admin' )

View File

@ -182,6 +182,7 @@ export default class VariationsReportTable extends Component {
labels={ labels }
query={ query }
getSummary={ this.getSummary }
searchBy="variations"
tableQuery={ {
orderby: query.orderby || 'items_sold',
order: query.order || 'desc',

View File

@ -245,6 +245,7 @@ class ProductsReportTable extends Component {
itemIdField="product_id"
labels={ labels }
query={ query }
searchBy="products"
tableQuery={ {
orderby: query.orderby || 'items_sold',
order: query.order || 'desc',
@ -259,14 +260,14 @@ class ProductsReportTable extends Component {
export default compose(
withSelect( select => {
const { getCategories, getCategoriesError, isGetCategoriesRequesting } = select( 'wc-api' );
const { getItems, getItemsError, isGetItemsRequesting } = select( 'wc-api' );
const tableQuery = {
per_page: -1,
};
const categories = getCategories( tableQuery );
const isError = Boolean( getCategoriesError( tableQuery ) );
const isRequesting = isGetCategoriesRequesting( tableQuery );
const categories = getItems( 'categories', tableQuery );
const isError = Boolean( getItemsError( 'categories', tableQuery ) );
const isRequesting = isGetItemsRequesting( 'categories', tableQuery );
return { categories, isError, isRequesting };
} )

View File

@ -150,6 +150,7 @@ export default class TaxesReportTable extends Component {
getSummary={ this.getSummary }
itemIdField="tax_rate_id"
query={ query }
searchBy="taxes"
tableQuery={ {
orderby: query.orderby || 'tax_rate_id',
} }

View File

@ -1,52 +0,0 @@
/** @format */
/**
* External dependencies
*/
import apiFetch from '@wordpress/api-fetch';
/**
* WooCommerce dependencies
*/
import { stringifyQuery } from '@woocommerce/navigation';
/**
* Internal dependencies
*/
import { isResourcePrefix, getResourceIdentifier, getResourceName } from '../utils';
import { NAMESPACE } from '../constants';
function read( resourceNames, fetch = apiFetch ) {
const filteredNames = resourceNames.filter( name => isResourcePrefix( name, 'category-query' ) );
return filteredNames.map( async resourceName => {
const query = getResourceIdentifier( resourceName );
const url = NAMESPACE + `/products/categories${ stringifyQuery( query ) }`;
try {
const categories = await fetch( {
path: url,
} );
const ids = categories.map( category => category.id );
const categoryResources = categories.reduce( ( resources, category ) => {
resources[ getResourceName( 'category', category.id ) ] = { data: category };
return resources;
}, {} );
return {
[ resourceName ]: {
data: ids,
totalCount: ids.length,
},
...categoryResources,
};
} catch ( error ) {
return { [ resourceName ]: { error } };
}
} );
}
export default {
read,
};

View File

@ -1,56 +0,0 @@
/** @format */
/**
* External dependencies
*/
import { isNil } from 'lodash';
/**
* Internal dependencies
*/
import { getResourceName } from '../utils';
import { DEFAULT_REQUIREMENT } from '../constants';
const getCategories = ( getResource, requireResource ) => (
query = {},
requirement = DEFAULT_REQUIREMENT
) => {
const resourceName = getResourceName( 'category-query', query );
const ids = requireResource( requirement, resourceName ).data || [];
const categories = ids.reduce(
( acc, id ) => ( {
...acc,
[ id ]: getResource( getResourceName( 'category', id ) ).data || {},
} ),
{}
);
return categories;
};
const getCategoriesTotalCount = getResource => ( query = {} ) => {
const resourceName = getResourceName( 'category-query', query );
return getResource( resourceName ).totalCount || 0;
};
const getCategoriesError = getResource => ( query = {} ) => {
const resourceName = getResourceName( 'category-query', query );
return getResource( resourceName ).error;
};
const isGetCategoriesRequesting = getResource => ( query = {} ) => {
const resourceName = getResourceName( 'category-query', query );
const { lastRequested, lastReceived } = getResource( resourceName );
if ( isNil( lastRequested ) || isNil( lastReceived ) ) {
return true;
}
return lastRequested > lastReceived;
};
export default {
getCategories,
getCategoriesError,
getCategoriesTotalCount,
isGetCategoriesRequesting,
};

View File

@ -0,0 +1,65 @@
/** @format */
/**
* External dependencies
*/
import apiFetch from '@wordpress/api-fetch';
/**
* WooCommerce dependencies
*/
import { stringifyQuery } from '@woocommerce/navigation';
/**
* Internal dependencies
*/
import { getResourceIdentifier, getResourcePrefix, getResourceName } from '../utils';
import { NAMESPACE } from '../constants';
const typeEndpointMap = {
'items-query-categories': 'products/categories',
'items-query-customers': 'customers',
'items-query-coupons': 'coupons',
'items-query-products': 'products',
'items-query-taxes': 'taxes',
};
function read( resourceNames, fetch = apiFetch ) {
const filteredNames = resourceNames.filter( name => {
const prefix = getResourcePrefix( name );
return Boolean( typeEndpointMap[ prefix ] );
} );
return filteredNames.map( async resourceName => {
const prefix = getResourcePrefix( resourceName );
const endpoint = typeEndpointMap[ prefix ];
const query = getResourceIdentifier( resourceName );
const url = NAMESPACE + `/${ endpoint }${ stringifyQuery( query ) }`;
try {
const items = await fetch( {
path: url,
} );
const ids = items.map( item => item.id );
const itemResources = items.reduce( ( resources, item ) => {
resources[ getResourceName( `${ prefix }-item`, item.id ) ] = { data: item };
return resources;
}, {} );
return {
[ resourceName ]: {
data: ids,
totalCount: ids.length,
},
...itemResources,
};
} catch ( error ) {
return { [ resourceName ]: { error } };
}
} );
}
export default {
read,
};

View File

@ -0,0 +1,57 @@
/** @format */
/**
* External dependencies
*/
import { isNil } from 'lodash';
/**
* Internal dependencies
*/
import { getResourceName } from '../utils';
import { DEFAULT_REQUIREMENT } from '../constants';
const getItems = ( getResource, requireResource ) => (
type,
query = {},
requirement = DEFAULT_REQUIREMENT
) => {
const resourceName = getResourceName( `items-query-${ type }`, query );
const ids = requireResource( requirement, resourceName ).data || [];
const items = ids.reduce(
( acc, id ) => ( {
...acc,
[ id ]: getResource( getResourceName( `items-query-${ type }-item`, id ) ).data || {},
} ),
{}
);
return items;
};
const getItemsTotalCount = getResource => ( type, query = {} ) => {
const resourceName = getResourceName( `items-query-${ type }`, query );
return getResource( resourceName ).totalCount || 0;
};
const getItemsError = getResource => ( type, query = {} ) => {
const resourceName = getResourceName( `items-query-${ type }`, query );
return getResource( resourceName ).error;
};
const isGetItemsRequesting = getResource => ( type, query = {} ) => {
const resourceName = getResourceName( `items-query-${ type }`, query );
const { lastRequested, lastReceived } = getResource( resourceName );
if ( isNil( lastRequested ) || isNil( lastReceived ) ) {
return true;
}
return lastRequested > lastReceived;
};
export default {
getItems,
getItemsError,
getItemsTotalCount,
isGetItemsRequesting,
};

View File

@ -0,0 +1,30 @@
/** @format */
/**
* External dependencies
*/
/**
* Returns items based on a search query.
*
* @param {Object} select Instance of @wordpress/select
* @param {String} endpoint Report API Endpoint
* @param {String} search Search strings separated by commas.
* @return {Object} Object Object containing the matching items.
*/
export function searchItemsByString( select, endpoint, search ) {
const { getItems } = select( 'wc-api' );
const searchWords = search.split( ',' );
const items = searchWords.reduce( ( acc, searchWord ) => {
return {
...acc,
...getItems( endpoint, {
search: searchWord,
per_page: 10,
} ),
};
}, [] );
return items;
}

View File

@ -37,6 +37,12 @@ const reportConfigs = {
};
export function getFilterQuery( endpoint, query ) {
if ( query.search ) {
return {
[ endpoint ]: query[ endpoint ],
};
}
if ( reportConfigs[ endpoint ] ) {
const { filters = [], advancedFilters = {} } = reportConfigs[ endpoint ];
return filters
@ -335,7 +341,7 @@ export function getReportTableQuery( endpoint, urlQuery, query ) {
*
* @param {String} endpoint Report API Endpoint
* @param {Object} urlQuery Query parameters in the url
* @param {object} select Instance of @wordpress/select
* @param {Object} select Instance of @wordpress/select
* @param {Object} query Query parameters specific for that endpoint
* @return {Object} Object Table data response
*/

View File

@ -3,7 +3,7 @@
/**
* Internal dependencies
*/
import categories from './categories';
import items from './items';
import notes from './notes';
import orders from './orders';
import reportItems from './reports/items';
@ -19,7 +19,7 @@ function createWcApiSpec() {
...user.mutations,
},
selectors: {
...categories.selectors,
...items.selectors,
...notes.selectors,
...orders.selectors,
...reportItems.selectors,
@ -31,7 +31,7 @@ function createWcApiSpec() {
operations: {
read( resourceNames ) {
return [
...categories.operations.read( resourceNames ),
...items.operations.read( resourceNames ),
...notes.operations.read( resourceNames ),
...orders.operations.read( resourceNames ),
...reportItems.operations.read( resourceNames ),

View File

@ -204,7 +204,8 @@ export class Autocomplete extends Component {
const expression = 'undefined' !== typeof completer.getSearchExpression
? completer.getSearchExpression( escapeRegExp( query ) )
: escapeRegExp( query );
const search = new RegExp( expression, 'i' );
// if there is no expression, match empty string
const search = expression ? new RegExp( expression, 'i' ) : /^$/;
// filter the options we already have
const filteredOptions = filterOptions( search, this.state.options, selected );
// update the state
@ -215,26 +216,36 @@ export class Autocomplete extends Component {
}
}
getOptions() {
const { allowFreeText, completer } = this.props;
const { getFreeTextOptions } = completer;
const { filteredOptions, query } = this.state;
const additionalOptions = allowFreeText && getFreeTextOptions ? getFreeTextOptions( query ) : [];
return additionalOptions.concat( filteredOptions );
}
handleKeyDown( event ) {
const { selectedIndex, filteredOptions } = this.state;
if ( filteredOptions.length === 0 ) {
const options = this.getOptions();
const { selectedIndex } = this.state;
if ( options.length === 0 ) {
return;
}
let nextSelectedIndex;
switch ( event.keyCode ) {
case UP:
nextSelectedIndex = ( selectedIndex === 0 ? filteredOptions.length : selectedIndex ) - 1;
nextSelectedIndex = ( selectedIndex === 0 ? options.length : selectedIndex ) - 1;
this.setState( { selectedIndex: nextSelectedIndex } );
break;
case TAB:
case DOWN:
nextSelectedIndex = ( selectedIndex + 1 ) % filteredOptions.length;
nextSelectedIndex = ( selectedIndex + 1 ) % options.length;
this.setState( { selectedIndex: nextSelectedIndex } );
break;
case ENTER:
this.select( filteredOptions[ selectedIndex ] );
this.select( options[ selectedIndex ] );
break;
case LEFT:
@ -263,9 +274,13 @@ export class Autocomplete extends Component {
this.node[ handler ]( 'keydown', this.handleKeyDown, true );
}
isExpanded( props, state ) {
return state.filteredOptions.length > 0 || ( props.completer.getFreeTextOptions && state.query );
}
componentDidUpdate( prevProps, prevState ) {
const isExpanded = this.state.filteredOptions.length > 0;
const wasExpanded = prevState.filteredOptions.length > 0;
const isExpanded = this.isExpanded( this.props, this.state );
const wasExpanded = this.isExpanded( prevProps, prevState );
if ( isExpanded && ! wasExpanded ) {
this.toggleKeyEvents( true );
} else if ( ! isExpanded && wasExpanded ) {
@ -280,9 +295,10 @@ export class Autocomplete extends Component {
render() {
const { children, instanceId, completer: { className = '' }, staticResults } = this.props;
const { selectedIndex, filteredOptions, query } = this.state;
const { key: selectedKey = '' } = filteredOptions[ selectedIndex ] || {};
const isExpanded = filteredOptions.length > 0 && !! query;
const { selectedIndex } = this.state;
const isExpanded = this.isExpanded( this.props, this.state );
const options = isExpanded ? this.getOptions() : [];
const { key: selectedKey = '' } = options[ selectedIndex ] || {};
const listBoxId = isExpanded ? `woocommerce-search__autocomplete-${ instanceId }` : null;
const activeId = isExpanded
? `woocommerce-search__autocomplete-${ instanceId }-${ selectedKey }`
@ -290,12 +306,13 @@ export class Autocomplete extends Component {
const resultsClasses = classnames( 'woocommerce-search__autocomplete-results', {
'is-static-results': staticResults,
} );
return (
<div ref={ this.bindNode } className="woocommerce-search__autocomplete">
{ children( { isExpanded, listBoxId, activeId, onChange: this.search } ) }
{ isExpanded && (
<div id={ listBoxId } role="listbox" className={ resultsClasses }>
{ filteredOptions.map( ( option, index ) => (
{ options.map( ( option, index ) => (
<Button
key={ option.key }
id={ `woocommerce-search__autocomplete-${ instanceId }-${ option.key }` }

View File

@ -2,7 +2,9 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import apiFetch from '@wordpress/api-fetch';
import interpolateComponents from 'interpolate-components';
/**
* WooCommerce dependencies
@ -33,12 +35,31 @@ export default {
};
payload = stringifyQuery( query );
}
return apiFetch( { path: `/wc/v3/products/categories${ payload }` } );
return apiFetch( { path: `/wc/v4/products/categories${ payload }` } );
},
isDebounced: true,
getOptionKeywords( cat ) {
return [ cat.name ];
},
getFreeTextOptions( query ) {
const label = (
<span key="name" className="woocommerce-search__result-name">
{ interpolateComponents( {
mixedString: __( 'All categories with titles that include {{query /}}', 'wc-admin' ),
components: {
query: <strong className="components-form-token-field__suggestion-match">{ query }</strong>,
},
} ) }
</span>
);
const titleOption = {
key: 'title',
label: label,
value: { id: query, name: query },
};
return [ titleOption ];
},
getOptionLabel( cat, query ) {
const match = computeSuggestionMatch( cat.name, query ) || {};
// @todo bring back ProductImage, but allow for product category image

View File

@ -2,7 +2,9 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import apiFetch from '@wordpress/api-fetch';
import interpolateComponents from 'interpolate-components';
/**
* WooCommerce dependencies
@ -38,6 +40,25 @@ export default {
getOptionKeywords( coupon ) {
return [ coupon.code ];
},
getFreeTextOptions( query ) {
const label = (
<span key="name" className="woocommerce-search__result-name">
{ interpolateComponents( {
mixedString: __( 'All coupons with codes that include {{query /}}', 'wc-admin' ),
components: {
query: <strong className="components-form-token-field__suggestion-match">{ query }</strong>,
},
} ) }
</span>
);
const codeOption = {
key: 'code',
label: label,
value: { id: query, code: query },
};
return [ codeOption ];
},
getOptionLabel( coupon, query ) {
const match = computeSuggestionMatch( coupon.code, query ) || {};
return [

View File

@ -2,7 +2,9 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import apiFetch from '@wordpress/api-fetch';
import interpolateComponents from 'interpolate-components';
/**
* WooCommerce dependencies
@ -29,7 +31,7 @@ export default {
let payload = '';
if ( name ) {
const query = {
name,
search: name,
per_page: 10,
};
payload = stringifyQuery( query );
@ -40,6 +42,25 @@ export default {
getOptionKeywords( customer ) {
return [ getName( customer ) ];
},
getFreeTextOptions( query ) {
const label = (
<span key="name" className="woocommerce-search__result-name">
{ interpolateComponents( {
mixedString: __( 'All customers with names that include {{query /}}', 'wc-admin' ),
components: {
query: <strong className="components-form-token-field__suggestion-match">{ query }</strong>,
},
} ) }
</span>
);
const nameOption = {
key: 'name',
label: label,
value: { id: query, first_name: query },
};
return [ nameOption ];
},
getOptionLabel( customer, query ) {
const match = computeSuggestionMatch( getName( customer ), query ) || {};
return [

View File

@ -2,7 +2,9 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import apiFetch from '@wordpress/api-fetch';
import interpolateComponents from 'interpolate-components';
/**
* WooCommerce dependencies
@ -40,6 +42,25 @@ export default {
getOptionKeywords( product ) {
return [ product.name ];
},
getFreeTextOptions( query ) {
const label = (
<span key="name" className="woocommerce-search__result-name">
{ interpolateComponents( {
mixedString: __( 'All products with titles that include {{query /}}', 'wc-admin' ),
components: {
query: <strong className="components-form-token-field__suggestion-match">{ query }</strong>,
},
} ) }
</span>
);
const titleOption = {
key: 'title',
label: label,
value: { id: query, name: query },
};
return [ titleOption ];
},
getOptionLabel( product, query ) {
const match = computeSuggestionMatch( product.name, query ) || {};
return [

View File

@ -2,7 +2,9 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import apiFetch from '@wordpress/api-fetch';
import interpolateComponents from 'interpolate-components';
/**
* WooCommerce dependencies
@ -38,6 +40,25 @@ export default {
getOptionKeywords( tax ) {
return [ tax.id, getTaxCode( tax ) ];
},
getFreeTextOptions( query ) {
const label = (
<span key="name" className="woocommerce-search__result-name">
{ interpolateComponents( {
mixedString: __( 'All taxes with codes that include {{query /}}', 'wc-admin' ),
components: {
query: <strong className="components-form-token-field__suggestion-match">{ query }</strong>,
},
} ) }
</span>
);
const codeOption = {
key: 'code',
label: label,
value: { id: query, name: query },
};
return [ codeOption ];
},
getOptionLabel( tax, query ) {
const match = computeSuggestionMatch( getTaxCode( tax ), query ) || {};
return [

View File

@ -27,12 +27,12 @@ export function computeSuggestionMatch( suggestion, query ) {
export function getTaxCode( tax ) {
return [ tax.country, tax.state, tax.name || __( 'TAX', 'wc-admin' ), tax.priority ]
.filter( Boolean )
.map( item =>
item
.toString()
.toUpperCase()
.trim()
)
.filter( Boolean )
.join( '-' );
}

View File

@ -149,7 +149,7 @@ class Search extends Component {
render() {
const autocompleter = this.getAutocompleter();
const { placeholder, inlineTags, selected, instanceId, className, staticResults } = this.props;
const { allowFreeTextSearch, placeholder, inlineTags, selected, instanceId, className, staticResults } = this.props;
const { value = '', isActive } = this.state;
const aria = {
'aria-labelledby': this.props[ 'aria-labelledby' ],
@ -164,6 +164,7 @@ class Search extends Component {
'has-inline-tags': inlineTags,
} ) }>
<Autocomplete
allowFreeText={ allowFreeTextSearch }
completer={ autocompleter }
onSelect={ this.selectResult }
selected={ selected.map( s => s.id ) }
@ -209,7 +210,7 @@ class Search extends Component {
{ ...aria }
/>
<span id={ `search-inline-input-${ instanceId }` } className="screen-reader-text">
{ __( 'Move backward for selected items' ) }
{ __( 'Move backward for selected items', 'wc-admin' ) }
</span>
</div>
</div>
@ -238,6 +239,10 @@ class Search extends Component {
}
Search.propTypes = {
/**
* Render additional options in the autocompleter to allow free text entering depending on the type.
*/
allowFreeTextSearch: PropTypes.bool,
/**
* Class name applied to parent div.
*/
@ -291,6 +296,7 @@ Search.propTypes = {
};
Search.defaultProps = {
allowFreeTextSearch: false,
onChange: noop,
selected: [],
inlineTags: false,

View File

@ -48,7 +48,7 @@ class TableCard extends Component {
const { compareBy, query } = props;
const showCols = props.headers.map( ( { key, hiddenByDefault } ) => ! hiddenByDefault && key ).filter( Boolean );
const selectedRows = getIdsFromQuery( query[ compareBy ] );
const selectedRows = query.filter ? getIdsFromQuery( query[ compareBy ] ) : [];
this.state = { showCols, selectedRows };
this.onColumnToggle = this.onColumnToggle.bind( this );
@ -61,14 +61,17 @@ class TableCard extends Component {
componentDidUpdate( { query: prevQuery, headers: prevHeaders } ) {
const { compareBy, headers, query } = this.props;
const prevIds = getIdsFromQuery( prevQuery[ compareBy ] );
const currentIds = getIdsFromQuery( query[ compareBy ] );
if ( ! isEqual( prevIds.sort(), currentIds.sort() ) ) {
/* eslint-disable react/no-did-update-set-state */
this.setState( {
selectedRows: currentIds,
} );
/* eslint-enable react/no-did-update-set-state */
if ( query.filter || prevQuery.filter ) {
const prevIds = prevQuery.filter ? getIdsFromQuery( prevQuery[ compareBy ] ) : [];
const currentIds = query.filter ? getIdsFromQuery( query[ compareBy ] ) : [];
if ( ! isEqual( prevIds.sort(), currentIds.sort() ) ) {
/* eslint-disable react/no-did-update-set-state */
this.setState( {
selectedRows: currentIds,
} );
/* eslint-enable react/no-did-update-set-state */
}
}
if ( ! isEqual( headers, prevHeaders ) ) {
/* eslint-disable react/no-did-update-set-state */
@ -148,19 +151,17 @@ class TableCard extends Component {
}
onSearch( values ) {
const { compareBy, compareParam, onQueryChange, searchBy, searchParam } = this.props;
const ids = values.map( v => v.id );
if ( compareBy ) {
const { selectedRows } = this.state;
onQueryChange( 'compare' )(
compareBy,
compareParam,
[ ...selectedRows, ...ids ].join( ',' )
);
} else if ( searchBy ) {
const { compareParam } = this.props;
const labels = values.map( v => v.label );
if ( labels.length ) {
updateQueryString( {
filter: 'advanced',
[ searchParam ]: ids.join( ',' ),
filter: undefined,
[ compareParam ]: undefined,
search: uniq( labels ).join( ',' ),
} );
} else {
updateQueryString( {
search: undefined,
} );
}
}
@ -236,13 +237,14 @@ class TableCard extends Component {
rowHeader,
rowsPerPage,
searchBy,
searchParam,
showMenu,
summary,
title,
totalRows,
} = this.props;
const { selectedRows, showCols } = this.state;
const searchedValues = query.search ? query.search.split( ',' ) : [];
const searchedLabels = searchedValues.map( v => ( { id: v, label: v } ) );
const allHeaders = this.props.headers;
let headers = this.getVisibleHeaders();
let rows = this.getVisibleRows();
@ -277,13 +279,15 @@ class TableCard extends Component {
{ labels.compareButton || __( 'Compare', 'wc-admin' ) }
</CompareButton>
),
( compareBy || searchBy ) && (
searchBy && (
<Search
allowFreeTextSearch={ true }
inlineTags
key="search"
placeholder={ labels.placeholder || __( 'Search by item name', 'wc-admin' ) }
type={ compareBy || searchBy }
onChange={ this.onSearch }
selected={ searchParam && getIdsFromQuery( query[ searchParam ] ).map( id => ( { id } ) ) }
placeholder={ labels.placeholder || __( 'Search by item name', 'wc-admin' ) }
selected={ searchedLabels }
type={ searchBy }
/>
),
( downloadable || onClickDownload ) && (
@ -438,10 +442,6 @@ TableCard.propTypes = {
* The string to use as a query parameter when searching row items.
*/
searchBy: PropTypes.string,
/**
* Url query parameter search function operates on
*/
searchParam: PropTypes.string,
/**
* Boolean to determine whether or not ellipsis menu is shown.
*/

View File

@ -54,7 +54,7 @@
}
}
&.has-search {
&.has-search:not(.has-compare) {
.woocommerce-card__action {
grid-template-columns: 1fr auto;