[Product Collection] Add `Attributes` filter control to sidebar settings (https://github.com/woocommerce/woocommerce-blocks/pull/9600)
* Add columns control to product collection block editor settings - `InspectorControls` from './inspector-controls' is now imported in `edit.tsx` and used in the returned JSX of `Edit` function. - A new file `columns-control.tsx` is added under 'product-collection' block's 'inspector-controls' directory which exports a `ColumnsControl` component. This component uses `RangeControl` from '@wordpress/components' to control the number of columns in the product collection display layout when the layout type is 'flex'. - The types file (`types.ts`) for 'product-collection' block is updated. The `Attributes` interface is renamed to `ProductCollectionAttributes` and the `ProductCollectionContext` interface is removed. The `ProductCollectionAttributes` now includes 'queryContext', 'templateSlug', and 'displayLayout' properties. * Refactor: Simplify Fallback Return in ColumnsControl Component This commit simplifies the fallback return value of the ColumnsControl component. Instead of returning an empty fragment (<> </>), it now returns null when the condition isn't met. This change improves readability and aligns with best practices for conditional rendering in React. * Feature: Add 'Order By' Control to Product Collection Inspector This commit adds a new 'Order By' control to the product collection inspector. The control allows users to specify the order of products in a collection by various attributes such as title and date. To support this, a new component 'OrderByControl' has been created and included in the product collection inspector. Additionally, the types for 'order' and 'orderBy' attributes have been updated and exported for reuse. * Add more options to OrderBy type * Add orderby handling on frontend & editor The main changes include: 1. Added a new property 'isProductCollectionBlock' in the block.json to denote if a block is a product collection block. 2. In the ProductCollection PHP class, a new initialization function has been defined to hook into the WordPress lifecycle, register the block, and update the query based on this block. 3. Added methods to manage query parameters for both frontend rendering and the Editor. 4. Expanded allowed 'collection_params' for the REST API to include custom 'orderby' values. 5. Defined a function to build the query based on block attributes, filters, and global WP_Query. 6. Created utility functions to handle complex query operations such as merging queries, handling custom sort values, and merging arrays recursively. These improvements allow for more flexible and robust handling of product collections in both the front-end display and the WordPress editor. It also extends support for custom 'orderby' values in the REST API, which allows for more advanced sorting options in product collections. * Add 'on sale' filter and enhance settings management in product collection block This commit introduces several changes to the product collection block. - First, it adds a new 'on sale' filter that can be used to display only the products that are currently on sale. - It also refactors the settings management in the product collection block to use the experimental ToolsPanel component from WordPress, which provides a more flexible and intuitive way to manage block settings. - It moves the 'Columns' control into the ToolsPanel, along with the 'Order by' control. - A new utility function `setQueryAttribute` is introduced to simplify setting nested query parameters. - The structure of the `ProductCollectionAttributes` and `ProductCollectionQuery` types have been adjusted to accommodate the changes. - Finally, it makes corresponding changes in the PHP part to handle the new 'on sale' query parameter. This should enhance the flexibility and user-friendliness of the product collection block. * Add stock status filter to WooCommerce product collection block This commit introduces a stock status filter to the WooCommerce product collection block. The changes include: 1. Added the ability to filter products based on their stock status within the 'product-collection' block. A new stock status control is created within the inspector-controls of the block. 2. A new 'get_stock_status_query' function is introduced in 'ProductCollection.php' which returns a query for products depending on their stock status. Please note that the stock status filter will only appear in the experimental build for now. * Refactor Stock Status control of Product Collection block This commit refactors the Stock Status control. The changes aim to improve the code organization and make the behavior of the component more explicit. The key modifications are: 1. Moved stock status related constants and functions from `inspector-controls/utils.tsx` to `inspector-controls/constants.ts`. This is done to ensure that all constants and similar utility functions are organized in one place. 2. Updated `product-collection/index.tsx` to import `getDefaultStockStatuses` from `inspector-controls/constants` instead of `inspector-controls/utils`. 3. Updated `stock-status-control.tsx` to determine whether the stock status has value or not by comparing with the default stock statuses using `fastDeepEqual`. If the stock status control is deselected, it resets the stock status to the default statuses. These changes do not introduce any new functionalities, but improve the readability and maintainability of the code. * Add keyword search control to Product Collection block This commit introduces a keyword search functionality to the Product Collection block. The update is aimed to provide users with more flexibility and precision in product collection queries. Key changes: 1. Introduced a new file `keyword-control.tsx` that creates a Keyword Control component. This component includes a TextControl field that allows inputting a search keyword. The keyword search is debounced to prevent unnecessary queries during input and updates the block's attributes accordingly. 2. Modified `inspector-controls/index.tsx` to include the KeywordControl in the ToolsPanel for the block's filters. 3. Adjusted `ProductCollection.php` to include the keyword search in the product query array. With these changes, users can now search for products by keyword in the Product Collection block. * Add product attributes filter control to ProductCollection block - This commit introduces the ability to filter products by attributes in ProductCollection block. - A new `woocommerceAttributes` key was added to the `block.json` file and the `ProductCollectionQuery` type. Also, a new file `attributes-control.tsx` was created, providing the UI component for the attribute filter control in the editor. - In addition, updates were made to the `ProductCollection.php` file in the backend to support filtering products by attributes, and the tax query was updated to include attribute queries. - Lastly, the `ProductCollectionInspectorControls` was updated to include the `AttributesControl` component, thus enabling users to filter products by attributes in the block editor."` * remove unused import of getDefaultStockStatuses * Delete a duplicate file * Remove console log * Address PR feedback & other improvements 1. Added `woocommerceAttributes` to `DEFAULT_FILTERS` in the `product-collection/constants.ts` file to fix `reset all` button issue. 2. Refactored `attributes-control.tsx` to make it more maintainable: - The constant `EDIT_ATTRIBUTES_URL` now uses `ADMIN_URL` from `@woocommerce/settings` for a more dynamic URL generation. - The interface `Props` has been renamed to `AttributesControlProps` for more explicit naming. - Removed the usage of `useState` and `useEffect` for selected attributes. Instead, `selectedAttributes` is now directly derived from `woocommerceAttributes`. - The CSS class `woocommerce-product-query-panel__external-link` was renamed to `wc-product-collection-panel__external-link` 3. Deleted the `product-collection/inspector-controls/constants.ts` file which was no longer necessary due to changes in product collection implementation. These changes contribute to codebase quality, improving readability and maintainability. * Add wc-block-editor prefix to className --------- Co-authored-by: Alexandre Lara <allexandrelara@gmail.com>
This commit is contained in:
parent
27e3a9a3cc
commit
a6c31a7878
|
@ -49,6 +49,7 @@ export const DEFAULT_ATTRIBUTES: Partial< ProductCollectionAttributes > = {
|
|||
isProductCollectionBlock: true,
|
||||
woocommerceOnSale: false,
|
||||
woocommerceStockStatus: getDefaultStockStatuses(),
|
||||
woocommerceAttributes: [],
|
||||
},
|
||||
tagName: 'div',
|
||||
displayLayout: {
|
||||
|
@ -75,4 +76,5 @@ export const DEFAULT_FILTERS = {
|
|||
woocommerceOnSale: ( DEFAULT_ATTRIBUTES.query as ProductCollectionQuery )
|
||||
.woocommerceOnSale,
|
||||
woocommerceStockStatus: getDefaultStockStatuses(),
|
||||
woocommerceAttributes: [],
|
||||
};
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import ProductAttributeTermControl from '@woocommerce/editor-components/product-attribute-term-control';
|
||||
import { AttributeMetadata } from '@woocommerce/types';
|
||||
import { SearchListItem } from '@woocommerce/editor-components/search-list-control/types';
|
||||
import { ADMIN_URL } from '@woocommerce/settings';
|
||||
import {
|
||||
ExternalLink,
|
||||
// @ts-expect-error Using experimental features
|
||||
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
||||
__experimentalToolsPanelItem as ToolsPanelItem,
|
||||
} from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ProductCollectionQuery } from '../types';
|
||||
|
||||
const EDIT_ATTRIBUTES_URL = `${ ADMIN_URL }edit.php?post_type=product&page=product_attributes`;
|
||||
|
||||
interface AttributesControlProps {
|
||||
woocommerceAttributes?: AttributeMetadata[];
|
||||
setQueryAttribute: ( value: Partial< ProductCollectionQuery > ) => void;
|
||||
}
|
||||
|
||||
const AttributesControl = ( {
|
||||
woocommerceAttributes,
|
||||
setQueryAttribute,
|
||||
}: AttributesControlProps ) => {
|
||||
const selectedAttributes = woocommerceAttributes?.map(
|
||||
( { termId: id } ) => ( {
|
||||
id,
|
||||
} )
|
||||
);
|
||||
|
||||
return (
|
||||
<ToolsPanelItem
|
||||
label={ __( 'Product Attributes', 'woo-gutenberg-products-block' ) }
|
||||
hasValue={ () => !! woocommerceAttributes?.length }
|
||||
>
|
||||
<ProductAttributeTermControl
|
||||
messages={ {
|
||||
search: __( 'Attributes', 'woo-gutenberg-products-block' ),
|
||||
} }
|
||||
selected={ selectedAttributes || [] }
|
||||
onChange={ ( searchListItems: SearchListItem[] ) => {
|
||||
const newValue = searchListItems.map(
|
||||
( { id, value } ) => ( {
|
||||
termId: id as number,
|
||||
taxonomy: value as string,
|
||||
} )
|
||||
);
|
||||
|
||||
setQueryAttribute( {
|
||||
woocommerceAttributes: newValue,
|
||||
} );
|
||||
} }
|
||||
operator={ 'any' }
|
||||
isCompact={ true }
|
||||
type={ 'token' }
|
||||
/>
|
||||
<ExternalLink
|
||||
className="wc-block-editor-product-collection-panel__manage-attributes-link"
|
||||
href={ EDIT_ATTRIBUTES_URL }
|
||||
>
|
||||
{ __( 'Manage attributes', 'woo-gutenberg-products-block' ) }
|
||||
</ExternalLink>
|
||||
</ToolsPanelItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default AttributesControl;
|
|
@ -1,53 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import { objectOmit } from '@woocommerce/utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import blockJson from '../block.json';
|
||||
import {
|
||||
ProductCollectionAttributes,
|
||||
ProductCollectionQuery,
|
||||
TProductCollectionOrder,
|
||||
TProductCollectionOrderBy,
|
||||
} from '../types';
|
||||
|
||||
const defaultQuery = blockJson.attributes.query.default;
|
||||
|
||||
export const STOCK_STATUS_OPTIONS = getSetting< Record< string, string > >(
|
||||
'stockStatusOptions',
|
||||
[]
|
||||
);
|
||||
|
||||
const GLOBAL_HIDE_OUT_OF_STOCK = getSetting< boolean >(
|
||||
'hideOutOfStockItems',
|
||||
false
|
||||
);
|
||||
|
||||
export const getDefaultStockStatuses = () => {
|
||||
return GLOBAL_HIDE_OUT_OF_STOCK
|
||||
? Object.keys( objectOmit( STOCK_STATUS_OPTIONS, 'outofstock' ) )
|
||||
: Object.keys( STOCK_STATUS_OPTIONS );
|
||||
};
|
||||
|
||||
export const DEFAULT_FILTERS: Partial< ProductCollectionQuery > = {
|
||||
woocommerceOnSale: defaultQuery.woocommerceOnSale,
|
||||
woocommerceStockStatus: getDefaultStockStatuses(),
|
||||
search: '',
|
||||
};
|
||||
|
||||
export const getDefaultSettings = (
|
||||
currentAttributes: ProductCollectionAttributes
|
||||
): Partial< ProductCollectionAttributes > => ( {
|
||||
displayLayout: blockJson.attributes.displayLayout.default,
|
||||
query: {
|
||||
...currentAttributes.query,
|
||||
orderBy: blockJson.attributes.query.default
|
||||
.orderBy as TProductCollectionOrderBy,
|
||||
order: blockJson.attributes.query.default
|
||||
.order as TProductCollectionOrder,
|
||||
},
|
||||
} );
|
|
@ -4,6 +4,8 @@
|
|||
import type { BlockEditProps } from '@wordpress/blocks';
|
||||
import { InspectorControls } from '@wordpress/block-editor';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useMemo } from '@wordpress/element';
|
||||
import { AttributeMetadata } from '@woocommerce/types';
|
||||
import {
|
||||
// @ts-expect-error Using experimental features
|
||||
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
||||
|
@ -21,10 +23,16 @@ import { setQueryAttribute } from './utils';
|
|||
import { DEFAULT_FILTERS, getDefaultSettings } from '../constants';
|
||||
import StockStatusControl from './stock-status-control';
|
||||
import KeywordControl from './keyword-control';
|
||||
import AttributesControl from './attributes-control';
|
||||
|
||||
const ProductCollectionInspectorControls = (
|
||||
props: BlockEditProps< ProductCollectionAttributes >
|
||||
) => {
|
||||
const setQueryAttributeBind = useMemo(
|
||||
() => setQueryAttribute.bind( null, props ),
|
||||
[ props ]
|
||||
);
|
||||
|
||||
return (
|
||||
<InspectorControls>
|
||||
<ToolsPanel
|
||||
|
@ -50,6 +58,14 @@ const ProductCollectionInspectorControls = (
|
|||
<OnSaleControl { ...props } />
|
||||
<StockStatusControl { ...props } />
|
||||
<KeywordControl { ...props } />
|
||||
<AttributesControl
|
||||
woocommerceAttributes={
|
||||
props.attributes.query &&
|
||||
( props.attributes.query
|
||||
.woocommerceAttributes as AttributeMetadata[] )
|
||||
}
|
||||
setQueryAttribute={ setQueryAttributeBind }
|
||||
/>
|
||||
</ToolsPanel>
|
||||
</InspectorControls>
|
||||
);
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { AttributeMetadata } from '@woocommerce/types';
|
||||
|
||||
export interface ProductCollectionAttributes {
|
||||
query: ProductCollectionQuery;
|
||||
queryId: number;
|
||||
|
@ -45,6 +50,7 @@ export interface ProductCollectionQuery {
|
|||
* ```
|
||||
*/
|
||||
woocommerceStockStatus?: string[];
|
||||
woocommerceAttributes?: AttributeMetadata[];
|
||||
isProductCollectionBlock?: boolean;
|
||||
}
|
||||
|
||||
|
|
|
@ -76,16 +76,18 @@ class ProductCollection extends AbstractBlock {
|
|||
return $args;
|
||||
}
|
||||
|
||||
$orderby = $request->get_param( 'orderBy' );
|
||||
$on_sale = $request->get_param( 'woocommerceOnSale' ) === 'true';
|
||||
$stock_status = $request->get_param( 'woocommerceStockStatus' );
|
||||
$orderby = $request->get_param( 'orderBy' );
|
||||
$on_sale = $request->get_param( 'woocommerceOnSale' ) === 'true';
|
||||
$stock_status = $request->get_param( 'woocommerceStockStatus' );
|
||||
$product_attributes = $request->get_param( 'woocommerceAttributes' );
|
||||
|
||||
return $this->get_final_query_args(
|
||||
$args,
|
||||
array(
|
||||
'orderby' => $orderby,
|
||||
'on_sale' => $on_sale,
|
||||
'stock_status' => $stock_status,
|
||||
'orderby' => $orderby,
|
||||
'on_sale' => $on_sale,
|
||||
'stock_status' => $stock_status,
|
||||
'product_attributes' => $product_attributes,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -101,7 +103,8 @@ class ProductCollection extends AbstractBlock {
|
|||
$on_sale_query = $this->get_on_sale_products_query( $query['on_sale'] );
|
||||
$stock_query = $this->get_stock_status_query( $query['stock_status'] );
|
||||
$visibility_query = is_array( $query['stock_status'] ) ? $this->get_product_visibility_query( $stock_query ) : [];
|
||||
$tax_query = $this->merge_tax_queries( $visibility_query );
|
||||
$attributes_query = $this->get_product_attributes_query( $query['product_attributes'] );
|
||||
$tax_query = $this->merge_tax_queries( $visibility_query, $attributes_query );
|
||||
|
||||
return $this->merge_queries( $common_query_values, $orderby_query, $on_sale_query, $stock_query, $tax_query );
|
||||
}
|
||||
|
@ -144,9 +147,10 @@ class ProductCollection extends AbstractBlock {
|
|||
return $this->get_final_query_args(
|
||||
$common_query_values,
|
||||
array(
|
||||
'on_sale' => $is_on_sale,
|
||||
'stock_status' => $block_context_query['woocommerceStockStatus'],
|
||||
'orderby' => $block_context_query['orderBy'],
|
||||
'on_sale' => $is_on_sale,
|
||||
'stock_status' => $block_context_query['woocommerceStockStatus'],
|
||||
'orderby' => $block_context_query['orderBy'],
|
||||
'product_attributes' => $block_context_query['woocommerceAttributes'],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -448,4 +452,42 @@ class ProductCollection extends AbstractBlock {
|
|||
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
|
||||
return [ 'tax_query' => $tax_query ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the `tax_query` for the requested attributes
|
||||
*
|
||||
* @param array $attributes Attributes and their terms.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_product_attributes_query( $attributes = array() ) {
|
||||
if ( empty( $attributes ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$grouped_attributes = array_reduce(
|
||||
$attributes,
|
||||
function ( $carry, $item ) {
|
||||
$taxonomy = sanitize_title( $item['taxonomy'] );
|
||||
|
||||
if ( ! key_exists( $taxonomy, $carry ) ) {
|
||||
$carry[ $taxonomy ] = array(
|
||||
'field' => 'term_id',
|
||||
'operator' => 'IN',
|
||||
'taxonomy' => $taxonomy,
|
||||
'terms' => array( $item['termId'] ),
|
||||
);
|
||||
} else {
|
||||
$carry[ $taxonomy ]['terms'][] = $item['termId'];
|
||||
}
|
||||
|
||||
return $carry;
|
||||
},
|
||||
array()
|
||||
);
|
||||
|
||||
return array(
|
||||
'tax_query' => array_values( $grouped_attributes ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue