Add product variations list to new product management experience (#35889)

* Add product variations section

* Add variations list

* Add util to get product stock status

* Add variation specific attribute type

* Add currency code to header column

* Fix up variations header width

* Add variations loading state

* Add changelog entries

* Convert spaces to tabs

* Fix status typo

* Fix up return type for stock status
This commit is contained in:
Joshua T Flowers 2022-12-13 15:29:05 -08:00 committed by GitHub
parent 51c33fa351
commit e1aabf2d9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 251 additions and 2 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Update attributes type for product variations data store

View File

@ -9,7 +9,18 @@ import { DispatchFromMap } from '@automattic/data-stores';
import { CrudActions, CrudSelectors } from '../crud/types';
import { Product, ProductQuery, ReadOnlyProperties } from '../products/types';
export type ProductVariation = Omit< Product, 'name' | 'slug' >;
export type ProductVariationAttribute = {
id: number;
name: string;
option: string;
};
export type ProductVariation = Omit<
Product,
'name' | 'slug' | 'attributes'
> & {
attributes: ProductVariationAttribute[];
};
type Query = Omit< ProductQuery, 'name' >;

View File

@ -0,0 +1 @@
export * from './variations';

View File

@ -0,0 +1,39 @@
.woocommerce-product-variations {
min-height: 300px;
&__header {
display: grid;
grid-template-columns: calc(38px + 25%) 25% 25%;
padding: $gap-small $gap;
h4 {
color: $gray-700;
font-size: 11px;
text-transform: uppercase;
margin: 0;
font-weight: 500;
}
}
.woocommerce-list-item {
display: grid;
grid-template-columns: 38px 25% 25% 25%;
margin-left: -1px;
margin-right: -1px;
margin-bottom: -1px;
}
.woocommerce-sortable {
margin: 0;
}
.components-spinner {
width: 34px;
height: 34px;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
position: absolute;
margin: 0;
}
}

View File

@ -0,0 +1,91 @@
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { Card, Spinner } from '@wordpress/components';
import {
EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME,
ProductVariation,
} from '@woocommerce/data';
import { ListItem, Sortable, Tag } from '@woocommerce/components';
import { useContext } from '@wordpress/element';
import { useParams } from 'react-router-dom';
import { useSelect } from '@wordpress/data';
/**
* Internal dependencies
*/
import { CurrencyContext } from '../../../lib/currency-context';
import { getProductStockStatus } from '../../utils/get-product-stock-status';
import './variations.scss';
export const Variations: React.FC = () => {
const { productId } = useParams();
const context = useContext( CurrencyContext );
const { formatAmount, getCurrencyConfig } = context;
const { isLoading, variations } = useSelect( ( select ) => {
const { getProductVariations, hasFinishedResolution } = select(
EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME
);
return {
isLoading: ! hasFinishedResolution( 'getProductVariations', [
{
product_id: productId,
},
] ),
variations: getProductVariations< ProductVariation[] >( {
product_id: productId,
} ),
};
} );
if ( ! variations || isLoading ) {
return (
<Card className="woocommerce-product-variations is-loading">
<Spinner />
</Card>
);
}
const currencyConfig = getCurrencyConfig();
return (
<Card className="woocommerce-product-variations">
<div className="woocommerce-product-variations__header">
<h4>{ __( 'Variation', 'woocommerce' ) }</h4>
<h4>
{ sprintf(
/** Translators: The 3 letter currency code for the store. */
__( 'Price (%s)', 'woocommerce' ),
currencyConfig.code
) }
</h4>
<h4>{ __( 'Quantity', 'woocommerce' ) }</h4>
</div>
<Sortable>
{ variations.map( ( variation ) => (
<ListItem key={ variation.id }>
<div className="woocommerce-product-variations__attributes">
{ variation.attributes.map( ( attribute ) => (
/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */
/* @ts-ignore Additional props are not required. */
<Tag
id={ attribute.id }
className="woocommerce-product-variations__attribute"
key={ attribute.id }
label={ attribute.option }
/>
) ) }
</div>
<div className="woocommerce-product-variations__price">
{ formatAmount( variation.price ) }
</div>
<div className="woocommerce-product-variations__quantity">
{ getProductStockStatus( variation ) }
</div>
</ListItem>
) ) }
</Sortable>
</Card>
);
};

View File

@ -14,6 +14,7 @@ import { ProductDetailsSection } from './sections/product-details-section';
import { ProductInventorySection } from './sections/product-inventory-section';
import { PricingSection } from './sections/pricing-section';
import { ProductShippingSection } from './sections/product-shipping-section';
import { ProductVariationsSection } from './sections/product-variations-section';
import { ImagesSection } from './sections/images-section';
import './product-page.scss';
import { validate } from './product-validation';
@ -56,6 +57,9 @@ export const ProductForm: React.FC< {
<ProductFormTab name="shipping" title="Shipping">
<ProductShippingSection product={ product } />
</ProductFormTab>
<ProductFormTab name="options" title="Options">
<ProductVariationsSection />
</ProductFormTab>
</ProductFormLayout>
<ProductFormFooter />
</Form>

View File

@ -0,0 +1,46 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { recordEvent } from '@woocommerce/tracks';
import { Link } from '@woocommerce/components';
/**
* Internal dependencies
*/
import { ProductSectionLayout } from '../layout/product-section-layout';
import { Variations } from '../fields/variations';
export const ProductVariationsSection: React.FC = () => {
return (
<ProductSectionLayout
title={ __( 'Variations', 'woocommerce' ) }
description={
<>
<span>
{ __(
'Manage individual product combinations created from options.',
'woocommerce'
) }
</span>
<Link
className="woocommerce-form-section__header-link"
href="https://woocommerce.com/posts/product-variations-display/"
target="_blank"
type="external"
onClick={ () => {
recordEvent( 'add_product_variation_help' );
} }
>
{ __(
'How to make variations work for you',
'woocommerce'
) }
</Link>
</>
}
>
<Variations />
</ProductSectionLayout>
);
};

View File

@ -28,7 +28,7 @@ export const PRODUCT_STATUS_LABELS = {
* Get the product status for use in the header.
*
* @param product Product instance.
* @return {PRODUCT_STATUS_KEYS} Product staus key.
* @return {PRODUCT_STATUS_KEYS} Product status key.
*/
export const getProductStatus = (
product: PartialProduct | undefined

View File

@ -0,0 +1,49 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { PartialProduct, ProductVariation } from '@woocommerce/data';
/**
* Labels for product stock statuses.
*/
export enum PRODUCT_STOCK_STATUS_KEYS {
instock = 'instock',
onbackorder = 'onbackorder',
outofstock = 'outofstock',
}
/**
* Labels for product stock statuses.
*/
export const PRODUCT_STOCK_STATUS_LABELS = {
[ PRODUCT_STOCK_STATUS_KEYS.instock ]: __( 'In stock', 'woocommerce' ),
[ PRODUCT_STOCK_STATUS_KEYS.onbackorder ]: __(
'On backorder',
'woocommerce'
),
[ PRODUCT_STOCK_STATUS_KEYS.outofstock ]: __(
'Out of stock',
'woocommerce'
),
};
/**
* Get the product stock quantity or stock status label.
*
* @param product Product instance.
* @return {PRODUCT_STOCK_STATUS_KEYS|number} Product stock quantity or product status key.
*/
export const getProductStockStatus = (
product: PartialProduct | Partial< ProductVariation >
): string | number => {
if ( product.manage_stock ) {
return product.stock_quantity || 0;
}
if ( product.stock_status ) {
return PRODUCT_STOCK_STATUS_LABELS[ product.stock_status ];
}
return PRODUCT_STOCK_STATUS_LABELS.instock;
};

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add product variations list to new product management experience