Product Collection - Add `Created` filter in inspector controls (https://github.com/woocommerce/woocommerce-blocks/pull/11562)
* Add time frame filter to Product Collection block This commit introduces the ability to filter products within the Product Collection block by a specified time frame. The changes include: - A new 'timeFrame' property added to the DEFAULT_QUERY constant in constants.ts, initialized as null, allowing for the storage of time frame data. - Creation of a new component `CreatedControl` in created-control.tsx that provides UI elements for selecting a time frame filter. - Inclusion of `CreatedControl` in the Product Collection Inspector Controls. - Expansion of the ProductCollectionQuery interface in types.ts to include a 'timeFrame' attribute. - Addition of the 'timeFrame' parameter handling within the ProductCollection PHP class to construct and execute the date query based on the provided time frame. The addition of the time frame filter offers enhanced flexibility in presenting products and allows users to dynamically segment their product lists based on product creation dates. * Refactor: Standardize 'timeFrame' to be 'undefined' instead of 'null' This commit includes a refactoring that changes the initialization and reset values for the `timeFrame` property from `null` to `undefined`. This standardization affects the constants, type definitions, and the handling of the `timeFrame` property in both the inspector controls and the PHP backend. * Switch date query to use post_date_gmt for DST consistency This commit changes the column reference in the date query from 'post_date' to 'post_date_gmt'. This update ensures that the product collection filtering is based on Coordinated Universal Time (UTC) rather than local time, which can be affected by Daylight Saving Time (DST) shifts. The modification will lead to more consistent and reliable behavior across different time zones and during DST changes. * Capitalize toggle group labels The following adjustments have been made: - Introduced a constant `uppercaseStyle` to store the `{ textTransform: 'uppercase' }` style. - Applied `uppercaseStyle` to both the 'IN' and 'NOT IN' toggle options to ensure label text is consistently uppercase. - Updated the 'Not in' label text to uppercase ('NOT IN') to match the newly applied style. These changes ensure that the toggle labels align with the design guidelines that call for uppercase styling in control elements. * Make first letter of first work capital * Rename to Within & Before * Update i18n for Product Collection query operators This commit updates the internationalization (i18n) for the Product Collection query operators in the 'Created' control component of the WooCommerce Blocks plugin. It replaces the '__' function with '_x' for translation and provides context comments for better translation handling. This improvement enhances the localization of the query operators for better multilingual support.
This commit is contained in:
parent
3f98449f90
commit
b177aa9048
|
@ -49,6 +49,7 @@ export const DEFAULT_QUERY: ProductCollectionQuery = {
|
||||||
woocommerceStockStatus: getDefaultStockStatuses(),
|
woocommerceStockStatus: getDefaultStockStatuses(),
|
||||||
woocommerceAttributes: [],
|
woocommerceAttributes: [],
|
||||||
woocommerceHandPickedProducts: [],
|
woocommerceHandPickedProducts: [],
|
||||||
|
timeFrame: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_ATTRIBUTES: Partial< ProductCollectionAttributes > = {
|
export const DEFAULT_ATTRIBUTES: Partial< ProductCollectionAttributes > = {
|
||||||
|
@ -86,4 +87,5 @@ export const DEFAULT_FILTERS: Partial< ProductCollectionQuery > = {
|
||||||
woocommerceAttributes: [],
|
woocommerceAttributes: [],
|
||||||
taxQuery: DEFAULT_QUERY.taxQuery,
|
taxQuery: DEFAULT_QUERY.taxQuery,
|
||||||
woocommerceHandPickedProducts: [],
|
woocommerceHandPickedProducts: [],
|
||||||
|
timeFrame: undefined,
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { __, _x } from '@wordpress/i18n';
|
||||||
|
import {
|
||||||
|
Flex,
|
||||||
|
FlexItem,
|
||||||
|
RadioControl,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore - Ignoring because `__experimentalToggleGroupControl` is not yet in the type definitions.
|
||||||
|
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
||||||
|
__experimentalToggleGroupControl as ToggleGroupControl,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore - Ignoring because `__experimentalToggleGroupControlOption` is not yet in the type definitions.
|
||||||
|
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
||||||
|
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
|
||||||
|
// @ts-expect-error Using experimental features
|
||||||
|
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
||||||
|
__experimentalToolsPanelItem as ToolsPanelItem,
|
||||||
|
} from '@wordpress/components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { ETimeFrameOperator, QueryControlProps } from '../types';
|
||||||
|
|
||||||
|
const CreatedControl = ( props: QueryControlProps ) => {
|
||||||
|
const { query, setQueryAttribute } = props;
|
||||||
|
const { timeFrame } = query;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolsPanelItem
|
||||||
|
label={ __( 'Created', 'woo-gutenberg-products-block' ) }
|
||||||
|
hasValue={ () => timeFrame?.operator && timeFrame?.value }
|
||||||
|
onDeselect={ () => {
|
||||||
|
setQueryAttribute( {
|
||||||
|
timeFrame: undefined,
|
||||||
|
} );
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
<Flex direction="column" gap={ 3 }>
|
||||||
|
<FlexItem>
|
||||||
|
<ToggleGroupControl
|
||||||
|
label={ __(
|
||||||
|
'Created',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
) }
|
||||||
|
isBlock
|
||||||
|
onChange={ ( value: ETimeFrameOperator ) => {
|
||||||
|
setQueryAttribute( {
|
||||||
|
timeFrame: {
|
||||||
|
...timeFrame,
|
||||||
|
operator: value,
|
||||||
|
},
|
||||||
|
} );
|
||||||
|
} }
|
||||||
|
value={ timeFrame?.operator || ETimeFrameOperator.IN }
|
||||||
|
>
|
||||||
|
<ToggleGroupControlOption
|
||||||
|
value={ ETimeFrameOperator.IN }
|
||||||
|
label={ _x(
|
||||||
|
'Within',
|
||||||
|
'Product Collection query operator',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
) }
|
||||||
|
/>
|
||||||
|
<ToggleGroupControlOption
|
||||||
|
value={ ETimeFrameOperator.NOT_IN }
|
||||||
|
label={ _x(
|
||||||
|
'Before',
|
||||||
|
'Product Collection query operator',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
) }
|
||||||
|
/>
|
||||||
|
</ToggleGroupControl>
|
||||||
|
</FlexItem>
|
||||||
|
<FlexItem>
|
||||||
|
<RadioControl
|
||||||
|
onChange={ ( value: string ) => {
|
||||||
|
setQueryAttribute( {
|
||||||
|
timeFrame: {
|
||||||
|
operator: ETimeFrameOperator.IN,
|
||||||
|
...timeFrame,
|
||||||
|
value,
|
||||||
|
},
|
||||||
|
} );
|
||||||
|
} }
|
||||||
|
options={ [
|
||||||
|
{
|
||||||
|
label: 'last 24 hours',
|
||||||
|
value: '-1 day',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'last 7 days',
|
||||||
|
value: '-7 days',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'last 30 days',
|
||||||
|
value: '-30 days',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'last 3 months',
|
||||||
|
value: '-3 months',
|
||||||
|
},
|
||||||
|
] }
|
||||||
|
selected={ timeFrame?.value }
|
||||||
|
/>
|
||||||
|
</FlexItem>
|
||||||
|
</Flex>
|
||||||
|
</ToolsPanelItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreatedControl;
|
|
@ -39,6 +39,7 @@ import AttributesControl from './attributes-control';
|
||||||
import TaxonomyControls from './taxonomy-controls';
|
import TaxonomyControls from './taxonomy-controls';
|
||||||
import HandPickedProductsControl from './hand-picked-products-control';
|
import HandPickedProductsControl from './hand-picked-products-control';
|
||||||
import LayoutOptionsControl from './layout-options-control';
|
import LayoutOptionsControl from './layout-options-control';
|
||||||
|
import CreatedControl from './created-control';
|
||||||
|
|
||||||
const ProductCollectionInspectorControls = (
|
const ProductCollectionInspectorControls = (
|
||||||
props: BlockEditProps< ProductCollectionAttributes >
|
props: BlockEditProps< ProductCollectionAttributes >
|
||||||
|
@ -98,6 +99,7 @@ const ProductCollectionInspectorControls = (
|
||||||
<KeywordControl { ...queryControlProps } />
|
<KeywordControl { ...queryControlProps } />
|
||||||
<AttributesControl { ...queryControlProps } />
|
<AttributesControl { ...queryControlProps } />
|
||||||
<TaxonomyControls { ...queryControlProps } />
|
<TaxonomyControls { ...queryControlProps } />
|
||||||
|
<CreatedControl { ...queryControlProps } />
|
||||||
</ToolsPanel>
|
</ToolsPanel>
|
||||||
) : null }
|
) : null }
|
||||||
<ProductCollectionFeedbackPrompt />
|
<ProductCollectionFeedbackPrompt />
|
||||||
|
|
|
@ -28,6 +28,16 @@ export interface ProductCollectionDisplayLayout {
|
||||||
shrinkColumns?: boolean;
|
shrinkColumns?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ETimeFrameOperator {
|
||||||
|
IN = 'in',
|
||||||
|
NOT_IN = 'not-in',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimeFrame {
|
||||||
|
operator?: ETimeFrameOperator;
|
||||||
|
value?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductCollectionQuery {
|
export interface ProductCollectionQuery {
|
||||||
exclude: string[];
|
exclude: string[];
|
||||||
inherit: boolean | null;
|
inherit: boolean | null;
|
||||||
|
@ -39,6 +49,7 @@ export interface ProductCollectionQuery {
|
||||||
postType: string;
|
postType: string;
|
||||||
search: string;
|
search: string;
|
||||||
taxQuery: Record< string, number[] >;
|
taxQuery: Record< string, number[] >;
|
||||||
|
timeFrame: TimeFrame | undefined;
|
||||||
woocommerceOnSale: boolean;
|
woocommerceOnSale: boolean;
|
||||||
/**
|
/**
|
||||||
* Filter products by their stock status.
|
* Filter products by their stock status.
|
||||||
|
|
|
@ -207,6 +207,7 @@ class ProductCollection extends AbstractBlock {
|
||||||
$stock_status = $request->get_param( 'woocommerceStockStatus' );
|
$stock_status = $request->get_param( 'woocommerceStockStatus' );
|
||||||
$product_attributes = $request->get_param( 'woocommerceAttributes' );
|
$product_attributes = $request->get_param( 'woocommerceAttributes' );
|
||||||
$handpicked_products = $request->get_param( 'woocommerceHandPickedProducts' );
|
$handpicked_products = $request->get_param( 'woocommerceHandPickedProducts' );
|
||||||
|
$time_frame = $request->get_param( 'timeFrame' );
|
||||||
// This argument is required for the tests to PHP Unit Tests to run correctly.
|
// This argument is required for the tests to PHP Unit Tests to run correctly.
|
||||||
// Most likely this argument is being accessed in the test environment image.
|
// Most likely this argument is being accessed in the test environment image.
|
||||||
$args['author'] = '';
|
$args['author'] = '';
|
||||||
|
@ -219,6 +220,7 @@ class ProductCollection extends AbstractBlock {
|
||||||
'stock_status' => $stock_status,
|
'stock_status' => $stock_status,
|
||||||
'product_attributes' => $product_attributes,
|
'product_attributes' => $product_attributes,
|
||||||
'handpicked_products' => $handpicked_products,
|
'handpicked_products' => $handpicked_products,
|
||||||
|
'timeFrame' => $time_frame,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -305,6 +307,7 @@ class ProductCollection extends AbstractBlock {
|
||||||
$product_attributes = $query['woocommerceAttributes'] ?? [];
|
$product_attributes = $query['woocommerceAttributes'] ?? [];
|
||||||
$taxonomies_query = $this->get_filter_by_taxonomies_query( $query['tax_query'] ?? [] );
|
$taxonomies_query = $this->get_filter_by_taxonomies_query( $query['tax_query'] ?? [] );
|
||||||
$handpicked_products = $query['woocommerceHandPickedProducts'] ?? [];
|
$handpicked_products = $query['woocommerceHandPickedProducts'] ?? [];
|
||||||
|
$time_frame = $query['timeFrame'] ?? null;
|
||||||
|
|
||||||
$final_query = $this->get_final_query_args(
|
$final_query = $this->get_final_query_args(
|
||||||
$common_query_values,
|
$common_query_values,
|
||||||
|
@ -315,6 +318,7 @@ class ProductCollection extends AbstractBlock {
|
||||||
'product_attributes' => $product_attributes,
|
'product_attributes' => $product_attributes,
|
||||||
'taxonomies_query' => $taxonomies_query,
|
'taxonomies_query' => $taxonomies_query,
|
||||||
'handpicked_products' => $handpicked_products,
|
'handpicked_products' => $handpicked_products,
|
||||||
|
'timeFrame' => $time_frame,
|
||||||
),
|
),
|
||||||
$is_exclude_applied_filters
|
$is_exclude_applied_filters
|
||||||
);
|
);
|
||||||
|
@ -338,11 +342,12 @@ class ProductCollection extends AbstractBlock {
|
||||||
$attributes_query = $this->get_product_attributes_query( $query['product_attributes'] );
|
$attributes_query = $this->get_product_attributes_query( $query['product_attributes'] );
|
||||||
$taxonomies_query = $query['taxonomies_query'] ?? [];
|
$taxonomies_query = $query['taxonomies_query'] ?? [];
|
||||||
$tax_query = $this->merge_tax_queries( $visibility_query, $attributes_query, $taxonomies_query );
|
$tax_query = $this->merge_tax_queries( $visibility_query, $attributes_query, $taxonomies_query );
|
||||||
|
$date_query = $this->get_date_query( $query['timeFrame'] ?? [] );
|
||||||
|
|
||||||
// We exclude applied filters to generate product ids for the filter blocks.
|
// We exclude applied filters to generate product ids for the filter blocks.
|
||||||
$applied_filters_query = $is_exclude_applied_filters ? [] : $this->get_queries_by_applied_filters();
|
$applied_filters_query = $is_exclude_applied_filters ? [] : $this->get_queries_by_applied_filters();
|
||||||
|
|
||||||
$merged_query = $this->merge_queries( $common_query_values, $orderby_query, $on_sale_query, $stock_query, $tax_query, $applied_filters_query );
|
$merged_query = $this->merge_queries( $common_query_values, $orderby_query, $on_sale_query, $stock_query, $tax_query, $applied_filters_query, $date_query );
|
||||||
|
|
||||||
return $this->filter_query_to_only_include_ids( $merged_query, $handpicked_products );
|
return $this->filter_query_to_only_include_ids( $merged_query, $handpicked_products );
|
||||||
}
|
}
|
||||||
|
@ -959,4 +964,38 @@ class ProductCollection extends AbstractBlock {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a date query for product filtering based on a specified time frame.
|
||||||
|
*
|
||||||
|
* @param array $time_frame {
|
||||||
|
* Associative array with 'operator' (in or not-in) and 'value' (date string).
|
||||||
|
*
|
||||||
|
* @type string $operator Determines the inclusion or exclusion of the date range.
|
||||||
|
* @type string $value The date around which the range is applied.
|
||||||
|
* }
|
||||||
|
* @return array Date query array; empty if parameters are invalid.
|
||||||
|
*/
|
||||||
|
private function get_date_query( array $time_frame ) : array {
|
||||||
|
// Validate time_frame elements.
|
||||||
|
if ( empty( $time_frame['operator'] ) || empty( $time_frame['value'] ) ) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the query operator based on the 'operator' value.
|
||||||
|
$query_operator = 'in' === $time_frame['operator'] ? 'after' : 'before';
|
||||||
|
|
||||||
|
// Construct and return the date query.
|
||||||
|
return array(
|
||||||
|
'date_query' => array(
|
||||||
|
array(
|
||||||
|
'column' => 'post_date_gmt',
|
||||||
|
$query_operator => $time_frame['value'],
|
||||||
|
'inclusive' => true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue