* 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

* Merge getOrders and getItems
This commit is contained in:
Albert Juhé Lluveras 2019-02-01 11:01:42 +01:00 committed by GitHub
parent d8ed3b6614
commit 666d8e4a3b
11 changed files with 76 additions and 223 deletions

View File

@ -18,7 +18,7 @@ export default class CategoryBreadcrumbs extends Component {
let parent = category.parent;
while ( parent ) {
ancestors.unshift( parent );
parent = categories[ parent ].parent;
parent = categories.get( parent ).parent;
}
return ancestors;
}
@ -30,20 +30,20 @@ export default class CategoryBreadcrumbs extends Component {
return;
}
if ( ancestorIds.length === 1 ) {
return categories[ first( ancestorIds ) ].name + ' ';
return categories.get( first( ancestorIds ) ).name + ' ';
}
if ( ancestorIds.length === 2 ) {
return (
categories[ first( ancestorIds ) ].name +
categories.get( first( ancestorIds ) ).name +
' ' +
categories[ last( ancestorIds ) ].name +
categories.get( last( ancestorIds ) ).name +
' '
);
}
return (
categories[ first( ancestorIds ) ].name +
categories.get( first( ancestorIds ) ).name +
' … ' +
categories[ last( ancestorIds ) ].name +
categories.get( last( ancestorIds ) ).name +
' '
);
}

View File

@ -71,7 +71,7 @@ class CategoriesReportTable extends Component {
return map( categoryStats, categoryStat => {
const { category_id, items_sold, net_revenue, products_count, orders_count } = categoryStat;
const { categories, query } = this.props;
const category = categories[ category_id ];
const category = categories.get( category_id );
const persistedQuery = getPersistedQuery( query );
return [

View File

@ -119,11 +119,11 @@ class ProductsReportTable extends Component {
filter: 'single_product',
products: product_id,
} );
const categories = this.props.categories;
const { categories } = this.props;
const productCategories =
( category_ids &&
category_ids.map( category_id => categories[ category_id ] ).filter( Boolean ) ) ||
category_ids.map( category_id => categories.get( category_id ) ).filter( Boolean ) ) ||
[];
return [

View File

@ -92,6 +92,51 @@ function OrdersPanel( { orders, isRequesting, isError } ) {
);
};
const cards = [];
orders.forEach( ( order, id ) => {
// We want the billing address, but shipping can be used as a fallback.
const address = { ...order.shipping, ...order.billing };
const productsCount = order.line_items.reduce( ( total, line ) => total + line.quantity, 0 );
const total = order.total;
const refundValue = getOrderRefundTotal( order );
const remainingTotal = getCurrencyFormatDecimal( order.total ) + refundValue;
cards.push(
<ActivityCard
key={ id }
className="woocommerce-order-activity-card"
title={ orderCardTitle( order, address ) }
date={ order.date_created }
subtitle={
<div>
<span>
{ sprintf(
_n( '%d product', '%d products', productsCount, 'wc-admin' ),
productsCount
) }
</span>
{ refundValue ? (
<span>
<s>{ formatCurrency( total, order.currency_symbol ) }</s>{' '}
{ formatCurrency( remainingTotal, order.currency_symbol ) }
</span>
) : (
<span>{ formatCurrency( total, order.currency_symbol ) }</span>
) }
</div>
}
actions={
<Button isDefault href={ getAdminLink( 'post.php?action=edit&post=' + order.id ) }>
{ __( 'Begin fulfillment' ) }
</Button>
}
>
<OrderStatus order={ order } />
</ActivityCard>
);
} );
return (
<Fragment>
<ActivityHeader title={ __( 'Orders', 'wc-admin' ) } menu={ menu } />
@ -105,55 +150,7 @@ function OrdersPanel( { orders, isRequesting, isError } ) {
/>
) : (
<Fragment>
{ orders.map( ( order, i ) => {
// We want the billing address, but shipping can be used as a fallback.
const address = { ...order.shipping, ...order.billing };
const productsCount = order.line_items.reduce(
( total, line ) => total + line.quantity,
0
);
const total = order.total;
const refundValue = getOrderRefundTotal( order );
const remainingTotal = getCurrencyFormatDecimal( order.total ) + refundValue;
return (
<ActivityCard
key={ i }
className="woocommerce-order-activity-card"
title={ orderCardTitle( order, address ) }
date={ order.date_created }
subtitle={
<div>
<span>
{ sprintf(
_n( '%d product', '%d products', productsCount, 'wc-admin' ),
productsCount
) }
</span>
{ refundValue ? (
<span>
<s>{ formatCurrency( total, order.currency_symbol ) }</s>{' '}
{ formatCurrency( remainingTotal, order.currency_symbol ) }
</span>
) : (
<span>{ formatCurrency( total, order.currency_symbol ) }</span>
) }
</div>
}
actions={
<Button
isDefault
href={ getAdminLink( 'post.php?action=edit&post=' + order.id ) }
>
{ __( 'Begin fulfillment' ) }
</Button>
}
>
<OrderStatus order={ order } />
</ActivityCard>
);
} ) }
{ cards }
<ActivityOutboundLink href={ 'edit.php?post_type=shop_order' }>
{ __( 'Manage all orders' ) }
</ActivityOutboundLink>
@ -165,29 +162,29 @@ function OrdersPanel( { orders, isRequesting, isError } ) {
}
OrdersPanel.propTypes = {
orders: PropTypes.array.isRequired,
orders: PropTypes.instanceOf( Map ).isRequired,
isError: PropTypes.bool,
isRequesting: PropTypes.bool,
};
OrdersPanel.defaultProps = {
orders: [],
orders: new Map(),
isError: false,
isRequesting: false,
};
export default compose(
withSelect( select => {
const { getOrders, getOrdersError, isGetOrdersRequesting } = select( 'wc-api' );
const { getItems, getItemsError, isGetItemsRequesting } = select( 'wc-api' );
const ordersQuery = {
page: 1,
per_page: QUERY_DEFAULTS.pageSize,
status: 'processing',
};
const orders = getOrders( ordersQuery );
const isError = Boolean( getOrdersError( ordersQuery ) );
const isRequesting = isGetOrdersRequesting( ordersQuery );
const orders = getItems( 'orders', ordersQuery );
const isError = Boolean( getItemsError( 'orders', ordersQuery ) );
const isRequesting = isGetItemsRequesting( 'orders', ordersQuery );
return { orders, isError, isRequesting };
} )

View File

@ -20,6 +20,7 @@ const typeEndpointMap = {
'items-query-categories': 'products/categories',
'items-query-customers': 'customers',
'items-query-coupons': 'coupons',
'items-query-orders': 'orders',
'items-query-products': 'products',
'items-query-taxes': 'taxes',
};

View File

@ -18,13 +18,10 @@ const getItems = ( getResource, requireResource ) => (
) => {
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 || {},
} ),
{}
);
const items = new Map();
ids.forEach( id => {
items.set( id, getResource( getResourceName( `items-query-${ type }-item`, id ) ).data );
} );
return items;
};

View File

@ -16,15 +16,16 @@ 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,
} ),
};
}, [] );
const items = {};
searchWords.forEach( searchWord => {
const newItems = getItems( endpoint, {
search: searchWord,
per_page: 10,
} );
newItems.forEach( ( item, id ) => {
items[ id ] = item;
} );
} );
return items;
}

