Merge pull request woocommerce/woocommerce-admin#1146 from woocommerce/add/date-filter-calendar

Advanced Filters: Add DatePicker
This commit is contained in:
Paul Sealock 2019-01-15 09:43:10 +13:00 committed by GitHub
commit 1fa45e4364
5 changed files with 265 additions and 2 deletions

View File

@ -251,6 +251,36 @@ export const advancedFilters = {
component: 'Currency',
},
},
registered: {
labels: {
add: __( 'Registered', 'wc-admin' ),
remove: __( 'Remove registered filter', 'wc-admin' ),
rule: __( 'Select a registered filter match', 'wc-admin' ),
/* translators: A sentence describing a Product filter. See screen shot for context: https://cloudup.com/cCsm3GeXJbE */
title: __( 'Registered {{rule /}} {{filter /}}', 'wc-admin' ),
filter: __( 'Select registered date', 'wc-admin' ),
},
rules: [
{
value: 'before',
/* translators: Sentence fragment, logical, "Before" refers to customers registered before a given date. Screenshot for context: https://cloudup.com/cCsm3GeXJbE */
label: _x( 'Before', 'date', 'wc-admin' ),
},
{
value: 'after',
/* translators: Sentence fragment, logical, "after" refers to customers registered after a given date. Screenshot for context: https://cloudup.com/cCsm3GeXJbE */
label: _x( 'After', 'date', 'wc-admin' ),
},
{
value: 'between',
/* translators: Sentence fragment, logical, "Between" refers to average order value of a customer, between two given amounts. Screenshot for context: https://cloudup.com/cCsm3GeXJbE */
label: _x( 'Between', 'date', 'wc-admin' ),
},
],
input: {
component: 'Date',
},
},
},
};
/*eslint-enable max-len*/

View File

