diff options
| author | it-fixcomart <it@fixcomart.co.id> | 2024-06-21 11:01:35 +0700 |
|---|---|---|
| committer | it-fixcomart <it@fixcomart.co.id> | 2024-06-21 11:01:35 +0700 |
| commit | 220190db66bcc1c6db78180c593f21e9cf8f363c (patch) | |
| tree | 1517faa9636a6b3b2cc8d468a57b1fe476c229d7 /src/pages | |
| parent | 208b234320b6c42491a4e87a1c3db3abab9c1715 (diff) | |
| parent | 1cf754b4d8da1aa28700ffc3dad67081f6daf9a5 (diff) | |
Merge branch 'promotion-program' into feature/all-promotion
Diffstat (limited to 'src/pages')
| -rw-r--r-- | src/pages/_app.jsx | 4 | ||||
| -rw-r--r-- | src/pages/api/shop/search.js | 118 | ||||
| -rw-r--r-- | src/pages/api/shop/variant-detail.js | 5 | ||||
| -rw-r--r-- | src/pages/google_merchant/products/[page].js | 130 | ||||
| -rw-r--r-- | src/pages/index.jsx | 34 | ||||
| -rw-r--r-- | src/pages/shop/product/variant/[slug].jsx | 86 | ||||
| -rw-r--r-- | src/pages/shop/promo/[slug].tsx | 523 | ||||
| -rw-r--r-- | src/pages/shop/promo/index.tsx | 186 | ||||
| -rw-r--r-- | src/pages/video.jsx | 2 |
9 files changed, 927 insertions, 161 deletions
diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx index 01dec611..bcb41dd6 100644 --- a/src/pages/_app.jsx +++ b/src/pages/_app.jsx @@ -89,9 +89,9 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }) { <AnimatePresence> {animateLoader && ( <motion.div - initial={{ opacity: 0.4 }} + initial={{ opacity: 0.25 }} animate={{ opacity: 1 }} - exit={{ opacity: 0.4 }} + exit={{ opacity: 0.25 }} transition={{ duration: 0.1, }} diff --git a/src/pages/api/shop/search.js b/src/pages/api/shop/search.js index adb23511..b6b8c795 100644 --- a/src/pages/api/shop/search.js +++ b/src/pages/api/shop/search.js @@ -1,6 +1,6 @@ -import { productMappingSolr } from '@/utils/solrMapping' -import axios from 'axios' -import camelcaseObjectDeep from 'camelcase-object-deep' +import { productMappingSolr } from '@/utils/solrMapping'; +import axios from 'axios'; +import camelcaseObjectDeep from 'camelcase-object-deep'; export default async function handler(req, res) { const { @@ -14,35 +14,36 @@ export default async function handler(req, res) { operation = 'AND', fq = '', limit = 30, - stock = '' - } = req.query + } = req.query; - let paramOrderBy = '' + let { stock = '' } = req.query; + + let paramOrderBy = ''; switch (orderBy) { case 'price-asc': - paramOrderBy += 'price_tier1_v2_f ASC' - break + paramOrderBy += 'price_tier1_v2_f ASC'; + break; case 'price-desc': - paramOrderBy += 'price_tier1_v2_f DESC' - break + paramOrderBy += 'price_tier1_v2_f DESC'; + break; case 'popular': - paramOrderBy += 'product_rating_f DESC, search_rank_i DESC,' - break + paramOrderBy += 'product_rating_f DESC, search_rank_i DESC,'; + break; case 'popular-weekly': - paramOrderBy += 'search_rank_weekly_i DESC' - break + paramOrderBy += 'search_rank_weekly_i DESC'; + break; case 'stock': - paramOrderBy += 'product_rating_f DESC, stock_total_f DESC' - break + paramOrderBy += 'product_rating_f DESC, stock_total_f DESC'; + break; case 'flashsale-price-asc': - paramOrderBy += 'flashsale_price_f ASC' - break + paramOrderBy += 'flashsale_price_f ASC'; + break; default: - paramOrderBy += 'product_rating_f DESC, price_discount_f DESC' - break + paramOrderBy += 'product_rating_f DESC, price_discount_f DESC'; + break; } - let offset = (page - 1) * limit + let offset = (page - 1) * limit; let parameter = [ 'facet.field=manufacture_name_s', 'facet.field=category_name', @@ -55,59 +56,82 @@ export default async function handler(req, res) { `start=${parseInt(offset)}`, `rows=${limit}`, `sort=${paramOrderBy}`, - `fq=-publish_b:false` - ] + `fq=-publish_b:false`, + ]; if (priceFrom > 0 || priceTo > 0) { parameter.push( `fq=price_tier1_v2_f:[${priceFrom == '' ? '*' : priceFrom} TO ${ priceTo == '' ? '*' : priceTo }]` - ) + ); + } + + let { auth } = req.cookies; + if (auth) { + auth = JSON.parse(auth); + if (auth.feature.onlyReadyStock) stock = true; } - 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 (brand) + parameter.push( + `fq=${brand + .split(',') + .map((manufacturer) => `manufacture_name:"${encodeURIComponent(manufacturer)}"`) + .join(' OR ')}` + ); + if (category) + parameter.push( + `fq=${category + .split(',') + .map((cat) => `category_name:"${encodeURIComponent(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 *}`) + if (stock) parameter.push(`fq=stock_total_f:{1 TO *}`); // Single fq in url params - if (typeof fq === 'string') parameter.push(`fq=${fq}`) + if (typeof fq === 'string') parameter.push(`fq=${fq}`); // Multi fq in url params - if (Array.isArray(fq)) parameter = parameter.concat(fq.map((val) => `fq=${val}`)) + if (Array.isArray(fq)) + parameter = parameter.concat(fq.map((val) => `fq=${val}`)); - let result = await axios(process.env.SOLR_HOST + '/solr/product/select?' + parameter.join('&')) + 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) + ); + 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 }) + res.status(400).json({ error: error.message }); } } const escapeSolrQuery = (query) => { - if (query == '*') return query + if (query == '*') return query; - const specialChars = /([\+\-\!\(\)\{\}\[\]\^"~\*\?:\\\/])/g - const words = query.split(/\s+/) + const specialChars = /([\+\-\!\(\)\{\}\[\]\^"~\*\?:\\\/])/g; + const words = query.split(/\s+/); const escapedWords = words.map((word) => { if (specialChars.test(word)) { - return `"${word.replace(specialChars, '\\$1')}"` + return `"${word.replace(specialChars, '\\$1')}"`; } - return word - }) + return word; + }); - return escapedWords.join(' ') -} + return escapedWords.join(' '); +}; /*const productResponseMap = (products, pricelist) => { return products.map((product) => { diff --git a/src/pages/api/shop/variant-detail.js b/src/pages/api/shop/variant-detail.js index fadbe000..08ce75b8 100644 --- a/src/pages/api/shop/variant-detail.js +++ b/src/pages/api/shop/variant-detail.js @@ -8,7 +8,10 @@ export default async function handler(req, res) { `/solr/variants/select?q=id:${req.query.id}&q.op=OR&indent=true` ) let auth = req.query.auth === 'false' ? JSON.parse(req.query.auth) : req.query.auth - let result = variantsMappingSolr('',productVariants.data.response.docs, auth || false) + let productTemplate = await axios( + process.env.SOLR_HOST + `/solr/product/select?q=id:${req.query.id}&q.op=OR&indent=true` + ) + let result = variantsMappingSolr(productTemplate.data.response.docs, productVariants.data.response.docs, auth || false) res.status(200).json(result) } catch (error) { diff --git a/src/pages/google_merchant/products/[page].js b/src/pages/google_merchant/products/[page].js index c8b4079b..6e0eb703 100644 --- a/src/pages/google_merchant/products/[page].js +++ b/src/pages/google_merchant/products/[page].js @@ -1,94 +1,102 @@ -import { createSlug } from '@/core/utils/slug' -import toTitleCase from '@/core/utils/toTitleCase' -import productSearchApi from '@/lib/product/api/productSearchApi' -import variantSearchApi from '@/lib/product/api/variantSearchApi' -import axios from 'axios' -import _ from 'lodash-contrib' -import { create } from 'xmlbuilder' +import { createSlug } from '@/core/utils/slug'; +import toTitleCase from '@/core/utils/toTitleCase'; +import variantSearchApi from '@/lib/product/api/variantSearchApi'; +import axios from 'axios'; +import _ from 'lodash-contrib'; +import { create } from 'xmlbuilder'; export async function getServerSideProps({ res, query }) { - const titleContent = 'Indoteknik.com: B2B Industrial Supply & Solution' + const titleContent = 'Indoteknik.com: B2B Industrial Supply & Solution'; const descriptionContent = - 'Temukan pilihan produk B2B Industri & Alat Teknik untuk Perusahaan, UMKM & Pemerintah dengan lengkap, mudah dan transparan.' + 'Temukan pilihan produk B2B Industri & Alat Teknik untuk Perusahaan, UMKM & Pemerintah dengan lengkap, mudah dan transparan.'; - const { page } = query - const limit = 5000 + const { page } = query; + const limit = 5000; const queries = { limit, page: page.replace('.xml', ''), priceFrom: 1, orderBy: 'popular', - fq: 'image_s:["" TO *]' - } - const products = await variantSearchApi({ query: _.toQuery(queries) }) + fq: 'image_s:["" TO *]', + }; + const products = await variantSearchApi({ query: _.toQuery(queries) }); - const brandsData = {} - const categoriesData = {} + const brandsData = {}; + const categoriesData = {}; - const productItems = [] + const productItems = []; for (const product of products.response.products) { - const productUrl = createSlug('/shop/product/variant/', product.name, product.id, true) - const productId = product.code != '' ? product.code : product.id - const regexHtmlTags = /(<([^>]+)>)/gi - product.description = product.description?.replace(regexHtmlTags, ' ').trim() + const productUrl = createSlug( + '/shop/product/variant/', + product.name, + product.id, + true + ); + const productId = product.code != '' ? product.code : product.id; + const regexHtmlTags = /(<([^>]+)>)/gi; + product.description = product.description + ?.replace(regexHtmlTags, ' ') + .trim(); const defaultProductDescription = - 'Indoteknik.com menawarkan berbagai produk industri, konstruksi, dan teknik terpercaya. Temukan mesin industri, peralatan listrik, alat pengukur, dan banyak lagi. Pengalaman berbelanja mudah dengan deskripsi produk lengkap, spesifikasi teknis, dan gambar jelas. Pembayaran aman, pengiriman cepat ke seluruh Indonesia. Solusi lengkap untuk kebutuhan Kantor, Industri & Teknik Anda.' + 'Indoteknik.com menawarkan berbagai produk industri, konstruksi, dan teknik terpercaya. Temukan mesin industri, peralatan listrik, alat pengukur, dan banyak lagi. Pengalaman berbelanja mudah dengan deskripsi produk lengkap, spesifikasi teknis, dan gambar jelas. Pembayaran aman, pengiriman cepat ke seluruh Indonesia. Solusi lengkap untuk kebutuhan Kantor, Industri & Teknik Anda.'; if (!product.description) { - product.description = defaultProductDescription + product.description = defaultProductDescription; } - let categoryName = null + let categoryName = null; - let brandId = product.manufacture?.id ?? null - let categoryId = null + let brandId = product.manufacture?.id ?? null; + let categoryId = null; if (brandId && brandId in brandsData) { - categoryId = brandsData[brandId].category_ids?.[0] ?? null + categoryId = brandsData[brandId].category_ids?.[0] ?? null; } else { - const solrBrand = await getBrandById(brandId) - brandsData[brandId] = solrBrand - categoryId = solrBrand?.category_ids?.[0] ?? null + const solrBrand = await getBrandById(brandId); + brandsData[brandId] = solrBrand; + categoryId = solrBrand?.category_ids?.[0] ?? null; } if (categoryId && categoryId in categoriesData) { - categoryName = categoriesData[categoryId].name_s ?? null + categoryName = categoriesData[categoryId].name_s ?? null; } else { - const solrCategory = await getCategoryById(categoryId) - categoriesData[categoryId] = solrCategory - categoryName = solrCategory?.name_s ?? null + const solrCategory = await getCategoryById(categoryId); + categoriesData[categoryId] = solrCategory; + categoryName = solrCategory?.name_s ?? null; } - const availability = 'in_stock' + const availability = 'in_stock'; const item = { 'g:id': { '#text': productId }, 'g:title': { '#text': toTitleCase(product.name) }, 'g:description': { '#text': product.description }, 'g:link': { '#text': productUrl }, - 'g:image_link': { '#text': product.image }, + 'g:image_link': { '#text': product.image + '?variant=True' }, 'g:condition': { '#text': 'new' }, 'g:availability': { '#text': availability }, 'g:brand': { '#text': product.manufacture?.name || '' }, - 'g:price': { '#text': `${Math.round(product.lowestPrice.price * 1.11)} IDR` } - } + 'g:price': { + '#text': `${Math.round(product.lowestPrice.price * 1.11)} IDR`, + }, + }; if (product.stockTotal == 0) { - item['g:custom_label_0'] = { '#text': 'Stok Tidak Tersedia' } + item['g:custom_label_0'] = { '#text': 'Stok Tidak Tersedia' }; } else { - item['g:custom_label_1'] = { '#text': 'Stok Tersedia' } + item['g:custom_label_1'] = { '#text': 'Stok Tersedia' }; } if (categoryName) { - item['g:custom_label_2'] = { '#text': categoryName } + item['g:custom_label_2'] = { '#text': categoryName }; } if (product.lowestPrice.discountPercentage > 0) { item['g:sale_price'] = { - '#text': `${Math.round(product.lowestPrice.priceDiscount * 1.11)} IDR` - } + '#text': `${Math.round(product.lowestPrice.priceDiscount * 1.11)} IDR`, + }; } - productItems.push(item) + productItems.push(item); } const googleMerchant = { @@ -99,28 +107,32 @@ export async function getServerSideProps({ res, query }) { title: { '#text': `<![CDATA[${titleContent}]]>` }, link: { '#text': process.env.SELF_HOST }, description: { '#text': `<![CDATA[${descriptionContent}]]>` }, - item: productItems - } - } - } + item: productItems, + }, + }, + }; - res.setHeader('Content-Type', 'text/xml;charset=iso-8859-1') - res.write(create(googleMerchant).end()) - res.end() + res.setHeader('Content-Type', 'text/xml;charset=iso-8859-1'); + res.write(create(googleMerchant).end()); + res.end(); - return { props: {} } + return { props: {} }; } const getBrandById = async (id) => { - const brand = await axios(`${process.env.SOLR_HOST}/solr/brands/select?q=id:${id}`) - return brand.data.response.docs[0] ?? null -} + const brand = await axios( + `${process.env.SOLR_HOST}/solr/brands/select?q=id:${id}` + ); + return brand.data.response.docs[0] ?? null; +}; const getCategoryById = async (id) => { - const category = await axios(`${process.env.SOLR_HOST}/solr/categories/select?q=id:${id}`) - return category.data.response.docs[0] ?? null -} + const category = await axios( + `${process.env.SOLR_HOST}/solr/categories/select?q=id:${id}` + ); + return category.data.response.docs[0] ?? null; +}; export default function GoogleMerchantPage() { - return null + return null; } diff --git a/src/pages/index.jsx b/src/pages/index.jsx index 65d953d2..80e9ef0e 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -1,14 +1,15 @@ import dynamic from 'next/dynamic'; -import MobileView from '@/core/components/views/MobileView'; -import DesktopView from '@/core/components/views/DesktopView'; import { useRef } from 'react'; -import Seo from '@/core/components/Seo'; -import DelayRender from '@/core/components/elements/DelayRender/DelayRender'; + import { HeroBannerSkeleton } from '@/components/skeleton/BannerSkeleton'; import { PopularProductSkeleton } from '@/components/skeleton/PopularProductSkeleton'; -import PromotinProgram from '@/lib/promotinProgram/components/HomePage'; -import PreferredBrandSkeleton from '@/lib/home/components/Skeleton/PreferredBrandSkeleton'; +import Seo from '@/core/components/Seo'; +import DelayRender from '@/core/components/elements/DelayRender/DelayRender'; +import DesktopView from '@/core/components/views/DesktopView'; +import MobileView from '@/core/components/views/MobileView'; import { FlashSaleSkeleton } from '@/lib/flashSale/skeleton/FlashSaleSkeleton'; +import PreferredBrandSkeleton from '@/lib/home/components/Skeleton/PreferredBrandSkeleton'; +import PromotinProgram from '@/lib/promotinProgram/components/HomePage'; import PagePopupIformation from '~/modules/popup-information'; const BasicLayout = dynamic(() => @@ -40,6 +41,11 @@ const FlashSale = dynamic( loading: () => <FlashSaleSkeleton />, } ); + +const ProgramPromotion = dynamic(() => + import('@/lib/home/components/PromotionProgram') +); + const BannerSection = dynamic(() => import('@/lib/home/components/BannerSection') ); @@ -74,8 +80,9 @@ export default function Home() { ]} /> + <PagePopupIformation /> + <DesktopView> - <PagePopupIformation /> <div className='container mx-auto'> <div className='flex min-h-[400px] h-[460px]' @@ -95,9 +102,12 @@ export default function Home() { </div> </div> - <div className='my-16 flex flex-col gap-y-16'> + <div className='my-16 flex flex-col gap-y-8'> <ServiceList /> - <PreferredBrand /> + <div id='flashsale'> + <PreferredBrand /> + </div> + <ProgramPromotion/> <FlashSale /> <PromotinProgram /> <CategoryHomeId /> @@ -108,7 +118,6 @@ export default function Home() { </DesktopView> <MobileView> - <PagePopupIformation /> <DelayRender renderAfter={200}> <HeroBanner /> </DelayRender> @@ -117,10 +126,13 @@ export default function Home() { <ServiceList /> </DelayRender> <DelayRender renderAfter={400}> - <PreferredBrand /> + <div id='flashsale'> + <PreferredBrand /> + </div> </DelayRender> <DelayRender renderAfter={600}> <FlashSale /> + <ProgramPromotion/> </DelayRender> <DelayRender renderAfter={600}> <PromotinProgram /> diff --git a/src/pages/shop/product/variant/[slug].jsx b/src/pages/shop/product/variant/[slug].jsx index 455b248b..cb335e0a 100644 --- a/src/pages/shop/product/variant/[slug].jsx +++ b/src/pages/shop/product/variant/[slug].jsx @@ -1,62 +1,68 @@ -import Seo from '@/core/components/Seo' -import LogoSpinner from '@/core/components/elements/Spinner/LogoSpinner' -import { getIdFromSlug } from '@/core/utils/slug' -import PageNotFound from '@/pages/404' -import dynamic from 'next/dynamic' -import { useRouter } from 'next/router' -import cookie from 'cookie' -import variantApi from '@/lib/product/api/variantApi' -import axios from 'axios' -import { useProductContext } from '@/contexts/ProductContext' -import { useEffect } from 'react' +import axios from 'axios'; +import cookie from 'cookie'; +import dynamic from 'next/dynamic'; +import { useRouter } from 'next/router'; +import { useEffect } from 'react'; -const BasicLayout = dynamic(() => import('@/core/components/layouts/BasicLayout')) -const Product = dynamic(() => import('@/lib/product/components/Product/Product')) +import { useProductContext } from '@/contexts/ProductContext'; +import Seo from '@/core/components/Seo'; +import LogoSpinner from '@/core/components/elements/Spinner/LogoSpinner'; +import { getIdFromSlug } from '@/core/utils/slug'; +import PageNotFound from '@/pages/404'; -export async function getServerSideProps(context) { - const { slug } = context.query - const cookies = context.req.headers.cookie - const cookieObj = cookies ? cookie.parse(cookies) : {} - const auth = cookieObj.auth ? JSON.parse(cookieObj.auth) : {} - const tier = auth.pricelist ? auth.pricelist : false - const authToken = auth?.token || '' +const BasicLayout = dynamic(() => + import('@/core/components/layouts/BasicLayout') +); +const Product = dynamic(() => + import('@/lib/product/components/Product/Product') +); +export async function getServerSideProps(context) { + const { slug } = context.query; + const cookies = context.req.headers.cookie; + const cookieObj = cookies ? cookie.parse(cookies) : {}; + const auth = cookieObj.auth ? JSON.parse(cookieObj.auth) : {}; + const tier = auth.pricelist ? auth.pricelist : false; + const authToken = auth?.token || ''; let response = await axios( - `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/variant-detail?id=` + getIdFromSlug(slug) +'&auth=' + tier - ) - let product = response.data + `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/variant-detail?id=` + + getIdFromSlug(slug) + + '&auth=' + + tier + ); + let product = response.data; // let product = await variantApi({ id: getIdFromSlug(slug), headers: { Token: authToken } }) - + if (product?.length == 1) { - product = product[0] - /* const regexHtmlTags = /(<([^>]+)>)/gi + product = product[0]; + /* const regexHtmlTags = /(<([^>]+)>)/gi const regexHtmlTagsExceptP = /<\/?(?!p\b)[^>]*>/g product.description = product.description .replace(regexHtmlTagsExceptP, ' ') .replace(regexHtmlTags, ' ') .trim()*/ } else { - product = null + product = null; } return { - props: { product } - } + props: { product }, + }; } export default function ProductDetail({ product }) { - const router = useRouter() + const router = useRouter(); - const { setProduct } = useProductContext() + const { setProduct } = useProductContext(); useEffect(() => { if (product) { - setProduct(product) + setProduct(product); } - }, [product, setProduct]) + }, [product, setProduct]); - if (!product) return <PageNotFound /> + if (!product) return <PageNotFound />; return ( <BasicLayout> @@ -70,16 +76,16 @@ export default function ProductDetail({ product }) { url: product?.image, width: 800, height: 800, - alt: product?.name - } + alt: product?.name, + }, ], - type: 'product' + type: 'product', }} additionalMetaTags={[ { name: 'keywords', - content: `${product?.name}, Harga ${product?.name}, Beli ${product?.name}, Spesifikasi ${product?.name}` - } + content: `${product?.name}, Harga ${product?.name}, Beli ${product?.name}, Spesifikasi ${product?.name}`, + }, ]} /> {!product && ( @@ -89,5 +95,5 @@ export default function ProductDetail({ product }) { )} {product && <Product product={product} isVariant={true} />} </BasicLayout> - ) + ); } diff --git a/src/pages/shop/promo/[slug].tsx b/src/pages/shop/promo/[slug].tsx new file mode 100644 index 00000000..bd69c071 --- /dev/null +++ b/src/pages/shop/promo/[slug].tsx @@ -0,0 +1,523 @@ +import dynamic from 'next/dynamic' +import NextImage from 'next/image'; +import { useEffect, useState } from 'react' +import { useRouter } from 'next/router' +import Seo from '../../../core/components/Seo' +import Promocrumb from '../../../lib/promo/components/Promocrumb' +import { fetchPromoItemsSolr, fetchVariantSolr } from '../../../api/promoApi' +import LogoSpinner from '../../../core/components/elements/Spinner/LogoSpinner.jsx' +import ProductPromoCard from '../../../../src-migrate/modules/product-promo/components/Card' +import { IPromotion } from '../../../../src-migrate/types/promotion' +import React from 'react' +import { SolrResponse } from "../../../../src-migrate/types/solr.ts"; +import DesktopView from '../../../core/components/views/DesktopView'; +import MobileView from '../../../core/components/views/MobileView'; +import 'swiper/swiper-bundle.css'; +import useDevice from '../../../core/hooks/useDevice' +import ProductFilterDesktop from '../../../lib/product/components/ProductFilterDesktopPromotion'; +import ProductFilter from '../../../lib/product/components/ProductFilter'; +import { HStack, Image, Tag, TagCloseButton, TagLabel } from '@chakra-ui/react'; +import { formatCurrency } from '../../../core/utils/formatValue'; +import Pagination from '../../../core/components/elements/Pagination/Pagination'; +import SideBanner from '../../../../src-migrate/modules/side-banner'; +import whatsappUrl from '../../../core/utils/whatsappUrl'; +import { cons, toQuery } from 'lodash-contrib'; +import _ from 'lodash'; +import useActive from '../../../core/hooks/useActive'; + +const BasicLayout = dynamic(() => import('../../../core/components/layouts/BasicLayout')) + +export default function PromoDetail() { + const router = useRouter() + const { slug = '', brand ='', category='', priceFrom = '', priceTo = '', page = '1' } = router.query + const [promoItems, setPromoItems] = useState<any[]>([]) + const [promoData, setPromoData] = useState<IPromotion[] | null>(null) + const [currentPage, setCurrentPage] = useState(parseInt(page as string, 10) || 1); + const itemsPerPage = 12; // Jumlah item yang ingin ditampilkan per halaman + const [loading, setLoading] = useState(true); + const { isMobile, isDesktop } = useDevice() + const [brands, setBrands] = useState<Brand[]>([]); + const [categories, setCategories] = useState<Category[]>([]); + const [brandValues, setBrandValues] = useState<string[]>([]); + const [categoryValues, setCategoryValues] = useState<string[]>([]); + const [orderBy, setOrderBy] = useState(router.query?.orderBy || 'popular'); + const popup = useActive(); + const prefixUrl = `/shop/promo/${slug}` + + useEffect(() => { + if (router.query.brand) { + let brandsArray: string[] = []; + if (Array.isArray(router.query.brand)) { + brandsArray = router.query.brand; + } else if (typeof router.query.brand === 'string') { + brandsArray = router.query.brand.split(',').map((brand) => brand.trim()); + } + setBrandValues(brandsArray); + } else { + setBrandValues([]); + } + + if (router.query.category) { + let categoriesArray: string[] = []; + + if (Array.isArray(router.query.category)) { + categoriesArray = router.query.category; + } else if (typeof router.query.category === 'string') { + categoriesArray = router.query.category.split(',').map((category) => category.trim()); + } + setCategoryValues(categoriesArray); + } else { + setCategoryValues([]); + } + }, [router.query.brand, router.query.category]); + + interface Brand { + brand: string; + qty: number; + } + + interface Category { + name: string; + qty: number; + } + + useEffect(() => { + const loadPromo = async () => { + setLoading(true); + const brandsData: Brand[] = []; + const categoriesData: Category[] = []; + + const pageNumber = Array.isArray(page) ? parseInt(page[0], 10) : parseInt(page, 10); + setCurrentPage(pageNumber) + + try { + const items = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug}`); + setPromoItems(items); + + if (items.length === 0) { + setPromoData([]) + setLoading(false); + return; + } + + const brandArray = Array.isArray(brand) ? brand : brand.split(','); + const categoryArray = Array.isArray(category) ? category : category.split(','); + + const promoDataPromises = items.map(async (item) => { + + try { + let brandQuery = ''; + if (brand) { + brandQuery = brandArray.map(b => `manufacture_name_s:${b}`).join(' OR '); + brandQuery = `(${brandQuery})`; + } + + let categoryQuery = ''; + if (category) { + categoryQuery = categoryArray.map(c => `category_name:${c}`).join(' OR '); + categoryQuery = `(${categoryQuery})`; + } + + let priceQuery = ''; + if (priceFrom && priceTo) { + priceQuery = `price_f:[${priceFrom} TO ${priceTo}]`; + } else if (priceFrom) { + priceQuery = `price_f:[${priceFrom} TO *]`; + } else if (priceTo) { + priceQuery = `price_f:[* TO ${priceTo}]`; + } + + let combinedQuery = ''; + let combinedQueryPrice = `${priceQuery}`; + if (brand && category && priceFrom || priceTo) { + combinedQuery = `${brandQuery} AND ${categoryQuery} `; + } else if (brand && category) { + combinedQuery = `${brandQuery} AND ${categoryQuery}`; + } else if (brand && priceFrom || priceTo) { + combinedQuery = `${brandQuery}`; + } else if (category && priceFrom || priceTo) { + combinedQuery = `${categoryQuery}`; + } else if (brand) { + combinedQuery = brandQuery; + } else if (category) { + combinedQuery = categoryQuery; + } + + if (combinedQuery && priceFrom || priceTo) { + const response = await fetchVariantSolr(`id:${item.product_id} AND ${combinedQuery}`); + const product = response.response.docs[0]; + const product_id = product.id; + const response2 = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug} AND product_ids:${product_id} AND ${combinedQueryPrice}`); + return response2; + }else if(combinedQuery){ + const response = await fetchVariantSolr(`id:${item.product_id} AND ${combinedQuery}`); + const product = response.response.docs[0]; + const product_id = product.id; + const response2 = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug} AND product_ids:${product_id} `); + return response2; + } else { + const response = await fetchPromoItemsSolr(`id:${item.id}`); + return response; + } + } catch (fetchError) { + return []; + } + }); + + const promoDataArray = await Promise.all(promoDataPromises); + const mergedPromoData = promoDataArray.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []); + setPromoData(mergedPromoData); + + const dataBrandCategoryPromises = promoDataArray.map(async (promoData) => { + if (promoData) { + const dataBrandCategory = promoData.map(async (item) => { + let response; + if(category){ + const categoryQuery = categoryArray.map(c => `category_name:${c}`).join(' OR '); + response = await fetchVariantSolr(`id:${item.products[0].product_id} AND (${categoryQuery})`); + }else{ + response = await fetchVariantSolr(`id:${item.products[0].product_id}`) + } + + + if (response.response?.docs?.length > 0) { + const product = response.response.docs[0]; + const manufactureNameS = product.manufacture_name; + if (Array.isArray(manufactureNameS)) { + for (let i = 0; i < manufactureNameS.length; i += 2) { + const brand = manufactureNameS[i]; + const qty = 1; + const existingBrandIndex = brandsData.findIndex(b => b.brand === brand); + if (existingBrandIndex !== -1) { + brandsData[existingBrandIndex].qty += qty; + } else { + brandsData.push({ brand, qty }); + } + } + } + + const categoryNameS = product.category_name; + if (Array.isArray(categoryNameS)) { + for (let i = 0; i < categoryNameS.length; i += 2) { + const name = categoryNameS[i]; + const qty = 1; + const existingCategoryIndex = categoriesData.findIndex(c => c.name === name); + if (existingCategoryIndex !== -1) { + categoriesData[existingCategoryIndex].qty += qty; + } else { + categoriesData.push({ name, qty }); + } + } + } + } + }); + + return Promise.all(dataBrandCategory); + } + }); + + await Promise.all(dataBrandCategoryPromises); + setBrands(brandsData); + setCategories(categoriesData); + setLoading(false); + + } catch (loadError) { + // console.error("Error loading promo items:", loadError) + setLoading(false); + } + } + + if (slug) { + loadPromo() + } + },[slug, brand, category, priceFrom, priceTo, currentPage]); + + + function capitalizeFirstLetter(string) { + string = string.replace(/_/g, ' '); + return string.replace(/(^\w|\s\w)/g, function(match) { + return match.toUpperCase(); + }); + } + + const handleDeleteFilter = async (source, value) => { + let params = { + q: router.query.q, + orderBy: '', + brand: brandValues.join(','), + category: categoryValues.join(','), + priceFrom: priceFrom || '', + priceTo: priceTo || '', + }; + + let brands = brandValues; + let catagories = categoryValues; + switch (source) { + case 'brands': + brands = brandValues.filter((item) => item !== value); + params.brand = brands.join(','); + await setBrandValues(brands); + break; + case 'category': + catagories = categoryValues.filter((item) => item !== value); + params.category = catagories.join(','); + await setCategoryValues(catagories); + break; + case 'price': + params.priceFrom = ''; + params.priceTo = ''; + break; + case 'delete': + params = { + q: router.query.q, + orderBy: '', + brand: '', + category: '', + priceFrom: '', + priceTo: '', + }; + break; + } + + handleSubmitFilter(params); + }; + const handleSubmitFilter = (params) => { + params = _.pickBy(params, _.identity); + params = toQuery(params); + router.push(`${slug}?${params}`); + }; + + const visiblePromotions = promoData?.slice( (currentPage-1) * itemsPerPage, currentPage * 12) + + const toQuery = (obj) => { + const str = Object.keys(obj) + .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`) + .join('&') + return str + } + + const whatPromo = capitalizeFirstLetter(slug) + const queryWithoutSlug = _.omit(router.query, ['slug']) + const queryString = toQuery(queryWithoutSlug) + + return ( + <BasicLayout> + <Seo + title={`Promo ${Array.isArray(slug) ? slug[0] : slug} Terkini`} + description='B2B Marketplace MRO & Industri dengan Layanan Pembayaran Tempo, Faktur Pajak, Online Quotation, Garansi Resmi & Harga Kompetitif' + /> + <Promocrumb brandName={whatPromo} /> + <MobileView> + <div className='p-4 pt-0'> + <h1 className='mb-2 font-semibold text-h-sm'>Promo {whatPromo}</h1> + + <FilterChoicesComponent + brandValues={brandValues} + categoryValues={categoryValues} + priceFrom={priceFrom} + priceTo={priceTo} + handleDeleteFilter={handleDeleteFilter} + /> + {promoItems.length >= 1 && ( + <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> + )} + + {loading ? ( + <div className='container flex justify-center my-4'> + <LogoSpinner width={48} height={48} /> + </div> + ) : promoData && promoItems.length >= 1 ? ( + <> + <div className='grid grid-cols-1 gap-x-1 gap-y-1'> + {visiblePromotions?.map((promotion) => ( + <div key={promotion.id} className="min-w-36 max-w-[400px] mb-[20px] sm:w-full md:w-1/2 lg:w-1/3 xl:w-1/4 "> + <ProductPromoCard promotion={promotion}/> + </div> + ))} + </div> + </> + ) : ( + <div className="text-center my-8"> + <p>Belum ada promo pada kategori ini</p> + </div> + )} + + <Pagination + pageCount={Math.ceil((promoData?.length ?? 0) / itemsPerPage)} + currentPage={currentPage} + url={`${prefixUrl}?${toQuery(_.omit(queryWithoutSlug, ['page']))}`} + className='mt-6 mb-2' + /> + <ProductFilter + active={popup.active} + close={popup.deactivate} + brands={brands || []} + categories={categories || []} + prefixUrl={router.asPath.includes('?') ? `${router.asPath}` : `${router.asPath}?`} + defaultBrand={null} + /> + </div> + + </MobileView> + <DesktopView> + <div className='container mx-auto flex mb-3 flex-col'> + <div className='w-full pl-6'> + <h1 className='text-2xl mb-2 font-semibold'>Promo {whatPromo}</h1> + <div className=' w-full h-full flex flex-row items-center '> + + <div className='detail-filter w-1/2 flex justify-start items-center mt-4'> + + <FilterChoicesComponent + brandValues={brandValues} + categoryValues={categoryValues} + priceFrom={priceFrom} + priceTo={priceTo} + handleDeleteFilter={handleDeleteFilter} + /> + </div> + <div className='Filter w-1/2 flex flex-col'> + + <ProductFilterDesktop + brands={brands || []} + categories={categories || []} + prefixUrl={'/shop/promo'} + // defaultBrand={null} + /> + </div> + </div> + {loading ? ( + <div className='container flex justify-center my-4'> + <LogoSpinner width={48} height={48} /> + </div> + ) : promoData && promoItems.length >= 1 ? ( + <> + <div className='grid grid-cols-1 gap-x-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3'> + {visiblePromotions?.map((promotion) => ( + <div key={promotion.id} className="min-w-[400px] max-w-[400px] mb-[20px] sm:min-w-[350px] md:min-w-[380px] lg:min-w-[400px] xl:min-w-[400px] "> + <ProductPromoCard promotion={promotion}/> + </div> + ))} + </div> + </> + ) : ( + <div className="text-center my-8"> + <p>Belum ada promo pada kategori ini</p> + </div> + )} + <div className='flex justify-between items-center mt-6 mb-2'> + <div className='pt-2 pb-6 flex items-center gap-x-3'> + <NextImage + src='/images/logo-question.png' + alt='Logo Question Indoteknik' + width={60} + height={60} + /> + <div className='text-gray_r-12/90'> + <span> + Barang yang anda cari tidak ada?{' '} + <a + href={ + router.query?.q + ? whatsappUrl('productSearch', { + name: router.query.q, + }) + : whatsappUrl() + } + className='text-danger-500' + > + Hubungi Kami + </a> + </span> + </div> + </div> + + + + <Pagination + pageCount={Math.ceil((promoData?.length ?? 0) / itemsPerPage)} + currentPage={currentPage} + url={`${prefixUrl}?${toQuery(_.omit(queryWithoutSlug, ['page']))}`} + className='mt-6 mb-2' + /> + </div> + + </div> + </div> + </DesktopView> + </BasicLayout> + ) + } + +const FilterChoicesComponent = ({ + brandValues, + categoryValues, + priceFrom, + priceTo, + handleDeleteFilter, + }) => ( + <div className='flex items-center mb-4'> + <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/pages/shop/promo/index.tsx b/src/pages/shop/promo/index.tsx new file mode 100644 index 00000000..89e88e29 --- /dev/null +++ b/src/pages/shop/promo/index.tsx @@ -0,0 +1,186 @@ +import dynamic from 'next/dynamic' +import { useEffect, useState } from 'react' +import { useRouter } from 'next/router' +import Seo from '../../../core/components/Seo.jsx' +import Promocrumb from '../../../lib/promo/components/Promocrumb.jsx' +import { fetchPromoItemsSolr } from '../../../api/promoApi.js' +import LogoSpinner from '../../../core/components/elements/Spinner/LogoSpinner.jsx' +import ProductPromoCard from '../../../../src-migrate/modules/product-promo/components/Card.tsx' +import { IPromotion } from '../../../../src-migrate/types/promotion.ts' +import React from 'react' +import { SolrResponse } from "../../../../src-migrate/types/solr.ts"; + +const BasicLayout = dynamic(() => import('../../../core/components/layouts/BasicLayout.jsx')) + +export default function Promo() { + const router = useRouter() + const { slug = '' } = router.query + const [promoItems, setPromoItems] = useState<any[]>([]) + const [promoData, setPromoData] = useState<IPromotion[] | null>(null) + const [loading, setLoading] = useState(true) + const [currentPage, setCurrentPage] = useState(1) + const [fetchingData, setFetchingData] = useState(false) + + useEffect(() => { + const loadPromo = async () => { + try { + const items = await fetchPromoItemsSolr(`*:*`) + console.log("slug sekarang ", slug) + + setPromoItems(items) + console.log("data dari promotion pakai SOLR", items) + + if (items.length === 0) { + setPromoData([]) + setLoading(false); + return; + } + + const promoDataPromises = items.map(async (item) => { + const queryParams = new URLSearchParams({ q: `id:${item.id}` }) + console.log("Constructed URL:", `/solr/promotion_program_lines/select?${queryParams.toString()}`) + + try { + const response = await fetch(`/solr/promotion_program_lines/select?${queryParams.toString()}`) + console.log("respon data ", response) + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data: SolrResponse<any[]> = await response.json() + console.log("data promo IPromotion[]", data) + + const promotions = await map(data.response.docs) + return promotions; + } catch (fetchError) { + console.error("Error fetching promotion data:", fetchError) + return []; + } + }); + + const promoDataArray = await Promise.all(promoDataPromises); + const mergedPromoData = promoDataArray.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []); + setPromoData(mergedPromoData); + setTimeout(() => setLoading(false), 120); // Menambahkan delay 200ms sebelum mengubah status loading + } catch (loadError) { + console.error("Error loading promo items:", loadError) + setLoading(false); + } + } + + if (slug) { + loadPromo() + } + }, [slug]) + + const map = async (promotions: any[]): Promise<IPromotion[]> => { + const result: IPromotion[] = [] + + for (const promotion of promotions) { + const data: IPromotion = { + id: promotion.id, + program_id: promotion.program_id_i, + name: promotion.name_s, + type: { + value: promotion.type_value_s, + label: promotion.type_label_s, + }, + limit: promotion.package_limit_i, + limit_user: promotion.package_limit_user_i, + limit_trx: promotion.package_limit_trx_i, + price: promotion.price_f, + total_qty: promotion.total_qty_i, + products: JSON.parse(promotion.products_s), + free_products: JSON.parse(promotion.free_products_s), + } + + result.push(data) + } + + return result + } + + console.log("data yg dikirim ke ProductPromoCard", promoData) + + + useEffect(() => { + const handleScroll = () => { + if ( + !fetchingData && + window.innerHeight + document.documentElement.scrollTop >= 0.95 * document.documentElement.offsetHeight + ) { + // User has scrolled to 95% of page height + + setTimeout(() => setFetchingData(true), 120); + setCurrentPage((prevPage) => prevPage + 1) + } + } + + window.addEventListener('scroll', handleScroll) + return () => window.removeEventListener('scroll', handleScroll) + }, [fetchingData]) + + useEffect(() => { + if (fetchingData) { + // Fetch more data + // You may need to adjust this logic according to your API + fetchMoreData() + } + }, [fetchingData]) + + const fetchMoreData = async () => { + try { + // Add a delay of approximately 150ms + setTimeout(async () => { + // Fetch more data + // Update promoData state with the new data + }, 150) + } catch (error) { + console.error('Error fetching more data:', error) + } finally { + setTimeout(() => setFetchingData(false), 120); + + } + } + + const visiblePromotions = promoData?.slice(0, currentPage * 12) + + return ( + <BasicLayout> + <Seo + title={`Promo ${Array.isArray(slug) ? slug[0] : slug} Terkini`} + description='B2B Marketplace MRO & Industri dengan Layanan Pembayaran Tempo, Faktur Pajak, Online Quotation, Garansi Resmi & Harga Kompetitif' + /> + {/* <Promocrumb brandName={capitalizeFirstLetter(Array.isArray(slug) ? slug[0] : slug)} /> */} + <div className='container mx-auto mt-1 flex mb-1'> + <div className=''> + <h1 className='font-semibold'>Semua Promo di Indoteknik</h1> + </div> + </div> + {loading ? ( + <div className='container flex justify-center my-4'> + <LogoSpinner width={48} height={48} /> + </div> + ) : promoData && promoItems.length >= 1 ? ( + <> + <div className='flex flex-wrap justify-center'> + {visiblePromotions?.map((promotion) => ( + <div key={promotion.id} className="min-w-[40px] max-w-[400px] mr-[20px] mb-[20px] sm:w-full md:w-1/2 lg:w-1/3 xl:w-1/4"> + <ProductPromoCard promotion={promotion} /> + </div> + ))} + </div> + {fetchingData && ( + <div className='container flex justify-center my-4'> + <LogoSpinner width={48} height={48} /> + </div> + )} + </> + ) : ( + <div className="text-center my-8"> + <p>Belum ada promo pada kategori ini</p> + </div> + )} + </BasicLayout> + ) +} diff --git a/src/pages/video.jsx b/src/pages/video.jsx index 61790dbb..7d1f8372 100644 --- a/src/pages/video.jsx +++ b/src/pages/video.jsx @@ -44,7 +44,7 @@ export default function Video() { </LazyLoadComponent> <div className='p-3'> <a - href='https://www.youtube.com/@indoteknikb2bindustriale-c778' + href='https://www.youtube.com/@indoteknikcom' className='text-danger-500 mb-2 block' > {video.channelName} |
