diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/constants.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/constants.ts index 6a5759c3597..6b5fdc6cbb4 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/constants.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/constants.ts @@ -49,6 +49,7 @@ export const DEFAULT_QUERY: ProductCollectionQuery = { woocommerceStockStatus: getDefaultStockStatuses(), woocommerceAttributes: [], woocommerceHandPickedProducts: [], + timeFrame: undefined, }; export const DEFAULT_ATTRIBUTES: Partial< ProductCollectionAttributes > = { @@ -86,4 +87,5 @@ export const DEFAULT_FILTERS: Partial< ProductCollectionQuery > = { woocommerceAttributes: [], taxQuery: DEFAULT_QUERY.taxQuery, woocommerceHandPickedProducts: [], + timeFrame: undefined, }; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/inspector-controls/created-control.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/inspector-controls/created-control.tsx new file mode 100644 index 00000000000..2c485da7346 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/inspector-controls/created-control.tsx @@ -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 ( + timeFrame?.operator && timeFrame?.value } + onDeselect={ () => { + setQueryAttribute( { + timeFrame: undefined, + } ); + } } + > + + + { + setQueryAttribute( { + timeFrame: { + ...timeFrame, + operator: value, + }, + } ); + } } + value={ timeFrame?.operator || ETimeFrameOperator.IN } + > + + + + + + { + 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 } + /> + + + + ); +}; + +export default CreatedControl; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/inspector-controls/index.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/inspector-controls/index.tsx index 8d8ffed8f85..3c22a7f7be3 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/inspector-controls/index.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/inspector-controls/index.tsx @@ -39,6 +39,7 @@ import AttributesControl from './attributes-control'; import TaxonomyControls from './taxonomy-controls'; import HandPickedProductsControl from './hand-picked-products-control'; import LayoutOptionsControl from './layout-options-control'; +import CreatedControl from './created-control'; const ProductCollectionInspectorControls = ( props: BlockEditProps< ProductCollectionAttributes > @@ -98,6 +99,7 @@ const ProductCollectionInspectorControls = ( + ) : null } diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/types.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/types.ts index ad2f0d4a26a..e64c4c4f0c4 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/types.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/types.ts @@ -28,6 +28,16 @@ export interface ProductCollectionDisplayLayout { shrinkColumns?: boolean; } +export enum ETimeFrameOperator { + IN = 'in', + NOT_IN = 'not-in', +} + +export interface TimeFrame { + operator?: ETimeFrameOperator; + value?: string; +} + export interface ProductCollectionQuery { exclude: string[]; inherit: boolean | null; @@ -39,6 +49,7 @@ export interface ProductCollectionQuery { postType: string; search: string; taxQuery: Record< string, number[] >; + timeFrame: TimeFrame | undefined; woocommerceOnSale: boolean; /** * Filter products by their stock status. diff --git a/plugins/woocommerce-blocks/src/BlockTypes/ProductCollection.php b/plugins/woocommerce-blocks/src/BlockTypes/ProductCollection.php index 675608d28d2..bede9b2ec3b 100644 --- a/plugins/woocommerce-blocks/src/BlockTypes/ProductCollection.php +++ b/plugins/woocommerce-blocks/src/BlockTypes/ProductCollection.php @@ -207,6 +207,7 @@ class ProductCollection extends AbstractBlock { $stock_status = $request->get_param( 'woocommerceStockStatus' ); $product_attributes = $request->get_param( 'woocommerceAttributes' ); $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. // Most likely this argument is being accessed in the test environment image. $args['author'] = ''; @@ -219,6 +220,7 @@ class ProductCollection extends AbstractBlock { 'stock_status' => $stock_status, 'product_attributes' => $product_attributes, 'handpicked_products' => $handpicked_products, + 'timeFrame' => $time_frame, ) ); } @@ -305,6 +307,7 @@ class ProductCollection extends AbstractBlock { $product_attributes = $query['woocommerceAttributes'] ?? []; $taxonomies_query = $this->get_filter_by_taxonomies_query( $query['tax_query'] ?? [] ); $handpicked_products = $query['woocommerceHandPickedProducts'] ?? []; + $time_frame = $query['timeFrame'] ?? null; $final_query = $this->get_final_query_args( $common_query_values, @@ -315,6 +318,7 @@ class ProductCollection extends AbstractBlock { 'product_attributes' => $product_attributes, 'taxonomies_query' => $taxonomies_query, 'handpicked_products' => $handpicked_products, + 'timeFrame' => $time_frame, ), $is_exclude_applied_filters ); @@ -338,11 +342,12 @@ class ProductCollection extends AbstractBlock { $attributes_query = $this->get_product_attributes_query( $query['product_attributes'] ); $taxonomies_query = $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. $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 ); } @@ -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, + ), + ), + ); + } + + }