diff options
Diffstat (limited to 'src-migrate/modules')
| -rw-r--r-- | src-migrate/modules/promo/components/Hero.tsx | 68 | ||||
| -rw-r--r-- | src-migrate/modules/promo/components/PromoList.jsx | 194 | ||||
| -rw-r--r-- | src-migrate/modules/promo/components/PromotinProgram.jsx | 95 | ||||
| -rw-r--r-- | src-migrate/modules/promo/components/PromotinProgram.tsx | 9 | ||||
| -rw-r--r-- | src-migrate/modules/promo/components/Voucher.tsx | 84 | ||||
| -rw-r--r-- | src-migrate/modules/promo/styles/hero.module.css | 27 | ||||
| -rw-r--r-- | src-migrate/modules/promo/styles/voucher.module.css | 43 |
7 files changed, 520 insertions, 0 deletions
diff --git a/src-migrate/modules/promo/components/Hero.tsx b/src-migrate/modules/promo/components/Hero.tsx new file mode 100644 index 00000000..d8bb0be4 --- /dev/null +++ b/src-migrate/modules/promo/components/Hero.tsx @@ -0,0 +1,68 @@ +import 'swiper/css'; + +import Image from 'next/image'; +import { useEffect, useMemo } from 'react'; +import { useQuery } from 'react-query'; +import { Swiper, SwiperProps, SwiperSlide } from 'swiper/react'; + +import { getBanner } from '~/services/banner'; +import style from '../styles/hero.module.css'; +import 'swiper/css/navigation'; +import { Navigation, Pagination } from 'swiper'; + +const swiperBanner: SwiperProps = { + modules:[Navigation, Pagination], + autoplay: { + delay: 6000, + disableOnInteraction: false + }, + loop: true, + className: 'h-[400px] w-full', + slidesPerView: 1, + spaceBetween: 10, + navigation:true, +} + +const Hero = () => { + const bannerQuery = useQuery({ + queryKey: ['banner.all-promo'], + queryFn: () => getBanner({ type: 'banner-promotion' }) + }) + + const banners = useMemo(() => bannerQuery.data || [], [bannerQuery.data]); + + useEffect(() => { + if (banners.length > 1) { + swiperBanner.slidesPerView = 1.1; + swiperBanner.loop = true; + } + }, [banners]); + + return ( + <div className={style['wrapper']}> + <div className={style['desc-section']}> + <div className={style['title']}>Pasti Hemat & Untung Selama Belanja di Indoteknik.com!</div> + <div className='h-4' /> + <div className={style['subtitle']}>Cari paket yang kami sediakan dengan penawaran harga & Nikmati kemudahan dalam setiap transaksi dengan fitur lengkap Pembayaran hingga barang sampai! </div> + </div> + + <div className={style['banner-section']}> + <Swiper {...swiperBanner}> + {banners.map((banner, index) => ( + <SwiperSlide key={index}> + <Image + src={banner.image} + alt={banner.name} + width={666} + height={450} + className='w-full h-full object-cover object-center rounded-2xl' + /> + </SwiperSlide> + ))} + </Swiper> + </div> + </div> + ) +} + +export default Hero
\ No newline at end of file diff --git a/src-migrate/modules/promo/components/PromoList.jsx b/src-migrate/modules/promo/components/PromoList.jsx new file mode 100644 index 00000000..af2958aa --- /dev/null +++ b/src-migrate/modules/promo/components/PromoList.jsx @@ -0,0 +1,194 @@ +import Image from 'next/image'; +import Link from '@/core/components/elements/Link/Link'; +import ProductPromoCard from '../../../../src-migrate/modules/product-promo/components/Card' +// import { fetchPromoItemsSolr, fetchVariantSolr } from '../../../api/promoApi' + + +export const fetchPromoItemsSolr = async (type) => { + // let query = type ? `type_value_s:${type}` : '*:*'; + let start = 0 + let rows = 100 + try { + const queryParams = new URLSearchParams({ q: type }); + const response = await fetch(`/solr/promotion_program_lines/select?${queryParams.toString()}&rows=${rows}&start=${start}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + const promotions = await map(data.response.docs); + return promotions; + } catch (error) { + console.error("Error fetching promotion data:", error); + return []; + } +}; + + +const PromoList = ({ selectedPromo }) => { + + useEffect(() => { + const loadPromo = async () => { + setLoading(true); + + + 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]); + + let title = ''; + + if (selectedPromo === 'Bundling') { + title = 'Kombinasi Kilat Pilihan Kami!'; + } else if (selectedPromo === 'Loading') { + title = 'Belanja Borong Pilihan Kami!'; + } else if (selectedPromo === 'Merchandise') { + title = 'Gratis Merchandise Spesial Indoteknik'; + } + + return ( + <div> + <h1 className='text-h-sm md:text-h-lg font-semibold mb-4'>{title}</h1> + <div>DISINI CARD {selectedPromo}</div> + </div> + ); +} + +export default PromoList; diff --git a/src-migrate/modules/promo/components/PromotinProgram.jsx b/src-migrate/modules/promo/components/PromotinProgram.jsx new file mode 100644 index 00000000..a7e5dfef --- /dev/null +++ b/src-migrate/modules/promo/components/PromotinProgram.jsx @@ -0,0 +1,95 @@ +import React from 'react'; +import Image from 'next/image'; +import { InfoIcon } from "lucide-react" + +const PromotionProgram = ({ selectedPromo, onSelectPromo }) => { + return ( + <> + <div className="text-h-sm md:text-h-lg font-semibold mb-4">Serba Serbi Promo</div> + <div className='px-4 sm:px-0'> + <div className='grid md:grid-cols-3 grid-cols-2 justify-between gap-2 items-center'> + <div className='w-full'> + <div + onClick={() => onSelectPromo('Bundling')} + className={`border p-2 flex items-center gap-x-2 rounded-lg cursor-pointer ${selectedPromo === 'Bundling' ? 'bg-red-50 border-red-500 text-red-500' : 'border-gray-200 text-gray-900'}`} + > + <div> + <Image + width={24} + height={24} + quality={100} + src='/images/icon_promo/silat.svg' + alt='' + className='h-20 w-20 rounded' + /> + </div> + <div> + <div className='flex w-full flex-row items-center justify-start'> + <h1 className={`mr-1 font-semibold text-base ${selectedPromo === 'Bundling' ? 'text-red-500' : 'text-gray-900'}`}>Paket Silat</h1> + <InfoIcon className='mt-[1px] text-red-500' size={14} /> + </div> + <p className={`text-xs md:text-sm ${selectedPromo === 'Bundling' ? 'text-red-500' : 'text-gray-500'}`}> + Pilihan bundling barang kombinasi Silat. + </p> + </div> + </div> + </div> + <div className='w-full'> + <div + onClick={() => onSelectPromo('Loading')} + className={`border p-2 flex items-center gap-x-2 rounded-lg cursor-pointer ${selectedPromo === 'Loading' ? 'bg-red-50 border-red-500 text-red-500' : 'border-gray-200 text-gray-900'}`} + > + <div> + <Image + width={24} + height={24} + quality={100} + src='/images/icon_promo/barong.svg' + alt='' + className='h-20 w-20 rounded' + /> + </div> + <div> + <div className='flex w-full flex-row items-center justify-start'> + <h1 className={`mr-1 font-semibold text-base ${selectedPromo === 'Loading' ? 'text-red-500' : 'text-gray-900'}`}>Paket Barong</h1> + <InfoIcon className='mt-[1px] text-red-500' size={14} /> + </div> + <p className={`text-xs md:text-sm ${selectedPromo === 'Loading' ? 'text-red-500' : 'text-gray-500'}`}> + Beli banyak barang/partai barang borong. + </p> + </div> + </div> + </div> + <div className='w-full'> + <div + onClick={() => onSelectPromo('Merchandise')} + className={`border p-2 flex items-center gap-x-2 rounded-lg cursor-pointer ${selectedPromo === 'Merchandise' ? 'bg-red-50 border-red-500 text-red-500' : 'border-gray-200 text-gray-900'}`} + > + <div> + <Image + width={24} + height={24} + quality={100} + src='/images/icon_promo/angklung.svg' + alt='' + className='h-20 w-20 rounded' + /> + </div> + <div > + <div className='flex w-full flex-row items-center justify-start '> + <h1 className={`mr-1 font-semibold text-base ${selectedPromo === 'Merchandise' ? 'text-red-500' : 'text-gray-900'}`}>Paket Angklung</h1> + <InfoIcon className='mt-[1px] text-red-500' size={14} /> + </div> + <p className={` m1 text-xs md:text-sm ${selectedPromo === 'Merchandise' ? 'text-red-500' : 'text-gray-500'}`}> + Gratis barang promosi/merchandise menang langsung. + </p> + </div> + </div> + </div> + </div> + </div> + </> + ); +}; + +export default PromotionProgram; diff --git a/src-migrate/modules/promo/components/PromotinProgram.tsx b/src-migrate/modules/promo/components/PromotinProgram.tsx new file mode 100644 index 00000000..19f228ea --- /dev/null +++ b/src-migrate/modules/promo/components/PromotinProgram.tsx @@ -0,0 +1,9 @@ +const PromotionProgram = () => { + return ( + <> + <div className="">Serba Serbi Promo</div> + </> + ) +} + +export default PromotionProgram
\ No newline at end of file diff --git a/src-migrate/modules/promo/components/Voucher.tsx b/src-migrate/modules/promo/components/Voucher.tsx new file mode 100644 index 00000000..14d3c301 --- /dev/null +++ b/src-migrate/modules/promo/components/Voucher.tsx @@ -0,0 +1,84 @@ +import { useMemo } from 'react' +import { useQuery } from 'react-query' +import { Swiper, SwiperProps, SwiperSlide } from 'swiper/react' +import { getVoucher } from '~/services/voucher' +import style from '../styles/voucher.module.css' +import Image from 'next/image' +import { useToast } from '@chakra-ui/react' + +const swiperVoucher: SwiperProps = { + autoplay: { + delay: 6000, + disableOnInteraction: false + }, + loop: false, + className: 'h-[160px] w-full', + slidesPerView: 3, + spaceBetween: 16 +} + +const Voucher = () => { + const toast = useToast(); + const voucherQuery = useQuery({ + queryKey: ['voucher.all-voucher'], + queryFn: getVoucher + }) + + const vouchers = useMemo(() => voucherQuery.data || [], [voucherQuery.data]); + + const copyText = (text: string) => { + navigator.clipboard.writeText(text) + toast({ + title: 'Salin ke papan klip', + description: 'Kode voucher berhasil disalin', + status: 'success', + duration: 3000, + isClosable: true, + position: 'bottom', + }) + } + + return ( + <> + <div className={style['title']}>Pakai Voucher Belanja</div> + + <div className='h-6' /> + + {voucherQuery.isLoading && ( + <div className='grid grid-cols-3 gap-x-4 animate-pulse'> + {Array.from({ length: 3 }).map((_, index) => ( + <div key={index} className='w-full h-[160px] bg-gray-200 rounded-xl' /> + ))} + </div> + )} + {!voucherQuery.isLoading && ( + <div className={style['voucher-section']}> + <Swiper {...swiperVoucher}> + {vouchers.map((voucher) => ( + <SwiperSlide key={voucher.id} className='pb-2'> + <div className={style['voucher-card']}> + <Image src={voucher.image} alt={voucher.name} width={128} height={128} className={style['voucher-image']} /> + + <div className={style['voucher-content']}> + <div className={style['voucher-title']}>{voucher.name}</div> + <div className={style['voucher-desc']}>{voucher.description}</div> + <div className={style['voucher-bottom']}> + <div> + <div className={style['voucher-code-desc']}>Kode Promo</div> + <div className={style['voucher-code']}>{voucher.code}</div> + </div> + <button className={style['voucher-copy']} onClick={() => copyText(voucher.code)}>Salin</button> + </div> + </div> + </div> + </SwiperSlide> + ))} + </Swiper> + </div> + )} + + </> + ) +} + +export default Voucher
\ No newline at end of file diff --git a/src-migrate/modules/promo/styles/hero.module.css b/src-migrate/modules/promo/styles/hero.module.css new file mode 100644 index 00000000..70d725be --- /dev/null +++ b/src-migrate/modules/promo/styles/hero.module.css @@ -0,0 +1,27 @@ +.wrapper { + @apply bg-warning-100/50 rounded-xl w-full h-[460px] flex; +} + +.desc-section { + @apply w-full md:w-5/12 + flex flex-col + md:justify-center + p-6 md:pl-10; +} + +.title { + @apply text-title-sm md:text-title-lg + leading-[30px] md:leading-[42px] + font-semibold; +} + +.subtitle { + @apply text-body-2 leading-7 text-gray-700; +} + +.banner-section { + @apply md:w-7/12 + flex flex-col + md:justify-center + md:pr-10; +} diff --git a/src-migrate/modules/promo/styles/voucher.module.css b/src-migrate/modules/promo/styles/voucher.module.css new file mode 100644 index 00000000..24d3aac2 --- /dev/null +++ b/src-migrate/modules/promo/styles/voucher.module.css @@ -0,0 +1,43 @@ +.title { + @apply text-h-sm md:text-h-lg font-semibold; +} + +.voucher-section { + @apply w-full; +} + +.voucher-card { + @apply w-full h-full rounded-xl border border-gray-200 shadow-md p-4 flex gap-x-4; +} + +.voucher-image { + @apply bg-gray-100 rounded-lg w-4/12 h-full object-contain object-center; +} + +.voucher-content { + @apply flex-1 flex flex-col; +} + +.voucher-title { + @apply font-medium text-body-1 leading-6 mb-1; +} + +.voucher-desc { + @apply text-gray-800 line-clamp-2 text-caption-1; +} + +.voucher-bottom { + @apply flex justify-between mt-auto; +} + +.voucher-code-desc { + @apply text-gray-500 text-caption-1; +} + +.voucher-code { + @apply text-red-700 font-medium; +} + +.voucher-copy { + @apply bg-gray-200 hover:bg-danger-500 text-danger-500 hover:text-white transition-colors rounded-lg flex items-center justify-center px-6; +} |
