Skip to content
This repository has been archived by the owner on Jun 7, 2022. It is now read-only.

Commit

Permalink
Add Customers Report page (#1018)
Browse files Browse the repository at this point in the history
* Create Customers Report page

* Hide date range filter if dateRangeFilter.show is false

* Rename dateRangeFilter.show to showDatePicker in filters config

* Add correct key 'average_order_value'

* Fix customers autocompleter not working
  • Loading branch information
Aljullu committed Dec 10, 2018
2 parents 4402944 + 5dd3065 commit 30755cc
Show file tree
Hide file tree
Showing 10 changed files with 331 additions and 4 deletions.
17 changes: 17 additions & 0 deletions client/analytics/report/customers/config.js
@@ -0,0 +1,17 @@
/** @format */
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';

export const showDatePicker = false;

export const filters = [
{
label: __( 'Show', 'wc-admin' ),
staticParams: [],
param: 'filter',
showFilters: () => true,
filters: [ { label: __( 'All Registered Customers', 'wc-admin' ), value: 'all' } ],
},
];
39 changes: 39 additions & 0 deletions client/analytics/report/customers/index.js
@@ -0,0 +1,39 @@
/** @format */
/**
* External dependencies
*/
import { Component, Fragment } from '@wordpress/element';
import PropTypes from 'prop-types';

/**
* WooCommerce dependencies
*/
import { ReportFilters } from '@woocommerce/components';

/**
* Internal dependencies
*/
import { filters, showDatePicker } from './config';
import CustomersReportTable from './table';

export default class CustomersReport extends Component {
render() {
const { query, path } = this.props;

return (
<Fragment>
<ReportFilters
query={ query }
path={ path }
filters={ filters }
showDatePicker={ showDatePicker }
/>
<CustomersReportTable query={ query } />
</Fragment>
);
}
}

CustomersReport.propTypes = {
query: PropTypes.object.isRequired,
};
185 changes: 185 additions & 0 deletions client/analytics/report/customers/table.js
@@ -0,0 +1,185 @@
/** @format */
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { format as formatDate } from '@wordpress/date';

/**
* WooCommerce dependencies
*/
import { getDateFormatsForInterval, getIntervalForQuery } from '@woocommerce/date';
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
import { Link } from '@woocommerce/components';

/**
* Internal dependencies
*/
import ReportTable from 'analytics/components/report-table';
import { numberFormat } from 'lib/number';

export default class CustomersReportTable extends Component {
constructor() {
super();

this.getHeadersContent = this.getHeadersContent.bind( this );
this.getRowsContent = this.getRowsContent.bind( this );
}

getHeadersContent() {
return [
{
label: __( 'Name', 'wc-admin' ),
key: 'name',
required: true,
isLeftAligned: true,
isSortable: true,
},
{
label: __( 'Username', 'wc-admin' ),
key: 'username',
hiddenByDefault: true,
},
{
label: __( 'Sign Up', 'wc-admin' ),
key: 'date_sign_up',
defaultSort: true,
isSortable: true,
},
{
label: __( 'Email', 'wc-admin' ),
key: 'email',
},
{
label: __( 'Orders', 'wc-admin' ),
key: 'orders_count',
isSortable: true,
isNumeric: true,
},
{
label: __( 'Lifetime Spend', 'wc-admin' ),
key: 'total_spend',
isSortable: true,
isNumeric: true,
},
{
label: __( 'AOV', 'wc-admin' ),
screenReaderLabel: __( 'Average Order Value', 'wc-admin' ),
key: 'average_order_value',
isNumeric: true,
},
{
label: __( 'Last Active', 'wc-admin' ),
key: 'date_last_active',
isSortable: true,
},
{
label: __( 'Country', 'wc-admin' ),
key: 'country',
},
{
label: __( 'City', 'wc-admin' ),
key: 'city',
hiddenByDefault: true,
},
{
label: __( 'Postal Code', 'wc-admin' ),
key: 'postal_code',
hiddenByDefault: true,
},
];
}

getRowsContent( customers ) {
const { query } = this.props;
const currentInterval = getIntervalForQuery( query );
const formats = getDateFormatsForInterval( currentInterval );

return customers.map( customer => {
const {
average_order_value,
id,
city,
country,
date_last_active,
date_sign_up,
email,
name,
orders_count,
postal_code,
username,
total_spend,
} = customer;

const customerNameLink = (
<Link href={ 'user-edit.php?user_id=' + id } type="wp-admin">
{ name }
</Link>
);

return [
{
display: customerNameLink,
value: name,
},
{
display: username,
value: username,
},
{
display: formatDate( formats.tableFormat, date_sign_up ),
value: date_sign_up,
},
{
display: <a href={ 'mailto:' + email }>{ email }</a>,
value: email,
},
{
display: numberFormat( orders_count ),
value: orders_count,
},
{
display: formatCurrency( total_spend ),
value: getCurrencyFormatDecimal( total_spend ),
},
{
display: average_order_value,
value: getCurrencyFormatDecimal( average_order_value ),
},
{
display: formatDate( formats.tableFormat, date_last_active ),
value: date_last_active,
},
{
display: country,
value: country,
},
{
display: city,
value: city,
},
{
display: postal_code,
value: postal_code,
},
];
} );
}

render() {
const { query } = this.props;

return (
<ReportTable
compareBy="customers"
endpoint="customers"
getHeadersContent={ this.getHeadersContent }
getRowsContent={ this.getRowsContent }
itemIdField="id"
query={ query }
title={ __( 'Registered Customers', 'wc-admin' ) }
/>
);
}
}
6 changes: 6 additions & 0 deletions client/analytics/report/index.js
Expand Up @@ -24,6 +24,7 @@ import RevenueReport from './revenue';
import CategoriesReport from './categories';
import CouponsReport from './coupons';
import TaxesReport from './taxes';
import CustomersReport from './customers';

const REPORTS_FILTER = 'woocommerce-reports-list';

Expand Down Expand Up @@ -59,6 +60,11 @@ const getReports = () => {
title: __( 'Taxes', 'wc-admin' ),
component: TaxesReport,
},
{
report: 'customers',
title: __( 'Customers', 'wc-admin' ),
component: CustomersReport,
},
] );

