diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/pages/api/shop/generate-recomendation.js | 60 | ||||
| -rw-r--r-- | src/pages/my/recomendation/api/recomendation.js | 21 | ||||
| -rw-r--r-- | src/pages/my/recomendation/components/products-recomendatison.jsx | 278 |
3 files changed, 322 insertions, 37 deletions
diff --git a/src/pages/api/shop/generate-recomendation.js b/src/pages/api/shop/generate-recomendation.js new file mode 100644 index 00000000..a2a9ee40 --- /dev/null +++ b/src/pages/api/shop/generate-recomendation.js @@ -0,0 +1,60 @@ +import { productMappingSolr } from '@/utils/solrMapping' +import axios from 'axios'; +import camelcaseObjectDeep from 'camelcase-object-deep'; + +export default async function handler(req, res) { + const { q = null } = req.query + + if (!q) { + return res.status(422).json({ error: 'parameter missing' }) + } + + let parameter = [ + `q=${escapeSolrQuery(q)}`, + `q.op=AND`, + `indent=true`, + `fq=-publish_b:false`, + `qf=name_s^2 description_s`, + `facetch=true`, + `fq=price_tier1_v2_f:[1 TO *]`, + `rows=1`, + `sort=product_rating_f DESC, price_discount_f DESC`, + ]; + let result = await axios( + process.env.SOLR_HOST + '/solr/product/select?' + parameter.join('&') + ); + try { + let { auth } = req.cookies; + if (auth) auth = JSON.parse(auth); + result.data.response.products = productMappingSolr( + result.data.response.docs, + auth?.pricelist || false + ); + result.data.responseHeader.params.start = parseInt( + result.data.responseHeader.params.start + ); + result.data.responseHeader.params.rows = parseInt( + result.data.responseHeader.params.rows + ); + delete result.data.response.docs; + result.data = camelcaseObjectDeep(result.data); + res.status(200).json(result.data); + } catch (error) { + res.status(400).json({ error: error.message }); + } +} + +const escapeSolrQuery = (query) => { + if (query == '*') return query; + + const specialChars = /([\+\-\!\(\)\{\}\[\]\^"~\*\?:\\\/])/g; + const words = query.split(/\s+/); + const escapedWords = words.map((word) => { + if (specialChars.test(word)) { + return `"${word.replace(specialChars, '\\$1')}"`; + } + return word; + }); + + return escapedWords.join(' '); +}; diff --git a/src/pages/my/recomendation/api/recomendation.js b/src/pages/my/recomendation/api/recomendation.js index 47ed743a..8ff760d0 100644 --- a/src/pages/my/recomendation/api/recomendation.js +++ b/src/pages/my/recomendation/api/recomendation.js @@ -1,8 +1,17 @@ -import axios from "axios" +import axios from 'axios'; +import { useQuery } from 'react-query'; -const GenerateRecomendations = async ({query}) => { - const GenerateRecomendationProducts = await axios(`${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/recomendation?${query}`) +const GenerateRecomendations = ({ query }) => { + const queryString = _.toQuery(query); + const GenerateRecomendationProducts = async () => + await axios( + `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/recomendation?${queryString}` + ); + const productSearch = useQuery( + `generateRecomendation-${ququeryStringery}`, + GenerateRecomendationProducts + ); - return GenerateRecomendationProducts -} -export default GenerateRecomendations
\ No newline at end of file + return productSearch; +}; +export default GenerateRecomendations; diff --git a/src/pages/my/recomendation/components/products-recomendatison.jsx b/src/pages/my/recomendation/components/products-recomendatison.jsx index c20aaa5b..72b858e8 100644 --- a/src/pages/my/recomendation/components/products-recomendatison.jsx +++ b/src/pages/my/recomendation/components/products-recomendatison.jsx @@ -1,19 +1,79 @@ import Menu from '@/lib/auth/components/Menu'; -import { useState } from 'react'; -import * as XLSX from 'xlsx'; +import { useEffect, useState } from 'react'; +import * as XLSX from 'xlsx'; +import GenerateRecomendations from '../api/recomendation'; +import axios from 'axios'; +import { Button, Link } from '@chakra-ui/react'; +import Image from 'next/image'; +import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; +import formatCurrency from '~/libs/formatCurrency'; const ProductsRecomendation = ({ id }) => { const [excelData, setExcelData] = useState(null); + const [products, setProducts] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [isOpen, setIsOpen] = useState(false); + const [variantsOpen, setVariantsOpen] = useState([]); - const handleSubmit = (e) => { + const mappingProducts = async ({ index, product, result, variants }) => { + const resultMapping = { + index : index, + product: product, + result: { + id: result?.id || '-', + name: result?.displayName || '-', + code: result?.code || '-', + image: result?.image || '-', + price: result?.lowestPrice.priceDiscount || '-', + manufacture: result?.manufacture.name || '-', + variantTotal: result?.variantTotal || '-', + variants: variants || '-', + }, + }; + + return resultMapping; + }; + + const searchRecomendation = async ({ product, index }) => { + let variants = []; + const searchProduct = await axios.post( + `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/generate-recomendation?q=${product}` + ); + + const result = + searchProduct.data.response.numFound > 0 + ? searchProduct.data.response.products[0] + : null; + + if (result?.variantTotal > 1) { + const searchVariants = await axios.post( + `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/product-detail?id=${result.id}` + ); + variants = searchVariants.data[0].variants; + } + + const resultMapping = await mappingProducts({ index, product, result, variants }); + + return resultMapping; + }; + + const handleSubmit = async (e) => { + setIsLoading(true); e.preventDefault(); if (excelData) { - // Lakukan operasi pencarian atau operasi lainnya di sini - console.log('ini data excel',excelData); // Contoh: Menampilkan data excel ke konsol + const results = await Promise.all( + excelData.map(async (row, i) => { + const index = i + 1; + const product = row[0]; + return await searchRecomendation({ product, index }); + }) + ).then((results) => setProducts(results)); + setIsLoading(false); } else { console.log('No excel data available'); } }; + const handleFileChange = (e) => { const file = e.target.files[0]; const reader = new FileReader(); @@ -27,36 +87,107 @@ const ProductsRecomendation = ({ id }) => { }; reader.readAsArrayBuffer(file); }; + + const handleVariantsOpen = ({ variants }) => { + setVariantsOpen(variants); + setIsOpen(true); + }; + const hadnliChooseVariants = ({ id, variant }) => { + let foundIndex = products.findIndex((item) => item.result.id === id); + if (foundIndex !== -1) { + products[foundIndex].result.code = variant?.code; + products[foundIndex].result.name = variant?.name; + } else { + console.log('Data not found.'); + } + setIsOpen(false); + }; return ( - <div className='container mx-auto flex py-10'> - <div className='w-3/12 pr-4'> - <Menu /> - </div> - <div className='w-9/12 p-4 bg-white border border-gray_r-6 rounded'> - <div className='flex mb-6 items-center justify-between'> - <h1 className='text-title-sm font-semibold'> - Generate Recomendation - </h1> - </div> - <div className='group'> - <h1 className='text-sm font-semibold'>Contoh Excel</h1> + <> + <BottomPopup + active={isOpen} + close={() => setIsOpen(false)} + className='w-full md:!w-[60%]' + title='List Variants' + > + <div className='container'> <table className='table-data'> <thead> <tr> - <th>Product</th> - <th>Qty</th> + <th>Part Number</th> + <th>Variants </th> + <th>Harga </th> + <th></th> </tr> </thead> <tbody> - <tr> - <td>Tekiro Long Nose Pliers Tang Lancip</td> - <td>10</td> - </tr> + {variantsOpen?.map((variant, index) => ( + <tr key={index}> + <td>{variant.code}</td> + <td>{variant.attributes.join(', ') || '-'}</td> + <td> + {variant.price.discountPercentage > 0 && ( + <div className='flex items-center gap-x-1'> + <div className={style['disc-badge']}> + {Math.floor(variant.price.discountPercentage)}% + </div> + <div className={style['disc-price']}> + Rp {formatCurrency(variant.price.price)} + </div> + </div> + )} + {variant.price.priceDiscount > 0 && + `Rp ${formatCurrency(variant.price.priceDiscount)}`} + {variant.price.priceDiscount === 0 && '-'} + </td> + <td> + <Button + size='sm' + w='100%' + onClick={() => + hadnliChooseVariants({ + id: variant.parent.id, + variant: variant, + }) + } + > + Pilih + </Button> + </td> + </tr> + ))} </tbody> </table> </div> - <div className='container mx-auto mt-8'> - <form onSubmit={handleSubmit}> + </BottomPopup> + <div className='container mx-auto flex py-10'> + <div className='w-3/12 pr-4'> + <Menu /> + </div> + <div className='w-9/12 p-4 bg-white border border-gray_r-6 rounded'> + <div className='flex mb-6 items-center justify-between'> + <h1 className='text-title-sm font-semibold'> + Generate Recomendation + </h1> + </div> + <div className='group'> + <h1 className='text-sm font-semibold'>Contoh Excel</h1> + <table className='table-data'> + <thead> + <tr> + <th>Product</th> + <th>Qty</th> + </tr> + </thead> + <tbody> + <tr> + <td>Tekiro Long Nose Pliers Tang Lancip</td> + <td>10</td> + </tr> + </tbody> + </table> + </div> + <div className='container mx-auto mt-8'> <div className='mb-4'> <label htmlFor='excelFile' className='text-sm font-semibold'> Upload Excel File (.xlsx) @@ -69,16 +200,101 @@ const ProductsRecomendation = ({ id }) => { className='mt-1 p-2 block w-full border border-gray-300 rounded-md focus:outline-none focus:border-blue-500' /> </div> - <button - type='submit' - className='bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600' + <Button + colorScheme='red' + w='l' + isDisabled={isLoading} + onClick={handleSubmit} > Generate - </button> - </form> + </Button> + </div> + <div className='grup mt-8'> + {products && products.length > 0 && ( + <div className='group'> + <table className='table-data'> + <thead> + <tr> + <th>Product</th> + <th>Item code</th> + <th>Keterangan</th> + <th>Brand</th> + <th>Harga</th> + <th>Image</th> + <th>lainnya</th> + </tr> + </thead> + <tbody> + {products.map((product, index) => ( + <tr key={index}> + <td>{product?.product}</td> + <td> + {product?.result?.code === '-' && + product.result.variantTotal > 1 && ( + <Button + border='2px' + borderColor='yellow.300' + size='sm' + onClick={() => + handleVariantsOpen({ + variants: product?.result?.variants, + }) + } + > + Lihat Variants + </Button> + )} + {product?.result.code !== '-' && + product?.result.variantTotal > 1 ? ( + <> + {product?.result.code} + <Button + variant='link' + colorScheme='yellow' + size='sm' + onClick={() => + handleVariantsOpen({ + variants: product?.result?.variants, + }) + } + > + Variants lainya + </Button> + </> + ) : ( + <>{product?.result.code}</> + )} + </td> + <td>{product?.result.name}</td> + <td>{product?.result.manufacture}</td> + <td> + {product?.result.price !== '-' + ? `Rp ${formatCurrency(product?.result.price)}` + : '-'} + </td> + <td> + {product?.result.image !== '-' ? ( + <Image + src={product?.result.image} + width={100} + height={100} + alt={product?.result.name} + /> + ) : ( + '-' + )} + </td> + <td></td> + </tr> + ))} + </tbody> + </table> + </div> + )} + </div> </div> </div> - </div> + </> ); }; |
