Add variation inventory quick actions (#39935)
* Add new inventory submenu to variations actions * Move pricing submenu up to its own function * Add tests * Move inventory markup to seperate component * Remove menu group label * Add changelog * Move pricing menu to own component * Fix lint errors * Update use of handlePrompt to make return more flexible
This commit is contained in:
parent
1356f76f7b
commit
db9cb4db4b
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: add
|
||||||
|
|
||||||
|
Add new action menu item to variations list for managing inventory.
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './inventory-menu-item';
|
|
@ -0,0 +1,213 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { ProductVariation } from '@woocommerce/data';
|
||||||
|
import { recordEvent } from '@woocommerce/tracks';
|
||||||
|
import { Dropdown, MenuGroup, MenuItem } from '@wordpress/components';
|
||||||
|
import { createElement } from '@wordpress/element';
|
||||||
|
import { chevronRight } from '@wordpress/icons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { TRACKS_SOURCE } from '../../../constants';
|
||||||
|
import { PRODUCT_STOCK_STATUS_KEYS } from '../../../utils/get-product-stock-status';
|
||||||
|
|
||||||
|
export type InventoryMenuItemProps = {
|
||||||
|
variation: ProductVariation;
|
||||||
|
handlePrompt(
|
||||||
|
label?: string,
|
||||||
|
parser?: ( value: string ) => Partial< ProductVariation > | null
|
||||||
|
): void;
|
||||||
|
onChange( values: Partial< ProductVariation > ): void;
|
||||||
|
onClose(): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function InventoryMenuItem( {
|
||||||
|
variation,
|
||||||
|
handlePrompt,
|
||||||
|
onChange,
|
||||||
|
onClose,
|
||||||
|
}: InventoryMenuItemProps ) {
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
position="middle right"
|
||||||
|
renderToggle={ ( { isOpen, onToggle } ) => (
|
||||||
|
<MenuItem
|
||||||
|
onClick={ () => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_inventory_click',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
onToggle();
|
||||||
|
} }
|
||||||
|
aria-expanded={ isOpen }
|
||||||
|
icon={ chevronRight }
|
||||||
|
iconPosition="right"
|
||||||
|
>
|
||||||
|
{ __( 'Inventory', 'woocommerce' ) }
|
||||||
|
</MenuItem>
|
||||||
|
) }
|
||||||
|
renderContent={ () => (
|
||||||
|
<div className="components-dropdown-menu__menu">
|
||||||
|
<MenuGroup>
|
||||||
|
<MenuItem
|
||||||
|
onClick={ () => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_inventory_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'stock_quantity_set',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
handlePrompt( undefined, ( value ) => {
|
||||||
|
const stockQuantity = Number( value );
|
||||||
|
if ( Number.isNaN( stockQuantity ) ) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_inventory_update',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'stock_quantity_set',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
stock_quantity: stockQuantity,
|
||||||
|
manage_stock: true,
|
||||||
|
};
|
||||||
|
} );
|
||||||
|
onClose();
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
{ __( 'Update stock', 'woocommerce' ) }
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={ () => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_inventory_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'manage_stock_toggle',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
onChange( {
|
||||||
|
manage_stock: ! variation.manage_stock,
|
||||||
|
} );
|
||||||
|
onClose();
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
{ __( 'Toggle "track quantity"', 'woocommerce' ) }
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={ () => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_inventory_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'set_status_in_stock',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
onChange( {
|
||||||
|
stock_status:
|
||||||
|
PRODUCT_STOCK_STATUS_KEYS.instock,
|
||||||
|
manage_stock: false,
|
||||||
|
} );
|
||||||
|
onClose();
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
{ __( 'Set status to In stock', 'woocommerce' ) }
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={ () => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_inventory_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'set_status_out_of_stock',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
onChange( {
|
||||||
|
stock_status:
|
||||||
|
PRODUCT_STOCK_STATUS_KEYS.outofstock,
|
||||||
|
manage_stock: false,
|
||||||
|
} );
|
||||||
|
onClose();
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
{ __(
|
||||||
|
'Set status to Out of stock',
|
||||||
|
'woocommerce'
|
||||||
|
) }
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={ () => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_inventory_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'set_status_on_back_order',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
onChange( {
|
||||||
|
stock_status:
|
||||||
|
PRODUCT_STOCK_STATUS_KEYS.onbackorder,
|
||||||
|
manage_stock: false,
|
||||||
|
} );
|
||||||
|
onClose();
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
{ __(
|
||||||
|
'Set status to On back order',
|
||||||
|
'woocommerce'
|
||||||
|
) }
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={ () => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_inventory_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'low_stock_amount_set',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
handlePrompt( undefined, ( value ) => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_inventory_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'low_stock_amount_set',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const lowStockAmount = Number( value );
|
||||||
|
if ( Number.isNaN( lowStockAmount ) ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
low_stock_amount: lowStockAmount,
|
||||||
|
manage_stock: true,
|
||||||
|
};
|
||||||
|
} );
|
||||||
|
onClose();
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
{ __( 'Edit low stock threshold', 'woocommerce' ) }
|
||||||
|
</MenuItem>
|
||||||
|
</MenuGroup>
|
||||||
|
</div>
|
||||||
|
) }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './pricing-menu-item';
|
|
@ -0,0 +1,357 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { ProductVariation } from '@woocommerce/data';
|
||||||
|
import { recordEvent } from '@woocommerce/tracks';
|
||||||
|
import { Dropdown, MenuGroup, MenuItem } from '@wordpress/components';
|
||||||
|
import { createElement } from '@wordpress/element';
|
||||||
|
import { chevronRight } from '@wordpress/icons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { TRACKS_SOURCE } from '../../../constants';
|
||||||
|
|
||||||
|
function isPercentage( value: string ) {
|
||||||
|
return value.endsWith( '%' );
|
||||||
|
}
|
||||||
|
|
||||||
|
function parsePercentage( value: string ) {
|
||||||
|
const stringNumber = value.substring( 0, value.length - 1 );
|
||||||
|
if ( Number.isNaN( Number( stringNumber ) ) ) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return Number( stringNumber );
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFixedOrPercentage(
|
||||||
|
value: string,
|
||||||
|
fixedOrPercentage: string,
|
||||||
|
increaseOrDecrease: 1 | -1 = 1
|
||||||
|
) {
|
||||||
|
if ( isPercentage( fixedOrPercentage ) ) {
|
||||||
|
if ( Number.isNaN( Number( value ) ) ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const percentage = parsePercentage( fixedOrPercentage );
|
||||||
|
if ( percentage === undefined ) {
|
||||||
|
return Number( value );
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
Number( value ) +
|
||||||
|
Number( value ) * ( percentage / 100 ) * increaseOrDecrease
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ( Number.isNaN( Number( value ) ) ) {
|
||||||
|
if ( Number.isNaN( Number( fixedOrPercentage ) ) ) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return Number( fixedOrPercentage );
|
||||||
|
}
|
||||||
|
return Number( value ) + Number( fixedOrPercentage ) * increaseOrDecrease;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PricingMenuItemProps = {
|
||||||
|
variation: ProductVariation;
|
||||||
|
handlePrompt(
|
||||||
|
label?: string,
|
||||||
|
parser?: ( value: string ) => Partial< ProductVariation >
|
||||||
|
): void;
|
||||||
|
onClose(): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PricingMenuItem( {
|
||||||
|
variation,
|
||||||
|
handlePrompt,
|
||||||
|
onClose,
|
||||||
|
}: PricingMenuItemProps ) {
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
position="middle right"
|
||||||
|
renderToggle={ ( { isOpen, onToggle } ) => (
|
||||||
|
<MenuItem
|
||||||
|
onClick={ () => {
|
||||||
|
recordEvent( 'product_variations_menu_pricing_click', {
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
variation_id: variation.id,
|
||||||
|
} );
|
||||||
|
onToggle();
|
||||||
|
} }
|
||||||
|
aria-expanded={ isOpen }
|
||||||
|
icon={ chevronRight }
|
||||||
|
iconPosition="right"
|
||||||
|
>
|
||||||
|
{ __( 'Pricing', 'woocommerce' ) }
|
||||||
|
</MenuItem>
|
||||||
|
) }
|
||||||
|
renderContent={ () => (
|
||||||
|
<div className="components-dropdown-menu__menu">
|
||||||
|
<MenuGroup label={ __( 'List price', 'woocommerce' ) }>
|
||||||
|
<MenuItem
|
||||||
|
onClick={ () => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_pricing_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'list_price_set',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
handlePrompt( undefined, ( value ) => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_pricing_update',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'list_price_set',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
regular_price: value,
|
||||||
|
};
|
||||||
|
} );
|
||||||
|
onClose();
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
{ __( 'Set list price', 'woocommerce' ) }
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={ () => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_pricing_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'list_price_increase',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
handlePrompt(
|
||||||
|
__(
|
||||||
|
'Enter a value (fixed or %)',
|
||||||
|
'woocommerce'
|
||||||
|
),
|
||||||
|
( value ) => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_pricing_update',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'list_price_increase',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
regular_price: addFixedOrPercentage(
|
||||||
|
variation.regular_price,
|
||||||
|
value
|
||||||
|
)?.toFixed( 2 ),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
onClose();
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
{ __( 'Increase list price', 'woocommerce' ) }
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={ () => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_pricing_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'list_price_decrease',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
handlePrompt(
|
||||||
|
__(
|
||||||
|
'Enter a value (fixed or %)',
|
||||||
|
'woocommerce'
|
||||||
|
),
|
||||||
|
( value ) => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_pricing_update',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'list_price_increase',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
regular_price: addFixedOrPercentage(
|
||||||
|
variation.regular_price,
|
||||||
|
value,
|
||||||
|
-1
|
||||||
|
)?.toFixed( 2 ),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
onClose();
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
{ __( 'Decrease list price', 'woocommerce' ) }
|
||||||
|
</MenuItem>
|
||||||
|
</MenuGroup>
|
||||||
|
<MenuGroup label={ __( 'Sale price', 'woocommerce' ) }>
|
||||||
|
<MenuItem
|
||||||
|
onClick={ () => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_pricing_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'sale_price_set',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
handlePrompt( undefined, ( value ) => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_pricing_update',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'sale_price_set',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
sale_price: value,
|
||||||
|
};
|
||||||
|
} );
|
||||||
|
onClose();
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
{ __( 'Set sale price', 'woocommerce' ) }
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={ () => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_pricing_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'sale_price_increase',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
handlePrompt(
|
||||||
|
__(
|
||||||
|
'Enter a value (fixed or %)',
|
||||||
|
'woocommerce'
|
||||||
|
),
|
||||||
|
( value ) => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_pricing_update',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'sale_price_increase',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
sale_price: addFixedOrPercentage(
|
||||||
|
variation.sale_price,
|
||||||
|
value
|
||||||
|
)?.toFixed( 2 ),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
onClose();
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
{ __( 'Increase sale price', 'woocommerce' ) }
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={ () => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_pricing_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'sale_price_decrease',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
handlePrompt(
|
||||||
|
__(
|
||||||
|
'Enter a value (fixed or %)',
|
||||||
|
'woocommerce'
|
||||||
|
),
|
||||||
|
( value ) => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_pricing_update',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'sale_price_decrease',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
sale_price: addFixedOrPercentage(
|
||||||
|
variation.sale_price,
|
||||||
|
value,
|
||||||
|
-1
|
||||||
|
)?.toFixed( 2 ),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
onClose();
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
{ __( 'Decrease sale price', 'woocommerce' ) }
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={ () => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_pricing_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'sale_price_schedule',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
handlePrompt(
|
||||||
|
__(
|
||||||
|
'Sale start date (YYYY-MM-DD format or leave blank)',
|
||||||
|
'woocommerce'
|
||||||
|
),
|
||||||
|
( value ) => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_pricing_update',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'sale_price_schedule',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
date_on_sale_from_gmt: value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
handlePrompt(
|
||||||
|
__(
|
||||||
|
'Sale end date (YYYY-MM-DD format or leave blank)',
|
||||||
|
'woocommerce'
|
||||||
|
),
|
||||||
|
( value ) => {
|
||||||
|
recordEvent(
|
||||||
|
'product_variations_menu_pricing_update',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'sale_price_schedule',
|
||||||
|
variation_id: variation.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
date_on_sale_to_gmt: value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
onClose();
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
{ __( 'Schedule sale', 'woocommerce' ) }
|
||||||
|
</MenuItem>
|
||||||
|
</MenuGroup>
|
||||||
|
</div>
|
||||||
|
) }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -49,24 +49,22 @@ export function ShippingMenuItem( {
|
||||||
variation_id: variation.id,
|
variation_id: variation.id,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
handlePrompt(
|
handlePrompt( undefined, ( value ) => {
|
||||||
'dimensions',
|
recordEvent(
|
||||||
undefined,
|
'product_variations_menu_shipping_update',
|
||||||
( value ) => {
|
{
|
||||||
recordEvent(
|
source: TRACKS_SOURCE,
|
||||||
'product_variations_menu_shipping_update',
|
action: 'dimensions_length_set',
|
||||||
{
|
variation_id: variation.id,
|
||||||
source: TRACKS_SOURCE,
|
}
|
||||||
action: 'dimensions_length_set',
|
);
|
||||||
variation_id: variation.id,
|
return {
|
||||||
}
|
dimensions: {
|
||||||
);
|
|
||||||
return {
|
|
||||||
...variation.dimensions,
|
...variation.dimensions,
|
||||||
length: value,
|
length: value,
|
||||||
};
|
},
|
||||||
}
|
};
|
||||||
);
|
} );
|
||||||
onClose();
|
onClose();
|
||||||
} }
|
} }
|
||||||
>
|
>
|
||||||
|
@ -82,24 +80,22 @@ export function ShippingMenuItem( {
|
||||||
variation_id: variation.id,
|
variation_id: variation.id,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
handlePrompt(
|
handlePrompt( undefined, ( value ) => {
|
||||||
'dimensions',
|
recordEvent(
|
||||||
undefined,
|
'product_variations_menu_shipping_update',
|
||||||
( value ) => {
|
{
|
||||||
recordEvent(
|
source: TRACKS_SOURCE,
|
||||||
'product_variations_menu_shipping_update',
|
action: 'dimensions_width_set',
|
||||||
{
|
variation_id: variation.id,
|
||||||
source: TRACKS_SOURCE,
|
}
|
||||||
action: 'dimensions_width_set',
|
);
|
||||||
variation_id: variation.id,
|
return {
|
||||||
}
|
dimensions: {
|
||||||
);
|
|
||||||
return {
|
|
||||||
...variation.dimensions,
|
...variation.dimensions,
|
||||||
width: value,
|
width: value,
|
||||||
};
|
},
|
||||||
}
|
};
|
||||||
);
|
} );
|
||||||
onClose();
|
onClose();
|
||||||
} }
|
} }
|
||||||
>
|
>
|
||||||
|
@ -115,24 +111,22 @@ export function ShippingMenuItem( {
|
||||||
variation_id: variation.id,
|
variation_id: variation.id,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
handlePrompt(
|
handlePrompt( undefined, ( value ) => {
|
||||||
'dimensions',
|
recordEvent(
|
||||||
undefined,
|
'product_variations_menu_shipping_update',
|
||||||
( value ) => {
|
{
|
||||||
recordEvent(
|
source: TRACKS_SOURCE,
|
||||||
'product_variations_menu_shipping_update',
|
action: 'dimensions_height_set',
|
||||||
{
|
variation_id: variation.id,
|
||||||
source: TRACKS_SOURCE,
|
}
|
||||||
action: 'dimensions_height_set',
|
);
|
||||||
variation_id: variation.id,
|
return {
|
||||||
}
|
dimensions: {
|
||||||
);
|
|
||||||
return {
|
|
||||||
...variation.dimensions,
|
...variation.dimensions,
|
||||||
height: value,
|
height: value,
|
||||||
};
|
},
|
||||||
}
|
};
|
||||||
);
|
} );
|
||||||
onClose();
|
onClose();
|
||||||
} }
|
} }
|
||||||
>
|
>
|
||||||
|
@ -148,7 +142,7 @@ export function ShippingMenuItem( {
|
||||||
variation_id: variation.id,
|
variation_id: variation.id,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
handlePrompt( 'weight', undefined, ( value ) => {
|
handlePrompt( undefined, ( value ) => {
|
||||||
recordEvent(
|
recordEvent(
|
||||||
'product_variations_menu_shipping_update',
|
'product_variations_menu_shipping_update',
|
||||||
{
|
{
|
||||||
|
@ -157,7 +151,7 @@ export function ShippingMenuItem( {
|
||||||
variation_id: variation.id,
|
variation_id: variation.id,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return value;
|
return { weight: value };
|
||||||
} );
|
} );
|
||||||
onClose();
|
onClose();
|
||||||
} }
|
} }
|
||||||
|
|
|
@ -6,9 +6,8 @@ import { ProductVariation } from '@woocommerce/data';
|
||||||
export type ShippingMenuItemProps = {
|
export type ShippingMenuItemProps = {
|
||||||
variation: ProductVariation;
|
variation: ProductVariation;
|
||||||
handlePrompt(
|
handlePrompt(
|
||||||
propertyName: keyof ProductVariation,
|
|
||||||
label?: string,
|
label?: string,
|
||||||
parser?: ( value: string ) => unknown
|
parser?: ( value: string ) => Partial< ProductVariation > | null
|
||||||
): void;
|
): void;
|
||||||
onClose(): void;
|
onClose(): void;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,319 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { render, fireEvent } from '@testing-library/react';
|
||||||
|
import { ProductVariation } from '@woocommerce/data';
|
||||||
|
import { recordEvent } from '@woocommerce/tracks';
|
||||||
|
import React, { createElement } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { VariationActionsMenu } from '../';
|
||||||
|
import { TRACKS_SOURCE } from '../../../../constants';
|
||||||
|
import { PRODUCT_STOCK_STATUS_KEYS } from '../../../../utils/get-product-stock-status';
|
||||||
|
|
||||||
|
jest.mock( '@woocommerce/tracks', () => ( {
|
||||||
|
recordEvent: jest.fn(),
|
||||||
|
} ) );
|
||||||
|
const mockVariation = {
|
||||||
|
id: 10,
|
||||||
|
manage_stock: false,
|
||||||
|
attributes: [],
|
||||||
|
} as ProductVariation;
|
||||||
|
|
||||||
|
describe( 'VariationActionsMenu', () => {
|
||||||
|
let onChangeMock: jest.Mock, onDeleteMock: jest.Mock;
|
||||||
|
beforeEach( () => {
|
||||||
|
onChangeMock = jest.fn();
|
||||||
|
onDeleteMock = jest.fn();
|
||||||
|
( recordEvent as jest.Mock ).mockClear();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should trigger product_variations_menu_view track when dropdown toggled', () => {
|
||||||
|
const { getByRole } = render(
|
||||||
|
<VariationActionsMenu
|
||||||
|
variation={ mockVariation }
|
||||||
|
onChange={ onChangeMock }
|
||||||
|
onDelete={ onDeleteMock }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
fireEvent.click( getByRole( 'button', { name: 'Actions' } ) );
|
||||||
|
expect( recordEvent ).toHaveBeenCalledWith(
|
||||||
|
'product_variations_menu_view',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
variation_id: 10,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should render dropdown with pricing, inventory, and delete options when opened', () => {
|
||||||
|
const { queryByText, getByRole } = render(
|
||||||
|
<VariationActionsMenu
|
||||||
|
variation={ mockVariation }
|
||||||
|
onChange={ onChangeMock }
|
||||||
|
onDelete={ onDeleteMock }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
fireEvent.click( getByRole( 'button', { name: 'Actions' } ) );
|
||||||
|
expect( queryByText( 'Pricing' ) ).toBeInTheDocument();
|
||||||
|
expect( queryByText( 'Inventory' ) ).toBeInTheDocument();
|
||||||
|
expect( queryByText( 'Delete' ) ).toBeInTheDocument();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should call onDelete when Delete menuItem is clicked', async () => {
|
||||||
|
const { getByRole, getByText } = render(
|
||||||
|
<VariationActionsMenu
|
||||||
|
variation={ mockVariation }
|
||||||
|
onChange={ onChangeMock }
|
||||||
|
onDelete={ onDeleteMock }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await fireEvent.click( getByRole( 'button', { name: 'Actions' } ) );
|
||||||
|
await fireEvent.click( getByText( 'Delete' ) );
|
||||||
|
expect( onDeleteMock ).toHaveBeenCalled();
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( 'Inventory sub-menu', () => {
|
||||||
|
it( 'should open Inventory sub-menu if Inventory is clicked with click track', async () => {
|
||||||
|
const { queryByText, getByRole, getByText } = render(
|
||||||
|
<VariationActionsMenu
|
||||||
|
variation={ { ...mockVariation } }
|
||||||
|
onChange={ onChangeMock }
|
||||||
|
onDelete={ onDeleteMock }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await fireEvent.click( getByRole( 'button', { name: 'Actions' } ) );
|
||||||
|
await fireEvent.click( getByText( 'Inventory' ) );
|
||||||
|
expect( recordEvent ).toHaveBeenCalledWith(
|
||||||
|
'product_variations_menu_inventory_click',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
variation_id: 10,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect( queryByText( 'Update stock' ) ).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
queryByText( 'Toggle "track quantity"' )
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
queryByText( 'Set status to In stock' )
|
||||||
|
).toBeInTheDocument();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should onChange with stock_quantity when Update stock is clicked', async () => {
|
||||||
|
window.prompt = jest.fn().mockReturnValue( '10' );
|
||||||
|
const { getByRole, getByText } = render(
|
||||||
|
<VariationActionsMenu
|
||||||
|
variation={ { ...mockVariation } }
|
||||||
|
onChange={ onChangeMock }
|
||||||
|
onDelete={ onDeleteMock }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await fireEvent.click( getByRole( 'button', { name: 'Actions' } ) );
|
||||||
|
await fireEvent.click( getByText( 'Inventory' ) );
|
||||||
|
await fireEvent.click( getByText( 'Update stock' ) );
|
||||||
|
|
||||||
|
expect( recordEvent ).toHaveBeenCalledWith(
|
||||||
|
'product_variations_menu_inventory_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'stock_quantity_set',
|
||||||
|
variation_id: 10,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect( onChangeMock ).toHaveBeenCalledWith( {
|
||||||
|
stock_quantity: 10,
|
||||||
|
manage_stock: true,
|
||||||
|
} );
|
||||||
|
expect( recordEvent ).toHaveBeenCalledWith(
|
||||||
|
'product_variations_menu_inventory_update',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'stock_quantity_set',
|
||||||
|
variation_id: 10,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should not call onChange when prompt is cancelled', async () => {
|
||||||
|
window.prompt = jest.fn().mockReturnValue( null );
|
||||||
|
const { getByRole, getByText } = render(
|
||||||
|
<VariationActionsMenu
|
||||||
|
variation={ { ...mockVariation } }
|
||||||
|
onChange={ onChangeMock }
|
||||||
|
onDelete={ onDeleteMock }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await fireEvent.click( getByRole( 'button', { name: 'Actions' } ) );
|
||||||
|
await fireEvent.click( getByText( 'Inventory' ) );
|
||||||
|
await fireEvent.click( getByText( 'Update stock' ) );
|
||||||
|
|
||||||
|
expect( recordEvent ).toHaveBeenCalledWith(
|
||||||
|
'product_variations_menu_inventory_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'stock_quantity_set',
|
||||||
|
variation_id: 10,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect( onChangeMock ).not.toHaveBeenCalledWith( {
|
||||||
|
stock_quantity: 10,
|
||||||
|
manage_stock: true,
|
||||||
|
} );
|
||||||
|
expect( recordEvent ).not.toHaveBeenCalledWith(
|
||||||
|
'product_variations_menu_inventory_update',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'stock_quantity_set',
|
||||||
|
variation_id: 10,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should call onChange with toggled manage_stock when toggle "track quantity" is clicked', async () => {
|
||||||
|
const { getByRole, getByText, rerender } = render(
|
||||||
|
<VariationActionsMenu
|
||||||
|
variation={ { ...mockVariation } }
|
||||||
|
onChange={ onChangeMock }
|
||||||
|
onDelete={ onDeleteMock }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await fireEvent.click( getByRole( 'button', { name: 'Actions' } ) );
|
||||||
|
await fireEvent.click( getByText( 'Inventory' ) );
|
||||||
|
await fireEvent.click( getByText( 'Toggle "track quantity"' ) );
|
||||||
|
|
||||||
|
expect( recordEvent ).toHaveBeenCalledWith(
|
||||||
|
'product_variations_menu_inventory_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'manage_stock_toggle',
|
||||||
|
variation_id: 10,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect( onChangeMock ).toHaveBeenCalledWith( {
|
||||||
|
manage_stock: true,
|
||||||
|
} );
|
||||||
|
onChangeMock.mockClear();
|
||||||
|
rerender(
|
||||||
|
<VariationActionsMenu
|
||||||
|
variation={ { ...mockVariation, manage_stock: true } }
|
||||||
|
onChange={ onChangeMock }
|
||||||
|
onDelete={ onDeleteMock }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await fireEvent.click( getByRole( 'button', { name: 'Actions' } ) );
|
||||||
|
await fireEvent.click( getByText( 'Inventory' ) );
|
||||||
|
await fireEvent.click( getByText( 'Toggle "track quantity"' ) );
|
||||||
|
expect( onChangeMock ).toHaveBeenCalledWith( {
|
||||||
|
manage_stock: false,
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should call onChange with toggled stock_status when toggle "Set status to In stock" is clicked', async () => {
|
||||||
|
const { getByRole, getByText } = render(
|
||||||
|
<VariationActionsMenu
|
||||||
|
variation={ { ...mockVariation } }
|
||||||
|
onChange={ onChangeMock }
|
||||||
|
onDelete={ onDeleteMock }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await fireEvent.click( getByRole( 'button', { name: 'Actions' } ) );
|
||||||
|
await fireEvent.click( getByText( 'Inventory' ) );
|
||||||
|
await fireEvent.click( getByText( 'Set status to In stock' ) );
|
||||||
|
|
||||||
|
expect( recordEvent ).toHaveBeenCalledWith(
|
||||||
|
'product_variations_menu_inventory_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'set_status_in_stock',
|
||||||
|
variation_id: 10,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect( onChangeMock ).toHaveBeenCalledWith( {
|
||||||
|
stock_status: PRODUCT_STOCK_STATUS_KEYS.instock,
|
||||||
|
manage_stock: false,
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should call onChange with toggled stock_status when toggle "Set status to Out of stock" is clicked', async () => {
|
||||||
|
const { getByRole, getByText } = render(
|
||||||
|
<VariationActionsMenu
|
||||||
|
variation={ { ...mockVariation } }
|
||||||
|
onChange={ onChangeMock }
|
||||||
|
onDelete={ onDeleteMock }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await fireEvent.click( getByRole( 'button', { name: 'Actions' } ) );
|
||||||
|
await fireEvent.click( getByText( 'Inventory' ) );
|
||||||
|
await fireEvent.click( getByText( 'Set status to Out of stock' ) );
|
||||||
|
|
||||||
|
expect( recordEvent ).toHaveBeenCalledWith(
|
||||||
|
'product_variations_menu_inventory_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'set_status_out_of_stock',
|
||||||
|
variation_id: 10,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect( onChangeMock ).toHaveBeenCalledWith( {
|
||||||
|
stock_status: PRODUCT_STOCK_STATUS_KEYS.outofstock,
|
||||||
|
manage_stock: false,
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should call onChange with toggled stock_status when toggle "Set status to On back order" is clicked', async () => {
|
||||||
|
const { getByRole, getByText } = render(
|
||||||
|
<VariationActionsMenu
|
||||||
|
variation={ { ...mockVariation } }
|
||||||
|
onChange={ onChangeMock }
|
||||||
|
onDelete={ onDeleteMock }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await fireEvent.click( getByRole( 'button', { name: 'Actions' } ) );
|
||||||
|
await fireEvent.click( getByText( 'Inventory' ) );
|
||||||
|
await fireEvent.click( getByText( 'Set status to On back order' ) );
|
||||||
|
|
||||||
|
expect( recordEvent ).toHaveBeenCalledWith(
|
||||||
|
'product_variations_menu_inventory_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'set_status_on_back_order',
|
||||||
|
variation_id: 10,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect( onChangeMock ).toHaveBeenCalledWith( {
|
||||||
|
stock_status: PRODUCT_STOCK_STATUS_KEYS.onbackorder,
|
||||||
|
manage_stock: false,
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should call onChange with low_stock_amount when Edit low stock threshold is clicked', async () => {
|
||||||
|
window.prompt = jest.fn().mockReturnValue( '7' );
|
||||||
|
const { getByRole, getByText } = render(
|
||||||
|
<VariationActionsMenu
|
||||||
|
variation={ { ...mockVariation } }
|
||||||
|
onChange={ onChangeMock }
|
||||||
|
onDelete={ onDeleteMock }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await fireEvent.click( getByRole( 'button', { name: 'Actions' } ) );
|
||||||
|
await fireEvent.click( getByText( 'Inventory' ) );
|
||||||
|
await fireEvent.click( getByText( 'Edit low stock threshold' ) );
|
||||||
|
|
||||||
|
expect( recordEvent ).toHaveBeenCalledWith(
|
||||||
|
'product_variations_menu_inventory_select',
|
||||||
|
{
|
||||||
|
source: TRACKS_SOURCE,
|
||||||
|
action: 'low_stock_amount_set',
|
||||||
|
variation_id: 10,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect( onChangeMock ).toHaveBeenCalledWith( {
|
||||||
|
low_stock_amount: 7,
|
||||||
|
manage_stock: true,
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -1,15 +1,10 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import {
|
import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components';
|
||||||
Dropdown,
|
|
||||||
DropdownMenu,
|
|
||||||
MenuGroup,
|
|
||||||
MenuItem,
|
|
||||||
} from '@wordpress/components';
|
|
||||||
import { createElement, Fragment } from '@wordpress/element';
|
import { createElement, Fragment } from '@wordpress/element';
|
||||||
import { __, sprintf } from '@wordpress/i18n';
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
import { chevronRight, moreVertical } from '@wordpress/icons';
|
import { moreVertical } from '@wordpress/icons';
|
||||||
import { ProductVariation } from '@woocommerce/data';
|
import { ProductVariation } from '@woocommerce/data';
|
||||||
import { recordEvent } from '@woocommerce/tracks';
|
import { recordEvent } from '@woocommerce/tracks';
|
||||||
|
|
||||||
|
@ -19,45 +14,8 @@ import { recordEvent } from '@woocommerce/tracks';
|
||||||
import { VariationActionsMenuProps } from './types';
|
import { VariationActionsMenuProps } from './types';
|
||||||
import { TRACKS_SOURCE } from '../../../constants';
|
import { TRACKS_SOURCE } from '../../../constants';
|
||||||
import { ShippingMenuItem } from '../shipping-menu-item';
|
import { ShippingMenuItem } from '../shipping-menu-item';
|
||||||
|
import { InventoryMenuItem } from '../inventory-menu-item';
|
||||||
function isPercentage( value: string ) {
|
import { PricingMenuItem } from '../pricing-menu-item';
|
||||||
return value.endsWith( '%' );
|
|
||||||
}
|
|
||||||
|
|
||||||
function parsePercentage( value: string ) {
|
|
||||||
const stringNumber = value.substring( 0, value.length - 1 );
|
|
||||||
if ( Number.isNaN( Number( stringNumber ) ) ) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return Number( stringNumber );
|
|
||||||
}
|
|
||||||
|
|
||||||
function addFixedOrPercentage(
|
|
||||||
value: string,
|
|
||||||
fixedOrPercentage: string,
|
|
||||||
increaseOrDecrease: 1 | -1 = 1
|
|
||||||
) {
|
|
||||||
if ( isPercentage( fixedOrPercentage ) ) {
|
|
||||||
if ( Number.isNaN( Number( value ) ) ) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const percentage = parsePercentage( fixedOrPercentage );
|
|
||||||
if ( percentage === undefined ) {
|
|
||||||
return Number( value );
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
Number( value ) +
|
|
||||||
Number( value ) * ( percentage / 100 ) * increaseOrDecrease
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if ( Number.isNaN( Number( value ) ) ) {
|
|
||||||
if ( Number.isNaN( Number( fixedOrPercentage ) ) ) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return Number( fixedOrPercentage );
|
|
||||||
}
|
|
||||||
return Number( value ) + Number( fixedOrPercentage ) * increaseOrDecrease;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function VariationActionsMenu( {
|
export function VariationActionsMenu( {
|
||||||
variation,
|
variation,
|
||||||
|
@ -65,17 +23,18 @@ export function VariationActionsMenu( {
|
||||||
onDelete,
|
onDelete,
|
||||||
}: VariationActionsMenuProps ) {
|
}: VariationActionsMenuProps ) {
|
||||||
function handlePrompt(
|
function handlePrompt(
|
||||||
propertyName: keyof ProductVariation,
|
|
||||||
label: string = __( 'Enter a value', 'woocommerce' ),
|
label: string = __( 'Enter a value', 'woocommerce' ),
|
||||||
parser: ( value: string ) => unknown = ( value ) => value
|
parser: ( value: string ) => Partial< ProductVariation > | null = () =>
|
||||||
|
null
|
||||||
) {
|
) {
|
||||||
// eslint-disable-next-line no-alert
|
// eslint-disable-next-line no-alert
|
||||||
const value = window.prompt( label );
|
const value = window.prompt( label );
|
||||||
if ( value === null ) return;
|
if ( value === null ) return;
|
||||||
|
|
||||||
onChange( {
|
const updates = parser( value.trim() );
|
||||||
[ propertyName ]: parser( value.trim() ),
|
if ( updates ) {
|
||||||
} );
|
onChange( updates );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -113,340 +72,17 @@ export function VariationActionsMenu( {
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuGroup>
|
</MenuGroup>
|
||||||
<MenuGroup>
|
<MenuGroup>
|
||||||
<Dropdown
|
<PricingMenuItem
|
||||||
position="middle right"
|
variation={ variation }
|
||||||
renderToggle={ ( { isOpen, onToggle } ) => (
|
handlePrompt={ handlePrompt }
|
||||||
<MenuItem
|
onClose={ onClose }
|
||||||
onClick={ () => {
|
/>
|
||||||
recordEvent(
|
<InventoryMenuItem
|
||||||
'product_variations_menu_pricing_click',
|
variation={ variation }
|
||||||
{
|
handlePrompt={ handlePrompt }
|
||||||
source: TRACKS_SOURCE,
|
onChange={ onChange }
|
||||||
variation_id: variation.id,
|
onClose={ onClose }
|
||||||
}
|
|
||||||
);
|
|
||||||
onToggle();
|
|
||||||
} }
|
|
||||||
aria-expanded={ isOpen }
|
|
||||||
icon={ chevronRight }
|
|
||||||
iconPosition="right"
|
|
||||||
>
|
|
||||||
{ __( 'Pricing', 'woocommerce' ) }
|
|
||||||
</MenuItem>
|
|
||||||
) }
|
|
||||||
renderContent={ () => (
|
|
||||||
<div className="components-dropdown-menu__menu">
|
|
||||||
<MenuGroup
|
|
||||||
label={ __(
|
|
||||||
'List price',
|
|
||||||
'woocommerce'
|
|
||||||
) }
|
|
||||||
>
|
|
||||||
<MenuItem
|
|
||||||
onClick={ () => {
|
|
||||||
recordEvent(
|
|
||||||
'product_variations_menu_pricing_select',
|
|
||||||
{
|
|
||||||
source: TRACKS_SOURCE,
|
|
||||||
action: 'list_price_set',
|
|
||||||
variation_id:
|
|
||||||
variation.id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
handlePrompt(
|
|
||||||
'regular_price',
|
|
||||||
undefined,
|
|
||||||
( value ) => {
|
|
||||||
recordEvent(
|
|
||||||
'product_variations_menu_pricing_update',
|
|
||||||
{
|
|
||||||
source: TRACKS_SOURCE,
|
|
||||||
action: 'list_price_set',
|
|
||||||
variation_id:
|
|
||||||
variation.id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
onClose();
|
|
||||||
} }
|
|
||||||
>
|
|
||||||
{ __(
|
|
||||||
'Set list price',
|
|
||||||
'woocommerce'
|
|
||||||
) }
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
onClick={ () => {
|
|
||||||
recordEvent(
|
|
||||||
'product_variations_menu_pricing_select',
|
|
||||||
{
|
|
||||||
source: TRACKS_SOURCE,
|
|
||||||
action: 'list_price_increase',
|
|
||||||
variation_id:
|
|
||||||
variation.id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
handlePrompt(
|
|
||||||
'regular_price',
|
|
||||||
__(
|
|
||||||
'Enter a value (fixed or %)',
|
|
||||||
'woocommerce'
|
|
||||||
),
|
|
||||||
( value ) => {
|
|
||||||
recordEvent(
|
|
||||||
'product_variations_menu_pricing_update',
|
|
||||||
{
|
|
||||||
source: TRACKS_SOURCE,
|
|
||||||
action: 'list_price_increase',
|
|
||||||
variation_id:
|
|
||||||
variation.id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return addFixedOrPercentage(
|
|
||||||
variation.regular_price,
|
|
||||||
value
|
|
||||||
)?.toFixed( 2 );
|
|
||||||
}
|
|
||||||
);
|
|
||||||
onClose();
|
|
||||||
} }
|
|
||||||
>
|
|
||||||
{ __(
|
|
||||||
'Increase list price',
|
|
||||||
'woocommerce'
|
|
||||||
) }
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
onClick={ () => {
|
|
||||||
recordEvent(
|
|
||||||
'product_variations_menu_pricing_select',
|
|
||||||
{
|
|
||||||
source: TRACKS_SOURCE,
|
|
||||||
action: 'list_price_decrease',
|
|
||||||
variation_id:
|
|
||||||
variation.id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
handlePrompt(
|
|
||||||
'regular_price',
|
|
||||||
__(
|
|
||||||
'Enter a value (fixed or %)',
|
|
||||||
'woocommerce'
|
|
||||||
),
|
|
||||||
( value ) => {
|
|
||||||
recordEvent(
|
|
||||||
'product_variations_menu_pricing_update',
|
|
||||||
{
|
|
||||||
source: TRACKS_SOURCE,
|
|
||||||
action: 'list_price_increase',
|
|
||||||
variation_id:
|
|
||||||
variation.id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return addFixedOrPercentage(
|
|
||||||
variation.regular_price,
|
|
||||||
value,
|
|
||||||
-1
|
|
||||||
)?.toFixed( 2 );
|
|
||||||
}
|
|
||||||
);
|
|
||||||
onClose();
|
|
||||||
} }
|
|
||||||
>
|
|
||||||
{ __(
|
|
||||||
'Decrease list price',
|
|
||||||
'woocommerce'
|
|
||||||
) }
|
|
||||||
</MenuItem>
|
|
||||||
</MenuGroup>
|
|
||||||
<MenuGroup
|
|
||||||
label={ __(
|
|
||||||
'Sale price',
|
|
||||||
'woocommerce'
|
|
||||||
) }
|
|
||||||
>
|
|
||||||
<MenuItem
|
|
||||||
onClick={ () => {
|
|
||||||
recordEvent(
|
|
||||||
'product_variations_menu_pricing_select',
|
|
||||||
{
|
|
||||||
source: TRACKS_SOURCE,
|
|
||||||
action: 'sale_price_set',
|
|
||||||
variation_id:
|
|
||||||
variation.id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
handlePrompt(
|
|
||||||
'sale_price',
|
|
||||||
undefined,
|
|
||||||
( value ) => {
|
|
||||||
recordEvent(
|
|
||||||
'product_variations_menu_pricing_update',
|
|
||||||
{
|
|
||||||
source: TRACKS_SOURCE,
|
|
||||||
action: 'sale_price_set',
|
|
||||||
variation_id:
|
|
||||||
variation.id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
onClose();
|
|
||||||
} }
|
|
||||||
>
|
|
||||||
{ __(
|
|
||||||
'Set sale price',
|
|
||||||
'woocommerce'
|
|
||||||
) }
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
onClick={ () => {
|
|
||||||
recordEvent(
|
|
||||||
'product_variations_menu_pricing_select',
|
|
||||||
{
|
|
||||||
source: TRACKS_SOURCE,
|
|
||||||
action: 'sale_price_increase',
|
|
||||||
variation_id:
|
|
||||||
variation.id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
handlePrompt(
|
|
||||||
'sale_price',
|
|
||||||
__(
|
|
||||||
'Enter a value (fixed or %)',
|
|
||||||
'woocommerce'
|
|
||||||
),
|
|
||||||
( value ) => {
|
|
||||||
recordEvent(
|
|
||||||
'product_variations_menu_pricing_update',
|
|
||||||
{
|
|
||||||
source: TRACKS_SOURCE,
|
|
||||||
action: 'sale_price_increase',
|
|
||||||
variation_id:
|
|
||||||
variation.id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return addFixedOrPercentage(
|
|
||||||
variation.sale_price,
|
|
||||||
value
|
|
||||||
)?.toFixed( 2 );
|
|
||||||
}
|
|
||||||
);
|
|
||||||
onClose();
|
|
||||||
} }
|
|
||||||
>
|
|
||||||
{ __(
|
|
||||||
'Increase sale price',
|
|
||||||
'woocommerce'
|
|
||||||
) }
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
onClick={ () => {
|
|
||||||
recordEvent(
|
|
||||||
'product_variations_menu_pricing_select',
|
|
||||||
{
|
|
||||||
source: TRACKS_SOURCE,
|
|
||||||
action: 'sale_price_decrease',
|
|
||||||
variation_id:
|
|
||||||
variation.id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
handlePrompt(
|
|
||||||
'sale_price',
|
|
||||||
__(
|
|
||||||
'Enter a value (fixed or %)',
|
|
||||||
'woocommerce'
|
|
||||||
),
|
|
||||||
( value ) => {
|
|
||||||
recordEvent(
|
|
||||||
'product_variations_menu_pricing_update',
|
|
||||||
{
|
|
||||||
source: TRACKS_SOURCE,
|
|
||||||
action: 'sale_price_decrease',
|
|
||||||
variation_id:
|
|
||||||
variation.id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return addFixedOrPercentage(
|
|
||||||
variation.sale_price,
|
|
||||||
value,
|
|
||||||
-1
|
|
||||||
)?.toFixed( 2 );
|
|
||||||
}
|
|
||||||
);
|
|
||||||
onClose();
|
|
||||||
} }
|
|
||||||
>
|
|
||||||
{ __(
|
|
||||||
'Decrease sale price',
|
|
||||||
'woocommerce'
|
|
||||||
) }
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
onClick={ () => {
|
|
||||||
recordEvent(
|
|
||||||
'product_variations_menu_pricing_select',
|
|
||||||
{
|
|
||||||
source: TRACKS_SOURCE,
|
|
||||||
action: 'sale_price_schedule',
|
|
||||||
variation_id:
|
|
||||||
variation.id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
handlePrompt(
|
|
||||||
'date_on_sale_from_gmt',
|
|
||||||
__(
|
|
||||||
'Sale start date (YYYY-MM-DD format or leave blank)',
|
|
||||||
'woocommerce'
|
|
||||||
),
|
|
||||||
( value ) => {
|
|
||||||
recordEvent(
|
|
||||||
'product_variations_menu_pricing_update',
|
|
||||||
{
|
|
||||||
source: TRACKS_SOURCE,
|
|
||||||
action: 'sale_price_schedule',
|
|
||||||
variation_id:
|
|
||||||
variation.id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
handlePrompt(
|
|
||||||
'date_on_sale_to_gmt',
|
|
||||||
__(
|
|
||||||
'Sale end date (YYYY-MM-DD format or leave blank)',
|
|
||||||
'woocommerce'
|
|
||||||
),
|
|
||||||
( value ) => {
|
|
||||||
recordEvent(
|
|
||||||
'product_variations_menu_pricing_update',
|
|
||||||
{
|
|
||||||
source: TRACKS_SOURCE,
|
|
||||||
action: 'sale_price_schedule',
|
|
||||||
variation_id:
|
|
||||||
variation.id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
onClose();
|
|
||||||
} }
|
|
||||||
>
|
|
||||||
{ __(
|
|
||||||
'Schedule sale',
|
|
||||||
'woocommerce'
|
|
||||||
) }
|
|
||||||
</MenuItem>
|
|
||||||
</MenuGroup>
|
|
||||||
</div>
|
|
||||||
) }
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ShippingMenuItem
|
<ShippingMenuItem
|
||||||
variation={ variation }
|
variation={ variation }
|
||||||
handlePrompt={ handlePrompt }
|
handlePrompt={ handlePrompt }
|
||||||
|
@ -456,6 +92,7 @@ export function VariationActionsMenu( {
|
||||||
<MenuGroup>
|
<MenuGroup>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
isDestructive
|
isDestructive
|
||||||
|
label={ __( 'Delete variation', 'woocommerce' ) }
|
||||||
variant="link"
|
variant="link"
|
||||||
onClick={ () => {
|
onClick={ () => {
|
||||||
onDelete( variation.id );
|
onDelete( variation.id );
|
||||||
|
|
Loading…
Reference in New Issue