return reports;
Expand Down
2 changes: 1 addition & 1 deletion client/store/reports/items/resolvers.js
Expand Up @@ -20,7 +20,7 @@ export default {
async getReportItems( ...args ) {
const [ endpoint, query ] = args.slice( -2 );

const swaggerEndpoints = [ 'categories', 'coupons', 'taxes' ];
const swaggerEndpoints = [ 'categories', 'coupons', 'customers', 'taxes' ];
if ( swaggerEndpoints.indexOf( endpoint ) >= 0 ) {
try {
const response = await fetch(
Expand Down
8 changes: 8 additions & 0 deletions lib/admin.php
Expand Up @@ -133,6 +133,14 @@ function wc_admin_register_pages() {
)
);

wc_admin_register_page(
array(
'title' => __( 'Customers', 'wc-admin' ),
'parent' => '/analytics/revenue',
'path' => '/analytics/customers',
)
);

if ( defined( 'WP_DEBUG' ) && WP_DEBUG && defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
wc_admin_register_page(
array(
Expand Down
11 changes: 9 additions & 2 deletions packages/components/src/filters/index.js
Expand Up @@ -57,13 +57,15 @@ class ReportFilters extends Component {
}

render() {
const { filters, query, path } = this.props;
const { filters, query, path, showDatePicker } = this.props;
return (
<Fragment>
<H className="screen-reader-text">{ __( 'Filters', 'wc-admin' ) }</H>
<Section component="div" className="woocommerce-filters">
<div className="woocommerce-filters__basic-filters">
<DatePicker key={ JSON.stringify( query ) } query={ query } path={ path } />
{ showDatePicker && (
<DatePicker key={ JSON.stringify( query ) } query={ query } path={ path } />
) }
{ filters.map( config => {
if ( config.showFilters( query ) ) {
return (
Expand Down Expand Up @@ -101,12 +103,17 @@ ReportFilters.propTypes = {
* The query string represented in object form
*/
query: PropTypes.object,
/**
* Whether the date picker must be shown..
*/
showDatePicker: PropTypes.bool,
};

ReportFilters.defaultProps = {
advancedFilters: {},
filters: [],
query: {},
showDatePicker: true,
};

export default ReportFilters;
62 changes: 62 additions & 0 deletions packages/components/src/search/autocompleters/customers.js
@@ -0,0 +1,62 @@
/** @format */
/**
* External dependencies
*/
import apiFetch from '@wordpress/api-fetch';

/**
* WooCommerce dependencies
*/
import { stringifyQuery } from '@woocommerce/navigation';

/**
* Internal dependencies
*/
import { computeSuggestionMatch } from './utils';

/**
* A customer completer.
* See https://github.com/WordPress/gutenberg/tree/master/packages/components/src/autocomplete#the-completer-interface
*
* @type {Completer}
*/
export default {
name: 'customers',
className: 'woocommerce-search__customers-result',
options( search ) {
let payload = '';
if ( search ) {
const query = {
search,
per_page: 10,
};
payload = stringifyQuery( query );
}
return apiFetch( { path: `/wc/v3/customers${ payload }` } );
},
isDebounced: true,
getOptionKeywords( customer ) {
return [ customer.name ];
},
getOptionLabel( customer, query ) {
const match = computeSuggestionMatch( customer.name, query ) || {};
return [
<span key="name" className="woocommerce-search__result-name" aria-label={ customer.name }>
{ match.suggestionBeforeMatch }
<strong className="components-form-token-field__suggestion-match">
{ match.suggestionMatch }
</strong>
{ match.suggestionAfterMatch }
</span>,
];
},
// This is slightly different than gutenberg/Autocomplete, we don't support different methods
// of replace/insertion, so we can just return the value.
getOptionCompletion( customer ) {
const value = {
id: customer.id,
label: customer.name,
};
return value;
},
};
1 change: 1 addition & 0 deletions packages/components/src/search/autocompleters/index.js
Expand Up @@ -3,6 +3,7 @@
* Export all autocompleters
*/
export { default as coupons } from './coupons';
export { default as customers } from './customers';
export { default as product } from './product';
export { default as productCategory } from './product-cat';
export { default as variations } from './variations';
Expand Down

0 comments on commit 30755cc

Please sign in to comment.