summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHATEC\SPVDEV001 <tri.susilo@altama.co.id>2024-03-25 16:52:42 +0700
committerHATEC\SPVDEV001 <tri.susilo@altama.co.id>2024-03-25 16:52:42 +0700
commit60497cfbdf29df106d3d4270bfd480f270f16098 (patch)
treeb81ae6acfa1abe5fa94ba344d988c04a84b680a9
parentd0d38a82bb3d368632243ffb1cd317bb41cb13a3 (diff)
generate recomendation
-rw-r--r--src/pages/api/shop/generate-recomendation.js60
-rw-r--r--src/pages/my/recomendation/api/recomendation.js21
-rw-r--r--src/pages/my/recomendation/components/products-recomendatison.jsx278
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>
+ </>
);
};