Merge pull request woocommerce/woocommerce-admin#1146 from woocommerce/add/date-filter-calendar
Advanced Filters: Add DatePicker
This commit is contained in:
commit
1fa45e4364
|
@ -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*/
|
||||
|
|
|
@ -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;
|
|
@ -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 }
|
||||
|
|
|
@ -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,
|
||||
} ) }
|
||||
>
|
||||
|
|
|
@ -171,7 +171,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.woocommerce-filters-advanced__input-numeric-range {
|
||||
.woocommerce-filters-advanced__input-range {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
|
|
Loading…
Reference in New Issue