From 753be9690f95c288aec2ab92269529131626254d Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 22 Aug 2025 15:30:08 +0700 Subject: Image slider when mobile --- .../product-detail/components/ProductDetail.tsx | 177 ++++++++++++++++----- 1 file changed, 134 insertions(+), 43 deletions(-) diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index 192e1dc3..a6b4e6de 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -2,7 +2,7 @@ import style from '../styles/product-detail.module.css'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState, UIEvent } from 'react'; import { Button } from '@chakra-ui/react'; import { MessageCircleIcon, Share2Icon } from 'lucide-react'; @@ -35,6 +35,7 @@ const ProductDetail = ({ product }: Props) => { const { isDesktop, isMobile } = useDevice(); const router = useRouter(); const auth = getAuth(); + const { setAskAdminUrl, askAdminUrl, @@ -71,70 +72,168 @@ const ProductDetail = ({ product }: Props) => { product?.variants?.find((variant) => variant.is_in_bu) || product?.variants?.[0]; setSelectedVariant(selectedVariant); - // setSelectedVariant(product?.variants[0]) - }, []); + }, []); // eslint-disable-line react-hooks/exhaustive-deps // Gabungkan semua gambar produk (utama + tambahan) - const allImages = product.image_carousel ? [...product.image_carousel] : []; - - if (product.image) { - allImages.unshift(product.image); // Tambahkan gambar utama di awal array - } - console.log(product); + // Gabungkan semua gambar produk (utama + tambahan) + const allImages = (() => { + const arr: string[] = []; + if (product?.image) arr.push(product.image); // selalu masukkan utama, baik mobile maupun desktop + if ( + Array.isArray(product?.image_carousel) && + product.image_carousel.length + ) { + // hindari duplikat jika image utama juga ada di carousel + const set = new Set(arr); + for (const img of product.image_carousel) { + if (!set.has(img)) { + arr.push(img); + set.add(img); + } + } + } + return arr; + })(); const [mainImage, setMainImage] = useState(allImages[0] || ''); + useEffect(() => { + // update mainImage jika sumber gambar berubah dan mainImage tidak ada di daftar + if (!allImages.includes(mainImage)) { + setMainImage(allImages[0] || ''); + } + }, [allImages]); // eslint-disable-line react-hooks/exhaustive-deps + + // ===== Slider mobile (tanpa dependency) ===== + const sliderRef = useRef(null); + const [currentIdx, setCurrentIdx] = useState(0); + + const handleMobileScroll = (e: UIEvent) => { + const el = e.currentTarget; + if (!el) return; + const idx = Math.round(el.scrollLeft / el.clientWidth); + if (idx !== currentIdx) { + setCurrentIdx(idx); + setMainImage(allImages[idx] || ''); + } + }; + + const scrollToIndex = (i: number) => { + const el = sliderRef.current; + if (!el) return; + el.scrollTo({ left: i * el.clientWidth, behavior: 'smooth' }); + setCurrentIdx(i); + setMainImage(allImages[i] || ''); + }; + // ============================================ + return ( <>
+
+ {/* ===== Kolom kiri: gambar ===== */}
- - - {/* Carousel horizontal */} - {allImages.length > 0 && ( -
-
- {allImages.map((img, index) => ( -
setMainImage(img)} - > + {/* === MOBILE: Slider swipeable, tanpa thumbnail carousel === */} + {isMobile ? ( +
+
+ {allImages.length > 0 ? ( + allImages.map((img, i) => ( {`Thumbnail { (e.target as HTMLImageElement).src = '/path/to/fallback-image.jpg'; }} /> -
- ))} + )) + ) : ( + Gambar produk + )}
+ + {/* Dots indicator */} + {allImages.length > 1 && ( +
+ {allImages.map((_, i) => ( +
+ )}
+ ) : ( + <> + {/* === DESKTOP: Tetap seperti sebelumnya === */} + + + {/* Carousel horizontal (thumbnail) – hanya desktop */} + {allImages.length > 0 && ( +
+
+ {allImages.map((img, index) => ( +
setMainImage(img)} + > + {`Thumbnail { + (e.target as HTMLImageElement).src = + '/path/to/fallback-image.jpg'; + }} + /> +
+ ))} +
+
+ )} + )}
+ {/* <<=== TUTUP kolom kiri */} + {/* ===== Kolom kanan: info ===== */}
-

{product.name}

-
- -
@@ -154,14 +253,6 @@ const ProductDetail = ({ product }: Props) => { /> )} - {/*
-

- Variant ({product.variant_total}) -

-
- -
*/} -
@@ -246,4 +337,4 @@ const ProductDetail = ({ product }: Props) => { ); }; -export default ProductDetail; +export default ProductDetail; \ No newline at end of file -- cgit v1.2.3 From a1b0e672387747085c85c6c446cea49f09af7719 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 23 Aug 2025 08:49:46 +0700 Subject: Move stock position mobile --- .../product-detail/components/PriceAction.tsx | 43 ++++++++++++++++------ .../product-detail/components/ProductDetail.tsx | 7 ++-- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx index 850c2d9d..f5c2d7b3 100644 --- a/src-migrate/modules/product-detail/components/PriceAction.tsx +++ b/src-migrate/modules/product-detail/components/PriceAction.tsx @@ -17,7 +17,9 @@ type Props = { product: IProductDetail; }; -const PPN : number = process.env.NEXT_PUBLIC_PPN ? parseFloat(process.env.NEXT_PUBLIC_PPN) : 0; +const PPN: number = process.env.NEXT_PUBLIC_PPN + ? parseFloat(process.env.NEXT_PUBLIC_PPN) + : 0; const PriceAction = ({ product }: Props) => { const { activePrice, @@ -104,6 +106,21 @@ const PriceAction = ({ product }: Props) => { Termasuk PPN: Rp{' '} {formatCurrency(Math.round(activePrice.price_discount * PPN))}
+ {isMobile && ( +
+ + Stock : {sla?.qty}{' '} + + {/* + {' '} + */} +
+ )} )} @@ -149,19 +166,21 @@ const PriceAction = ({ product }: Props) => {
-
- - Stock : {sla?.qty}{' '} - - {/* + {isDesktop && ( +
+ + Stock : {sla?.qty}{' '} + + {/* {' '} */} -
+
+ )}
{qtyPickUp > 0 && ( diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index a6b4e6de..e6b77fb9 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -72,9 +72,8 @@ const ProductDetail = ({ product }: Props) => { product?.variants?.find((variant) => variant.is_in_bu) || product?.variants?.[0]; setSelectedVariant(selectedVariant); - }, []); // eslint-disable-line react-hooks/exhaustive-deps + }, []); - // Gabungkan semua gambar produk (utama + tambahan) // Gabungkan semua gambar produk (utama + tambahan) const allImages = (() => { const arr: string[] = []; @@ -147,8 +146,8 @@ const ProductDetail = ({ product }: Props) => { className='flex overflow-x-auto snap-x snap-mandatory scroll-smooth no-scrollbar' style={{ scrollBehavior: 'smooth', - msOverflowStyle: 'none', // IE/Edge lama - scrollbarWidth: 'none', // Firefox + msOverflowStyle: 'none', + scrollbarWidth: 'none', }} > {allImages.length > 0 ? ( -- cgit v1.2.3 From 47cc09c7f74b2c37f0fc1a98e50f7d76774ea4a4 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 23 Aug 2025 08:55:34 +0700 Subject: Move price --- .../product-detail/components/PriceAction.tsx | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx index f5c2d7b3..7548e016 100644 --- a/src-migrate/modules/product-detail/components/PriceAction.tsx +++ b/src-migrate/modules/product-detail/components/PriceAction.tsx @@ -92,13 +92,13 @@ const PriceAction = ({ product }: Props) => {
{Math.floor(activePrice.discount_percentage)}%
-
- Rp {formatCurrency(activePrice.price || 0)} +
+ Rp {formatCurrency(activePrice.price_discount || 0)}
)} -
- Rp {formatCurrency(activePrice.price_discount || 0)} +
+ Rp {formatCurrency(activePrice.price || 0)}
@@ -107,19 +107,19 @@ const PriceAction = ({ product }: Props) => { {formatCurrency(Math.round(activePrice.price_discount * PPN))}
{isMobile && ( -
- - Stock : {sla?.qty}{' '} - - {/* +
+ + Stock : {sla?.qty}{' '} + + {/* {' '} */} -
+
)} )} -- cgit v1.2.3 From bb2763a47ae15b718b9ed532b4b3bdd68d0d8867 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 23 Aug 2025 09:32:03 +0700 Subject: Button Position --- .../product-detail/components/AddToQuotation.tsx | 4 +- .../product-detail/components/PriceAction.tsx | 68 ++++++++++++++++------ 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/src-migrate/modules/product-detail/components/AddToQuotation.tsx b/src-migrate/modules/product-detail/components/AddToQuotation.tsx index f9b6c2b3..0a901448 100644 --- a/src-migrate/modules/product-detail/components/AddToQuotation.tsx +++ b/src-migrate/modules/product-detail/components/AddToQuotation.tsx @@ -16,6 +16,7 @@ import { useProductCartContext } from '@/contexts/ProductCartContext'; import { createSlug } from '~/libs/slug'; import formatCurrency from '~/libs/formatCurrency'; import { useProductDetail } from '../stores/useProductDetail'; +import useDevice from '@/core/hooks/useDevice'; type Props = { variantId: number | null; @@ -40,6 +41,7 @@ const AddToQuotation = ({ }); const { askAdminUrl } = useProductDetail(); + const { isMobile, isDesktop } = useDevice(); const [product, setProducts] = useState(products); const [status, setStatus] = useState('idle'); @@ -129,7 +131,7 @@ const AddToQuotation = ({ width={25} height={25} /> - Penawaran Harga Instan + {isDesktop ? 'Penawaran Harga Instan' : ''} {
)}
- -
- - {!isApproval && ( + +
+ {!isApproval && ( + + )} +
+
+ - )} -
-
- -
+
+ + +
+
+ +
+
+ {!isApproval && ( + + )} +
+
+ +
+
+
); }; -- cgit v1.2.3 From 5c0f43f53f330387cc86afd51beaeebf4069cad9 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 23 Aug 2025 10:06:10 +0700 Subject: variant --- .../product-detail/components/AddToCart.tsx | 23 ++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src-migrate/modules/product-detail/components/AddToCart.tsx b/src-migrate/modules/product-detail/components/AddToCart.tsx index 95bc1d88..9f66345e 100644 --- a/src-migrate/modules/product-detail/components/AddToCart.tsx +++ b/src-migrate/modules/product-detail/components/AddToCart.tsx @@ -17,6 +17,9 @@ import formatCurrency from '~/libs/formatCurrency'; import { useProductDetail } from '../stores/useProductDetail'; import { gtagAddToCart } from '@/core/utils/googleTag'; import axios from 'axios'; +import useDevice from '@/core/hooks/useDevice'; +import MobileView from '@/core/components/views/MobileView'; +import DesktopView from '@/core/components/views/DesktopView'; type Props = { variantId: number | null; quantity?: number; @@ -39,6 +42,8 @@ const AddToCart = ({ isClosable: true, }); + const { isMobile, isDesktop } = useDevice(); + const { askAdminUrl } = useProductDetail(); const [product, setProducts] = useState(products); @@ -158,17 +163,30 @@ const AddToCart = ({ const btnConfig = { add_to_cart: { - colorScheme: 'yellow', + colorScheme: isDesktop ? 'yellow' : 'red', + variant: 'solid', text: 'Keranjang', }, buy: { colorScheme: 'red', - text: 'Beli', + variant: isDesktop ? 'solid' : 'outline', + text: isDesktop ? 'Beli' : 'Beli Sekarang', }, }; return (
+ + + + + Date: Sat, 23 Aug 2025 10:21:38 +0700 Subject: More mobile change --- .../product-detail/components/PriceAction.tsx | 44 +++++++++++++++------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx index 9d45a7c3..2cdfc9a2 100644 --- a/src-migrate/modules/product-detail/components/PriceAction.tsx +++ b/src-migrate/modules/product-detail/components/PriceAction.tsx @@ -89,19 +89,37 @@ const PriceAction = ({ product }: Props) => { {!!activePrice && activePrice.price > 0 && ( <>
- {activePrice.discount_percentage > 0 && ( - <> -
- {Math.floor(activePrice.discount_percentage)}% -
-
- Rp {formatCurrency(activePrice.price_discount || 0)} -
- - )} -
- Rp {formatCurrency(activePrice.price || 0)} -
+ + {activePrice.discount_percentage > 0 && ( + <> +
+ {Math.floor(activePrice.discount_percentage)}% +
+
+ Rp {formatCurrency(activePrice.price_discount || 0)} +
+ + )} +
+ Rp {formatCurrency(activePrice.price || 0)} +
+
+ + {activePrice.discount_percentage > 0 && ( + <> +
+ {Math.floor(activePrice.discount_percentage)}% +
+ +
+ Rp {formatCurrency(activePrice.price || 0)} +
+ + )} +
+ Rp {formatCurrency(activePrice.price_discount || 0)} +
+
-- cgit v1.2.3 From 8067589f21bc41b651622240c491bf1a9e5e9d51 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 23 Aug 2025 11:21:21 +0700 Subject: Done --- .../product-detail/components/PriceAction.tsx | 227 +++++++++++++-------- 1 file changed, 147 insertions(+), 80 deletions(-) diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx index 2cdfc9a2..a90faee0 100644 --- a/src-migrate/modules/product-detail/components/PriceAction.tsx +++ b/src-migrate/modules/product-detail/components/PriceAction.tsx @@ -88,59 +88,49 @@ const PriceAction = ({ product }: Props) => { > {!!activePrice && activePrice.price > 0 && ( <> -
- + +
{activePrice.discount_percentage > 0 && ( <>
{Math.floor(activePrice.discount_percentage)}%
-
- Rp {formatCurrency(activePrice.price_discount || 0)} +
+ Rp {formatCurrency(activePrice.price || 0)}
)} -
- Rp {formatCurrency(activePrice.price || 0)} +
+ Rp {formatCurrency(activePrice.price_discount || 0)}
- - +
+
+
+ Termasuk PPN: Rp{' '} + {formatCurrency(Math.round(activePrice.price_discount * PPN))} +
+ + +
{activePrice.discount_percentage > 0 && ( <>
{Math.floor(activePrice.discount_percentage)}% -
- -
- Rp {formatCurrency(activePrice.price || 0)} +
{' '} +
+ Rp {formatCurrency(activePrice.price_discount || 0)}
)} -
- Rp {formatCurrency(activePrice.price_discount || 0)} +
+ Rp {formatCurrency(activePrice.price || 0)}
- -
-
-
- Termasuk PPN: Rp{' '} - {formatCurrency(Math.round(activePrice.price_discount * PPN))} -
- {isMobile && ( -
- - Stock : {sla?.qty}{' '} - - {/* - {' '} - */}
- )} +
+ Termasuk PPN: Rp{' '} + {formatCurrency(Math.round(activePrice.price_discount * PPN))} +
+ )} @@ -157,70 +147,147 @@ const PriceAction = ({ product }: Props) => { )} +
-
-
- - setQuantityInput(e.target.value)} - className={style['quantity-input']} - /> - -
+
+ {/* Qty */} +
+ + setQuantityInput(e.target.value)} + className='w-12 text-center outline-none border-x' + /> + +
- {isDesktop && ( + {/* Stok */}
Stock : {sla?.qty}{' '} - {/* - {' '} - */}
- )} -
- {qtyPickUp > 0 && ( - - pickup now + {qtyPickUp > 0 && ( + + pickup now + + )} +
+
+ + + {/* ===== MOBILE: grid kiri-kanan, kanan hanya qty ===== */} + +
+ {/* Kiri */} +
+ + Stock : {sla?.qty}{' '} + + + {qtyPickUp > 0 && ( +
+ + pickup now + +
+ * {qtyPickUp} barang bisa di pickup +
+
+ )} +
+ + {/* Kanan: hanya qty, rata kanan */} +
+
+ + setQuantityInput(e.target.value)} + className='w-12 text-center outline-none border-x' /> - - )} + +
+
-
+ {qtyPickUp > 0 && (
* {qtyPickUp} barang bisa di pickup
)}
+
Date: Sat, 23 Aug 2025 12:04:15 +0700 Subject: Fix Price --- .../product-detail/components/PriceAction.tsx | 26 +++++++++++++++------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx index a90faee0..25a1aa47 100644 --- a/src-migrate/modules/product-detail/components/PriceAction.tsx +++ b/src-migrate/modules/product-detail/components/PriceAction.tsx @@ -112,22 +112,32 @@ const PriceAction = ({ product }: Props) => {
- {activePrice.discount_percentage > 0 && ( + {activePrice.discount_percentage > 0 ? ( <>
{Math.floor(activePrice.discount_percentage)}% -
{' '} +
+ + {/* harga setelah diskon (main-price) di kiri */}
Rp {formatCurrency(activePrice.price_discount || 0)}
+ + {/* harga coret di kanan */} +
+ Rp {formatCurrency(activePrice.price || 0)} +
+ ) : ( + // kalau tidak ada diskon, tampilkan harga normal saja +
+ Rp {formatCurrency(activePrice.price || 0)} +
)} -
- Rp {formatCurrency(activePrice.price || 0)} -
-
- Termasuk PPN: Rp{' '} + +
+ Termasuk PPN: Rp{' '} {formatCurrency(Math.round(activePrice.price_discount * PPN))}
@@ -148,7 +158,7 @@ const PriceAction = ({ product }: Props) => { )} -
+
{/* Qty */}
-- cgit v1.2.3 From 7911c3d291b322a9832f9f4c65392552c11c8d10 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 26 Aug 2025 09:35:51 +0700 Subject: Fix --- .../product-detail/components/PriceAction.tsx | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx index 25a1aa47..60834880 100644 --- a/src-migrate/modules/product-detail/components/PriceAction.tsx +++ b/src-migrate/modules/product-detail/components/PriceAction.tsx @@ -206,18 +206,23 @@ const PriceAction = ({ product }: Props) => { {/* Pickup badge */}
{qtyPickUp > 0 && ( - - pickup now - +
+ + pickup now + +
)}
+ + * {qtyPickUp} barang bisa di pickup + {/* ===== MOBILE: grid kiri-kanan, kanan hanya qty ===== */} @@ -291,11 +296,6 @@ const PriceAction = ({ product }: Props) => {
- {qtyPickUp > 0 && ( -
- * {qtyPickUp} barang bisa di pickup -
- )}
-- cgit v1.2.3 From 28f9a1235fcf6fdde1983d0a4cf88fc3ba4fbdd2 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 26 Aug 2025 09:53:09 +0700 Subject: Fix lg --- .../product-detail/components/PriceAction.tsx | 54 +++++++++++++--------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx index 60834880..2c678c6b 100644 --- a/src-migrate/modules/product-detail/components/PriceAction.tsx +++ b/src-migrate/modules/product-detail/components/PriceAction.tsx @@ -229,42 +229,47 @@ const PriceAction = ({ product }: Props) => {
{/* Kiri */} -
- - Stock : {sla?.qty}{' '} - +
+
+ + Stock : {sla?.qty} + - {qtyPickUp > 0 && ( -
+ {qtyPickUp > 0 && ( pickup now -
- * {qtyPickUp} barang bisa di pickup -
+ )} +
+ + {qtyPickUp > 0 && ( +
+ * {qtyPickUp} barang bisa di pickup
)}
{/* Kanan: hanya qty, rata kanan */}
-
+
+ setQuantityInput(e.target.value)} - className='w-12 text-center outline-none border-x' + className='h-11 md:h-12 w-16 md:w-20 text-center text-lg md:text-xl outline-none border-x + [appearance:textfield] + [&::-webkit-outer-spin-button]:appearance-none + [&::-webkit-inner-spin-button]:appearance-none' /> +
-- cgit v1.2.3 From 8e92e0b4bfb74ebdf99fbc9d4ca7d47e2513c4e0 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 26 Aug 2025 10:42:38 +0700 Subject: bring back old set qty for desktop view --- .../modules/product-detail/components/PriceAction.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx index 2c678c6b..5daf5bed 100644 --- a/src-migrate/modules/product-detail/components/PriceAction.tsx +++ b/src-migrate/modules/product-detail/components/PriceAction.tsx @@ -161,16 +161,15 @@ const PriceAction = ({ product }: Props) => {
{/* Qty */} -
+
{ min={1} value={quantityInput} onChange={(e) => setQuantityInput(e.target.value)} - className='w-12 text-center outline-none border-x' + className={style['quantity-input']} /> -- cgit v1.2.3 From bcf7be5a0c1d9635383605afc0600386b0b356ea Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 26 Aug 2025 11:41:54 +0700 Subject: Push --- .../product-detail/components/Breadcrumb.tsx | 79 +++-- .../product-detail/components/ProductDetail.tsx | 347 +++++++++++---------- 2 files changed, 232 insertions(+), 194 deletions(-) diff --git a/src-migrate/modules/product-detail/components/Breadcrumb.tsx b/src-migrate/modules/product-detail/components/Breadcrumb.tsx index f41859a9..ba53aca4 100644 --- a/src-migrate/modules/product-detail/components/Breadcrumb.tsx +++ b/src-migrate/modules/product-detail/components/Breadcrumb.tsx @@ -1,41 +1,70 @@ -import React, { Fragment } from 'react' -import { useQuery } from 'react-query' -import { getProductCategoryBreadcrumb } from '~/services/product' -import Link from 'next/link' -import { createSlug } from '~/libs/slug' +import React, { Fragment } from 'react'; +import { useQuery } from 'react-query'; +import { getProductCategoryBreadcrumb } from '~/services/product'; +import Link from 'next/link'; +import { createSlug } from '~/libs/slug'; type Props = { - id: number, - name: string -} + id: number; + name: string; +}; + +const MAX_VISIBLE_CATEGORIES = 2; // tampilkan 2 level terakhir const Breadcrumb = ({ id, name }: Props) => { - const query = useQuery({ - queryKey: ['product-category-breadcrumb'], + const { data: breadcrumbs = [] } = useQuery({ + queryKey: ['product-category-breadcrumb', id], // penting: ikutkan id queryFn: () => getProductCategoryBreadcrumb(id), - refetchOnWindowFocus: false - }) + refetchOnWindowFocus: false, + }); - const breadcrumbs = query.data || [] + const total = breadcrumbs.length; + const showEllipsis = total > MAX_VISIBLE_CATEGORIES; + const visible = showEllipsis + ? breadcrumbs.slice(total - MAX_VISIBLE_CATEGORIES) + : breadcrumbs; + const hiddenText = showEllipsis + ? breadcrumbs + .slice(0, total - MAX_VISIBLE_CATEGORIES) + .map((c) => c.name) + .join(' / ') + : ''; return ( -
- Home - / - {breadcrumbs.map((category, index) => ( - +
+ + Home + + / + + {showEllipsis && ( + <> + + … + + / + + )} + + {visible.map((category, index) => ( + {category.name} - / + / ))} - {name} + + {name}
- ) -} + ); +}; -export default Breadcrumb \ No newline at end of file +export default Breadcrumb; diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index e6b77fb9..8ecdb2fc 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -126,214 +126,223 @@ const ProductDetail = ({ product }: Props) => { }; // ============================================ - return ( - <> -
-
- -
+return ( + <> +
+
+ +
-
-
- {/* ===== Kolom kiri: gambar ===== */} -
- {/* === MOBILE: Slider swipeable, tanpa thumbnail carousel === */} - {isMobile ? ( -
-
- {allImages.length > 0 ? ( - allImages.map((img, i) => ( +
+
+ {/* ===== Kolom kiri: gambar ===== */} +
+ {/* === MOBILE: Slider swipeable, tanpa thumbnail carousel === */} + {isMobile ? ( +
+
+ {allImages.length > 0 ? ( + allImages.map((img, i) => ( + // slide tetap selebar viewport untuk snap mulus +
+ {/* gambar diperkecil */} {`Gambar { (e.target as HTMLImageElement).src = '/path/to/fallback-image.jpg'; }} /> - )) - ) : ( +
+ )) + ) : ( +
Gambar produk - )} -
- - {/* Dots indicator */} - {allImages.length > 1 && ( -
- {allImages.map((_, i) => ( -
)}
- ) : ( - <> - {/* === DESKTOP: Tetap seperti sebelumnya === */} - - - {/* Carousel horizontal (thumbnail) – hanya desktop */} - {allImages.length > 0 && ( -
-
- {allImages.map((img, index) => ( -
setMainImage(img)} - > - {`Thumbnail { - (e.target as HTMLImageElement).src = - '/path/to/fallback-image.jpg'; - }} - /> -
- ))} -
-
- )} - - )} -
- {/* <<=== TUTUP kolom kiri */} - - {/* ===== Kolom kanan: info ===== */} -
-
-

{product.name}

-
- -
-
-
-
- {isMobile && ( -
- + {/* Dots indicator */} + {allImages.length > 1 && ( +
+ {allImages.map((_, i) => ( +
+ )}
+ ) : ( + <> + {/* === DESKTOP: Tetap seperti sebelumnya === */} + + + {/* Carousel horizontal (thumbnail) – hanya desktop */} + {allImages.length > 0 && ( +
+
+ {allImages.map((img, index) => ( +
setMainImage(img)} + > + {`Thumbnail { + (e.target as HTMLImageElement).src = + '/path/to/fallback-image.jpg'; + }} + /> +
+ ))} +
+
+ )} + )} +
+ {/* <<=== TUTUP kolom kiri */} + + {/* ===== Kolom kanan: info ===== */} +
+
+

{product.name}

+
+ +
+
+
-
- {!!activeVariantId && !isApproval && ( - - )} +
+ {isMobile && ( +
+ +
+ )} -
+
+ {!!activeVariantId && !isApproval && ( + + )} -
-

Informasi Produk

-
-
-

' - ? 'Belum ada deskripsi' - : product.description, - }} - /> -
+
+ +
+

Informasi Produk

+
+
+

' + ? 'Belum ada deskripsi' + : product.description, + }} + />
+
- {isDesktop && ( -
- -
+ {isDesktop && ( +
+ +
+ + + | + + + + | + + + +
- | - - - - | +
+
Produk Serupa
- - - -
+
-
-
Produk Serupa
+ +
+ )} -
+
+
Kamu Mungkin Juga Suka
- -
- )} +
-
-
Kamu Mungkin Juga Suka
+ + + +
-
+
+
+ +); - - - -
-
-
- - ); }; -export default ProductDetail; \ No newline at end of file +export default ProductDetail; -- cgit v1.2.3 From 2403eae1686c565364691022d294123a16f4f031 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 26 Aug 2025 12:04:46 +0700 Subject: Fix Breadcrumb & image --- .../product-detail/components/Breadcrumb.tsx | 139 ++++++++++++++++----- .../product-detail/components/ProductDetail.tsx | 4 +- 2 files changed, 108 insertions(+), 35 deletions(-) diff --git a/src-migrate/modules/product-detail/components/Breadcrumb.tsx b/src-migrate/modules/product-detail/components/Breadcrumb.tsx index ba53aca4..d495b77b 100644 --- a/src-migrate/modules/product-detail/components/Breadcrumb.tsx +++ b/src-migrate/modules/product-detail/components/Breadcrumb.tsx @@ -1,68 +1,141 @@ import React, { Fragment } from 'react'; import { useQuery } from 'react-query'; -import { getProductCategoryBreadcrumb } from '~/services/product'; import Link from 'next/link'; +import { getProductCategoryBreadcrumb } from '~/services/product'; import { createSlug } from '~/libs/slug'; +import useDevice from '@/core/hooks/useDevice'; -type Props = { - id: number; - name: string; -}; - -const MAX_VISIBLE_CATEGORIES = 2; // tampilkan 2 level terakhir +type Props = { id: number; name: string }; const Breadcrumb = ({ id, name }: Props) => { + const { isDesktop, isMobile } = useDevice(); + const { data: breadcrumbs = [] } = useQuery({ - queryKey: ['product-category-breadcrumb', id], // penting: ikutkan id + queryKey: ['product-category-breadcrumb', id], queryFn: () => getProductCategoryBreadcrumb(id), refetchOnWindowFocus: false, }); const total = breadcrumbs.length; - const showEllipsis = total > MAX_VISIBLE_CATEGORIES; - const visible = showEllipsis - ? breadcrumbs.slice(total - MAX_VISIBLE_CATEGORIES) - : breadcrumbs; - const hiddenText = showEllipsis + const lastCat = total ? breadcrumbs[total - 1] : null; + const hasHidden = total > 1; + const hiddenText = hasHidden ? breadcrumbs - .slice(0, total - MAX_VISIBLE_CATEGORIES) + .slice(0, total - 1) .map((c) => c.name) .join(' / ') : ''; - return ( -
- + // ===== MOBILE: Home + .. + lastCat + product (truncate) ===== + if (isMobile) { + const crumbsMobile: React.ReactNode[] = []; + + // Home + crumbsMobile.push( + Home - / + ); + + // Indicator kategori tersembunyi (tanpa slash bawaan) + if (hasHidden) { + crumbsMobile.push( + + .. + + ); + } - {showEllipsis && ( - <> - - … - - / - - )} + // Kategori terakhir + if (lastCat) { + crumbsMobile.push( + + {lastCat.name} + + ); + } - {visible.map((category, index) => ( - + // Nama produk (dipotong bila tidak muat) + crumbsMobile.push( + + {name} + + ); + + return ( +
+ {crumbsMobile.map((node, i) => ( + + {node} + {i < crumbsMobile.length - 1 && ( + / + )} + + ))} +
+ ); + } + + // ===== DESKTOP: biarkan seperti semula ===== + if (isDesktop) { + return ( +
+ + Home + + / + {breadcrumbs.map((category, index) => ( + + + {category.name} + + / + + ))} + {name} +
+ ); + } + + // Fallback → layout desktop + return ( +
+ + Home + + / + {breadcrumbs.map((category, index) => ( + {category.name} - / + / ))} - - {name} + {name}
); }; diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index 8ecdb2fc..35df97df 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -161,7 +161,7 @@ return ( {`Gambar { (e.target as HTMLImageElement).src = '/path/to/fallback-image.jpg'; @@ -174,7 +174,7 @@ return ( Gambar produk
)} -- cgit v1.2.3 From 8290efa6a098cdef393c255b8384de9c60803f04 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 26 Aug 2025 12:29:24 +0700 Subject: Detail product done --- public/images/document.png | Bin 0 -> 529 bytes .../modules/product-detail/components/AddToQuotation.tsx | 2 +- .../modules/product-detail/components/Breadcrumb.tsx | 8 ++------ 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 public/images/document.png diff --git a/public/images/document.png b/public/images/document.png new file mode 100644 index 00000000..d1772b8c Binary files /dev/null and b/public/images/document.png differ diff --git a/src-migrate/modules/product-detail/components/AddToQuotation.tsx b/src-migrate/modules/product-detail/components/AddToQuotation.tsx index 0a901448..17a62eee 100644 --- a/src-migrate/modules/product-detail/components/AddToQuotation.tsx +++ b/src-migrate/modules/product-detail/components/AddToQuotation.tsx @@ -125,7 +125,7 @@ const AddToQuotation = ({ className='w-full border-2 p-2 gap-1 hover:bg-slate-100 flex items-center' > { .join(' / ') : ''; - // ===== MOBILE: Home + .. + lastCat + product (truncate) ===== if (isMobile) { const crumbsMobile: React.ReactNode[] = []; - // Home crumbsMobile.push( Home ); - // Indicator kategori tersembunyi (tanpa slash bawaan) if (hasHidden) { crumbsMobile.push( { ); } - // Nama produk (dipotong bila tidak muat) + // Nama produk (dipotong kalau gk muat) crumbsMobile.push( {name} @@ -85,7 +82,7 @@ const Breadcrumb = ({ id, name }: Props) => { ); } - // ===== DESKTOP: biarkan seperti semula ===== + // ===== DESKTOP ===== if (isDesktop) { return (
@@ -113,7 +110,6 @@ const Breadcrumb = ({ id, name }: Props) => { ); } - // Fallback → layout desktop return (
-- cgit v1.2.3 From a078561462fb33f24d838c974894b3fc7d6c9eb7 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 26 Aug 2025 12:54:05 +0700 Subject: sementara --- src/lib/category/components/Breadcrumb.jsx | 177 ++++++++++++++++++++++++----- 1 file changed, 150 insertions(+), 27 deletions(-) diff --git a/src/lib/category/components/Breadcrumb.jsx b/src/lib/category/components/Breadcrumb.jsx index 127904ee..b369d781 100644 --- a/src/lib/category/components/Breadcrumb.jsx +++ b/src/lib/category/components/Breadcrumb.jsx @@ -1,45 +1,168 @@ -import odooApi from '@/core/api/odooApi' -import { createSlug } from '@/core/utils/slug' +import odooApi from '@/core/api/odooApi'; +import { createSlug } from '@/core/utils/slug'; import { Breadcrumb as ChakraBreadcrumb, BreadcrumbItem, BreadcrumbLink, - Skeleton -} from '@chakra-ui/react' -import Link from 'next/link' -import React from 'react' -import { useQuery } from 'react-query' - -/** - * Render a breadcrumb component. - * - * @param {object} categoryId - The ID of the category. - * @return {JSX.Element} The breadcrumb component. - */ + Skeleton, +} from '@chakra-ui/react'; +import Link from 'next/link'; +import React from 'react'; +import { useQuery } from 'react-query'; +import useDevice from '@/core/hooks/useDevice'; + const Breadcrumb = ({ categoryId }) => { + const { isDesktop, isMobile } = useDevice(); + const breadcrumbs = useQuery( - `category-breadcrumbs/${categoryId}`, - async () => await odooApi('GET', `/api/v1/category/${categoryId}/category-breadcrumb`) - ) + ['category-breadcrumbs', categoryId], + async () => + await odooApi('GET', `/api/v1/category/${categoryId}/category-breadcrumb`) + ); + + const cats = breadcrumbs.data ?? []; + const total = cats.length; + + const showEllipsis = total > 2; + const parent = total >= 2 ? cats[total - 2] : null; + const last = total ? cats[total - 1] : null; + const hiddenText = showEllipsis + ? cats + .slice(0, total - 2) + .map((c) => c.name) + .join(' / ') + : ''; + + // ===== MOBILE: 2 BARIS -> (1) Home/..//Parent (2) Last (wrap) ===== + if (isMobile) { + return ( +
+ + {/* Baris 1 */} + + + + Home + + + {showEllipsis && ( + + + .. + + + )} + + {parent && ( + + + {parent.name} + + + )} + + + {/* Baris 2 */} + {last && ( + + + + {last.name} + + + + )} + +
+ ); + } + + // ===== DESKTOP: biarkan seperti semula ===== + if (isDesktop) { + return ( +
+ + + + + Home + + + + {cats.map((category, index) => ( + + {index === cats.length - 1 ? ( + + {category.name} + + ) : ( + + {category.name} + + )} + + ))} + + +
+ ); + } + + // Fallback → layout desktop return (
- + Home - - {breadcrumbs.data?.map((category, index) => ( - - {index === breadcrumbs.data.length - 1 ? ( - {category.name} + {cats.map((category, index) => ( + + {index === cats.length - 1 ? ( + + {category.name} + ) : ( {category.name} @@ -50,7 +173,7 @@ const Breadcrumb = ({ categoryId }) => {
- ) -} + ); +}; -export default Breadcrumb +export default Breadcrumb; -- cgit v1.2.3 From 37bda78dec58cb8c218849a77620d95682b201b9 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 26 Aug 2025 15:38:29 +0700 Subject: Major Fix --- public/images/doc.svg | 3 + public/images/doc_red.svg | 5 + public/images/document.png | Bin 529 -> 0 bytes .../product-detail/components/AddToCart.tsx | 9 +- .../product-detail/components/AddToQuotation.tsx | 7 +- .../product-detail/components/PriceAction.tsx | 14 +- .../product-detail/components/ProductDetail.tsx | 4 +- src/lib/category/components/Breadcrumb.jsx | 180 +++++---------------- 8 files changed, 70 insertions(+), 152 deletions(-) create mode 100644 public/images/doc.svg create mode 100644 public/images/doc_red.svg delete mode 100644 public/images/document.png diff --git a/public/images/doc.svg b/public/images/doc.svg new file mode 100644 index 00000000..5e811ab2 --- /dev/null +++ b/public/images/doc.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/doc_red.svg b/public/images/doc_red.svg new file mode 100644 index 00000000..7816ac92 --- /dev/null +++ b/public/images/doc_red.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/public/images/document.png b/public/images/document.png deleted file mode 100644 index d1772b8c..00000000 Binary files a/public/images/document.png and /dev/null differ diff --git a/src-migrate/modules/product-detail/components/AddToCart.tsx b/src-migrate/modules/product-detail/components/AddToCart.tsx index 9f66345e..1cb58a75 100644 --- a/src-migrate/modules/product-detail/components/AddToCart.tsx +++ b/src-migrate/modules/product-detail/components/AddToCart.tsx @@ -1,6 +1,6 @@ import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; import style from '../styles/price-action.module.css'; -import { Button, Link, useToast } from '@chakra-ui/react'; +import { Button, color, Link, useToast } from '@chakra-ui/react'; import product from 'next-seo/lib/jsonld/product'; import { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; @@ -163,13 +163,13 @@ const AddToCart = ({ const btnConfig = { add_to_cart: { - colorScheme: isDesktop ? 'yellow' : 'red', - variant: 'solid', + colorScheme: 'red', + variant: 'outline', text: 'Keranjang', }, buy: { colorScheme: 'red', - variant: isDesktop ? 'solid' : 'outline', + variant: 'solid', text: isDesktop ? 'Beli' : 'Beli Sekarang', }, }; @@ -190,6 +190,7 @@ const AddToCart = ({
+
+ +
{!isApproval && ( { /> )}
-
- -
diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index 35df97df..79921e22 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -161,7 +161,7 @@ return ( {`Gambar { (e.target as HTMLImageElement).src = '/path/to/fallback-image.jpg'; @@ -174,7 +174,7 @@ return ( Gambar produk
)} diff --git a/src/lib/category/components/Breadcrumb.jsx b/src/lib/category/components/Breadcrumb.jsx index b369d781..e691e379 100644 --- a/src/lib/category/components/Breadcrumb.jsx +++ b/src/lib/category/components/Breadcrumb.jsx @@ -9,106 +9,57 @@ import { import Link from 'next/link'; import React from 'react'; import { useQuery } from 'react-query'; -import useDevice from '@/core/hooks/useDevice'; const Breadcrumb = ({ categoryId }) => { - const { isDesktop, isMobile } = useDevice(); - const breadcrumbs = useQuery( ['category-breadcrumbs', categoryId], async () => await odooApi('GET', `/api/v1/category/${categoryId}/category-breadcrumb`) ); - const cats = breadcrumbs.data ?? []; - const total = cats.length; - - const showEllipsis = total > 2; - const parent = total >= 2 ? cats[total - 2] : null; - const last = total ? cats[total - 1] : null; - const hiddenText = showEllipsis - ? cats - .slice(0, total - 2) - .map((c) => c.name) - .join(' / ') - : ''; - - // ===== MOBILE: 2 BARIS -> (1) Home/..//Parent (2) Last (wrap) ===== - if (isMobile) { - return ( -
- - {/* Baris 1 */} - - - - Home - - - - {showEllipsis && ( - - - .. - - - )} - - {parent && ( - - - {parent.name} - - - )} - - - {/* Baris 2 */} - {last && ( - - - - {last.name} - - - - )} - -
- ); - } + const items = breadcrumbs.data ?? []; + const lastIdx = items.length - 1; - // ===== DESKTOP: biarkan seperti semula ===== - if (isDesktop) { - return ( -
- - - - - Home - - + return ( +
+ + + {/* Home */} + + + Home + + - {cats.map((category, index) => ( - - {index === cats.length - 1 ? ( - + {/* Categories */} + {items.map((category, index) => { + const isLast = index === lastIdx; + return ( + + {isLast ? ( + // HANYA yang terakhir boleh turun/wrap di mobile + {category.name} ) : ( @@ -119,57 +70,14 @@ const Breadcrumb = ({ categoryId }) => { category.name, category.id )} - className='!text-danger-500 whitespace-nowrap' + className='!text-danger-500' > {category.name} )} - ))} - - -
- ); - } - - // Fallback → layout desktop - return ( -
- - - - - Home - - - {cats.map((category, index) => ( - - {index === cats.length - 1 ? ( - - {category.name} - - ) : ( - - {category.name} - - )} - - ))} + ); + })}
-- cgit v1.2.3