Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Product Collection - Add Created filter in inspector controls #11562

Merged
merged 13 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions assets/js/blocks/product-collection/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const DEFAULT_QUERY: ProductCollectionQuery = {
woocommerceStockStatus: getDefaultStockStatuses(),
woocommerceAttributes: [],
woocommerceHandPickedProducts: [],
timeFrame: undefined,
};

export const DEFAULT_ATTRIBUTES: Partial< ProductCollectionAttributes > = {
Expand Down Expand Up @@ -86,4 +87,5 @@ export const DEFAULT_FILTERS: Partial< ProductCollectionQuery > = {
woocommerceAttributes: [],
taxQuery: DEFAULT_QUERY.taxQuery,
woocommerceHandPickedProducts: [],
timeFrame: undefined,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* External dependencies
*/
import { __ } 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={ __( 'IN', 'woo-gutenberg-products-block' ) }
/>
<ToggleGroupControlOption
value={ ETimeFrameOperator.NOT_IN }
label={ __(
'Not in',
'woo-gutenberg-products-block'
) }
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: is it intentional that IN is uppercase while Not in isn't?

imatge

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Aljullu, I've referenced the design from the provided screenshot; however, I'm unable to locate it in Figma. @kmanijak, could you please share the Figma design's URL? 🙂

To ensure consistency, we could:

  • Capitalize all letters: IN & NOT IN
  • Or capitalize only the first letter: In & Not in

@manospsyx What do you think? 🙂

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say to use capitalized letters since the "In" might not look great, but I'd wait for Mano's input!

Another nit: It would be better to use the _x() function with context instead of the generic __(). These words are small, making it difficult to understand what is actually being translated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably:

  • capitalize both (preferably via CSS; it's not great to rely on localizable strings for styling), and
  • use a slightly smaller font size, perhaps by 5-10%, or 1-2px :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xristos3490

Another nit: It would be better to use the _x() function with context instead of the generic __(). These words are small, making it difficult to understand what is actually being translated.

Makes sense to me. What do you think about the following contexts?

_x('Not in', 'Exclude products created within a certain time frame', 'woo-gutenberg-products-block');

_x('In', 'Include products created within a certain time frame', 'woo-gutenberg-products-block');

I can make the changes once we finalize the context text.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good practice for translators to have context when dealing with short phrases. There is no set rule for when to use the _x() function, but I believe this is a valid reason to use it.

That said, It's also advisable to provide context for other instances here like "Stack" or "Grid".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xristos3490 Makes sense. Let's try to figure out what text we can use for context 🙂

_x('Not in', 'Product Collection query operator', 'woo-gutenberg-products-block');
_x('In', 'Product Collection query operator', 'woo-gutenberg-products-block');

This doesn't seem relevant anymore as we have changed the text to "Within" & "Before". What alternative text you would suggest? Here is my suggestion:

_x('Within', 'filter option for products created within a certain time frame', 'woo-gutenberg-products-block');
_x('Before', 'filter option for products created before a certain time', 'woo-gutenberg-products-block');

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, It's also advisable to provide context for other instances here like "Stack" or "Grid".

Makes sense. What do you think about the following?

_x('Stack', 'layout style for displaying products in a single column', 'woo-gutenberg-products-block');
_x('Grid', 'layout style for displaying products in multiple columns', 'woo-gutenberg-products-block');

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, providing an exact description of the context could result in excessive wordiness. A simple term such as "Product Collection query operator" should suffice for translators to comprehend the intended meaning. However, I do not have a strong preference, so I leave it up to you to decide. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a strong opinion either. I will go ahead and add "Product Collection query operator". Thanks 🙏🏻

</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;
Original file line number Diff line number Diff line change
Expand Up @@ -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 >
Expand Down Expand Up @@ -98,6 +99,7 @@ const ProductCollectionInspectorControls = (
<KeywordControl { ...queryControlProps } />
<AttributesControl { ...queryControlProps } />
<TaxonomyControls { ...queryControlProps } />
<CreatedControl { ...queryControlProps } />
</ToolsPanel>
) : null }
<ProductCollectionFeedbackPrompt />
Expand Down
11 changes: 11 additions & 0 deletions assets/js/blocks/product-collection/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down
41 changes: 40 additions & 1 deletion src/BlockTypes/ProductCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ public function update_rest_query_in_editor( $args, $request ): array {
$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'] = '';
Expand All @@ -219,6 +220,7 @@ public function update_rest_query_in_editor( $args, $request ): array {
'stock_status' => $stock_status,
'product_attributes' => $product_attributes,
'handpicked_products' => $handpicked_products,
'timeFrame' => $time_frame,
)
);
}
Expand Down Expand Up @@ -305,6 +307,7 @@ private function get_final_frontend_query( $query, $page = 1, $is_exclude_applie
$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,
Expand All @@ -315,6 +318,7 @@ private function get_final_frontend_query( $query, $page = 1, $is_exclude_applie
'product_attributes' => $product_attributes,
'taxonomies_query' => $taxonomies_query,
'handpicked_products' => $handpicked_products,
'timeFrame' => $time_frame,
),
$is_exclude_applied_filters
);
Expand All @@ -338,11 +342,12 @@ private function get_final_query_args( $common_query_values, $query, $is_exclude
$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 );
}
Expand Down Expand Up @@ -959,4 +964,38 @@ function( $rating ) use ( $product_visibility_terms ) {
),
);
}

/**
* 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',
imanish003 marked this conversation as resolved.
Show resolved Hide resolved
$query_operator => $time_frame['value'],
'inclusive' => true,
),
),
);
}


}