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,
+ ),
+ ),
+ );
+ }
+
+
}