summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/components/elements/Footer/BasicFooter.jsx2
-rw-r--r--src/core/components/elements/Navbar/NavbarDesktop.jsx76
-rw-r--r--src/core/components/elements/Navbar/TopBanner.jsx9
-rw-r--r--src/lib/category/api/popularProduct.js31
-rw-r--r--src/lib/category/components/Category.jsx35
-rw-r--r--src/lib/category/components/PopularBrand.jsx83
-rw-r--r--src/lib/flashSale/components/FlashSaleNonDisplay.jsx66
-rw-r--r--src/lib/home/components/CategoryDynamic.jsx3
-rw-r--r--src/lib/product/components/ProductCard.jsx4
-rw-r--r--src/lib/product/components/ProductSearch.jsx5
-rw-r--r--src/pages/api/shop/search.js10
11 files changed, 274 insertions, 50 deletions
diff --git a/src/core/components/elements/Footer/BasicFooter.jsx b/src/core/components/elements/Footer/BasicFooter.jsx
index 6129143d..4beea604 100644
--- a/src/core/components/elements/Footer/BasicFooter.jsx
+++ b/src/core/components/elements/Footer/BasicFooter.jsx
@@ -259,7 +259,7 @@ const InformationCenter = () => (
<li className='text-gray_r-12/80 flex items-center'>
<PhoneArrowUpRightIcon className='w-[18px] mr-2' />
<a href='tel:02129338828' target='_blank' rel='noreferrer'>
- (021) 2933-8828 / 29
+ (021) 2933-8828
</a>
</li>
<li className='text-gray_r-12/80 flex items-center'>
diff --git a/src/core/components/elements/Navbar/NavbarDesktop.jsx b/src/core/components/elements/Navbar/NavbarDesktop.jsx
index 2e8c982e..b23b256e 100644
--- a/src/core/components/elements/Navbar/NavbarDesktop.jsx
+++ b/src/core/components/elements/Navbar/NavbarDesktop.jsx
@@ -12,11 +12,12 @@ import {
ChevronDownIcon,
DocumentCheckIcon,
HeartIcon,
+ ArrowUpRightIcon,
} from '@heroicons/react/24/outline';
import dynamic from 'next/dynamic';
import Image from 'next/image';
import { useRouter } from 'next/router';
-import { useEffect, useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
import DesktopView from '../../views/DesktopView';
import Link from '../Link/Link';
import NavbarUserDropdown from './NavbarUserDropdown';
@@ -62,6 +63,46 @@ const NavbarDesktop = () => {
(transaction) => transaction.status === 'draft'
);
+ const [showPopup, setShowPopup] = useState(false);
+ const [isTop, setIsTop] = useState(true);
+
+ const handleTopBannerLoad = useCallback(() => {
+ const showTimer = setTimeout(() => {
+ setShowPopup(true);
+ }, 500);
+
+ const hideTimer = setTimeout(() => {
+ // setShowPopup(false);
+ }, 9500);
+
+ return () => {
+ clearTimeout(showTimer);
+ clearTimeout(hideTimer);
+ };
+ }, []);
+
+ useEffect(() => {
+ const handleScroll = () => {
+ setIsTop(window.scrollY < 100);
+ };
+
+ window.addEventListener('scroll', handleScroll);
+ return () => {
+ window.removeEventListener('scroll', handleScroll);
+ };
+ }, []);
+
+ useEffect(() => {
+ const handleScroll = () => {
+ setIsTop(window.scrollY < 100);
+ };
+
+ window.addEventListener('scroll', handleScroll);
+ return () => {
+ window.removeEventListener('scroll', handleScroll);
+ };
+ }, []);
+
useEffect(() => {
setPendingTransactions(data);
}, [transactions.data]);
@@ -115,7 +156,7 @@ const NavbarDesktop = () => {
return (
<DesktopView>
- <TopBanner />
+ <TopBanner onLoad={handleTopBannerLoad} />
<div className='py-2 bg-warning-400' id='desktop-nav-top'>
<div className='container mx-auto flex justify-between'>
<div className='flex items-start gap-5'>
@@ -230,6 +271,7 @@ const NavbarDesktop = () => {
</div>
</button>
<div className='w-6/12 flex px-1 divide-x divide-gray_r-6'>
+
<Link
href="/shop/promo"
className={`${
@@ -238,19 +280,29 @@ const NavbarDesktop = () => {
target="_blank"
rel="noreferrer"
>
- <p className="absolute inset-0 flex justify-center items-center group-hover:scale-105 group-hover:text-red-500 transition-transform duration-200 z-10">Semua Promo</p>
- {/* <div className='w-full h-full flex justify-end items-start'>
- <Image
- src='/images/ICON PROMO DISKON.svg'
- alt='promo'
- width={100}
- height={100}
+ {showPopup && (
+ <div className='w-full h-full relative justify-end items-start'>
+ <Image
+ src='/images/penawaran-terbatas.jpg'
+ alt='penawaran terbatas'
+ width={1440}
+ height={160}
quality={100}
- className={`inline-block z-20`}
+ // className={`fixed ${isTop ? 'md:top-[145px] lg:top-[160px] ' : 'lg:top-[85px] top-[80px]'} rounded-3xl md:left-1/4 lg:left-1/4 xl:left-1/4 left-2/3 w-40 h-12 p-2 z-50 transition-all duration-300 animate-pulse`}
+ className={`inline-block relative -top-8 transition-all duration-300 z-20 animate-pulse`}
/>
- </div> */}
+ </div>
+ )}
+ <p className="absolute inset-0 flex justify-center items-center group-hover:scale-105 group-hover:text-red-500 transition-transform duration-200 z-10">Semua Promo</p>
</Link>
-
+ {/* {showPopup && router.pathname === '/' && (
+ <div className={`fixed ${isTop ? 'top-[170px]' : 'top-[90px]'} rounded-3xl left-[700px] w-fit object-center bg-green-50 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20 text-center p-2 z-50 transition-all duration-300`}>
+ <p className='w-36 h-3'>
+ Penawaran Terbatas
+ </p>
+ </div>
+ )} */}
+
<Link
href='/shop/brands'
diff --git a/src/core/components/elements/Navbar/TopBanner.jsx b/src/core/components/elements/Navbar/TopBanner.jsx
index df47e87d..265dfb5e 100644
--- a/src/core/components/elements/Navbar/TopBanner.jsx
+++ b/src/core/components/elements/Navbar/TopBanner.jsx
@@ -3,9 +3,10 @@ import { useQuery } from 'react-query';import useDevice from '@/core/hooks/useDe
import odooApi from '@/core/api/odooApi';
import SmoothRender from '~/components/ui/smooth-render';
import Link from '../Link/Link';
+import { useEffect } from 'react';
import { background } from '@chakra-ui/react';
-const TopBanner = () => {
+const TopBanner = ({ onLoad }) => {
const { isDesktop, isMobile } = useDevice()
const topBanner = useQuery({
queryKey: 'topBanner',
@@ -17,6 +18,12 @@ const TopBanner = () => {
const hasData = topBanner.data?.length > 0;
const data = topBanner.data?.[0] || null;
+ useEffect(() => {
+ if (hasData) {
+ onLoad();
+ }
+ }, [hasData, onLoad]);
+
return (
<SmoothRender
isLoaded={hasData}
diff --git a/src/lib/category/api/popularProduct.js b/src/lib/category/api/popularProduct.js
new file mode 100644
index 00000000..e17e0ae5
--- /dev/null
+++ b/src/lib/category/api/popularProduct.js
@@ -0,0 +1,31 @@
+
+export const fetchPromoItemsSolr = async (category_id_ids) => {
+ let sort ='sort=qty_sold_f desc';
+ try {
+ const queryParams = new URLSearchParams({ q: category_id_ids });
+ const response = await fetch(`/solr/product/select?${queryParams.toString()}&rows=2000&fl=manufacture_name_s,manufacture_id_i,id,display_name_s&${sort}`);
+ 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 map = async (promotions) => {
+ const result = [];
+ for (const promotion of promotions) {
+ const data = {
+ id: promotion.id,
+ name: promotion.display_name_s,
+ manufacture_name: promotion.manufacture_name_s,
+ manufacture_id: promotion.manufacture_id_i,
+ };
+ result.push(data);
+ }
+ return result;
+ }; \ No newline at end of file
diff --git a/src/lib/category/components/Category.jsx b/src/lib/category/components/Category.jsx
index c147a3b3..ff958378 100644
--- a/src/lib/category/components/Category.jsx
+++ b/src/lib/category/components/Category.jsx
@@ -5,6 +5,7 @@ import { createSlug } from '@/core/utils/slug'
import { ChevronRightIcon } from '@heroicons/react/24/outline'
import Image from 'next/image'
import { useEffect, useState } from 'react'
+import PopularBrand from './PopularBrand'
const Category = () => {
const [categories, setCategories] = useState([])
@@ -30,6 +31,7 @@ const Category = () => {
}
loadCategories()
}, [])
+ // console.log("categories",categories)
return (
<DesktopView>
@@ -38,8 +40,11 @@ const Category = () => {
<div key={category.id} className='flex'>
<Link
href={createSlug('/shop/category/', category.name, category.id)}
- className='category-mega-box__parent'
+ className='category-mega-box__parent flex items-center'
>
+ <div className='w-6 h-6 border mr-2 rounded-full flex justify-center items-center'>
+ <Image src={category.image} alt='' width={16} height={16} />
+ </div>
{category.name}
</Link>
<div className='category-mega-box__child-wrapper'>
@@ -80,33 +85,7 @@ const Category = () => {
))}
</div>
<div className='category-mega-box__child-wrapper !w-[260px] !flex !flex-col !gap-4'>
- <div className='flex flex-col'>
- <div className='grid grid-cols-2 max-h-full w-full gap-2'>
- {category.childs.map((brand, index) => (
- (index < 8 ) && (
- <div key={brand.id} className='w-full flex items-center justify-center pb-2'>
- <Link
- href={createSlug('/shop/category/', brand.name, brand.id)}
- className='category-mega-box__child-one w-fit h-full flex items-center justify-center '
- >
- <Image src='https://erp.indoteknik.com/api/image/x_manufactures/x_logo_manufacture/661' alt='' width={104} height={44} objectFit='cover' />
- </Link>
- </div>
- )
- ))}
- </div>
- {category.childs.length > 8 && (
- <div className='flex hover:bg-gray_r-8/35 rounded-10'>
- <Link
- href={createSlug('/shop/category/', category.name, category.id)}
- className='category-mega-box__child-one flex items-center gap-4 font-bold hover:ml-4'
- >
- <p className='mt-2 mb-0 text-danger-500 font-semibold'>Lihat Semua Brand</p>
- <ChevronRightIcon className='w-4 text-danger-500 font-bold' />
- </Link>
- </div>
- )}
- </div>
+ <PopularBrand category={category} />
<div className='flex w-60 h-20 object-cover'>
<Image src='https://erp.indoteknik.com/api/image/x_banner.banner/x_banner_image/397' alt='' width={275} height={4} />
</div>
diff --git a/src/lib/category/components/PopularBrand.jsx b/src/lib/category/components/PopularBrand.jsx
new file mode 100644
index 00000000..dca731e8
--- /dev/null
+++ b/src/lib/category/components/PopularBrand.jsx
@@ -0,0 +1,83 @@
+import odooApi from '@/core/api/odooApi'
+import React, { useEffect, useState } from 'react'
+import axios from 'axios';
+import { useQuery } from 'react-query'
+import Link from '@/core/components/elements/Link/Link'
+import { createSlug } from '@/core/utils/slug'
+import Image from 'next/image'
+import { ChevronRightIcon } from '@heroicons/react/24/outline'
+import useProductSearch from '../../../lib/product/hooks/useProductSearch';
+import { SolrResponse } from "~/types/solr";
+import { fetchPromoItemsSolr } from '../api/popularProduct'
+
+const SOLR_HOST = process.env.SOLR_HOST
+
+const PopularBrand = ({ category }) => {
+ const [topBrands, setTopBrands] = useState([]);
+
+ const fetchTopBrands = async () => {
+ try {
+ const items = await fetchPromoItemsSolr(`category_id_ids:(${category.categoryDataIds.join(' OR ')})`);
+ // console.log("id",items)
+ // Fungsi untuk deduplikasi dan mengambil 12 nama brand teratas
+ const getTop12UniqueBrands = (prod) => {
+ const brandSet = new Set();
+ const topBrands = [];
+
+ for (const product of prod) {
+ if (!brandSet.has(product.manufacture_name)) {
+ brandSet.add(product.manufacture_name);
+ topBrands.push({ name: product.manufacture_name, id: product.manufacture_id });
+ }else{
+ }
+ if (topBrands.length === 18) break;
+ }
+ return topBrands;
+ }
+
+ // Menggunakan hasil pencarian produk
+ const products = items;
+ const top12UniqueBrands = getTop12UniqueBrands(products);
+
+ // console.log('top12UniqueBrands', top12UniqueBrands);
+ setTopBrands(top12UniqueBrands);
+ } catch (error) {
+ console.error("Error fetching data from Solr", error);
+ throw error;
+ }
+ }
+
+ useEffect(() => {
+ fetchTopBrands();
+ }, [category]);
+
+ return (
+ <div className='flex flex-col'>
+ <div className='grid grid-cols-3 max-h-full w-full gap-2'>
+ {topBrands.map((brand, index) => (
+ <div key={index} className='w-full flex items-center justify-center pb-2'>
+ <Link
+ href={createSlug('/shop/brands/', brand.name, brand.id)}
+ className='category-mega-box__child-one w-8 h-full flex items-center justify-center '
+ >
+ <Image src={`https://erp.indoteknik.com/api/image/x_manufactures/x_logo_manufacture/${brand.id}` } alt={`${brand.name}`} width={104} height={44} objectFit='cover' />
+ </Link>
+ </div>
+ ))}
+ </div>
+ {/* {topBrands.length > 8 && (
+ <div className='flex hover:bg-gray_r-8/35 rounded-10'>
+ <Link
+ href={createSlug('/shop/category/', category.name, category.id)}
+ className='category-mega-box__child-one flex items-center gap-4 font-bold hover:ml-4'
+ >
+ <p className='mt-2 mb-0 text-danger-500 font-semibold'>Lihat Semua Brand</p>
+ <ChevronRightIcon className='w-4 text-danger-500 font-bold' />
+ </Link>
+ </div>
+ )} */}
+ </div>
+ )
+}
+
+export default PopularBrand;
diff --git a/src/lib/flashSale/components/FlashSaleNonDisplay.jsx b/src/lib/flashSale/components/FlashSaleNonDisplay.jsx
new file mode 100644
index 00000000..8dc15b05
--- /dev/null
+++ b/src/lib/flashSale/components/FlashSaleNonDisplay.jsx
@@ -0,0 +1,66 @@
+import Image from 'next/image';
+import { useEffect, useState } from 'react';
+
+import CountDown from '@/core/components/elements/CountDown/CountDown';
+import productSearchApi from '@/lib/product/api/productSearchApi';
+import ProductSlider from '@/lib/product/components/ProductSlider';
+
+import flashSaleApi from '../api/flashSaleApi';
+import { FlashSaleSkeleton } from '../skeleton/FlashSaleSkeleton';
+
+const FlashSaleNonDisplay = () => {
+ const [flashSales, setFlashSales] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+
+ useEffect(() => {
+ const loadFlashSales = async () => {
+ const dataFlashSales = await flashSaleApi();
+ setFlashSales(dataFlashSales);
+ setIsLoading(false);
+ };
+ loadFlashSales();
+ }, []);
+
+ if (isLoading) {
+ return <FlashSaleSkeleton />;
+ }
+
+ return (
+ flashSales?.length > 0 && (
+ <div className='px-4 sm:px-0 grid grid-cols-1 gap-y-8'>
+ {flashSales.map((flashSale, index) => (
+ <div key={index}>
+ <div className='flex gap-x-3 mb-4 justify-between sm:justify-start'>
+ <div className='font-medium sm:text-h-lg mt-1.5'>
+ Penawaran Terbatas
+ </div>
+ </div>
+
+ <div className='relative'>
+ <FlashSaleProduct flashSaleId={flashSale.pricelistId} />
+ </div>
+ </div>
+ ))}
+ </div>
+ )
+ );
+};
+
+const FlashSaleProduct = ({ flashSaleId }) => {
+ const [products, setProducts] = useState(null);
+
+ useEffect(() => {
+ const loadProducts = async () => {
+ const dataProducts = await productSearchApi({
+ query: `fq=-flashsale_id_i:${flashSaleId}&fq=flashsale_price_f:[1 TO *]&limit=500&orderBy=flashsale-discount-desc`,
+ operation: 'AND',
+ });
+ setProducts(dataProducts.response);
+ };
+ loadProducts();
+ }, [flashSaleId]);
+
+ return <ProductSlider products={products} />;
+};
+
+export default FlashSaleNonDisplay;
diff --git a/src/lib/home/components/CategoryDynamic.jsx b/src/lib/home/components/CategoryDynamic.jsx
index f2d1a16f..0cc43d91 100644
--- a/src/lib/home/components/CategoryDynamic.jsx
+++ b/src/lib/home/components/CategoryDynamic.jsx
@@ -33,9 +33,8 @@ const CategoryDynamic = () => {
};
fetchCategoryData();
- }, [categoryManagement, categoryData]);
+ }, [categoryManagement.isLoading]);
-
return (
<div>
{categoryManagement && categoryManagement.data?.map((category) => {
diff --git a/src/lib/product/components/ProductCard.jsx b/src/lib/product/components/ProductCard.jsx
index 94db144d..35e2a665 100644
--- a/src/lib/product/components/ProductCard.jsx
+++ b/src/lib/product/components/ProductCard.jsx
@@ -17,8 +17,8 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
const [discount, setDiscount] = useState(0);
let voucherPastiHemat = 0;
-
- if (product?.voucherPastiHemat?.length > 0) {
+
+ if (product?.voucherPastiHemat ? product?.voucherPastiHemat.length : voucherPastiHemat > 0) {
const stringVoucher = product?.voucherPastiHemat[0];
const validJsonString = stringVoucher.replace(/'/g, '"');
voucherPastiHemat = JSON.parse(validJsonString);
diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx
index 09534a5d..a83e5e1e 100644
--- a/src/lib/product/components/ProductSearch.jsx
+++ b/src/lib/product/components/ProductSearch.jsx
@@ -41,8 +41,6 @@ const ProductSearch = ({
const { page = 1 } = query;
const [q, setQ] = useState(query?.q || '*');
const [search, setSearch] = useState(query?.q || '*');
- const [limit, setLimit] = useState(router.query?.limit || 30);
- const [orderBy, setOrderBy] = useState(router.query?.orderBy || 'popular');
const [finalQuery, setFinalQuery] = useState({});
const [queryFinal, setQueryFinal] = useState({});
const [dataCategoriesProduct, setDataCategoriesProduct] = useState([])
@@ -50,6 +48,8 @@ const ProductSearch = ({
const categoryId = getIdFromSlug(prefixUrl)
const [data, setData] = useState([])
const [dataLob, setDataLob] = useState([]);
+ const [limit, setLimit] = useState(query?.limit || 30);
+ const [orderBy, setOrderBy] = useState(router.query?.orderBy);
if (defaultBrand) query.brand = defaultBrand.toLowerCase();
const dataIdCategories = []
useEffect(() => {
@@ -267,6 +267,7 @@ const ProductSearch = ({
const orderOptions = [
+ { value: '', label: 'Pilih Filter' },
{ value: 'price-asc', label: 'Harga Terendah' },
{ value: 'price-desc', label: 'Harga Tertinggi' },
{ value: 'popular', label: 'Populer' },
diff --git a/src/pages/api/shop/search.js b/src/pages/api/shop/search.js
index b6b8c795..87d7c580 100644
--- a/src/pages/api/shop/search.js
+++ b/src/pages/api/shop/search.js
@@ -3,6 +3,7 @@ import axios from 'axios';
import camelcaseObjectDeep from 'camelcase-object-deep';
export default async function handler(req, res) {
+
const {
q = '*',
page = 1,
@@ -20,6 +21,9 @@ export default async function handler(req, res) {
let paramOrderBy = '';
switch (orderBy) {
+ case 'flashsale-discount-desc':
+ paramOrderBy += 'flashsale_discount_f DESC';
+ break;
case 'price-asc':
paramOrderBy += 'price_tier1_v2_f ASC';
break;
@@ -39,7 +43,7 @@ export default async function handler(req, res) {
paramOrderBy += 'flashsale_price_f ASC';
break;
default:
- paramOrderBy += 'product_rating_f DESC, price_discount_f DESC';
+ paramOrderBy += '';
break;
}
@@ -56,9 +60,10 @@ export default async function handler(req, res) {
`start=${parseInt(offset)}`,
`rows=${limit}`,
`sort=${paramOrderBy}`,
- `fq=-publish_b:false`,
+ `fq=-publish_b:false, product_rating_f:[13 TO *], discount_tier1_v2_f:[1 TO *]`,
];
+
if (priceFrom > 0 || priceTo > 0) {
parameter.push(
`fq=price_tier1_v2_f:[${priceFrom == '' ? '*' : priceFrom} TO ${
@@ -99,6 +104,7 @@ export default async function handler(req, res) {
let result = await axios(
process.env.SOLR_HOST + '/solr/product/select?' + parameter.join('&')
);
+
try {
result.data.response.products = productMappingSolr(
result.data.response.docs,