diff options
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/product/components/ProductFilter.jsx | 118 | ||||
| -rw-r--r-- | src/lib/product/components/ProductFilterDesktop.jsx | 140 | ||||
| -rw-r--r-- | src/lib/product/components/ProductSearch.jsx | 87 |
3 files changed, 285 insertions, 60 deletions
diff --git a/src/lib/product/components/ProductFilter.jsx b/src/lib/product/components/ProductFilter.jsx index 34357526..40bfc824 100644 --- a/src/lib/product/components/ProductFilter.jsx +++ b/src/lib/product/components/ProductFilter.jsx @@ -3,6 +3,7 @@ import { useRouter } from 'next/router' import { useState } from 'react' import _ from 'lodash' import { toQuery } from 'lodash-contrib' +import { Checkbox } from '@chakra-ui/react' const orderOptions = [ { value: 'price-asc', label: 'Harga Terendah' }, @@ -20,6 +21,45 @@ const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBr const [priceFrom, setPriceFrom] = useState(query?.priceFrom) const [priceTo, setPriceTo] = useState(query?.priceTo) + const [stock, setStock] = useState(query?.stock) + + const [activeRange, setActiveRange] = useState(null) + + const priceRange = [ + { + priceFrom: 100000, + priceTo: 200000 + }, + { + priceFrom: 200000, + priceTo: 300000 + }, + { + priceFrom: 300000, + priceTo: 400000 + }, + { + priceFrom: 400000, + priceTo: 500000 + } + ] + + const handlePriceFromChange = async (priceFromr, priceTor, index) => { + await setPriceFrom(priceFromr) + await setPriceTo(priceTor) + setActiveRange(index) + } + + const handleReadyStockChange = (event) => { + const value = event.target.value + const isChecked = event.target.checked + if (isChecked) { + setStock(value) + } else { + setStock(null) + } + } + const handleSubmit = () => { let params = { q: router.query.q, @@ -27,34 +67,47 @@ const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBr brand, category, priceFrom, - priceTo + priceTo, + stock: stock } params = _.pickBy(params, _.identity) params = toQuery(params) 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}` + } + } + return ( <BottomPopup active={active} close={close} title='Filter Produk'> <div className='flex flex-col gap-y-4'> - {!defaultBrand && ( - <div> - <label>Brand</label> - <select - name='brand' - className='form-input mt-2' - value={brand} - onChange={(e) => setBrand(e.target.value)} - > - <option value=''>Pilih Brand...</option> - {brands.map((brand, index) => ( - <option value={brand} key={index}> - {brand} - </option> - ))} - </select> - </div> - )} + {!router.pathname.includes('brands') && + !defaultBrand && ( + <div> + <label>Brand</label> + <select + name='brand' + className='form-input mt-2' + value={brand} + onChange={(e) => setBrand(e.target.value)} + > + <option value=''>Pilih Brand...</option> + {brands.map((brand, index) => ( + <option value={brand.brand} key={index}> + {brand.brand} <span className='text-sm text-gray-200'>({brand.qty})</span> + </option> + ))} + </select> + </div> + ) + } + <div> <label>Kategori</label> <select @@ -106,6 +159,33 @@ const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBr onChange={(e) => setPriceTo(e.target.value)} /> </div> + <div className='grid grid-cols-2 gap-x-3 gap-y-2 mt-2'> + {priceRange.map((price, i) => ( + <button + key={i} + onClick={() => handlePriceFromChange(price.priceFrom, price.priceTo, i)} + className={`w-full border ${ + i === activeRange ? 'border-red-600' : 'border-gray-400' + } + py-2 p-3 rounded-full text-sm whitespace-nowrap`} + > + {formatCurrency(price.priceFrom)} - {formatCurrency(price.priceTo)} + </button> + ))} + </div> + </div> + <div> + <label>Ketersedian Stok</label> + <div className='mt-2'> + <Checkbox + isChecked={stock === 'ready stock'} + onChange={handleReadyStockChange} + value={'ready stock'} + size='md' + > + Ketersedian Stok + </Checkbox> + </div> </div> <button type='button' className='btn-solid-red w-full mt-2' onClick={handleSubmit}> Terapkan Filter diff --git a/src/lib/product/components/ProductFilterDesktop.jsx b/src/lib/product/components/ProductFilterDesktop.jsx index b64349c7..cdfd85e8 100644 --- a/src/lib/product/components/ProductFilterDesktop.jsx +++ b/src/lib/product/components/ProductFilterDesktop.jsx @@ -1,5 +1,5 @@ import { useRouter } from 'next/router' -import { useState } from 'react' +import { useEffect, useState } from 'react' import _ from 'lodash' import { toQuery } from 'lodash-contrib' import { @@ -17,6 +17,7 @@ import { Stack, VStack } from '@chakra-ui/react' +import Image from '@/core/components/elements/Image/Image' const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = null }) => { const router = useRouter() @@ -26,6 +27,27 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu const [categoryValues, setCategory] = useState(query?.category?.split(',') || []) const [priceFrom, setPriceFrom] = useState(query?.priceFrom) const [priceTo, setPriceTo] = useState(query?.priceTo) + const [stock, setStock] = useState(query?.stock) + const [activeRange, setActiveRange] = useState(null) + + const priceRange = [ + { + priceFrom: 100000, + priceTo: 200000 + }, + { + priceFrom: 200000, + priceTo: 300000 + }, + { + priceFrom: 300000, + priceTo: 400000 + }, + { + priceFrom: 400000, + priceTo: 500000 + } + ] const handleCategoriesChange = (event) => { const value = event.target.value @@ -46,6 +68,22 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu } } + const handleReadyStockChange = (event) => { + const value = event.target.value + const isChecked = event.target.checked + if (isChecked) { + setStock(value) + } else { + setStock(null) + } + } + + const handlePriceFromChange = async (priceFromr, priceTor, index) => { + await setPriceFrom(priceFromr) + await setPriceTo(priceTor) + setActiveRange(index) + } + const handleSubmit = () => { let params = { q: router.query.q, @@ -53,41 +91,57 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu brand: brandValues.join(','), category: categoryValues.join(','), priceFrom, - priceTo + priceTo, + stock: stock } params = _.pickBy(params, _.identity) params = toQuery(params) 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}` + } + } + return ( <> <Accordion defaultIndex={[0]} allowMultiple> - <AccordionItem> - <AccordionButton padding={[2, 4]}> - <Box as='span' flex='1' textAlign='left' fontWeight='semibold'> - Brand - </Box> - <AccordionIcon /> - </AccordionButton> + + {!router.pathname.includes('brands') && ( + <AccordionItem> + <AccordionButton padding={[2, 4]}> + <Box as='span' flex='1' textAlign='left' fontWeight='semibold'> + Brand + </Box> + <AccordionIcon /> + </AccordionButton> - <AccordionPanel> - <Stack gap={3} direction='column' maxH={'240px'} overflow='auto'> - {brands.map((brand, index) => ( - <div className='flex items-center gap-2' key={index}> - <Checkbox - isChecked={brandValues.includes(brand)} - onChange={handleBrandsChange} - value={brand} - size='md' - > - {brand} - </Checkbox> - </div> - ))} - </Stack> - </AccordionPanel> - </AccordionItem> + <AccordionPanel> + <Stack gap={3} direction='column' maxH={'240px'} overflow='auto'> + {brands.map((brand, index) => ( + <div className='flex items-center gap-2 ' key={index}> + <Checkbox + isChecked={brandValues.includes(brand.brand)} + onChange={handleBrandsChange} + value={brand.brand} + size='md' + > + <div className='flex items-center gap-2'> + <span>{brand.brand} </span> + <span className='text-sm text-gray-600'>({brand.qty})</span> + </div> + </Checkbox> + </div> + ))} + </Stack> + </AccordionPanel> + </AccordionItem> + )} <AccordionItem> <AccordionButton padding={[2, 4]}> @@ -143,9 +197,43 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu onChange={(e) => setPriceTo(e.target.value)} /> </InputGroup> + <div className='grid grid-cols-2 gap-x-3 gap-y-2'> + {priceRange.map((price, i) => ( + <button + key={i} + onClick={() => handlePriceFromChange(price.priceFrom, price.priceTo, i)} + className={`w-full border ${ + i === activeRange ? 'border-red-600' : 'border-gray-400' + } + py-2 p-3 rounded-full text-sm whitespace-nowrap`} + > + {formatCurrency(price.priceFrom)} - {formatCurrency(price.priceTo)} + </button> + ))} + </div> </VStack> </AccordionPanel> </AccordionItem> + + <AccordionItem> + <AccordionButton padding={[2, 4]}> + <Box as='span' flex='1' textAlign='left' fontWeight='semibold'> + Ketersedian Stok + </Box> + <AccordionIcon /> + </AccordionButton> + + <AccordionPanel paddingY={4}> + <Checkbox + isChecked={stock === 'ready stock'} + onChange={handleReadyStockChange} + value={'ready stock'} + size='md' + > + Ketersedian Stock + </Checkbox> + </AccordionPanel> + </AccordionItem> </Accordion> <Button className='w-full mt-6' colorScheme='red' onClick={handleSubmit}> diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx index f6987051..edc98197 100644 --- a/src/lib/product/components/ProductSearch.jsx +++ b/src/lib/product/components/ProductSearch.jsx @@ -20,17 +20,17 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { const router = useRouter() const { page = 1 } = query const [q, setQ] = useState(query?.q || '*') + const [limit, setLimit] = useState(query?.limit || 30) if (defaultBrand) query.brand = defaultBrand.toLowerCase() - const { productSearch } = useProductSearch({ query: { ...query, q } }) + const { productSearch } = useProductSearch({ query: { ...query, q, limit } }) const [products, setProducts] = useState(null) const [spellings, setSpellings] = useState(null) const popup = useActive() + const numRows = [30, 50, 80, 100] - const pageCount = Math.ceil( - productSearch.data?.response.numFound / productSearch.data?.responseHeader.params.rows - ) + const pageCount = Math.ceil(productSearch.data?.response.numFound / limit) const productStart = productSearch.data?.responseHeader.params.start - const productRows = productSearch.data?.responseHeader.params.rows + const productRows = limit const productFound = productSearch.data?.response.numFound useEffect(() => { @@ -63,13 +63,26 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { } }, [productFound, query, spellings]) - const brands = productSearch.data?.facetCounts?.facetFields?.manufactureName?.filter( + const brands = [] + for ( + let i = 0; + i < productSearch.data?.facetCounts?.facetFields?.manufactureName.length; + i += 2 + ) { + const brand = productSearch.data?.facetCounts?.facetFields?.manufactureName[i] + const qty = productSearch.data?.facetCounts?.facetFields?.manufactureName[i + 1] + brands.push({ brand, qty }) + } + /*const brandsList = productSearch.data?.facetCounts?.facetFields?.manufactureName?.filter( (value, index) => { if (index % 2 === 0) { - return true + const brand = value + const qty = index + 1 + brands.push({ brand, qty }) } } - ) + )*/ + const categories = productSearch.data?.facetCounts?.facetFields?.categoryName?.filter( (value, index) => { if (index % 2 === 0) { @@ -95,6 +108,16 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { router.push(`${prefixUrl}?${params}`) } + const handleLimit = (e) => { + let params = { + ...router.query, + limit: e.target.value + } + params = _.pickBy(params, _.identity) + params = toQuery(params) + router.push(`${prefixUrl}?${params}`) + } + useEffect(() => { setProducts(productSearch.data?.response?.products) }, [productSearch]) @@ -127,9 +150,9 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { {pageCount > 1 ? ( <> {productStart + 1}- - {productStart + productRows > productFound + {parseInt(productStart) + parseInt(productRows) > productFound ? productFound - : productStart + productRows} + : parseInt(productStart) + parseInt(productRows)} dari </> ) : ( @@ -149,9 +172,28 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { </div> {productFound > 0 && ( - <button className='btn-light mb-6 py-2 px-5' onClick={popup.activate}> - Filter - </button> + <div className='flex items-center gap-x-2 mb-5 justify-between'> + <div> + <button className='btn-light py-2 px-5 h-[40px]' onClick={popup.activate}> + Filter + </button> + </div> + <div className=''> + <select + name='limit' + className='form-input w-24' + value={router.query?.limit || ''} + onChange={(e) => handleLimit(e)} + > + {numRows.map((option, index) => ( + <option key={index} value={option}> + {' '} + {option}{' '} + </option> + ))} + </select> + </div> + </div> )} <div className='grid grid-cols-2 gap-3'> @@ -197,9 +239,9 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { {pageCount > 1 ? ( <> {productStart + 1}- - {productStart + productRows > productFound + {parseInt(productStart) + parseInt(productRows) > productFound ? productFound - : productStart + productRows} + : parseInt(productStart) + parseInt(productRows)} dari </> ) : ( @@ -234,6 +276,21 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { ))} </select> </div> + <div className='ml-3'> + <select + name='limit' + className='form-input mt-2' + value={router.query?.limit || ''} + onChange={(e) => handleLimit(e)} + > + {numRows.map((option, index) => ( + <option key={index} value={option}> + {' '} + {option}{' '} + </option> + ))} + </select> + </div> </div> </div> {productSearch.isLoading && <ProductSearchSkeleton />} |
