woocommerce/plugins/woocommerce-admin/client/header/activity-panel/panels/orders.js

346 lines
9.4 KiB
JavaScript
Raw Normal View History

/** @format */
/**
* External dependencies
*/
import { __, _n, sprintf } from '@wordpress/i18n';
2018-08-01 19:07:17 +00:00
import { Button } from '@wordpress/components';
import { Component, Fragment } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import Gridicon from 'gridicons';
import PropTypes from 'prop-types';
import interpolateComponents from 'interpolate-components';
import { keyBy, map, merge } from 'lodash';
/**
* WooCommerce dependencies
*/
import { EmptyContent, Flag, Link, OrderStatus, Section } from '@woocommerce/components';
import { formatCurrency } from 'lib/currency-format';
import { getNewPath } from '@woocommerce/navigation';
import { getAdminLink, getSetting } from '@woocommerce/wc-admin-settings';
/**
* Internal dependencies
*/
import { ActivityCard, ActivityCardPlaceholder } from '../activity-card';
import ActivityHeader from '../activity-header';
import ActivityOutboundLink from '../activity-outbound-link';
import { DEFAULT_ACTIONABLE_STATUSES, QUERY_DEFAULTS } from 'wc-api/constants';
import withSelect from 'wc-api/with-select';
class OrdersPanel extends Component {
renderEmptyCard() {
const { hasNonActionableOrders } = this.props;
if ( hasNonActionableOrders ) {
return (
<ActivityCard
className="woocommerce-empty-activity-card"
title={ __( 'You have no orders to fulfill', 'woocommerce-admin' ) }
icon={ <Gridicon icon="checkmark" size={ 48 } /> }
>
{ __( "Good job, you've fulfilled all of your new orders!", 'woocommerce-admin' ) }
</ActivityCard>
);
}
return (
<ActivityCard
className="woocommerce-empty-activity-card"
title={ __( 'You have no orders to fulfill', 'woocommerce-admin' ) }
icon={ <Gridicon icon="time" size={ 48 } /> }
actions={
<Button
href="https://docs.woocommerce.com/document/managing-orders/"
isDefault
target="_blank"
>
{ __( 'Learn more', 'woocommerce-admin' ) }
</Button>
}
>
{ __(
"You're still waiting for your customers to make their first orders. " +
'While you wait why not learn how to manage orders?',
'woocommerce-admin'
) }
</ActivityCard>
);
}
renderOrders() {
const { orders } = this.props;
if ( orders.length === 0 ) {
return this.renderEmptyCard();
}
const getCustomerString = order => {
const extended_info = order.extended_info || {};
const { first_name, last_name } = extended_info.customer || {};
if ( ! first_name && ! last_name ) {
return '';
}
const name = [ first_name, last_name ].join( ' ' );
return sprintf(
__(
/* translators: describes who placed an order, e.g. Order #123 placed by John Doe */
'placed by {{customerLink}}%(customerName)s{{/customerLink}}',
'woocommerce-admin'
),
{
customerName: name,
}
);
};
const orderCardTitle = order => {
const { extended_info, order_id, order_number } = order;
const { customer } = extended_info || {};
const customerUrl = customer.customer_id
? getNewPath( {}, '/analytics/customers', {
filter: 'single_customer',
customers: customer.customer_id,
} )
: null;
return (
<Fragment>
{ interpolateComponents( {
mixedString: sprintf(
__(
'Order {{orderLink}}#%(orderNumber)s{{/orderLink}} %(customerString)s {{destinationFlag/}}',
'woocommerce-admin'
),
{
orderNumber: order_number,
customerString: getCustomerString( order ),
}
),
components: {
orderLink: (
<Link
href={ getAdminLink( 'post.php?action=edit&post=' + order_id ) }
type="wp-admin"
/>
),
destinationFlag: customer.country ? (
<Flag code={ customer.country } round={ false } />
) : null,
customerLink: customerUrl ? <Link href={ customerUrl } type="wc-admin" /> : <span />,
},
} ) }
</Fragment>
);
};
const cards = [];
orders.forEach( order => {
const extended_info = order.extended_info || {};
const productsCount =
extended_info && extended_info.products ? extended_info.products.length : 0;
Correcting and clarifying analytics terms and calculations (https://github.com/woocommerce/woocommerce-admin/pull/3104) * Relabel Net Revenue to Net Sales, revert previous refund work on Gross revenue and rename to total sales. Update the orer of all the things * Add gross sales calculation to revenue stats endpoint. * Restore coupon_total when updating order stats. * Wire up gross sales to revenue report. * Fix revenue report refunds calculation when there are no refunds. * update net sales labels and cases in order, product and category tables * Subtract refunded shipping and taxes from gross sales. * pluses to minuses to fix the gross revenue and refund totals when refunding * Add gross_sales to revenue stats orderby enum. * Change refund labels to Returns * Remove usage of defunct coupon_total column. * Store refunded amount in stats table. * Rename "gross_total" column to "total_sales". * Net total for refund orders can be used instead of a new column. * Rename gross_revenue to total_sales. * Coalesce coupons total in order stats query. SUM()ing all nulls gives null, not zero. * Use segmentation selections to backfill missing data. Fo when report columns and segmentation columns don't match. * Remove errant gross_sales from expected interval test data. * Fix gross sales tests for revenue/stats. * Move missing segment fills back to their original locations. * Fix remaining tests failing because of gross sales. * Fix db upgrade function rename of gross_total column. * Fix linter errors.
2019-11-22 15:06:14 +00:00
const total = order.total_sales;
cards.push(
<ActivityCard
key={ order.order_id }
className="woocommerce-order-activity-card"
title={ orderCardTitle( order ) }
date={ order.date_created_gmt }
subtitle={
<div>
<span>
{ sprintf(
_n( '%d product', '%d products', productsCount, 'woocommerce-admin' ),
productsCount
) }
</span>
<span>{ formatCurrency( total ) }</span>
</div>
}
actions={
<Button
isDefault
href={ getAdminLink( 'post.php?action=edit&post=' + order.order_id ) }
>
{ __( 'Begin fulfillment' ) }
</Button>
}
>
<OrderStatus order={ order } orderStatusMap={ getSetting( 'orderStatuses', {} ) } />
</ActivityCard>
);
} );
return (
<Fragment>
{ cards }
<ActivityOutboundLink href={ 'edit.php?post_type=shop_order' }>
{ __( 'Manage all orders', 'woocommerce-admin' ) }
</ActivityOutboundLink>
</Fragment>
);
}
render() {
const { orders, isRequesting, isError, orderStatuses } = this.props;
if ( isError ) {
if ( ! orderStatuses.length ) {
return (
<EmptyContent
title={ __(
"You currently don't have any actionable statuses. " +
'To display orders here, select orders that require further review in settings.',
'woocommerce-admin'
) }
actionLabel={ __( 'Settings', 'woocommerce-admin' ) }
actionURL={ getAdminLink( 'admin.php?page=wc-admin&path=/analytics/settings' ) }
/>
);
}
const title = __(
'There was an error getting your orders. Please try again.',
'woocommerce-admin'
);
const actionLabel = __( 'Reload', 'woocommerce-admin' );
const actionCallback = () => {
// @todo Add tracking for how often an error is displayed, and the reload action is clicked.
window.location.reload();
};
return (
<Fragment>
<EmptyContent
title={ title }
actionLabel={ actionLabel }
actionURL={ null }
actionCallback={ actionCallback }
/>
</Fragment>
);
}
const title =
isRequesting || orders.length
? __( 'Orders', 'woocommerce-admin' )
: __( 'No orders to ship', 'woocommerce-admin' );
return (
<Fragment>
<ActivityHeader title={ title } />
<Section>
{ isRequesting ? (
<ActivityCardPlaceholder
className="woocommerce-order-activity-card"
hasAction
hasDate
lines={ 2 }
/>
) : (
this.renderOrders()
) }
</Section>
</Fragment>
);
}
}
OrdersPanel.propTypes = {
orders: PropTypes.array.isRequired,
isError: PropTypes.bool,
isRequesting: PropTypes.bool,
};
2018-08-07 20:11:30 +00:00
OrdersPanel.defaultProps = {
orders: [],
isError: false,
isRequesting: false,
2018-08-07 20:11:30 +00:00
};
export default compose(
withSelect( ( select, props ) => {
const { hasActionableOrders } = props;
const {
getItems,
getItemsTotalCount,
getItemsError,
isGetItemsRequesting,
getReportItems,
getReportItemsError,
isReportItemsRequesting,
} = select( 'wc-api' );
const {
woocommerce_actionable_order_statuses: orderStatuses = DEFAULT_ACTIONABLE_STATUSES,
} = getSetting( 'wcAdminSettings', {} );
if ( ! orderStatuses.length ) {
return { orders: [], isError: true, isRequesting: false, orderStatuses };
}
2019-07-17 18:36:32 +00:00
if ( hasActionableOrders ) {
// Query the core Orders endpoint for the most up-to-date statuses.
const actionableOrdersQuery = {
page: 1,
per_page: QUERY_DEFAULTS.pageSize,
status: orderStatuses,
_fields: [ 'id', 'date_created_gmt', 'status' ],
};
const actionableOrders = Array.from( getItems( 'orders', actionableOrdersQuery ).values() );
const isRequestingActionable = isGetItemsRequesting( 'orders', actionableOrdersQuery );
if ( isRequestingActionable ) {
return {
isError: Boolean( getItemsError( 'orders', actionableOrdersQuery ) ),
isRequesting: isRequestingActionable,
orderStatuses,
};
}
// Retrieve the Order stats data from our reporting table.
const ordersQuery = {
page: 1,
per_page: QUERY_DEFAULTS.pageSize,
extended_info: true,
order_includes: map( actionableOrders, 'id' ),
};
const reportOrders = getReportItems( 'orders', ordersQuery ).data;
const isError = Boolean( getReportItemsError( 'orders', ordersQuery ) );
const isRequesting = isReportItemsRequesting( 'orders', ordersQuery );
let orders = [];
if ( reportOrders && reportOrders.length ) {
// Merge the core endpoint data with our reporting table.
const actionableOrdersById = keyBy( actionableOrders, 'id' );
orders = reportOrders.map( order =>
merge( {}, order, actionableOrdersById[ order.order_id ] || {} )
);
}
return { orders, isError, isRequesting, orderStatuses };
}
2019-07-17 18:36:32 +00:00
// Get a count of all orders for messaging purposes.
// @todo Add a property to settings api for this?
2019-07-17 18:36:32 +00:00
const allOrdersQuery = {
page: 1,
per_page: 1,
_fields: [ 'id' ],
};
getItems( 'orders', allOrdersQuery );
const totalNonActionableOrders = getItemsTotalCount( 'orders', allOrdersQuery );
const isError = Boolean( getItemsError( 'orders', allOrdersQuery ) );
const isRequesting = isGetItemsRequesting( 'orders', allOrdersQuery );
return {
hasNonActionableOrders: totalNonActionableOrders > 0,
isError,
isRequesting,
orderStatuses,
};
} )
)( OrdersPanel );