diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/product/components/Product/ProductDesktopVariant.jsx | 13 | ||||
| -rw-r--r-- | src/lib/product/components/Product/ProductMobileVariant.jsx | 9 | ||||
| -rw-r--r-- | src/pages/api/shop/search.js | 116 | ||||
| -rw-r--r-- | src/utils/solrMapping.js | 3 |
4 files changed, 123 insertions, 18 deletions
diff --git a/src/lib/product/components/Product/ProductDesktopVariant.jsx b/src/lib/product/components/Product/ProductDesktopVariant.jsx index 6b4ab1e1..1584dc92 100644 --- a/src/lib/product/components/Product/ProductDesktopVariant.jsx +++ b/src/lib/product/components/Product/ProductDesktopVariant.jsx @@ -314,6 +314,11 @@ const ProductDesktopVariant = ({ fetchData(); }, [product]); + const getImageVariant = (img) => { + if (!img || img.trim() === '') return '/images/noimage.jpeg'; + return `${img}?variant=True`; + }; + return ( <DesktopView> <div className='relative'> @@ -332,8 +337,8 @@ const ProductDesktopVariant = ({ <div className='w-full flex flex-wrap'> <div className='w-5/12'> <Image - src={product.image + '?variant=True'} - alt={product.name} + src={getImageVariant(product?.image)} + alt={product?.name} className='w-full h-[350px]' /> </div> @@ -345,9 +350,9 @@ const ProductDesktopVariant = ({ size={18} className='text-red-600 shrink-0 mx-2' /> - <h1 className='text-red-600 font-normal text-h-sm'> + <div className='text-red-600 font-normal text-h-lg p-2'> Maaf untuk saat ini Produk yang anda cari tidak tersedia - </h1> + </div> </div> )} <h1 className='text-title-md leading-10 font-medium'> diff --git a/src/lib/product/components/Product/ProductMobileVariant.jsx b/src/lib/product/components/Product/ProductMobileVariant.jsx index 0f4953df..9d375deb 100644 --- a/src/lib/product/components/Product/ProductMobileVariant.jsx +++ b/src/lib/product/components/Product/ProductMobileVariant.jsx @@ -181,6 +181,11 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { return Math.floor(Math.random() * 100) + 1; }); + const getImageVariant = (img) => { + if (!img || img.trim() === '') return '/images/noimage.jpeg'; + return `${img}?variant=True`; + }; + return ( <MobileView> <div className='relative'> @@ -338,7 +343,7 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { </div> <Image - src={product.image + '?variant=True'} + src={getImageVariant(product?.image)} alt={product.name} className='h-72 object-contain object-center w-full border-b border-gray_r-4' /> @@ -532,7 +537,7 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { <div className='flex mt-4'> <div className='w-[15%]'> <Image - src={product.image + '?variant=True'} + src={getImageVariant(product?.image)} alt={product.name} className='h-20 object-contain object-center w-full border border-gray_r-4' /> diff --git a/src/pages/api/shop/search.js b/src/pages/api/shop/search.js index 7d4adfcb..5ea6a70a 100644 --- a/src/pages/api/shop/search.js +++ b/src/pages/api/shop/search.js @@ -2,6 +2,14 @@ import { productMappingSolr } from '@/utils/solrMapping'; import axios from 'axios'; import camelcaseObjectDeep from 'camelcase-object-deep'; +const escapeSolrQuery = (query) => { + if (query == '*') return query; + query = query.replace(/-/g, ' '); + const specialChars = /([\+\!\(\)\{\}\[\]\^"~\*\?:\\\/])/g; + const words = query.split(/\s+/); + return words.map((word) => specialChars.test(word) ? word.replace(specialChars, '\\$1') : word).join(' '); +}; + export default async function handler(req, res) { const { q = '*', @@ -20,7 +28,99 @@ export default async function handler(req, res) { let { stock = '' } = req.query; // ============================================================ - // LOGIC KHUSUS UPSELL (Simple & Direct) + // [PERBAIKAN] 1. LOGIC KHUSUS COMPARE (PAKAI URLSearchParams) + // ============================================================ + if (source === 'compare') { + try { + let qCompare = q === '*' ? '*:*' : q; + + // Sanitasi Query + if (qCompare !== '*:*') { + // Kita escape, tapi biarkan stringnya bersih (jangan ditambah wildcard * manual) + // karena kita serahkan ke 'edismax' parser + qCompare = escapeSolrQuery(qCompare); + } + + // [SOLUSI] Gunakan URLSearchParams untuk menyusun URL dengan aman + const params = new URLSearchParams(); + + params.append('q', qCompare); + params.append('rows', limit); + params.append('wt', 'json'); + params.append('indent', 'true'); + + // Gunakan eDisMax parser (Otak Cerdas) + params.append('defType', 'edismax'); + + // Set Prioritas Pencarian (Boost ^) + // 1. default_code_s^20 : SKU persis (Prioritas Tertinggi) + // 2. search_keywords_t^10 : Field baru (Case insensitive) + // 3. display_name_s^1 : Cadangan + params.append('qf', 'default_code_s^20 search_keywords_t^10 display_name_s^1'); + + // Minimum Match 100% (Semua kata harus ada), ubah jika ingin lebih longgar + params.append('mm', '100%'); + + // Grouping + params.append('group', 'true'); + params.append('group.field', 'template_id_i'); + params.append('group.limit', '1'); + params.append('group.main', 'true'); + + // Field List (fl) + params.append('fl', 'id,display_name_s,default_code_s,image_s,price_tier1_v2_f,attribute_set_id_i,attribute_set_name_s,template_id_i,product_id_i'); + + // Filter Query (fq) Dasar + params.append('fq', '-publish_b:false'); + params.append('fq', 'price_tier1_v2_f:[1 TO *]'); + + // Logic Locking (Filter Attribute Set ID dari Frontend) + if (fq) { + if (Array.isArray(fq)) { + fq.forEach(f => params.append('fq', f)); + } else { + params.append('fq', fq); + } + } + + // Target Core: VARIANTS + // HAPUS parameter manual dari string URL, gunakan params object + const solrUrl = process.env.SOLR_HOST + '/solr/variants/select'; + + // Axios akan otomatis handle encoding % dan & dengan benar + const result = await axios.get(solrUrl, { params: params }); + + // Mapping Result + const mappedProducts = productMappingSolr( + result.data.response.docs, + false + ); + + const finalResponse = { + ...result.data, + response: { + ...result.data.response, + products: mappedProducts + } + }; + + delete finalResponse.response.docs; + const camelCasedData = camelcaseObjectDeep(finalResponse); + + return res.status(200).json(camelCasedData); + + } catch (e) { + console.error('[COMPARE SEARCH ERROR]', e.message); + if (e.response && e.response.data) { + // Log detail error dari Solr + console.error('[SOLR DETAILS]:', JSON.stringify(e.response.data, null, 2)); + } + return res.status(200).json({ response: { products: [], numFound: 0 } }); + } + } + + // ============================================================ + // LOGIC KHUSUS UPSELL (KODE LAMA ANDA) // ============================================================ if (source === 'upsell') { try { @@ -88,7 +188,7 @@ export default async function handler(req, res) { } // ============================================================ - // SITEMAP (Biarkan tetap sama) + // SITEMAP (KODE LAMA ANDA) // ============================================================ if (source === 'sitemap') { try { @@ -113,7 +213,7 @@ export default async function handler(req, res) { } // ============================================================ - // SEARCH NORMAL (LOGIKA LAMA) + // SEARCH NORMAL (KODE LAMA ANDA) // ============================================================ let paramOrderBy = ''; @@ -219,12 +319,4 @@ export default async function handler(req, res) { } catch (error) { res.status(400).json({ error: error.message }); } -} - -const escapeSolrQuery = (query) => { - if (query == '*') return query; - query = query.replace(/-/g, ' '); - const specialChars = /([\+\!\(\)\{\}\[\]\^"~\*\?:\\\/])/g; - const words = query.split(/\s+/); - return words.map((word) => specialChars.test(word) ? word.replace(specialChars, '\\$1') : word).join(' '); -};
\ No newline at end of file +}
\ No newline at end of file diff --git a/src/utils/solrMapping.js b/src/utils/solrMapping.js index 33f0cbaf..8c0abcf1 100644 --- a/src/utils/solrMapping.js +++ b/src/utils/solrMapping.js @@ -127,6 +127,9 @@ export const variantsMappingSolr = (parent, products, pricelist) => { variantTotal: product.variant_total_i || 0, stockTotal: product.stock_total_f || 0, weight: product.weight_f || 0, + attribute_set_id: product.attribute_set_id_i || 0, + attribute_set_name: product.attribute_set_name_s || '', + search_keywords: product.search_keywords_t || '', manufacture: {}, parent: {}, qtySold: product?.qty_sold_f || 0, |
