diff --git a/plugins/woocommerce-admin/client/analytics/report/orders/index.js b/plugins/woocommerce-admin/client/analytics/report/orders/index.js
index 3e976f69d7d..3d11b985419 100644
--- a/plugins/woocommerce-admin/client/analytics/report/orders/index.js
+++ b/plugins/woocommerce-admin/client/analytics/report/orders/index.js
@@ -4,34 +4,23 @@
*/
import { Component, Fragment } from '@wordpress/element';
import { compose } from '@wordpress/compose';
-import { Button } from '@wordpress/components';
-import { withSelect, withDispatch } from '@wordpress/data';
-import moment from 'moment';
-import { partial } from 'lodash';
+import { withSelect } from '@wordpress/data';
/**
* Internal dependencies
*/
-import { Card, ReportFilters } from '@woocommerce/components';
+import { ReportFilters } from '@woocommerce/components';
import { filters, advancedFilterConfig } from './config';
+import OrdersReportTable from './table';
class OrdersReport extends Component {
constructor( props ) {
super( props );
-
- this.toggleStatus = this.toggleStatus.bind( this );
- }
-
- toggleStatus( order ) {
- const { requestUpdateOrder } = this.props;
- const updatedOrder = { ...order };
- const status = updatedOrder.status === 'completed' ? 'processing' : 'completed';
- updatedOrder.status = status;
- requestUpdateOrder( updatedOrder );
}
render() {
- const { orders, orderIds, query, path } = this.props;
+ const { isRequesting, orders, path, query } = this.props;
+
return (
- Below is a temporary example
-
-
-
-
- Id |
- Date |
- Total |
- Status |
- Action |
-
-
-
- { orderIds &&
- orderIds.map( id => {
- const order = orders[ id ];
- return (
-
- { id } |
- { moment( order.date_created ).format( 'LL' ) } |
- { order.total } |
- { order.status } |
-
-
- |
-
- );
- } ) }
-
-
-
+
);
}
@@ -80,17 +37,9 @@ class OrdersReport extends Component {
export default compose(
withSelect( select => {
- const { getOrders, getOrderIds } = select( 'wc-admin' );
- return {
- orders: getOrders(),
- orderIds: getOrderIds(),
- };
- } ),
- withDispatch( dispatch => {
- return {
- requestUpdateOrder: function( updatedOrder ) {
- dispatch( 'wc-admin' ).requestUpdateOrder( updatedOrder );
- },
- };
+ const { getOrders } = select( 'wc-admin' );
+ const orders = getOrders();
+ const isRequesting = select( 'core/data' ).isResolving( 'wc-admin', 'getOrders' );
+ return { isRequesting, orders };
} )
)( OrdersReport );
diff --git a/plugins/woocommerce-admin/client/analytics/report/orders/table.js b/plugins/woocommerce-admin/client/analytics/report/orders/table.js
new file mode 100644
index 00000000000..7440b82731b
--- /dev/null
+++ b/plugins/woocommerce-admin/client/analytics/report/orders/table.js
@@ -0,0 +1,265 @@
+/** @format */
+/**
+ * External dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { Component, Fragment } from '@wordpress/element';
+import { format as formatDate } from '@wordpress/date';
+import { map, orderBy } from 'lodash';
+
+/**
+ * Internal dependencies
+ */
+import { Card, OrderStatus, TableCard, TablePlaceholder } from '@woocommerce/components';
+import { downloadCSVFile, generateCSVDataFromTable, generateCSVFileName } from 'lib/csv';
+import { formatCurrency, getCurrencyFormatDecimal } from 'lib/currency';
+import { getIntervalForQuery, getDateFormatsForInterval } from 'lib/date';
+import { getAdminLink, onQueryChange } from 'lib/nav-utils';
+
+export default class OrdersReportTable extends Component {
+ constructor( props ) {
+ super( props );
+ }
+
+ onDownload( headers, rows, query ) {
+ // @TODO The current implementation only downloads the contents displayed in the table.
+ // Another solution is required when the data set is larger (see #311).
+ return () => {
+ downloadCSVFile(
+ generateCSVFileName( 'orders', query ),
+ generateCSVDataFromTable( headers, rows )
+ );
+ };
+ }
+
+ getHeadersContent() {
+ return [
+ {
+ label: __( 'Date', 'wc-admin' ),
+ key: 'date_created',
+ required: true,
+ defaultSort: true,
+ isLeftAligned: true,
+ isSortable: true,
+ },
+ {
+ label: __( 'Order #', 'wc-admin' ),
+ key: 'id',
+ required: true,
+ isLeftAligned: true,
+ isSortable: true,
+ },
+ {
+ label: __( 'Status', 'wc-admin' ),
+ key: 'status',
+ required: false,
+ isSortable: true,
+ },
+ {
+ label: __( 'Customer', 'wc-admin' ),
+ key: 'customer_id',
+ required: false,
+ isSortable: true,
+ },
+ {
+ label: __( 'Product(s)', 'wc-admin' ),
+ key: 'products',
+ required: false,
+ isSortable: false,
+ },
+ {
+ label: __( 'Items Sold', 'wc-admin' ),
+ key: 'items_sold',
+ required: false,
+ isSortable: true,
+ isNumeric: true,
+ },
+ {
+ label: __( 'Coupon(s)', 'wc-admin' ),
+ key: 'coupons',
+ required: false,
+ isSortable: false,
+ },
+ {
+ label: __( 'N. Revenue', 'wc-admin' ),
+ key: 'net_revenue',
+ required: true,
+ isSortable: true,
+ isNumeric: true,
+ },
+ ];
+ }
+
+ formatTableData( data ) {
+ return map( data, row => {
+ const {
+ date_created,
+ id,
+ status,
+ customer_id,
+ line_items,
+ coupon_lines,
+ currency,
+ total,
+ total_tax,
+ shipping_total,
+ discount_total,
+ } = row;
+
+ return {
+ date_created,
+ id,
+ status,
+ customer_id,
+ line_items,
+ items_sold: line_items.reduce( ( acc, item ) => item.quantity + acc, 0 ),
+ coupon_lines,
+ currency,
+ net_revenue: getCurrencyFormatDecimal(
+ total - total_tax - shipping_total - discount_total
+ ),
+ };
+ } );
+ }
+
+ getRowsContent( tableData ) {
+ const { query } = this.props;
+ const currentInterval = getIntervalForQuery( query );
+ const { tableFormat } = getDateFormatsForInterval( currentInterval );
+
+ return map( tableData, row => {
+ const {
+ date_created,
+ id,
+ status,
+ customer_id,
+ line_items,
+ items_sold,
+ coupon_lines,
+ currency,
+ net_revenue,
+ } = row;
+
+ return [
+ {
+ display: formatDate( tableFormat, date_created ),
+ value: date_created,
+ },
+ {
+ display: { id },
+ value: id,
+ },
+ {
+ display: ,
+ value: status,
+ },
+ {
+ // @TODO This should display customer type (new/returning) once it's
+ // implemented in the API.
+ display: customer_id,
+ value: customer_id,
+ },
+ {
+ display: this.renderList(
+ line_items.map( item => ( {
+ href: getAdminLink( 'post.php?post=' + item.product_id + '&action=edit' ),
+ label: item.name,
+ } ) )
+ ),
+ value: line_items
+ .map( item => item.name )
+ .join()
+ .toLowerCase(),
+ },
+ {
+ display: items_sold,
+ value: items_sold,
+ },
+ {
+ display: this.renderList(
+ coupon_lines.map( coupon => ( {
+ // @TODO It should link to the coupons report.
+ href: getAdminLink( 'edit.php?s=' + coupon.code + '&post_type=shop_coupon' ),
+ label: coupon.code,
+ } ) )
+ ),
+ value: coupon_lines
+ .map( item => item.code )
+ .join()
+ .toLowerCase(),
+ },
+ {
+ display: formatCurrency( net_revenue, currency ),
+ value: net_revenue,
+ },
+ ];
+ } );
+ }
+
+ renderList( items ) {
+ // @TODO Use ViewMore component if there are many items.
+ return items.map( ( item, i ) => (
+
+ { i > 0 ? ', ' : null }
+ 1 ? 'is-inline' : null } href={ item.href }>
+ { item.label }
+
+
+ ) );
+ }
+
+ renderPlaceholderTable() {
+ const headers = this.getHeadersContent();
+
+ return (
+
+
+
+ );
+ }
+
+ renderTable() {
+ const { orders, query } = this.props;
+
+ const page = parseInt( query.page ) || 1;
+ const rowsPerPage = parseInt( query.per_page ) || 25;
+ const rows = this.getRowsContent(
+ orderBy(
+ this.formatTableData( orders ),
+ query.orderby || 'date_created',
+ query.order || 'asc'
+ ).slice( ( page - 1 ) * rowsPerPage, page * rowsPerPage )
+ );
+
+ const headers = this.getHeadersContent();
+
+ const tableQuery = {
+ ...query,
+ orderby: query.orderby || 'date_created',
+ order: query.order || 'asc',
+ };
+
+ return (
+
+ );
+ }
+
+ render() {
+ const { isRequesting } = this.props;
+
+ return isRequesting ? this.renderPlaceholderTable() : this.renderTable();
+ }
+}
diff --git a/plugins/woocommerce-admin/client/analytics/report/products/index.js b/plugins/woocommerce-admin/client/analytics/report/products/index.js
index 913cd7a7202..1f656494d4f 100644
--- a/plugins/woocommerce-admin/client/analytics/report/products/index.js
+++ b/plugins/woocommerce-admin/client/analytics/report/products/index.js
@@ -24,6 +24,7 @@ export default class extends Component {
label: __( 'Product Title', 'wc-admin' ),
key: 'name',
required: true,
+ isLeftAligned: true,
isSortable: true,
},
{
diff --git a/plugins/woocommerce-admin/client/analytics/report/revenue/index.js b/plugins/woocommerce-admin/client/analytics/report/revenue/index.js
index 1258801552a..8ffdd0395fe 100644
--- a/plugins/woocommerce-admin/client/analytics/report/revenue/index.js
+++ b/plugins/woocommerce-admin/client/analytics/report/revenue/index.js
@@ -93,6 +93,7 @@ export class RevenueReport extends Component {
key: 'date',
required: true,
defaultSort: true,
+ isLeftAligned: true,
isSortable: true,
},
{
@@ -100,6 +101,7 @@ export class RevenueReport extends Component {
key: 'orders_count',
required: false,
isSortable: true,
+ isNumeric: true,
},
{
label: __( 'Gross Revenue', 'wc-admin' ),
diff --git a/plugins/woocommerce-admin/client/components/table/style.scss b/plugins/woocommerce-admin/client/components/table/style.scss
index 67416a99d06..eeb4cadd24c 100644
--- a/plugins/woocommerce-admin/client/components/table/style.scss
+++ b/plugins/woocommerce-admin/client/components/table/style.scss
@@ -58,9 +58,11 @@
text-align: left;
a {
- display: block;
- margin: ($gap*-1) ($gap-large*-1);
- padding: $gap $gap-large;
+ &:not(.is-inline) {
+ display: block;
+ margin: ($gap*-1) ($gap-large*-1);
+ padding: $gap $gap-large;
+ }
&:hover,
&:focus {
@@ -80,21 +82,28 @@
width: 80%;
}
- &.is-numeric {
+ &:not(.is-left-aligned) {
text-align: right;
+ .rtl & {
+ text-align: left;
+ }
+
button {
justify-content: flex-end;
}
+ }
- .is-placeholder {
- max-width: 40px;
- }
+ &.is-numeric .is-placeholder {
+ max-width: 40px;
}
}
-.woocommerce-table__header,
th.woocommerce-table__item {
+ font-weight: normal;
+}
+
+.woocommerce-table__header {
font-weight: bold;
white-space: nowrap;
}
@@ -106,6 +115,11 @@ th.woocommerce-table__item {
& + .woocommerce-table__header {
border-left: 1px solid $core-grey-light-700;
+
+ .rtl & {
+ border-left: 0;
+ border-right: 1px solid $core-grey-light-700;
+ }
}
.components-button.is-button {
@@ -118,6 +132,10 @@ th.woocommerce-table__item {
background: transparent;
box-shadow: none !important;
+ .rtl & {
+ padding: $gap-smaller 0 $gap-smaller $gap-large;
+ }
+
// @todo Add interactive styles
&:hover {
box-shadow: none !important;
diff --git a/plugins/woocommerce-admin/client/components/table/table.js b/plugins/woocommerce-admin/client/components/table/table.js
index fe585864df9..f4d13a4b5c1 100644
--- a/plugins/woocommerce-admin/client/components/table/table.js
+++ b/plugins/woocommerce-admin/client/components/table/table.js
@@ -120,10 +120,11 @@ class Table extends Component {
{ headers.map( ( header, i ) => {
- const { isSortable, isNumeric, key, label } = header;
+ const { isLeftAligned, isSortable, isNumeric, key, label } = header;
const labelId = `header-${ instanceId } -${ i }`;
const thProps = {
className: classnames( 'woocommerce-table__header', {
+ 'is-left-aligned': isLeftAligned,
'is-sortable': isSortable,
'is-sorted': sortedBy === key,
'is-numeric': isNumeric,
@@ -173,10 +174,11 @@ class Table extends Component {
{ rows.map( ( row, i ) => (
{ row.map( ( cell, j ) => {
- const { isNumeric } = headers[ j ];
+ const { isLeftAligned, isNumeric } = headers[ j ];
const isHeader = rowHeader === j;
const Cell = isHeader ? 'th' : 'td';
const cellClasses = classnames( 'woocommerce-table__item', {
+ 'is-left-aligned': isLeftAligned,
'is-numeric': isNumeric,
} );
return (
@@ -217,6 +219,10 @@ Table.propTypes = {
* Boolean, true if this column is the default for sorting. Only one column should have this set.
*/
defaultSort: PropTypes.bool,
+ /**
+ * Boolean, true if this column should be aligned to the left.
+ */
+ isLeftAligned: PropTypes.bool,
/**
* Boolean, true if this column is a number value.
*/
diff --git a/plugins/woocommerce-admin/client/dashboard/top-selling-products/index.js b/plugins/woocommerce-admin/client/dashboard/top-selling-products/index.js
index 4232abe6719..ebcad1ff697 100644
--- a/plugins/woocommerce-admin/client/dashboard/top-selling-products/index.js
+++ b/plugins/woocommerce-admin/client/dashboard/top-selling-products/index.js
@@ -25,6 +25,7 @@ export class TopSellingProducts extends Component {
label: __( 'Product', 'wc-admin' ),
key: 'product',
required: true,
+ isLeftAligned: true,
isSortable: false,
},
{
diff --git a/plugins/woocommerce-admin/client/lib/csv/__mocks__/mock-csv-data.js b/plugins/woocommerce-admin/client/lib/csv/__mocks__/mock-csv-data.js
index a0fa38d93ba..faaaa6156eb 100644
--- a/plugins/woocommerce-admin/client/lib/csv/__mocks__/mock-csv-data.js
+++ b/plugins/woocommerce-admin/client/lib/csv/__mocks__/mock-csv-data.js
@@ -1,4 +1,4 @@
/** @format */
-export default `Date,Orders,Gross Revenue,Refunds,Coupons,Taxes,Shipping,Net Revenue
-2018-04-29T00:00:00,30,200,19,19,100,19,200`;
+export default `Date,Orders,Description,Gross Revenue,Refunds,Coupons,Taxes,Shipping,Net Revenue
+2018-04-29T00:00:00,30,lorem ipsum,200,19,19,100,19,200`;
diff --git a/plugins/woocommerce-admin/client/lib/csv/__mocks__/mock-headers.js b/plugins/woocommerce-admin/client/lib/csv/__mocks__/mock-headers.js
index 7b9a3fe6ec5..9a4a8f73593 100644
--- a/plugins/woocommerce-admin/client/lib/csv/__mocks__/mock-headers.js
+++ b/plugins/woocommerce-admin/client/lib/csv/__mocks__/mock-headers.js
@@ -9,6 +9,10 @@ export default [
label: 'Orders',
key: 'orders_count',
},
+ {
+ label: 'Description',
+ key: 'description',
+ },
{
label: 'Gross Revenue',
key: 'gross_revenue',
diff --git a/plugins/woocommerce-admin/client/lib/csv/__mocks__/mock-rows.js b/plugins/woocommerce-admin/client/lib/csv/__mocks__/mock-rows.js
index 1e6ad6b08b7..41728c6b157 100644
--- a/plugins/woocommerce-admin/client/lib/csv/__mocks__/mock-rows.js
+++ b/plugins/woocommerce-admin/client/lib/csv/__mocks__/mock-rows.js
@@ -10,6 +10,10 @@ export default [
display: 'Product 30',
value: '30',
},
+ {
+ display: 'Lorem, ipsum',
+ value: 'lorem, ipsum',
+ },
{
display: '€200.00',
value: 200,
diff --git a/plugins/woocommerce-admin/client/lib/csv/index.js b/plugins/woocommerce-admin/client/lib/csv/index.js
index 1a29b89affa..4a8a3ab3e49 100644
--- a/plugins/woocommerce-admin/client/lib/csv/index.js
+++ b/plugins/woocommerce-admin/client/lib/csv/index.js
@@ -11,7 +11,11 @@ function getCSVHeaders( headers ) {
function getCSVRows( rows ) {
return Array.isArray( rows )
- ? rows.map( row => row.map( rowItem => rowItem.value ).join( ',' ) ).join( '\n' )
+ ? rows
+ .map( row =>
+ row.map( rowItem => rowItem.value.toString().replace( /,/g, '' ) ).join( ',' )
+ )
+ .join( '\n' )
: [];
}
diff --git a/plugins/woocommerce-admin/client/store/orders/reducer.js b/plugins/woocommerce-admin/client/store/orders/reducer.js
index 5d7397f8ed3..2f6996ac27b 100644
--- a/plugins/woocommerce-admin/client/store/orders/reducer.js
+++ b/plugins/woocommerce-admin/client/store/orders/reducer.js
@@ -1,8 +1,4 @@
/** @format */
-/**
- * External dependencies
- */
-import { union } from 'lodash';
const DEFAULT_STATE = {
orders: {},
@@ -13,7 +9,6 @@ export default function ordersReducer( state = DEFAULT_STATE, action ) {
switch ( action.type ) {
case 'SET_ORDERS':
const { orders } = action;
- const ids = orders.map( order => order.id );
const ordersMap = orders.reduce( ( map, order ) => {
map[ order.id ] = order;
return map;
@@ -21,7 +16,6 @@ export default function ordersReducer( state = DEFAULT_STATE, action ) {
return {
...state,
orders: Object.assign( {}, state.orders, ordersMap ),
- ids: union( state.ids, ids ),
};
case 'UPDATE_ORDER':
const updatedOrders = { ...state.orders };
diff --git a/plugins/woocommerce-admin/client/store/orders/selectors.js b/plugins/woocommerce-admin/client/store/orders/selectors.js
index db392cf6846..7dd92c42949 100644
--- a/plugins/woocommerce-admin/client/store/orders/selectors.js
+++ b/plugins/woocommerce-admin/client/store/orders/selectors.js
@@ -4,7 +4,4 @@ export default {
getOrders( state ) {
return state.orders.orders;
},
- getOrderIds( state ) {
- return state.orders.ids;
- },
};