@ -0,0 +1,223 @@
/** @format */
/**
* External dependencies
*/
import { Component, Fragment } from '@wordpress/element';
import interpolateComponents from 'interpolate-components';
import { SelectControl } from '@wordpress/components';
import { find, partial } from 'lodash';
import classnames from 'classnames';
import { __, _x } from '@wordpress/i18n';
/**
* WooCommerce dependencies
*/
import { isoDateFormat, toMoment } from '@woocommerce/date';
/**
* Internal dependencies
*/
import DatePicker from '../../calendar/date-picker';
import { textContent } from './utils';
const dateStringFormat = __( 'MMM D, YYYY', 'wc-admin' );
const dateFormat = __( 'MM/DD/YYYY', 'wc-admin' );
class DateFilter extends Component {
constructor( { filter } ) {
super( ...arguments );
const [ isoAfter, isoBefore ] = ( filter.value || '' ).split( ',' );
const after = isoAfter ? toMoment( isoDateFormat, isoAfter ) : null;
const before = isoBefore ? toMoment( isoDateFormat, isoBefore ) : null;
this.state = {
before,
beforeText: before ? before.format( dateFormat ) : '',
beforeError: null,
after,
afterText: after ? after.format( dateFormat ) : '',
afterError: null,
};
this.onSingleDateChange = this.onSingleDateChange.bind( this );
this.onRangeDateChange = this.onRangeDateChange.bind( this );
}
getBetweenString() {
return _x(
'{{after /}}{{span}} and {{/span}}{{before /}}',
'Date range inputs arranged on a single line',
'wc-admin'
);
}
getScreenReaderText( filter, config ) {
const rule = find( config.rules, { value: filter.rule } ) || {};
const { before, after } = this.state;
// Return nothing if we're missing input(s)
if ( ! before || 'between' === rule.value && ! after ) {
return '';
}
let filterStr = before.format( dateStringFormat );
if ( 'between' === rule.value ) {
filterStr = interpolateComponents( {
mixedString: this.getBetweenString(),
components: {
after: <Fragment>{ after.format( dateStringFormat ) }</Fragment>,
before: <Fragment>{ before.format( dateStringFormat ) }</Fragment>,
span: <Fragment />,
},
} );
}
return textContent( interpolateComponents( {
mixedString: config.labels.title,
components: {
filter: <Fragment>{ filterStr }</Fragment>,
rule: <Fragment>{ rule.label }</Fragment>,
},
} ) );
}
onSingleDateChange( { date, text, error } ) {
const { filter, onFilterChange } = this.props;
this.setState( { before: date, beforeText: text, beforeError: error } );
if ( date ) {
onFilterChange( filter.key, 'value', date.format( isoDateFormat ) );
}
}
onRangeDateChange( input, { date, text, error } ) {
const { filter, onFilterChange } = this.props;
this.setState( {
[ input ]: date,
[ input + 'Text' ]: text,
[ input + 'Error' ]: error,
} );
if ( date ) {
const { before, after } = this.state;
let nextAfter = null;
let nextBefore = null;
if ( 'after' === input ) {
nextAfter = date.format( isoDateFormat );
nextBefore = before ? before.format( isoDateFormat ) : null;
}
if ( 'before' === input ) {
nextAfter = after ? after.format( isoDateFormat ) : null;
nextBefore = date.format( isoDateFormat );
}
if ( nextAfter && nextBefore ) {
onFilterChange( filter.key, 'value', [ nextAfter, nextBefore ].join( ',' ) );
}
}
}
getFilterInputs() {
const { filter } = this.props;
const { before, beforeText, beforeError, after, afterText, afterError } = this.state;
if ( 'between' === filter.rule ) {
return interpolateComponents( {
mixedString: this.getBetweenString(),
components: {
after: (
<DatePicker
date={ after }
text={ afterText }
error={ afterError }
onUpdate={ partial( this.onRangeDateChange, 'after' ) }
dateFormat={ dateFormat }
invalidDays="none"
/>
),
before: (
<DatePicker
date={ before }
text={ beforeText }
error={ beforeError }
onUpdate={ partial( this.onRangeDateChange, 'before' ) }
dateFormat={ dateFormat }
invalidDays="none"
/>
),
span: <span className="separator" />,
},
} );
}
return (
<DatePicker
date={ before }
text={ beforeText }
error={ beforeError }
onUpdate={ this.onSingleDateChange }
dateFormat={ dateFormat }
invalidDays="none"
/>
);
}
render() {
const { config, filter, onFilterChange, isEnglish } = this.props;
const { key, rule } = filter;
const { labels, rules } = config;
const screenReaderText = this.getScreenReaderText( filter, config );
const children = interpolateComponents( {
mixedString: labels.title,
components: {
rule: (
<SelectControl
className="woocommerce-filters-advanced__rule"
options={ rules }
value={ rule }
onChange={ partial( onFilterChange, key, 'rule' ) }
aria-label={ labels.rule }
/>
),
filter: (
<div
className={ classnames( 'woocommerce-filters-advanced__input-range', {
'is-between': 'between' === rule,
} ) }
>
{ this.getFilterInputs() }
</div>
),
},
} );
/*eslint-disable jsx-a11y/no-noninteractive-tabindex*/
return (
<fieldset tabIndex="0">
<legend className="screen-reader-text">
{ labels.add || '' }
</legend>
<div
className={ classnames( 'woocommerce-filters-advanced__fieldset', {
'is-english': isEnglish,
} ) }
>
{ children }
</div>
{ screenReaderText && (
<span className="screen-reader-text">
{ screenReaderText }
</span>
) }
</fieldset>
);
/*eslint-enable jsx-a11y/no-noninteractive-tabindex*/
}
}
export default DateFilter;

View File

@ -28,6 +28,7 @@ import Link from '../../link';
import SelectFilter from './select-filter';
import SearchFilter from './search-filter';
import NumberFilter from './number-filter';
import DateFilter from './date-filter';
const matches = [
{ value: 'all', label: __( 'All', 'wc-admin' ) },
@ -210,6 +211,15 @@ class AdvancedFilters extends Component {
query={ query }
/>
) }
{ 'Date' === input.component && (
<DateFilter
filter={ filter }
config={ config.filters[ key ] }
onFilterChange={ this.onFilterChange }
isEnglish={ isEnglish }
query={ query }
/>
) }
<IconButton
className="woocommerce-filters-advanced__remove"
label={ labels.remove }

View File

@ -203,7 +203,7 @@ class NumberFilter extends Component {
),
filter: (
<div
className={ classnames( 'woocommerce-filters-advanced__input-numeric-range', {
className={ classnames( 'woocommerce-filters-advanced__input-range', {
'is-between': 'between' === rule,
} ) }
>

View File

@ -171,7 +171,7 @@
}
}
.woocommerce-filters-advanced__input-numeric-range {
.woocommerce-filters-advanced__input-range {
align-items: center;
display: grid;
grid-template-columns: 1fr;