diff options
| author | Miqdad <ahmadmiqdad27@gmail.com> | 2025-08-26 11:41:54 +0700 |
|---|---|---|
| committer | Miqdad <ahmadmiqdad27@gmail.com> | 2025-08-26 11:41:54 +0700 |
| commit | bcf7be5a0c1d9635383605afc0600386b0b356ea (patch) | |
| tree | 83ebd96627aa3d405c5debfee15f1aaeeab0a240 /src-migrate | |
| parent | 8e92e0b4bfb74ebdf99fbc9d4ca7d47e2513c4e0 (diff) | |
<Miqdad>Push
Diffstat (limited to 'src-migrate')
| -rw-r--r-- | src-migrate/modules/product-detail/components/Breadcrumb.tsx | 79 | ||||
| -rw-r--r-- | src-migrate/modules/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 ( - <div className='line-clamp-2 md:line-clamp-1 leading-7 text-caption-1'> - <Link href='/' className='text-danger-500'>Home</Link> - <span className='mx-2'>/</span> - {breadcrumbs.map((category, index) => ( - <Fragment key={index}> + <div className='flex items-center whitespace-nowrap overflow-hidden text-caption-1 leading-7'> + <Link href='/' className='text-danger-500 shrink-0'> + Home + </Link> + <span className='mx-2 shrink-0'>/</span> + + {showEllipsis && ( + <> + <span className='text-danger-500 shrink-0' title={hiddenText}> + … + </span> + <span className='mx-2 shrink-0'>/</span> + </> + )} + + {visible.map((category, index) => ( + <Fragment key={category.id ?? index}> <Link - href={createSlug('/shop/category/', category.name, category.id.toString())} - className='text-danger-500' + href={createSlug( + '/shop/category/', + category.name, + String(category.id) + )} + className='text-danger-500 shrink-0' > {category.name} </Link> - <span className='mx-2'>/</span> + <span className='mx-2 shrink-0'>/</span> </Fragment> ))} - <span>{name}</span> + + <span className='truncate min-w-0 flex-1'>{name}</span> </div> - ) -} + ); +}; -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 ( - <> - <div className='md:flex md:flex-wrap'> - <div className='w-full mb-4 md:mb-0 px-4 md:px-0'> - <Breadcrumb id={product.id} name={product.name} /> - </div> +return ( + <> + <div className='md:flex md:flex-wrap'> + <div className='w-full mb-4 md:mb-0 px-4 md:px-0'> + <Breadcrumb id={product.id} name={product.name} /> + </div> - <div className='md:w-9/12 md:flex md:flex-col md:pr-4 md:pt-6'> - <div className='md:flex md:flex-wrap'> - {/* ===== Kolom kiri: gambar ===== */} - <div className='md:w-4/12'> - {/* === MOBILE: Slider swipeable, tanpa thumbnail carousel === */} - {isMobile ? ( - <div className='relative'> - <div - ref={sliderRef} - onScroll={handleMobileScroll} - className='flex overflow-x-auto snap-x snap-mandatory scroll-smooth no-scrollbar' - style={{ - scrollBehavior: 'smooth', - msOverflowStyle: 'none', - scrollbarWidth: 'none', - }} - > - {allImages.length > 0 ? ( - allImages.map((img, i) => ( + <div className='md:w-9/12 md:flex md:flex-col md:pr-4 md:pt-6'> + <div className='md:flex md:flex-wrap'> + {/* ===== Kolom kiri: gambar ===== */} + <div className='md:w-4/12'> + {/* === MOBILE: Slider swipeable, tanpa thumbnail carousel === */} + {isMobile ? ( + <div className='relative'> + <div + ref={sliderRef} + onScroll={handleMobileScroll} + className='flex overflow-x-auto snap-x snap-mandatory scroll-smooth no-scrollbar' + style={{ + scrollBehavior: 'smooth', + msOverflowStyle: 'none', + scrollbarWidth: 'none', + }} + > + {allImages.length > 0 ? ( + allImages.map((img, i) => ( + // slide tetap selebar viewport untuk snap mulus + <div + key={i} + className='w-full flex-shrink-0 snap-center flex justify-center items-center' + > + {/* gambar diperkecil */} <img - key={i} src={img} alt={`Gambar ${i + 1}`} - className='w-full aspect-square object-cover flex-shrink-0 snap-center' + className='w-[80%] max-w-[320px] aspect-square object-contain' onError={(e) => { (e.target as HTMLImageElement).src = '/path/to/fallback-image.jpg'; }} /> - )) - ) : ( + </div> + )) + ) : ( + <div className='w-full flex-shrink-0 snap-center flex justify-center items-center'> <img src={mainImage || '/path/to/fallback-image.jpg'} alt='Gambar produk' - className='w-full aspect-square object-cover flex-shrink-0 snap-center' + className='w-[80%] max-w-[320px] aspect-square object-contain' /> - )} - </div> - - {/* Dots indicator */} - {allImages.length > 1 && ( - <div className='absolute bottom-2 left-0 right-0 flex justify-center gap-2'> - {allImages.map((_, i) => ( - <button - key={i} - aria-label={`Ke slide ${i + 1}`} - className={`w-2 h-2 rounded-full ${ - currentIdx === i ? 'bg-gray-800' : 'bg-gray-300' - }`} - onClick={() => scrollToIndex(i)} - /> - ))} </div> )} </div> - ) : ( - <> - {/* === DESKTOP: Tetap seperti sebelumnya === */} - <ProductImage product={{ ...product, image: mainImage }} /> - - {/* Carousel horizontal (thumbnail) – hanya desktop */} - {allImages.length > 0 && ( - <div className='mt-4 overflow-x-auto'> - <div className='flex space-x-3 pb-3'> - {allImages.map((img, index) => ( - <div - key={index} - className={`flex-shrink-0 w-16 h-16 cursor-pointer border-2 rounded-md transition-colors ${ - mainImage === img - ? 'border-red-500 ring-2 ring-red-200' - : 'border-gray-200 hover:border-gray-300' - }`} - onClick={() => setMainImage(img)} - > - <img - src={img} - alt={`Thumbnail ${index + 1}`} - className='w-full h-full object-cover rounded-sm' - loading='lazy' - onError={(e) => { - (e.target as HTMLImageElement).src = - '/path/to/fallback-image.jpg'; - }} - /> - </div> - ))} - </div> - </div> - )} - </> - )} - </div> - {/* <<=== TUTUP kolom kiri */} - - {/* ===== Kolom kanan: info ===== */} - <div className='md:w-8/12 px-4 md:pl-6'> - <div className='h-6 md:h-0' /> - <h1 className={style['title']}>{product.name}</h1> - <div className='h-3 md:h-0' /> - <Information product={product} /> - <div className='h-6' /> - </div> - </div> - <div className='h-full'> - {isMobile && ( - <div className='px-4 pt-6'> - <PriceAction product={product} /> + {/* Dots indicator */} + {allImages.length > 1 && ( + <div className='absolute bottom-2 left-0 right-0 flex justify-center gap-2'> + {allImages.map((_, i) => ( + <button + key={i} + aria-label={`Ke slide ${i + 1}`} + className={`w-2 h-2 rounded-full ${ + currentIdx === i ? 'bg-gray-800' : 'bg-gray-300' + }`} + onClick={() => scrollToIndex(i)} + /> + ))} + </div> + )} </div> + ) : ( + <> + {/* === DESKTOP: Tetap seperti sebelumnya === */} + <ProductImage product={{ ...product, image: mainImage }} /> + + {/* Carousel horizontal (thumbnail) – hanya desktop */} + {allImages.length > 0 && ( + <div className='mt-4 overflow-x-auto'> + <div className='flex space-x-3 pb-3'> + {allImages.map((img, index) => ( + <div + key={index} + className={`flex-shrink-0 w-16 h-16 cursor-pointer border-2 rounded-md transition-colors ${ + mainImage === img + ? 'border-red-500 ring-2 ring-red-200' + : 'border-gray-200 hover:border-gray-300' + }`} + onClick={() => setMainImage(img)} + > + <img + src={img} + alt={`Thumbnail ${index + 1}`} + className='w-full h-full object-cover rounded-sm' + loading='lazy' + onError={(e) => { + (e.target as HTMLImageElement).src = + '/path/to/fallback-image.jpg'; + }} + /> + </div> + ))} + </div> + </div> + )} + </> )} + </div> + {/* <<=== TUTUP kolom kiri */} + + {/* ===== Kolom kanan: info ===== */} + <div className='md:w-8/12 px-4 md:pl-6'> + <div className='h-6 md:h-0' /> + <h1 className={style['title']}>{product.name}</h1> + <div className='h-3 md:h-0' /> + <Information product={product} /> + <div className='h-6' /> + </div> + </div> - <div className='h-4 md:h-10' /> - {!!activeVariantId && !isApproval && ( - <ProductPromoSection - product={product} - productId={activeVariantId} - /> - )} + <div className='h-full'> + {isMobile && ( + <div className='px-4 pt-6'> + <PriceAction product={product} /> + </div> + )} - <div className='h-0 md:h-6' /> + <div className='h-4 md:h-10' /> + {!!activeVariantId && !isApproval && ( + <ProductPromoSection + product={product} + productId={activeVariantId} + /> + )} - <div className={style['section-card']}> - <h2 className={style['heading']}>Informasi Produk</h2> - <div className='h-4' /> - <div className='overflow-x-auto'> - <div - className={style['description']} - dangerouslySetInnerHTML={{ - __html: - !product.description || - product.description == '<p><br></p>' - ? 'Belum ada deskripsi' - : product.description, - }} - /> - </div> + <div className='h-0 md:h-6' /> + + <div className={style['section-card']}> + <h2 className={style['heading']}>Informasi Produk</h2> + <div className='h-4' /> + <div className='overflow-x-auto'> + <div + className={style['description']} + dangerouslySetInnerHTML={{ + __html: + !product.description || product.description == '<p><br></p>' + ? 'Belum ada deskripsi' + : product.description, + }} + /> </div> </div> </div> + </div> - {isDesktop && ( - <div className='md:w-3/12'> - <PriceAction product={product} /> - <div className='flex gap-x-5 items-center justify-center'> + {isDesktop && ( + <div className='md:w-3/12'> + <PriceAction product={product} /> + <div className='flex gap-x-5 items-center justify-center'> + <Button + as={Link} + href={askAdminUrl} + variant='link' + target='_blank' + colorScheme='gray' + leftIcon={<MessageCircleIcon size={18} />} + > + Ask Admin + </Button> + + <span>|</span> + + <AddToWishlist productId={product.id} /> + + <span>|</span> + + <RWebShare + data={{ + text: 'Check out this product', + title: `${product.name} - Indoteknik.com`, + url: SELF_HOST + router.asPath, + }} + > <Button - as={Link} - href={askAdminUrl} variant='link' - target='_blank' colorScheme='gray' - leftIcon={<MessageCircleIcon size={18} />} + leftIcon={<Share2Icon size={18} />} > - Ask Admin + Share </Button> + </RWebShare> + </div> - <span>|</span> - - <AddToWishlist productId={product.id} /> - - <span>|</span> + <div className='h-6' /> + <div className={style['heading']}>Produk Serupa</div> - <RWebShare - data={{ - text: 'Check out this product', - title: `${product.name} - Indoteknik.com`, - url: SELF_HOST + router.asPath, - }} - > - <Button - variant='link' - colorScheme='gray' - leftIcon={<Share2Icon size={18} />} - > - Share - </Button> - </RWebShare> - </div> + <div className='h-4' /> - <div className='h-6' /> - <div className={style['heading']}>Produk Serupa</div> + <SimilarSide product={product} /> + </div> + )} - <div className='h-4' /> + <div className='md:w-full pt-4 md:py-10 px-4 md:px-0'> + <div className={style['heading']}>Kamu Mungkin Juga Suka</div> - <SimilarSide product={product} /> - </div> - )} + <div className='h-6' /> - <div className='md:w-full pt-4 md:py-10 px-4 md:px-0'> - <div className={style['heading']}>Kamu Mungkin Juga Suka</div> + <LazyLoadComponent> + <SimilarBottom product={product} /> + </LazyLoadComponent> + </div> - <div className='h-6' /> + <div className='h-6 md:h-0' /> + </div> + </> +); - <LazyLoadComponent> - <SimilarBottom product={product} /> - </LazyLoadComponent> - </div> - <div className='h-6 md:h-0' /> - </div> - </> - ); }; -export default ProductDetail;
\ No newline at end of file +export default ProductDetail; |
