diff --git a/packages/peregrine/lib/talons/RootComponents/Category/__tests__/__snapshots__/useCategoryContent.spec.js.snap b/packages/peregrine/lib/talons/RootComponents/Category/__tests__/__snapshots__/useCategoryContent.spec.js.snap index 8aa379def7..9ddebf6b30 100644 --- a/packages/peregrine/lib/talons/RootComponents/Category/__tests__/__snapshots__/useCategoryContent.spec.js.snap +++ b/packages/peregrine/lib/talons/RootComponents/Category/__tests__/__snapshots__/useCategoryContent.spec.js.snap @@ -5,6 +5,7 @@ Object { "availableSortMethods": null, "categoryDescription": "Jewelry category", "categoryName": "Jewelry", + "filterOptions": undefined, "filters": null, "items": Array [ null, @@ -17,6 +18,7 @@ Object { null, null, ], + "setFilterOptions": [Function], "totalCount": null, "totalPagesFromData": null, } @@ -32,6 +34,7 @@ Object { ], "categoryDescription": "Jewelry category", "categoryName": "Jewelry", + "filterOptions": undefined, "filters": Array [ Object { "label": "Label", @@ -47,6 +50,7 @@ Object { "name": "Necklace", }, ], + "setFilterOptions": [Function], "totalCount": 2, "totalPagesFromData": 1, } diff --git a/packages/peregrine/lib/talons/RootComponents/Category/__tests__/useCategoryContent.spec.js b/packages/peregrine/lib/talons/RootComponents/Category/__tests__/useCategoryContent.spec.js index d22cfd685a..7f3fd22ac6 100644 --- a/packages/peregrine/lib/talons/RootComponents/Category/__tests__/useCategoryContent.spec.js +++ b/packages/peregrine/lib/talons/RootComponents/Category/__tests__/useCategoryContent.spec.js @@ -26,6 +26,18 @@ jest.mock('@apollo/client', () => { }; }); +const mockGetFiltersAttributeCode = { + data: { + products: { + aggregations: [ + { + label: 'Label' + } + ] + } + } +}; + const mockProductFiltersByCategoryData = { data: { products: { @@ -91,6 +103,7 @@ const mockCategoryData = { const mockGetSortMethods = jest.fn(); const mockGetFilters = jest.fn(); +const mockfilterData = jest.fn(); jest.mock('@magento/peregrine/lib/context/eventing', () => ({ useEventingContext: jest.fn().mockReturnValue([{}, { dispatch: jest.fn() }]) @@ -98,7 +111,6 @@ jest.mock('@magento/peregrine/lib/context/eventing', () => ({ const Component = props => { const talonprops = useCategoryContent(props); - return ; }; @@ -106,6 +118,7 @@ useQuery.mockReturnValue({ data: mockCategoryData }); describe('useCategoryContent tests', () => { it('returns the proper shape', () => { useLazyQuery + .mockReturnValueOnce([mockfilterData, mockGetFiltersAttributeCode]) .mockReturnValueOnce([ mockGetFilters, mockProductFiltersByCategoryData @@ -124,6 +137,7 @@ describe('useCategoryContent tests', () => { it('handles default category id', () => { useLazyQuery + .mockReturnValueOnce([mockfilterData, mockGetFiltersAttributeCode]) .mockReturnValueOnce([ mockGetFilters, mockProductFiltersByCategoryData diff --git a/packages/peregrine/lib/talons/RootComponents/Category/categoryContent.gql.js b/packages/peregrine/lib/talons/RootComponents/Category/categoryContent.gql.js index 12bc33fd42..eb7a7e36fa 100644 --- a/packages/peregrine/lib/talons/RootComponents/Category/categoryContent.gql.js +++ b/packages/peregrine/lib/talons/RootComponents/Category/categoryContent.gql.js @@ -1,9 +1,7 @@ import { gql } from '@apollo/client'; export const GET_PRODUCT_FILTERS_BY_CATEGORY = gql` - query getProductFiltersByCategory( - $filters: ProductAttributeFilterInput! - ) { + query getProductFiltersByCategory($filters: ProductAttributeFilterInput!) { products(filter: $filters) { aggregations { label diff --git a/packages/peregrine/lib/talons/RootComponents/Category/useCategoryContent.js b/packages/peregrine/lib/talons/RootComponents/Category/useCategoryContent.js index 03527a110b..659fdb400a 100644 --- a/packages/peregrine/lib/talons/RootComponents/Category/useCategoryContent.js +++ b/packages/peregrine/lib/talons/RootComponents/Category/useCategoryContent.js @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useMemo } from 'react'; import { useLazyQuery, useQuery } from '@apollo/client'; import mergeOperations from '../../../util/shallowMerge'; @@ -29,39 +29,101 @@ export const useCategoryContent = props => { getCategoryAvailableSortMethodsQuery } = operations; - const placeholderItems = Array.from({ length: pageSize }).fill(null); + const [ + getFiltersAttributeCode, + { data: filterAttributeData } + ] = useLazyQuery(getProductFiltersByCategoryQuery, { + fetchPolicy: 'cache-and-network', + nextFetchPolicy: 'cache-first' + }); - const [getFilters, { data: filterData }] = useLazyQuery( - getProductFiltersByCategoryQuery, - { - fetchPolicy: 'cache-and-network', - nextFetchPolicy: 'cache-first' + useEffect(() => { + if (categoryId) { + getFiltersAttributeCode({ + variables: { + filters: { + category_uid: { eq: categoryId } + } + } + }); } - ); + }, [categoryId, getFiltersAttributeCode]); - const [filterOptions, setFilterOptions] = useState(); - - const availableFilterData = filterData - ? filterData.products?.aggregations + const availableFilterData = filterAttributeData + ? filterAttributeData.products?.aggregations : null; const availableFilters = availableFilterData ?.map(eachitem => eachitem.attribute_code) ?.sort(); - const selectedFilters = {}; - - if (filterOptions) { - for (const [group, items] of filterOptions) { - availableFilters?.map(eachitem => { - if (eachitem === group) { - const sampleArray = []; - for (const item of items) { - sampleArray.push(item.value); + + const handlePriceFilter = priceFilter => { + if (priceFilter && priceFilter.size > 0) { + for (const price of priceFilter) { + const [from, to] = price.value.split('_'); + return { price: { from, to } }; + } + } + return {}; + }; + + const [filterOptions, setFilterOptions] = useState(); + + const selectedFilters = useMemo(() => { + const filters = {}; + if (filterOptions) { + for (const [group, items] of filterOptions.entries()) { + availableFilters?.map(eachitem => { + if (eachitem === group && group !== 'price') { + const sampleArray = []; + for (const item of items) { + sampleArray.push(item.value); + } + filters[group] = sampleArray; } - selectedFilters[group] = sampleArray; + }); + } + } + + if (filterOptions && filterOptions.has('price')) { + const priceFilter = filterOptions.get('price'); + const priceRange = handlePriceFilter(priceFilter); + if (priceRange.price) { + filters.price = priceRange.price; + } + } + + return filters; + }, [filterOptions, availableFilters]); + + const dynamicQueryVariables = useMemo(() => { + const generateDynamicFiltersQuery = filterParams => { + let filterConditions = { + category_uid: { eq: categoryId } + }; + + Object.keys(filterParams).forEach(key => { + let filter = {}; + if (key !== 'price') { + filter = { [key]: { in: filterParams[key] } }; } + filterConditions = { ...filterConditions, ...filter }; }); + + return filterConditions; + }; + + return generateDynamicFiltersQuery(selectedFilters); + }, [selectedFilters, categoryId]); + + const placeholderItems = Array.from({ length: pageSize }).fill(null); + + const [getFilters, { data: filterData }] = useLazyQuery( + getProductFiltersByCategoryQuery, + { + fetchPolicy: 'cache-and-network', + nextFetchPolicy: 'cache-first' } - } + ); const [getSortMethods, { data: sortData }] = useLazyQuery( getCategoryAvailableSortMethodsQuery, @@ -84,29 +146,26 @@ export const useCategoryContent = props => { ); const [, { dispatch }] = useEventingContext(); - + const [previousFilters, setPreviousFilters] = useState(null); useEffect(() => { - if (categoryId) { - let dynamicFilters = { - category_uid: { eq: categoryId } - }; - Object.keys(selectedFilters).forEach(key => { - let filter = {}; - if (key === 'price') { - let [from, to] = selectedFilters[key][0].split('_'); - filter = { [key]: { from: from, to: to }}; - } else { - filter = { [key]: { in: selectedFilters[key]}}; - } - dynamicFilters = { ...dynamicFilters, ...filter }; - }); + if ( + categoryId && + JSON.stringify(selectedFilters) !== JSON.stringify(previousFilters) + ) { getFilters({ variables: { - filters: dynamicFilters + filters: dynamicQueryVariables } }); + setPreviousFilters(selectedFilters); } - }, [categoryId, getFilters]); + }, [ + categoryId, + selectedFilters, + dynamicQueryVariables, + previousFilters, + getFilters + ]); useEffect(() => { if (categoryId) { @@ -120,7 +179,7 @@ export const useCategoryContent = props => { } }, [categoryId, getSortMethods]); - const filters = filterData ? filterData.products.aggregations : null; + const filters = filterData ? filterData.products?.aggregations : null; const items = data ? data.products.items : placeholderItems; const totalPagesFromData = data ? data.products.page_info.total_pages @@ -139,7 +198,7 @@ export const useCategoryContent = props => { : null; useEffect(() => { - if (!categoryLoading && categoryData.categories.items.length > 0) { + if (!categoryLoading && categoryData?.categories.items.length > 0) { dispatch({ type: 'CATEGORY_PAGE_VIEW', payload: { @@ -157,6 +216,7 @@ export const useCategoryContent = props => { categoryName, categoryDescription, filters, + filterOptions, setFilterOptions, items, totalCount, diff --git a/packages/venia-ui/lib/components/FilterSidebar/__tests__/filterSidebar.spec.js b/packages/venia-ui/lib/components/FilterSidebar/__tests__/filterSidebar.spec.js index 9fafd2bbf0..2c4e635dad 100644 --- a/packages/venia-ui/lib/components/FilterSidebar/__tests__/filterSidebar.spec.js +++ b/packages/venia-ui/lib/components/FilterSidebar/__tests__/filterSidebar.spec.js @@ -51,6 +51,8 @@ const mockHandleApply = jest.fn(); const mockScrollTo = jest.fn(); +const mockFilterOptions = jest.fn(); + const mockGetBoundingClientRect = jest.fn(); let mockFilterState; @@ -125,7 +127,8 @@ const Component = () => { const givenDefaultValues = () => { inputProps = { - filters: [] + filters: [], + setFilterOptions: mockFilterOptions }; mockFilterState = new Map(); @@ -133,13 +136,15 @@ const givenDefaultValues = () => { const givenFilters = () => { inputProps = { - filters: mockFilters + filters: mockFilters, + setFilterOptions: mockFilterOptions }; }; const givenSelectedFilters = () => { inputProps = { - filters: mockFilters + filters: mockFilters, + setFilterOptions: mockFilterOptions }; mockFilterState = new Map([['group', 'item']]); @@ -148,7 +153,8 @@ const givenSelectedFilters = () => { const givenFiltersAndAmountToShow = () => { inputProps = { filters: mockFilters, - filterCountToOpen: mockFiltersOpenCount + filterCountToOpen: mockFiltersOpenCount, + setFilterOptions: mockFilterOptions }; };