View File

@ -1,11 +0,0 @@
/** @format */
/**
* Internal dependencies
*/
import operations from './operations';
import selectors from './selectors';
export default {
operations,
selectors,
};

View File

@ -1,76 +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 ) {
return [ ...readOrders( resourceNames, fetch ), ...readOrderQueries( resourceNames, fetch ) ];
}
function readOrderQueries( resourceNames, fetch ) {
const filteredNames = resourceNames.filter( name => isResourcePrefix( name, 'order-query' ) );
return filteredNames.map( async resourceName => {
const query = getResourceIdentifier( resourceName );
const url = `${ NAMESPACE }/orders${ stringifyQuery( query ) }`;
try {
const response = await fetch( {
parse: false,
path: url,
} );
const orders = await response.json();
const totalCount = parseInt( response.headers.get( 'x-wp-total' ) );
const ids = orders.map( order => order.id );
const orderResources = orders.reduce( ( resources, order ) => {
resources[ getResourceName( 'order', order.id ) ] = { data: order };
return resources;
}, {} );
return {
[ resourceName ]: {
data: ids,
totalCount,
},
...orderResources,
};
} catch ( error ) {
return { [ resourceName ]: { error } };
}
} );
}
function readOrders( resourceNames, fetch ) {
const filteredNames = resourceNames.filter( name => isResourcePrefix( name, 'order' ) );
return filteredNames.map( resourceName => readOrder( resourceName, fetch ) );
}
function readOrder( resourceName, fetch ) {
const id = getResourceIdentifier( resourceName );
const url = `${ NAMESPACE }/orders/${ id }`;
return fetch( { path: url } )
.then( order => {
return { [ resourceName ]: { data: order } };
} )
.catch( error => {
return { [ resourceName ]: { error } };
} );
}
export default {
read,
};

View File

@ -1,53 +0,0 @@
/** @format */
/**
* External dependencies
*/
import { isNil } from 'lodash';
/**
* Internal dependencies
*/
import { getResourceName } from '../utils';
import { DEFAULT_REQUIREMENT } from '../constants';
const getOrders = ( getResource, requireResource ) => (
query = {},
requirement = DEFAULT_REQUIREMENT
) => {
const resourceName = getResourceName( 'order-query', query );
const ids = requireResource( requirement, resourceName ).data || [];
const orders = ids.map( id => getResource( getResourceName( 'order', id ) ).data || {} );
return orders;
};
const getOrdersError = getResource => ( query = {} ) => {
const resourceName = getResourceName( 'order-query', query );
return getResource( resourceName ).error;
};
const getOrdersTotalCount = ( getResource, requireResource ) => (
query = {},
requirement = DEFAULT_REQUIREMENT
) => {
const resourceName = getResourceName( 'order-query', query );
return requireResource( requirement, resourceName ).totalCount || 0;
};
const isGetOrdersRequesting = getResource => ( query = {} ) => {
const resourceName = getResourceName( 'order-query', query );
const { lastRequested, lastReceived } = getResource( resourceName );
if ( isNil( lastRequested ) || isNil( lastReceived ) ) {
return true;
}
return lastRequested > lastReceived;
};
export default {
getOrders,
getOrdersError,
getOrdersTotalCount,
isGetOrdersRequesting,
};

View File

@ -5,7 +5,6 @@
*/
import items from './items';
import notes from './notes';
import orders from './orders';
import reportItems from './reports/items';
import reportStats from './reports/stats';
import reviews from './reviews';
@ -21,7 +20,6 @@ function createWcApiSpec() {
selectors: {
...items.selectors,
...notes.selectors,
...orders.selectors,
...reportItems.selectors,
...reportStats.selectors,
...reviews.selectors,
@ -33,7 +31,6 @@ function createWcApiSpec() {
return [
...items.operations.read( resourceNames ),
...notes.operations.read( resourceNames ),
...orders.operations.read( resourceNames ),
...reportItems.operations.read( resourceNames ),
...reportStats.operations.read( resourceNames ),
...reviews.operations.read( resourceNames ),