diff options
| author | trisusilo <tri.susilo@altama.co.id> | 2023-10-24 03:07:57 +0000 |
|---|---|---|
| committer | trisusilo <tri.susilo@altama.co.id> | 2023-10-24 03:07:57 +0000 |
| commit | e6c0f277f47b6aeaa55a19216615840f61bfeb1e (patch) | |
| tree | ebaef18f5b7d6925f8eddd029df336fe408f6261 | |
| parent | bf33b9a9493aeab84e72647fad384bed43feabd5 (diff) | |
| parent | 7c08e60acc68f4fbd3ff6af756aeae7c1e7b866c (diff) | |
Merged in CR/UI (pull request #110)
CR/UI
| -rw-r--r-- | src/core/utils/formatValue.js | 11 | ||||
| -rw-r--r-- | src/lib/brand/components/Brand.jsx | 24 | ||||
| -rw-r--r-- | src/lib/product/components/ProductFilterDesktop.jsx | 10 | ||||
| -rw-r--r-- | src/lib/product/components/ProductSearch.jsx | 162 | ||||
| -rw-r--r-- | src/lib/product/hooks/useProductSearch.js | 4 | ||||
| -rw-r--r-- | src/pages/api/shop/search.js | 5 | ||||
| -rw-r--r-- | src/pages/shop/brands/[slug].jsx | 6 |
7 files changed, 163 insertions, 59 deletions
diff --git a/src/core/utils/formatValue.js b/src/core/utils/formatValue.js index f2c17769..f0f65c69 100644 --- a/src/core/utils/formatValue.js +++ b/src/core/utils/formatValue.js @@ -7,4 +7,13 @@ const sellingProductFormat = (value) => { } } -export { sellingProductFormat } +const formatCurrency = (value) => { + if (value >= 1000) { + const thousands = Math.floor(value / 1000) // Menghitung ribuan + return `Rp${thousands}k` + } else { + return `Rp${value}` + } +} + +export { sellingProductFormat, formatCurrency} diff --git a/src/lib/brand/components/Brand.jsx b/src/lib/brand/components/Brand.jsx index 4afbcb3e..78a9d5bd 100644 --- a/src/lib/brand/components/Brand.jsx +++ b/src/lib/brand/components/Brand.jsx @@ -20,8 +20,8 @@ const swiperBanner = { modules: [Pagination, Autoplay] } -const Brand = ({ id }) => { - const { brand } = useBrand({ id }) +const Brand = ({ brand }) => { + // const { brand } = useBrand({ id }) return ( <> @@ -91,7 +91,7 @@ const Brand = ({ id }) => { </MobileView> <DesktopView> - <div className='container mx-auto'> + <div className='container mx-auto mb-10'> <Skeleton isLoaded={!brand.isLoading} aspectRatio='4/1' @@ -131,23 +131,7 @@ const Brand = ({ id }) => { ))} </Swiper> - <div className='p-4'> - <div className='text-caption-1 text-gray_r-11 mb-2'>Produk dari brand:</div> - {brand?.data?.logo && ( - <Image - src={brand?.data?.logo} - alt={brand?.data?.name} - className='w-32 p-2 border borde-gray_r-6 rounded' - width={1024} - height={512} - /> - )} - {!brand?.data?.logo && ( - <div className='bg-danger-500 text-white text-center text-body-1 py-2 px-4 rounded w-fit'> - {brand?.data?.name} - </div> - )} - </div> + </> )} </Skeleton> diff --git a/src/lib/product/components/ProductFilterDesktop.jsx b/src/lib/product/components/ProductFilterDesktop.jsx index 6118ed6b..e4a62abb 100644 --- a/src/lib/product/components/ProductFilterDesktop.jsx +++ b/src/lib/product/components/ProductFilterDesktop.jsx @@ -18,6 +18,7 @@ import { VStack } from '@chakra-ui/react' import Image from '@/core/components/elements/Image/Image' +import { formatCurrency } from '@/core/utils/formatValue' const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = null }) => { const router = useRouter() @@ -104,14 +105,7 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu router.push(`${prefixUrl}?${params}`) } - const formatCurrency = (value) => { - if (value >= 1000) { - const thousands = Math.floor(value / 1000) // Menghitung ribuan - return `Rp${thousands}k` - } else { - return `Rp${value}` - } - } + /*const handleIndexAccordion = async () => { if (brandValues) { diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx index 9d59b305..3740d264 100644 --- a/src/lib/product/components/ProductSearch.jsx +++ b/src/lib/product/components/ProductSearch.jsx @@ -15,23 +15,33 @@ import { useRouter } from 'next/router' import searchSpellApi from '@/core/api/searchSpellApi' import Link from '@/core/components/elements/Link/Link' import whatsappUrl from '@/core/utils/whatsappUrl' -import { Image } from '@chakra-ui/react' +import { HStack, Image, Tag, TagCloseButton, TagLabel } from '@chakra-ui/react' import odooApi from '@/core/api/odooApi' +import { formatCurrency } from '@/core/utils/formatValue' -const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { +const ProductSearch = ({ query, prefixUrl, defaultBrand = null, brand = null }) => { const router = useRouter() const { page = 1 } = query const [q, setQ] = useState(query?.q || '*') const [limit, setLimit] = useState(query?.limit || 30) const [orderBy, setOrderBy] = useState(router.query?.orderBy || 'popular') if (defaultBrand) query.brand = defaultBrand.toLowerCase() - const { productSearch } = useProductSearch({ query: { ...query, q, limit, orderBy } }) + const { productSearch } = useProductSearch({ + query: { ...query, q, limit, orderBy }, + operation: 'AND' + }) const [products, setProducts] = useState(null) const [spellings, setSpellings] = useState(null) const [bannerPromotionHeader, setBannerPromotionHeader] = useState(null) const [bannerPromotionFooter, setBannerPromotionFooter] = useState(null) const popup = useActive() const numRows = [30, 50, 80, 100] + const [brandValues, setBrand] = useState( + !router.pathname.includes('brands') ? (query.brand ? query.brand.split(',') : []) : [] + ) + const [categoryValues, setCategory] = useState(query?.category?.split(',') || []) + const [priceFrom, setPriceFrom] = useState(query?.priceFrom || null) + const [priceTo, setPriceTo] = useState(query?.priceTo || null) const pageCount = Math.ceil(productSearch.data?.response.numFound / limit) const productStart = productSearch.data?.responseHeader.params.start @@ -80,15 +90,6 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { brands.push({ brand, qty }) } } - /*const brandsList = productSearch.data?.facetCounts?.facetFields?.manufactureName?.filter( - (value, index) => { - if (index % 2 === 0) { - const brand = value - const qty = index + 1 - brands.push({ brand, qty }) - } - } - )*/ const categories = [] for (let i = 0; i < productSearch.data?.facetCounts?.facetFields?.categoryName.length; i += 2) { @@ -98,14 +99,6 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { categories.push({ name, qty }) } } - - /*const categories = productSearch.data?.facetCounts?.facetFields?.categoryName?.filter( - (value, index) => { - if (index % 2 === 0) { - return true - } - } - )*/ const orderOptions = [ { value: 'price-asc', label: 'Harga Terendah' }, @@ -155,7 +148,7 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { const SpellingComponent = useMemo(() => { return ( <> - Mungkin yang anda cari{' '} + {spellings?.length > 0 ? <>Mungkin yang anda cari </> : <>Produk yang cari anda tidak ada</>} {spellings?.map((spelling, i) => ( <Link href={`/shop/search?q=${spelling}`} key={i} className='inline'> {spelling} @@ -166,12 +159,62 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { ) }, [spellings]) + const handleDeleteFilter = async (source, value) => { + let params = { + q: router.query.q, + orderBy: orderBy, + brand: brandValues.join(','), + category: categoryValues.join(','), + priceFrom, + priceTo + } + + let brands = brandValues + let catagories = categoryValues + switch (source) { + case 'brands': + brands = brandValues.filter((item) => item !== value) + params.brand = brands.join(',') + await setBrand(brands) + break + case 'category': + catagories = categoryValues.filter((item) => item !== value) + params.category = catagories.join(',') + await setCategory(catagories) + break + case 'price': + params.priceFrom = null + params.priceTo = null + break + case 'delete': + params = { + q: router.query.q, + orderBy: orderBy + } + break + } + + handleSubmitFilter(params) + } + const handleSubmitFilter = (params) => { + params = _.pickBy(params, _.identity) + params = toQuery(params) + router.push(`${prefixUrl}?${params}`) + } + return ( <> <MobileView> {productSearch.isLoading && <ProductSearchSkeleton />} <div className='p-4 pt-0'> <h1 className='mb-2 font-semibold text-h-sm'>Produk</h1> + <FilterChoicesComponent + brandValues={brandValues} + categoryValues={categoryValues} + priceFrom={priceFrom} + priceTo={priceTo} + handleDeleteFilter={handleDeleteFilter} + /> <div className='mb-2 leading-6 text-gray_r-11'> {!spellings ? ( @@ -252,6 +295,24 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { <DesktopView> <div className='container mx-auto flex mb-3'> <div className='w-3/12'> + {brand && ( + <div className='p-4'> + <div className='text-caption-1 text-gray_r-11 mb-2'>Produk dari brand:</div> + {brand?.data?.logo && ( + <Image + src={brand?.data?.logo} + alt={brand?.data?.name} + className='w-32 p-2 border borde-gray_r-6 rounded' + /> + )} + {!brand?.data?.logo && ( + <div className='bg-danger-500 text-white text-center text-body-1 py-2 px-4 rounded w-fit'> + {brand?.data?.name} + </div> + )} + </div> + )} + <ProductFilterDesktop brands={brands || []} categories={categories || []} @@ -271,8 +332,15 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { )} <h1 className='text-2xl mb-2 font-semibold'>Hasil Pencarian</h1> - <div className='flex justify-between items-center mb-2'> - <div className='mb-2 leading-6 text-gray_r-11'> + <FilterChoicesComponent + brandValues={brandValues} + categoryValues={categoryValues} + priceFrom={priceFrom} + priceTo={priceTo} + handleDeleteFilter={handleDeleteFilter} + /> + <div className='flex justify-between items-center mb-5'> + <div className='leading-6 text-gray_r-11'> {!spellings ? ( <> Menampilkan @@ -303,7 +371,7 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { <div className='ml-3'> <select name='urutan' - className='form-input mt-2' + className='form-input' value={orderBy} onChange={(e) => handleOrderBy(e)} > @@ -318,7 +386,7 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { <div className='ml-3'> <select name='limit' - className='form-input mt-2' + className='form-input' value={router.query?.limit || ''} onChange={(e) => handleLimit(e)} > @@ -388,3 +456,47 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { } export default ProductSearch + +const FilterChoicesComponent = ({ + brandValues, + categoryValues, + priceFrom, + priceTo, + handleDeleteFilter +}) => ( + <div className='flex items-center'> + <HStack spacing={2} className='flex-wrap'> + {brandValues.map((value, index) => ( + <Tag size='lg' key={index} borderRadius='lg' variant='outline' colorScheme='gray'> + <TagLabel>{value}</TagLabel> + <TagCloseButton onClick={() => handleDeleteFilter('brands', value)} /> + </Tag> + ))} + + {categoryValues.map((value, index) => ( + <Tag size='lg' key={index} borderRadius='lg' variant='outline' colorScheme='gray'> + <TagLabel>{value}</TagLabel> + <TagCloseButton onClick={() => handleDeleteFilter('category', value)} /> + </Tag> + ))} + {priceFrom && priceTo && ( + <Tag size='lg' borderRadius='lg' variant='outline' colorScheme='gray'> + <TagLabel>{formatCurrency(priceFrom) + '-' + formatCurrency(priceTo)}</TagLabel> + <TagCloseButton onClick={() => handleDeleteFilter('price', priceFrom)} /> + </Tag> + )} + {brandValues.length > 0 || categoryValues.length > 0 || priceFrom || priceTo ? ( + <span> + <button + className='btn-transparent py-2 px-5 h-[40px] text-red-700' + onClick={() => handleDeleteFilter('delete')} + > + Hapus Semua + </button> + </span> + ) : ( + '' + )} + </HStack> + </div> +) diff --git a/src/lib/product/hooks/useProductSearch.js b/src/lib/product/hooks/useProductSearch.js index 4c9272f1..f3b7530a 100644 --- a/src/lib/product/hooks/useProductSearch.js +++ b/src/lib/product/hooks/useProductSearch.js @@ -2,9 +2,9 @@ import { useQuery } from 'react-query' import productSearchApi from '../api/productSearchApi' import _ from 'lodash-contrib' -const useProductSearch = ({ query }) => { +const useProductSearch = ({ query, operation }) => { const queryString = _.toQuery(query) - const fetchProductSearch = async () => await productSearchApi({ query: queryString }) + const fetchProductSearch = async () => await productSearchApi({ query: queryString , operation : operation}) const productSearch = useQuery(`productSearch-${queryString}`, fetchProductSearch) return { diff --git a/src/pages/api/shop/search.js b/src/pages/api/shop/search.js index b4d67c5d..576d028a 100644 --- a/src/pages/api/shop/search.js +++ b/src/pages/api/shop/search.js @@ -66,8 +66,9 @@ export default async function handler(req, res) { ) } - if (brand) parameter.push(`fq=manufacture_name:${brand.replace(/,/g, ' OR ')}`) - if (category) parameter.push(`fq=category_name:${category}`) + if (brand) parameter.push(`fq=${brand.split(',').map(manufacturer => `manufacture_name:"${manufacturer}"`).join(" OR ")}`) + if (category) parameter.push(`fq=${category.split(',').map(cat => `category_name:"${cat}"`).join(' OR ')}`) + // if (category) parameter.push(`fq=category_name:${capitalizeFirstLetter(category.replace(/,/g, ' OR '))}`) if (stock) parameter.push(`fq=stock_total_f:{1 TO *}`) // Single fq in url params diff --git a/src/pages/shop/brands/[slug].jsx b/src/pages/shop/brands/[slug].jsx index d75475b7..c3a7299f 100644 --- a/src/pages/shop/brands/[slug].jsx +++ b/src/pages/shop/brands/[slug].jsx @@ -4,6 +4,7 @@ import { useRouter } from 'next/router' import _ from 'lodash' import Seo from '@/core/components/Seo' import Breadcrumb from '@/lib/brand/components/Breadcrumb' +import useBrand from '@/lib/brand/hooks/useBrand' const BasicLayout = dynamic(() => import('@/core/components/layouts/BasicLayout')) const ProductSearch = dynamic(() => import('@/lib/product/components/ProductSearch')) @@ -14,6 +15,8 @@ export default function BrandDetail() { const { slug = '' } = router.query const brandName = getNameFromSlug(slug) + const id = getIdFromSlug(slug) + const {brand} = useBrand({id}) return ( <BasicLayout> <Seo @@ -29,12 +32,13 @@ export default function BrandDetail() { <Breadcrumb brandName={brandName} /> - <Brand id={getIdFromSlug(slug)} /> + <Brand brand={brand} /> {!_.isEmpty(router.query) && ( <ProductSearch query={_.omit(router.query, 'slug')} prefixUrl={`/shop/brands/${slug}`} defaultBrand={getNameFromSlug(slug)} + brand={brand} /> )} </BasicLayout> |
