feat: filter product

:%s
This commit is contained in:
lytrankieio123
2021-10-06 17:44:36 +07:00
parent c49ba5062a
commit a91417eca9
16 changed files with 222 additions and 58 deletions

View File

@@ -14,7 +14,7 @@ export default function getAllProductsOperation({
variables?: ProductVariables
config?: Partial<VendureConfig>
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<VendureConfig>
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,
}
}

View File

@@ -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'
}
}

View File

@@ -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
}

View File

@@ -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 (
<>
<ProductListBanner />
<ProductListFilter collections={collections} facets={facets} products={products} />
<ProductListFilter
collections={collections}
facets={facets}
products={productsResult.products}
total={productsResult.totalItems} />
<ViewedProducts />
</>
)
@@ -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 {

View File

@@ -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;
}
}

View File

@@ -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 (
<section className={s.menuNavigationWrapper}>
<h2 className={s.menuNavigationHeading}>{heading}({categories.length})</h2>
@@ -19,6 +20,7 @@ const MenuNavigation = ({ heading, queryKey, categories }: Props) => {
name={item.name}
value={item.slug || item.code || ''}
queryKey={queryKey}
isSingleSelect={isSingleSelect}
/>)
}
</ul>

View File

@@ -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<boolean>()
@@ -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}
</li>)
}
export default MenuNavigationItem

View File

@@ -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;
}
}
}
}

View File

@@ -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 (
<div className={s.wrapper}>
<div className={s.list}>
{
data.slice(currentPage*DEFAULT_PAGE_SIZE,(currentPage+1)* DEFAULT_PAGE_SIZE).map((product,index)=>{
return <ProductCard {...product} key={index}/>
data.map((product, index) => {
return <ProductCard {...product} key={index} />
})
}
{
data.length === 0 && <div className={s.empty}>
<EmptyCommon />
<ButtonCommon onClick={handleShowAllProduct}>Show all products</ButtonCommon>
</div>
}
</div>
<div className={s.pagination}>
<PaginationCommon total={data.length} pageSize={DEFAULT_PAGE_SIZE} onChange={onPageChange}/>
<div className={classNames(s.pagination, { [s.hide]: data.length === 0 })}>
<PaginationCommon defaultCurrent={defaultCurrentPage} total={total} pageSize={DEFAULT_PAGE_SIZE} onChange={handlePageChange} />
</div>
</div>
)

View File

@@ -0,0 +1,3 @@
export { default as useSearchProducts } from './useSearchProducts'

View File

@@ -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>([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

View File

@@ -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 {

View File

@@ -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<boolean>(true)
const [optionQueryProduct, setOptionQueryProduct] = useState<QuerySearchArgs>({ 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 (
<div className={s.warpper}>
@@ -31,12 +94,17 @@ const ProductListFilter = ({ facets, collections, products }: ProductListFilterP
<div className={s.main}>
<ProductsMenuNavigationTablet facets={facets} collections={collections} />
<div className={s.list}>
<HeadingCommon align="left">SPECIAL RECIPES</HeadingCommon>
<div className={s.top}>
<HeadingCommon align="left">SPECIAL RECIPES</HeadingCommon>
<div className={s.boxSelect}>
<ProductSort/>
<div className={s.boxSelect}>
<ProductSort />
</div>
</div>
<ProductList data={products} />
{
(!initialQueryFlag && loading && !productSearchResult) && <SkeletonImage />
}
<ProductList data={initialQueryFlag ? products : (productSearchResult || [])} total={totalItems !== undefined ? totalItems : total} onPageChange={onPageChange} defaultCurrentPage={currentPage} />
</div>
</div>
</div>

View File

@@ -13,7 +13,11 @@ interface Props {
const ProductsMenuNavigationTablet = ({ facets, collections }: Props) => {
return (
<div className={s.productsMenuNavigationTablet}>
<MenuNavigation categories={collections} heading="Categories" queryKey={QUERY_KEY.CATEGORY} />
<MenuNavigation
heading="Categories"
categories={collections}
queryKey={QUERY_KEY.CATEGORY}
isSingleSelect={true} />
{
facets.map(item => <MenuNavigation
key={item.id}

View File

@@ -52,7 +52,8 @@ export const QUERY_KEY = {
BRAND: 'brand',
FEATURED: 'featured',
SORTBY: 'sortby',
RECIPES: 'recipes'
RECIPES: 'recipes',
PAGE: 'page',
}
export const PRODUCT_SORT_OPTION_VALUE = {

View File

@@ -7,6 +7,19 @@ export function isMobile() {
return window.innerWidth < 768
}
export function getPageFromQuery(pageQuery: string) {
let page = 0
try {
page = +pageQuery
if (isNaN(page)) {
page = 0
}
} catch (err) {
page = 0
}
return page
}
export function removeItem<T>(arr: Array<T>, value: T): Array<T> {
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)
}