/** * External dependencies */ import { __, _n, sprintf } from '@wordpress/i18n'; import { Component, Fragment } from '@wordpress/element'; import { addQueryArgs } from '@wordpress/url'; import apiFetch from '@wordpress/api-fetch'; import { debounce, find, escapeRegExp, isEmpty } from 'lodash'; import PropTypes from 'prop-types'; import { SearchListControl, SearchListItem, } from '@woocommerce/components'; import { Spinner, MenuItem } from '@wordpress/components'; import classnames from 'classnames'; /** * Internal dependencies */ import { ENDPOINTS } from '../../constants'; import { isLargeCatalog, getProducts } from '../utils'; import { IconRadioSelected, IconRadioUnselected, } from '../icons'; import './style.scss'; function getHighlightedName( name, search ) { if ( ! search ) { return name; } const re = new RegExp( escapeRegExp( search ), 'ig' ); return name.replace( re, '$&' ); } const getInteractionIcon = ( isSelected = false ) => { return isSelected ? : ; }; class ProductControl extends Component { constructor() { super( ...arguments ); this.state = { products: [], product: 0, variationsList: {}, variationsLoading: false, loading: true, }; this.debouncedOnSearch = debounce( this.onSearch.bind( this ), 400 ); this.debouncedGetVariations = debounce( this.getVariations.bind( this ), 200 ); this.renderItem = this.renderItem.bind( this ); this.onProductSelect = this.onProductSelect.bind( this ); } componentDidMount() { const { selected } = this.props; getProducts( { selected } ) .then( ( products ) => { products = products.map( ( product ) => { const count = product.variations ? product.variations.length : 0; return { ...product, parent: 0, count: count, }; } ); this.setState( { products, loading: false } ); } ) .catch( () => { this.setState( { products: [], loading: false } ); } ); } componentDidUpdate( prevProps, prevState ) { if ( prevState.product !== this.state.product ) { this.debouncedGetVariations(); } } getVariations() { const { product, variationsList } = this.state; if ( ! product ) { this.setState( { variationsList: {}, variationsLoading: false, } ); return; } const productDetails = this.state.products.find( ( findProduct ) => findProduct.id === product ); if ( ! productDetails.variations || productDetails.variations.length === 0 ) { return; } if ( ! variationsList[ product ] ) { this.setState( { variationsLoading: true } ); } apiFetch( { path: addQueryArgs( `${ ENDPOINTS.products }/${ product }/variations`, { per_page: -1, } ), } ) .then( ( variations ) => { variations = variations.map( ( variation ) => ( { ...variation, parent: product } ) ); this.setState( ( prevState ) => ( { variationsList: { ...prevState.variationsList, [ product ]: variations }, variationsLoading: false, } ) ); } ) .catch( () => { this.setState( { termsLoading: false } ); } ); } onSearch( search ) { const { selected } = this.props; getProducts( { selected, search } ) .then( ( products ) => { this.setState( { products, loading: false } ); } ) .catch( () => { this.setState( { products: [], loading: false } ); } ); } onProductSelect( item, isSelected ) { return () => { this.setState( { product: isSelected ? 0 : item.id, } ); }; } renderItem( args ) { const { item, search, depth = 0, isSelected, onSelect } = args; const { product, variationsLoading } = this.state; const classes = classnames( 'woocommerce-search-product__item', 'woocommerce-search-list__item', `depth-${ depth }`, { 'is-searching': search.length > 0, 'is-skip-level': depth === 0 && item.parent !== 0, 'is-variable': item.count > 0, } ); const itemArgs = Object.assign( {}, args ); delete itemArgs.isSingle; const a11yProps = { role: 'menuitemradio', }; if ( item.breadcrumbs.length ) { a11yProps[ 'aria-label' ] = `${ item.breadcrumbs[ 0 ] }: ${ item.name }`; } if ( item.count ) { a11yProps[ 'aria-expanded' ] = item.id === product; } // Top level items custom rendering based on SearchListItem. if ( ! item.breadcrumbs.length ) { return [ { onSelect( item )(); this.onProductSelect( item, isSelected )(); } } > { getInteractionIcon( isSelected ) } { item.count ? ( { sprintf( _n( '%d variation', '%d variations', item.count, 'woo-gutenberg-products-block' ), item.count ) } ) : null } , product === item.id && item.count > 0 && variationsLoading && (
), ]; } if ( ! isEmpty( item.variation ) ) { item.name = item.variation; } return ( ); } render() { const { products, loading, product, variationsList } = this.state; const { onChange, selected } = this.props; const currentVariations = variationsList[ product ] || []; const currentList = [ ...products, ...currentVariations ]; const messages = { list: __( 'Products', 'woo-gutenberg-products-block' ), noItems: __( "Your store doesn't have any products.", 'woo-gutenberg-products-block' ), search: __( 'Search for a product to display', 'woo-gutenberg-products-block' ), updated: __( 'Product search results updated.', 'woo-gutenberg-products-block' ), }; const selectedListItems = selected ? [ find( currentList, { id: selected } ) ] : []; return ( ); } } ProductControl.propTypes = { /** * Callback to update the selected products. */ onChange: PropTypes.func.isRequired, /** * The ID of the currently selected product. */ selected: PropTypes.number.isRequired, }; export default ProductControl;