diff --git a/framework/vendure/api/operations/get-all-products.ts b/framework/vendure/api/operations/get-all-products.ts index 1f558a7cb..5fb458f67 100644 --- a/framework/vendure/api/operations/get-all-products.ts +++ b/framework/vendure/api/operations/get-all-products.ts @@ -14,7 +14,7 @@ export default function getAllProductsOperation({ variables?: ProductVariables config?: Partial preview?: boolean - }): Promise<{ products: Product[] }> + }): Promise<{ products: Product[], totalItems: number }> async function getAllProducts({ query = getAllProductsQuery, @@ -25,7 +25,7 @@ export default function getAllProductsOperation({ variables?: ProductVariables config?: Partial preview?: boolean - } = {}): Promise<{ products: Product[] | any[] }> { + } = {}): Promise<{ products: Product[] | any[], totalItems: number }> { const config = commerce.getConfig(cfg) const variables = { input: { @@ -40,6 +40,7 @@ export default function getAllProductsOperation({ return { products: data.search.items.map((item) => normalizeSearchResult(item)), + totalItems: data.search.totalItems as number, } } diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts index 5faa87b43..8f49e2d2d 100644 --- a/framework/vendure/schema.d.ts +++ b/framework/vendure/schema.d.ts @@ -3219,7 +3219,8 @@ export type GetAllProductsQueryVariables = Exact<{ export type GetAllProductsQuery = { __typename?: 'Query' } & { search: { __typename?: 'SearchResponse' } & { - items: Array<{ __typename?: 'SearchResult' } & SearchResultFragment> + items: Array<{ __typename?: 'SearchResult' } & SearchResultFragment>, + 'totalItems' } } diff --git a/framework/vendure/utils/queries/get-all-products-query.ts b/framework/vendure/utils/queries/get-all-products-query.ts index 1b44b2017..007c6594d 100644 --- a/framework/vendure/utils/queries/get-all-products-query.ts +++ b/framework/vendure/utils/queries/get-all-products-query.ts @@ -3,6 +3,7 @@ import { searchResultFragment } from '../fragments/search-result-fragment' export const getAllProductsQuery = /* GraphQL */ ` query getAllProducts($input: SearchInput!) { search(input: $input) { + totalItems items { ...SearchResult } diff --git a/pages/products.tsx b/pages/products.tsx index 93dda5cdb..9559538df 100644 --- a/pages/products.tsx +++ b/pages/products.tsx @@ -13,16 +13,19 @@ import ProductListBanner from '../src/components/modules/product-list/ProductLis interface Props { facets: Facet[], collections: Collection[], - products: ProductCard[], + productsResult: { products: ProductCard[], totalItems: number }, } -export default function Products({ facets, collections, products }: Props) { - // console.log("facets: ", products) +export default function Products({ facets, collections, productsResult }: Props) { return ( <> - + ) @@ -70,7 +73,7 @@ export async function getStaticProps({ config, preview, }) - promisesWithKey.push({ key: 'products', promise: productsPromise, keyResult: 'products' }) + promisesWithKey.push({ key: 'productsResult', promise: productsPromise }) try { diff --git a/src/components/common/EmptyCommon/EmptyCommon.module.scss b/src/components/common/EmptyCommon/EmptyCommon.module.scss index a31ba4374..4014faeea 100644 --- a/src/components/common/EmptyCommon/EmptyCommon.module.scss +++ b/src/components/common/EmptyCommon/EmptyCommon.module.scss @@ -4,13 +4,16 @@ padding: 1.6rem; margin: auto; .imgWrap { - min-width: 10rem; + min-height: 10rem; text-align: center; + img { + min-height: 10rem; + } } .description { color: var(--disabled); text-align: center; - margin-top: .8rem; + margin-top: 0.8rem; } } diff --git a/src/components/common/MenuNavigation/MenuNavigation.tsx b/src/components/common/MenuNavigation/MenuNavigation.tsx index 669eacf59..a7cc9a3d7 100644 --- a/src/components/common/MenuNavigation/MenuNavigation.tsx +++ b/src/components/common/MenuNavigation/MenuNavigation.tsx @@ -6,9 +6,10 @@ interface Props { heading: string, queryKey: string, categories: { name: string, slug?: string, code?: string }[] + isSingleSelect?: boolean } -const MenuNavigation = ({ heading, queryKey, categories }: Props) => { +const MenuNavigation = ({ heading, queryKey, categories, isSingleSelect }: Props) => { return (

{heading}({categories.length})

@@ -19,6 +20,7 @@ const MenuNavigation = ({ heading, queryKey, categories }: Props) => { name={item.name} value={item.slug || item.code || ''} queryKey={queryKey} + isSingleSelect={isSingleSelect} />) } diff --git a/src/components/common/MenuNavigation/MenuNavigationItem/MenuNavigationItem.tsx b/src/components/common/MenuNavigation/MenuNavigationItem/MenuNavigationItem.tsx index c7aeba724..e1abee867 100644 --- a/src/components/common/MenuNavigation/MenuNavigationItem/MenuNavigationItem.tsx +++ b/src/components/common/MenuNavigation/MenuNavigationItem/MenuNavigationItem.tsx @@ -1,16 +1,18 @@ import classNames from 'classnames' import { useRouter } from 'next/router' import { useEffect, useState } from 'react' -import { QUERY_SPLIT_SEPERATOR, ROUTE } from 'src/utils/constanst.utils' +import { QUERY_KEY, QUERY_SPLIT_SEPERATOR, ROUTE } from 'src/utils/constanst.utils' import s from './MenuNavigationItem.module.scss' interface Props { name: string value: string - queryKey: string, + queryKey: string + isSingleSelect?: boolean + } -const MenuNavigationItem = ({ name, value, queryKey }: Props) => { +const MenuNavigationItem = ({ name, value, queryKey, isSingleSelect }: Props) => { const router = useRouter() const [isActive, setIsActive] = useState() @@ -26,21 +28,30 @@ const MenuNavigationItem = ({ name, value, queryKey }: Props) => { const handleClick = () => { const queryString = router.query[queryKey] as string || '' const prevQuery = queryString.split(QUERY_SPLIT_SEPERATOR) - - let newQuery = [] as string[] - if (isActive) { - newQuery = prevQuery.filter(item => item !== value) + + let newQuery = '' + if (isSingleSelect) { + newQuery = isActive ? '' : value } else { - newQuery = [...prevQuery, value] + if (isActive) { + newQuery = prevQuery.filter(item => item !== value).join(QUERY_SPLIT_SEPERATOR) + } else { + newQuery = [...prevQuery, value].join(QUERY_SPLIT_SEPERATOR) + } + } + + const query = { + ...router.query, + [queryKey]: newQuery + } + + if (queryKey === QUERY_KEY.CATEGORY) { + query[QUERY_KEY.PAGE] = "0" } - // setIsActive(!isActive) router.push({ pathname: ROUTE.PRODUCTS, - query: { - ...router.query, - [queryKey]: newQuery.join(QUERY_SPLIT_SEPERATOR) - } + query }, undefined, { shallow: true } ) @@ -50,8 +61,6 @@ const MenuNavigationItem = ({ name, value, queryKey }: Props) => { onClick={handleClick}> {name} ) - - } export default MenuNavigationItem diff --git a/src/components/common/ProductList/ProductList.module.scss b/src/components/common/ProductList/ProductList.module.scss index e58b37f86..b032f409d 100644 --- a/src/components/common/ProductList/ProductList.module.scss +++ b/src/components/common/ProductList/ProductList.module.scss @@ -1,10 +1,19 @@ -.wrapper{ +.wrapper { margin-top: 4rem; - .list{ + .list { @apply flex flex-wrap justify-around; + .empty { + button { + margin-top: 1.6rem; + width: 100%; + } + } } - .pagination{ + .pagination { padding-top: 4.8rem; - @apply flex justify-center items-center ; + @apply flex justify-center items-center; + &.hide { + @apply hidden; + } } -} \ No newline at end of file +} diff --git a/src/components/common/ProductList/ProductList.tsx b/src/components/common/ProductList/ProductList.tsx index 0c5deb91e..c901b4d46 100644 --- a/src/components/common/ProductList/ProductList.tsx +++ b/src/components/common/ProductList/ProductList.tsx @@ -1,28 +1,50 @@ -import React, { useState } from 'react' -import { DEFAULT_PAGE_SIZE } from 'src/utils/constanst.utils' +import classNames from 'classnames' +import { useRouter } from 'next/router' +import React from 'react' +import { DEFAULT_PAGE_SIZE, ROUTE } from 'src/utils/constanst.utils' +import { ButtonCommon, EmptyCommon } from '..' import PaginationCommon from '../PaginationCommon/PaginationCommon' import ProductCard, { ProductCardProps } from '../ProductCard/ProductCard' import s from "./ProductList.module.scss" + interface ProductListProps { - data: ProductCardProps[] + data: ProductCardProps[], + total?: number, + defaultCurrentPage?: number + onPageChange?: (page: number) => void } -const ProductList = ({data}: ProductListProps) => { - const [currentPage, setCurrentPage] = useState(0) - const onPageChange = (page:number) => { - setCurrentPage(page) +const ProductList = ({ data, total = data.length, defaultCurrentPage, onPageChange }: ProductListProps) => { + const router = useRouter() + const handlePageChange = (page: number) => { + onPageChange && onPageChange(page) } + + const handleShowAllProduct = () => { + router.push({ + pathname: ROUTE.PRODUCTS, + }, + undefined, { shallow: true } + ) + } + return (
{ - data.slice(currentPage*DEFAULT_PAGE_SIZE,(currentPage+1)* DEFAULT_PAGE_SIZE).map((product,index)=>{ - return + data.map((product, index) => { + return }) } + { + data.length === 0 &&
+ + Show all products +
+ }
-
- +
+
) diff --git a/src/components/hooks/product/index.ts b/src/components/hooks/product/index.ts new file mode 100644 index 000000000..ea2afe03a --- /dev/null +++ b/src/components/hooks/product/index.ts @@ -0,0 +1,3 @@ +export { default as useSearchProducts } from './useSearchProducts' + + diff --git a/src/components/hooks/product/useSearchProducts.tsx b/src/components/hooks/product/useSearchProducts.tsx new file mode 100644 index 000000000..e9d2ca127 --- /dev/null +++ b/src/components/hooks/product/useSearchProducts.tsx @@ -0,0 +1,14 @@ +import { GetAllProductsQuery, QuerySearchArgs } from '@framework/schema' +import { normalizeSearchResult } from '@framework/utils/normalize' +import { getAllProductsQuery } from '@framework/utils/queries/get-all-products-query' +import gglFetcher from 'src/utils/gglFetcher' +import useSWR from 'swr' + +const useSearchProducts = (options?: QuerySearchArgs) => { + const { data, isValidating, ...rest } = useSWR([getAllProductsQuery, options], gglFetcher) + console.log("on search ", data?.search.totalItems, options, data?.search.items) + + return { products: data?.search.items.map((item) => normalizeSearchResult(item)), totalItems: data?.search.totalItems, loading: isValidating, ...rest } +} + +export default useSearchProducts diff --git a/src/components/modules/product-list/ProductListFilter/ProductListFilter.module.scss b/src/components/modules/product-list/ProductListFilter/ProductListFilter.module.scss index fea3af574..84951b540 100644 --- a/src/components/modules/product-list/ProductListFilter/ProductListFilter.module.scss +++ b/src/components/modules/product-list/ProductListFilter/ProductListFilter.module.scss @@ -14,12 +14,12 @@ @apply flex; } .list{ - @screen md { - @apply flex justify-between flex-wrap w-full; - margin: 1rem 0; - } - @screen xl { - width:75%; + @apply w-full; + .top { + @screen md { + @apply flex justify-between flex-wrap w-full; + margin: 1rem 0; + } } .inner{ @screen md { diff --git a/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx b/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx index 827ff966c..e35df74e1 100644 --- a/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx +++ b/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx @@ -1,8 +1,13 @@ import { ProductCard } from '@commerce/types/product' -import { Collection, Facet } from '@framework/schema' -import React from 'react' +import { Collection, Facet, FacetValue, QuerySearchArgs } from '@framework/schema' +import { useRouter } from 'next/router' +import React, { useEffect, useState } from 'react' import { HeadingCommon, ProductList } from 'src/components/common' import BreadcrumbCommon from 'src/components/common/BreadcrumbCommon/BreadcrumbCommon' +import SkeletonImage from 'src/components/common/SkeletonCommon/SkeletonImage/SkeletonImage' +import { useSearchProducts } from 'src/components/hooks/product' +import { DEFAULT_PAGE_SIZE, QUERY_KEY, QUERY_SPLIT_SEPERATOR, ROUTE } from 'src/utils/constanst.utils' +import { getFacetIdsFromCodes, getPageFromQuery } from 'src/utils/funtion.utils' import s from './ProductListFilter.module.scss' import ProductsMenuNavigationTablet from './ProductsMenuNavigationTablet/ProductsMenuNavigationTablet' import ProductSort from './ProductSort/ProductSort' @@ -11,6 +16,7 @@ interface ProductListFilterProps { facets: Facet[] collections: Collection[] products: ProductCard[] + total: number } @@ -21,7 +27,64 @@ const BREADCRUMB = [ }, ] -const ProductListFilter = ({ facets, collections, products }: ProductListFilterProps) => { + +const DEFAULT_SEARCH_ARGS = { + groupByProduct: true, take: DEFAULT_PAGE_SIZE +} + +const ProductListFilter = ({ facets, collections, products, total }: ProductListFilterProps) => { + const router = useRouter() + const [initialQueryFlag, setInitialQueryFlag] = useState(true) + const [optionQueryProduct, setOptionQueryProduct] = useState({ input: DEFAULT_SEARCH_ARGS }) + const { products: productSearchResult, totalItems, loading } = useSearchProducts(optionQueryProduct) + const [currentPage, setCurrentPage] = useState(0) + + useEffect(() => { + const page = getPageFromQuery(router.query[QUERY_KEY.PAGE] as string) + setCurrentPage(page) + }, [router.query]) + + const onPageChange = (page: number) => { + setCurrentPage(page) + + router.push({ + pathname: ROUTE.PRODUCTS, + query: { + ...router.query, + [QUERY_KEY.PAGE]: page + } + }, + undefined, { shallow: true } + ) + } + + useEffect(() => { + const query = { input: { ...DEFAULT_SEARCH_ARGS } } as QuerySearchArgs + + const page = getPageFromQuery(router.query[QUERY_KEY.PAGE] as string) + query.input.skip = page * DEFAULT_PAGE_SIZE + + // collections + const categoryQuery = router.query[QUERY_KEY.CATEGORY] as string + if (categoryQuery) { + query.input.collectionSlug = categoryQuery + } + + // facets + const facetsQuery = [router.query[QUERY_KEY.FEATURED] as string, router.query[QUERY_KEY.BRAND] as string].join(QUERY_SPLIT_SEPERATOR) + if (facetsQuery) { + const facetsValue = [] as FacetValue[] + facets.map((item: Facet) => { + facetsValue.push(...item.values) + return null + }) + + query.input.facetValueIds = getFacetIdsFromCodes(facetsValue, facetsQuery.split(QUERY_SPLIT_SEPERATOR)) + } + + setOptionQueryProduct(query) + setInitialQueryFlag(false) + }, [router.query, facets]) return (
@@ -31,12 +94,17 @@ const ProductListFilter = ({ facets, collections, products }: ProductListFilterP
- SPECIAL RECIPES +
+ SPECIAL RECIPES -
- +
+ +
- + { + (!initialQueryFlag && loading && !productSearchResult) && + } +
diff --git a/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.tsx b/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.tsx index 47127d53e..d31609639 100644 --- a/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.tsx +++ b/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.tsx @@ -13,7 +13,11 @@ interface Props { const ProductsMenuNavigationTablet = ({ facets, collections }: Props) => { return (
- + { facets.map(item => (arr: Array, value: T): Array { const index = arr.indexOf(value); if (index > -1) { @@ -58,6 +71,16 @@ export function getFacetNamesFromIds(facets: FacetValue[], ids?: string[]): stri return names.join(", ") } -export function getAllPromies (promies: PromiseWithKey[]) { +export function getFacetIdsFromCodes(facets: FacetValue[], codes?: string[]): string[] { + if (!codes || codes?.length === 0) { + return [] + } + + const facetItems = facets.filter((item: FacetValue) => codes.includes(item.code)) + const ids = facetItems.map((item: FacetValue) => item.id) + return ids +} + +export function getAllPromies(promies: PromiseWithKey[]) { return promies.map(item => item.promise) }