Remove updated stock products from Activity Panel (https://github.com/woocommerce/woocommerce-admin/pull/2442)

* Remove updated stock products from Activity Panel

* Use prefers-reduced-motion preference

* Update comparison to check if stock quantity is 'lower or equal to' lowStockAmount

* Focus quantityInput on 'beginEdit' instead of 'componentDidUpdate'

* Add comment explaining why we hide cards

* Refactor updateProductStock action

* Add type and parent_id properties to update
This commit is contained in:
Albert Juhé Lluveras 2019-06-27 11:21:43 +02:00 committed by GitHub
parent 2f4b8272e6
commit 808143d7c9
7 changed files with 146 additions and 33 deletions

View File

@ -310,6 +310,14 @@
}
.woocommerce-stock-activity-card {
@media screen and (prefers-reduced-motion: no-preference) {
transition: opacity 0.3s;
}
&.is-dimmed {
opacity: 0.7;
}
.woocommerce-stock-activity-card__stock-quantity {
background: $core-grey-light-300;
color: $core-grey-dark-500;

View File

@ -27,27 +27,36 @@ class ProductStockCard extends Component {
this.state = {
quantity: props.product.stock_quantity,
editing: false,
edited: false,
};
this.beginEdit = this.beginEdit.bind( this );
this.cancelEdit = this.cancelEdit.bind( this );
this.onQuantityChange = this.onQuantityChange.bind( this );
this.handleKeyDown = this.handleKeyDown.bind( this );
this.updateStock = this.updateStock.bind( this );
}
componentDidUpdate() {
this.quantityInput && this.quantityInput.focus();
this.onSubmit = this.onSubmit.bind( this );
}
beginEdit() {
this.setState( { editing: true } );
const { product } = this.props;
this.setState(
{
editing: true,
quantity: product.stock_quantity,
},
() => {
this.quantityInput && this.quantityInput.focus();
}
);
}
cancelEdit() {
const { product } = this.props;
this.setState( {
editing: false,
quantity: this.props.product.stock_quantity,
quantity: product.stock_quantity,
} );
}
@ -61,18 +70,13 @@ class ProductStockCard extends Component {
this.setState( { quantity: event.target.value } );
}
updateStock() {
const { product, updateItem } = this.props;
onSubmit() {
const { product, updateProductStock } = this.props;
const { quantity } = this.state;
this.setState( { editing: false }, () => {
const data = {
stock_quantity: this.state.quantity,
type: product.type,
parent_id: product.parent_id,
};
this.setState( { editing: false, edited: true } );
updateItem( 'products', product.id, data );
} );
updateProductStock( product, quantity );
}
getActions() {
@ -95,6 +99,7 @@ class ProductStockCard extends Component {
}
getBody() {
const { product } = this.props;
const { editing, quantity } = this.state;
if ( editing ) {
@ -119,14 +124,27 @@ class ProductStockCard extends Component {
return (
<span className="woocommerce-stock-activity-card__stock-quantity">
{ sprintf( __( '%d in stock', 'woocommerce-admin' ), quantity ) }
{ sprintf( __( '%d in stock', 'woocommerce-admin' ), product.stock_quantity ) }
</span>
);
}
render() {
const { product } = this.props;
const { editing } = this.state;
const { edited, editing } = this.state;
const { notifyLowStockAmount } = wcSettings;
const lowStockAmount = Number.isFinite( product.low_stock_amount )
? product.low_stock_amount
: notifyLowStockAmount;
const isLowStock = product.stock_quantity <= lowStockAmount;
// Hide cards that are not in low stock and have not been edited.
// This allows clearing cards which are no longer in low stock after
// closing & opening the panel without having to make another request.
if ( ! isLowStock && ! edited ) {
return null;
}
const title = (
<Link
href={ 'post.php?action=edit&post=' + ( product.parent_id || product.id ) }
@ -157,10 +175,13 @@ class ProductStockCard extends Component {
</div>
</div>
);
const activityCardClasses = classnames( 'woocommerce-stock-activity-card', {
'is-dimmed': ! editing && ! isLowStock,
} );
const activityCard = (
<ActivityCard
className="woocommerce-stock-activity-card"
className={ activityCardClasses }
title={ title }
subtitle={ subtitle }
icon={ icon }
@ -172,7 +193,7 @@ class ProductStockCard extends Component {
if ( editing ) {
return (
<form onReset={ this.cancelEdit } onSubmit={ this.updateStock }>
<form onReset={ this.cancelEdit } onSubmit={ this.onSubmit }>
{ activityCard }
</form>
);
@ -184,9 +205,10 @@ class ProductStockCard extends Component {
export default compose(
withDispatch( dispatch => {
const { updateItem } = dispatch( 'wc-api' );
const { updateProductStock } = dispatch( 'wc-api' );
return {
updateItem,
updateProductStock,
};
} )
)( ProductStockCard );

View File

@ -1,15 +1,52 @@
/** @format */
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { dispatch } from '@wordpress/data';
/**
* Internal dependencies
*/
import { getResourceName } from '../utils';
const updateItem = operations => ( type, id, itemData ) => {
const resourceName = getResourceName( `items-query-${ type }-item`, id );
operations.update( [ resourceName ], { [ resourceName ]: { id, ...itemData } } );
const updateProductStock = operations => async ( product, newStock ) => {
const { addNotice } = dispatch( 'wc-admin' );
const oldStockQuantity = product.stock_quantity;
const resourceName = getResourceName( 'items-query-products-item', product.id );
// Optimistically update product stock
operations.updateLocally( [ resourceName ], {
[ resourceName ]: { ...product, stock_quantity: newStock },
} );
const result = await operations.update( [ resourceName ], {
[ resourceName ]: {
id: product.id,
type: product.type,
parent_id: product.parent_id,
stock_quantity: newStock,
},
} );
const response = result[ 0 ][ resourceName ];
if ( response && response.data ) {
addNotice( {
status: 'success',
message: sprintf( __( '%s stock updated.', 'woocommerce-admin' ), product.name ),
} );
}
if ( response && response.error ) {
addNotice( {
status: 'error',
message: sprintf( __( '%s stock could not be updated.', 'woocommerce-admin' ), product.name ),
} );
// Revert local changes if the operation failed in the server
operations.updateLocally( [ resourceName ], {
[ resourceName ]: { ...product, stock_quantity: oldStockQuantity },
} );
}
};
export default {
updateItem,
updateProductStock,
};

View File

@ -115,7 +115,19 @@ function update( resourceNames, data, fetch = apiFetch ) {
} );
}
function updateLocally( resourceNames, data ) {
const updateableTypes = [ 'items-query-products-item' ];
const filteredNames = resourceNames.filter( name => {
return updateableTypes.includes( getResourcePrefix( name ) );
} );
return filteredNames.map( async resourceName => {
return { [ resourceName ]: { data: data[ resourceName ] } };
} );
}
export default {
read,
update,
updateLocally,
};

View File

@ -62,6 +62,9 @@ function createWcApiSpec() {
...user.operations.update( resourceNames, data ),
];
},
updateLocally( resourceNames, data ) {
return [ ...items.operations.updateLocally( resourceNames, data ) ];
},
},
};
}

View File

@ -99,16 +99,46 @@ class WC_Admin_REST_Products_Controller extends WC_REST_Products_Controller {
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
add_filter( 'posts_fields', array( __CLASS__, 'add_wp_query_fields' ), 10, 2 );
add_filter( 'posts_where', array( __CLASS__, 'add_wp_query_filter' ), 10, 2 );
add_filter( 'posts_join', array( __CLASS__, 'add_wp_query_join' ), 10, 2 );
add_filter( 'posts_groupby', array( __CLASS__, 'add_wp_query_group_by' ), 10, 2 );
$response = parent::get_items( $request );
remove_filter( 'posts_fields', array( __CLASS__, 'add_wp_query_fields' ), 10 );
remove_filter( 'posts_where', array( __CLASS__, 'add_wp_query_filter' ), 10 );
remove_filter( 'posts_join', array( __CLASS__, 'add_wp_query_join' ), 10 );
remove_filter( 'posts_groupby', array( __CLASS__, 'add_wp_query_group_by' ), 10 );
return $response;
}
/**
* Add `low_stock_amount` property to product data
*
* @param WC_Data $object Object data.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response
*/
public function prepare_object_for_response( $object, $request ) {
$data = parent::prepare_object_for_response( $object, $request );
if ( $request->get_param( 'low_in_stock' ) && is_numeric( $object->low_stock_amount ) ) {
$data->data['low_stock_amount'] = $object->low_stock_amount;
}
return $data;
}
/**
* Add in conditional select fields to the query.
*
* @param string $select Select clause used to select fields from the query.
* @param object $wp_query WP_Query object.
* @return string
*/
public static function add_wp_query_fields( $select, $wp_query ) {
if ( $wp_query->get( 'low_in_stock' ) ) {
return $select . ', low_stock_amount_meta.meta_value AS low_stock_amount';
}
}
/**
* Add in conditional search filters for products.
*

View File

@ -519,12 +519,13 @@ class WC_Admin_Loader {
$current_user_data[ $user_field ] = json_decode( get_user_meta( get_current_user_id(), 'wc_admin_' . $user_field, true ) );
}
$settings['orderStatuses'] = self::get_order_statuses( wc_get_order_statuses() );
$settings['currentUserData'] = $current_user_data;
$settings['currency'] = self::get_currency_settings();
$settings['reviewsEnabled'] = get_option( 'woocommerce_enable_reviews' );
$settings['manageStock'] = get_option( 'woocommerce_manage_stock' );
$settings['commentModeration'] = get_option( 'comment_moderation' );
$settings['orderStatuses'] = self::get_order_statuses( wc_get_order_statuses() );
$settings['currentUserData'] = $current_user_data;
$settings['currency'] = self::get_currency_settings();
$settings['reviewsEnabled'] = get_option( 'woocommerce_enable_reviews' );
$settings['manageStock'] = get_option( 'woocommerce_manage_stock' );
$settings['commentModeration'] = get_option( 'comment_moderation' );
$settings['notifyLowStockAmount'] = get_option( 'woocommerce_notify_low_stock_amount' );
// @todo On merge, once plugin images are added to core WooCommerce, `wcAdminAssetUrl` can be retired,
// and `wcAssetUrl` can be used in its place throughout the codebase.
$settings['wcAdminAssetUrl'] = plugins_url( 'images/', plugin_dir_path( dirname( __FILE__ ) ) . 'woocommerce-admin.php' );