diff options
| author | IT Fixcomart <it@fixcomart.co.id> | 2026-01-31 16:22:14 +0000 |
|---|---|---|
| committer | IT Fixcomart <it@fixcomart.co.id> | 2026-01-31 16:22:14 +0000 |
| commit | 8c6f1b3bf6eac52041337b33e746888933e1e34a (patch) | |
| tree | 0787627b48ef1b1db8e6d76066eb3400afb71123 /src-migrate/modules/product-detail/components/ProductDetail.tsx | |
| parent | ec7ab4c654fc5b29b277d42ad84986f4c1220134 (diff) | |
| parent | 99aa3500fc5bbb3bb24d73461639e6fc88042a85 (diff) | |
Merged in magento-v2.1 (pull request #472)
Magento v2.1
Diffstat (limited to 'src-migrate/modules/product-detail/components/ProductDetail.tsx')
| -rw-r--r-- | src-migrate/modules/product-detail/components/ProductDetail.tsx | 717 |
1 files changed, 575 insertions, 142 deletions
diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index 129ca8de..35726437 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -2,15 +2,37 @@ import style from '../styles/product-detail.module.css'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import { useEffect, useRef, useState, UIEvent } from 'react'; - -import { Button } from '@chakra-ui/react'; +import { useEffect, useRef, useState, UIEvent, useMemo } from 'react'; + +// Import komponen Chakra UI +import { + Button, + Tabs, + TabList, + TabPanels, + Tab, + TabPanel, + Table, + Tbody, + Tr, + Td, + Th, + Thead, + Box, + Spinner, + Center, + Text, + Stack +} from '@chakra-ui/react'; + +// Import Icons import { - AlertCircle, AlertTriangle, MessageCircleIcon, Share2Icon, + ExternalLink } from 'lucide-react'; + import { LazyLoadComponent } from 'react-lazy-load-image-component'; import useDevice from '@/core/hooks/useDevice'; @@ -28,7 +50,11 @@ import SimilarBottom from './SimilarBottom'; import SimilarSide from './SimilarSide'; import dynamic from 'next/dynamic'; +// 1. IMPORT MODAL (Baru) +import ProductComparisonModal from './ProductComparisonModal'; + import { gtagProductDetail } from '@/core/utils/googleTag'; +import Skeleton from 'react-loading-skeleton'; type Props = { product: IProductDetail; @@ -39,12 +65,61 @@ const RWebShare = dynamic( { ssr: false } ); +// 1. STYLE DESKTOP (Tebal, Jelas, dengan Border/Padding) +const cssScrollbarDesktop = { + '&::-webkit-scrollbar': { + width: '10px', + height: '10px', + }, + '&::-webkit-scrollbar-track': { + background: '#f1f1f1', + borderRadius: '4px', + }, + '&::-webkit-scrollbar-thumb': { + backgroundColor: '#9ca3af', // Gray-400 + borderRadius: '6px', + border: '2px solid #f1f1f1', // Efek padding + }, + '&::-webkit-scrollbar-thumb:hover': { + backgroundColor: '#6b7280', + }, +}; + +// 2. STYLE MOBILE (Tipis, Minimalis, Tanpa Border) +const cssScrollbarMobile = { + '&::-webkit-scrollbar': { + width: '3px', // Sangat tipis vertikal + height: '3px', // Sangat tipis horizontal + }, + '&::-webkit-scrollbar-track': { + background: 'transparent', + }, + '&::-webkit-scrollbar-thumb': { + backgroundColor: '#cbd5e1', // Gray-300 + borderRadius: '3px', + }, +}; + const SELF_HOST = process.env.NEXT_PUBLIC_SELF_HOST; const ProductDetail = ({ product }: Props) => { const { isDesktop, isMobile } = useDevice(); const router = useRouter(); const [auth, setAuth] = useState<any>(null); + + console.log('Render ProductDetail for product ID:', product); + + // State Data dari Magento + const [specsMatrix, setSpecsMatrix] = useState<any[]>([]); + const [upsellIds, setUpsellIds] = useState<number[]>([]); + const [relatedIds, setRelatedIds] = useState<number[]>([]); + const [descriptionMap, setDescriptionMap] = useState<Record<string, string>>({}); + + const [loadingSpecs, setLoadingSpecs] = useState(false); + + // 2. STATE MODAL COMPARE (Baru) + const [isCompareOpen, setCompareOpen] = useState(false); + useEffect(() => { try { setAuth(getAuth() ?? null); @@ -61,8 +136,8 @@ const ProductDetail = ({ product }: Props) => { activeVariantId, setIsApproval, isApproval, + selectedVariant, setSelectedVariant, - setSla, } = useProductDetail(); useEffect(() => { @@ -95,15 +170,183 @@ const ProductDetail = ({ product }: Props) => { // }); // }, [product?.id]); + // 1. LOGIC INISIALISASI VARIANT useEffect(() => { if (typeof auth === 'object') { setIsApproval(auth?.feature?.soApproval); } - const selectedVariant = + const variantInit = product?.variants?.find((variant) => variant.is_in_bu) || product?.variants?.[0]; - setSelectedVariant(selectedVariant); - }, []); + + setSelectedVariant(variantInit); + + setSpecsMatrix([]); + setUpsellIds([]); + setRelatedIds([]); + + }, [product, auth]); + + // 2. LOGIC FETCH DATA + useEffect(() => { + const fetchMagentoData = async () => { + const allVariantIds = product.variants.map(v => v.id); + + if (allVariantIds.length === 0) return; + + const mainId = allVariantIds[0]; + + setLoadingSpecs(true); + + try { + const params = new URLSearchParams({ + skus: allVariantIds.join(','), + main_sku: String(mainId) + }); + + const endpoint = `/api/magento-product?${params.toString()}`; + + const response = await fetch(endpoint, { + method: 'GET', + headers: { 'Content-Type': 'application/json' } + }); + + if (!response.ok) { + setSpecsMatrix([]); + setUpsellIds([]); + setRelatedIds([]); + return; + } + + const data = await response.json(); + + // 1. Specs Matrix (Processed Grouping) + if (data.specsMatrix && Array.isArray(data.specsMatrix)) { + // const filteredMatrix = data.specsMatrix.filter((item: any) => { + // const code = item.code || ''; + // return !code.includes('z_brand'); + // }); + const processed = processMatrixData(data.specsMatrix); + setSpecsMatrix(processed); + // const processed = processMatrixData(filteredMatrix); + // setSpecsMatrix(processed); + } else { + setSpecsMatrix([]); + } + + if (data.descriptions){ + setDescriptionMap(data.descriptions); + } + + // 2. Upsell & Related + if (data.upsell_ids && Array.isArray(data.upsell_ids)) setUpsellIds(data.upsell_ids); + else setUpsellIds([]); + + if (data.related_ids && Array.isArray(data.related_ids)) setRelatedIds(data.related_ids); + else setRelatedIds([]); + + } catch (error) { + console.error("Gagal mengambil data Magento:", error); + setSpecsMatrix([]); + } finally { + setLoadingSpecs(false); + } + }; + + fetchMagentoData(); + + }, [product.id]); + + // HELPER 1: GROUPING DATA BY LABEL + const processMatrixData = (rawMatrix: any[]) => { + const groups: any = {}; + const result: any[] = []; + + rawMatrix.forEach(item => { + if (item.label && item.label.includes(' : ')) { + const parts = item.label.split(' : '); + const groupName = parts[0].trim(); + const childLabel = parts.slice(1).join(' : ').trim(); + + if (!groups[groupName]) { + groups[groupName] = { + type: 'group', + label: groupName, + children: [] + }; + result.push(groups[groupName]); + } + + groups[groupName].children.push({ + ...item, + label: childLabel + }); + + } else { + result.push({ ...item, type: 'single' }); + } + }); + + return result; + }; + + + // HELPER 2: RENDER SPEC VALUE + const renderSpecValue = (val: any) => { + if (!val) return '-'; + const strVal = String(val).trim(); + + const isUrl = !strVal.includes(' ') && ( + strVal.startsWith('http') || + strVal.startsWith('www.') + ); + if (isUrl) { + const href = strVal.startsWith('http') ? strVal : `https://${strVal}`; + return ( + <a + href={href} + target="_blank" + rel="noopener noreferrer" + className="text-red-600 hover:underline inline-flex items-center gap-1" + > + <ExternalLink size={14} /> Link + </a> + ); + } + + if (strVal.includes('<') && strVal.includes('>')) { + return ( + <Box + className="prose prose-sm text-gray-700" + sx={{ + '& ul, & ol': { + paddingLeft: '1.2rem', + margin: 0, + textAlign: 'left' + }, + '& li': { + fontWeight: 'normal', + marginBottom: '4px', + textAlign: 'left' + }, + '& strong': { + display: 'block', + marginBottom: '2px', + fontWeight: 'bold' + }, + '& p': { + margin: 0, + textAlign: 'left' + } + }} + dangerouslySetInnerHTML={{ __html: strVal }} + /> + ); + } + + return strVal; + }; + const allImages = (() => { const arr: string[] = []; @@ -146,15 +389,59 @@ const ProductDetail = ({ product }: Props) => { }; const scrollToIndex = (i: number) => { - const el = sliderRef.current; - if (!el) return; + const el = sliderRef.current; + if (!el) return; el.scrollTo({ left: i * el.clientWidth, behavior: 'smooth' }); setCurrentIdx(i); setMainImage(allImages[i] || ''); }; + const sortedVariants = useMemo(() => { + if (!product?.variants) return []; + + return [...product.variants].sort((a, b) => { + const labelA = a.attributes && a.attributes.length > 0 + ? a.attributes.join(' - ') + : a.code || ''; + + const labelB = b.attributes && b.attributes.length > 0 + ? b.attributes.join(' - ') + : b.code || ''; + + const getNumber = (str: string) => { + const match = String(str).match(/(\d+(\.\d+)?)/); + return match ? parseFloat(match[0]) : null; + }; + + const numA = getNumber(labelA); + const numB = getNumber(labelB); + + if (numA !== null && numB !== null && numA !== numB) { + return numA - numB; + } + + return String(labelA).localeCompare(String(labelB), undefined, { + numeric: true, + sensitivity: 'base' + }); + }); + }, [product.variants]); + + const activeMagentoDesc = selectedVariant?.id ? descriptionMap[String(selectedVariant.id)] : ''; + const finalDescription = activeMagentoDesc || product.description || 'Deskripsi produk tidak tersedia.'; + const cleanDescription = finalDescription === '<p><br></p>' ? 'Deskripsi produk tidak tersedia.' : finalDescription; + return ( <> + {/* 3. MODAL POPUP DIRENDER DISINI */} + {/* Render di luar layout utama agar tidak tertutup elemen lain */} + <ProductComparisonModal + isOpen={isCompareOpen} + onClose={() => setCompareOpen(false)} + mainProduct={product} + selectedVariant={selectedVariant} + /> + <div className='relative'> {isDesktop && !hasPrice && ( <div className='absolute inset-0 z-[20] flex items-center justify-center pointer-events-none select-none'> @@ -188,93 +475,39 @@ const ProductDetail = ({ product }: Props) => { <div className='md:flex md:flex-wrap'> {/* ===== Kolom kiri: gambar ===== */} <div className='md:w-4/12'> - {/* === MOBILE: Slider swipeable, tanpa thumbnail carousel === */} + {/* ... Image Slider ... */} {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', - }} - > + <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 - key={i} - className='w-full flex-shrink-0 snap-center flex justify-center items-center' - > - {/* gambar diperkecil */} - <img - src={img} - alt={`Gambar ${i + 1}`} - className='w-[85%] aspect-square object-contain' - onError={(e) => { - (e.target as HTMLImageElement).src = - '/images/noimage.jpeg'; - }} - /> + <div key={i} className='w-full flex-shrink-0 snap-center flex justify-center items-center'> + <img src={img} alt={`Gambar ${i + 1}`} className='w-[85%] aspect-square object-contain' onError={(e) => { (e.target as HTMLImageElement).src = '/images/noimage.jpeg'; }} /> </div> )) ) : ( <div className='w-full flex-shrink-0 snap-center flex justify-center items-center'> - <img - src={mainImage || '/images/noimage.jpeg'} - alt='Gambar produk' - className='w-[85%] aspect-square object-contain' - /> + <img src={mainImage || '/images/noimage.jpeg'} alt='Gambar produk' className='w-[85%] aspect-square object-contain' /> </div> )} </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)} - /> + <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 = - '/images/noimage.jpeg'; - }} - /> + <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 = '/images/noimage.jpeg'; }} /> </div> ))} </div> @@ -283,7 +516,6 @@ const ProductDetail = ({ product }: Props) => { </> )} </div> - {/* <<=== TUTUP kolom kiri */} {/* ===== Kolom kanan: info ===== */} {isDesktop && ( @@ -294,9 +526,9 @@ const ProductDetail = ({ product }: Props) => { size={18} className='text-red-600 shrink-0 mx-2' /> - <div className='text-red-600 font-normal text-h-sm p-2'> + <h1 className='text-red-600 font-normal text-h-sm'> Maaf untuk saat ini Produk yang anda cari tidak tersedia - </div> + </h1> </div> )} <div className='h-6 md:h-0' /> @@ -314,27 +546,27 @@ const ProductDetail = ({ product }: Props) => { size={18} className='text-red-600 shrink-0 mx-2' /> - <div className='text-red-600 font-normal text-h-sm p-2'> + <h1 className='text-red-600 font-normal text-h-sm'> Maaf untuk saat ini Produk yang anda cari tidak tersedia - </div> + </h1> </div> )} <h1 className={style['title']}>{product.name}</h1> <div className='h-3 md:h-0' /> <Information product={product} /> - <div className='h-6' /> + <div className='h-2' /> </div> )} </div> <div className='h-full'> {isMobile && ( - <div className='px-4 pt-6'> + <div className='px-4 pt-2'> <PriceAction product={product} /> </div> )} - <div className='h-4 md:h-10' /> + <div className='h-2 md:h-10' /> {!!activeVariantId && !isApproval && ( <ProductPromoSection product={product} @@ -344,94 +576,295 @@ const ProductDetail = ({ product }: Props) => { <div className='h-0 md:h-6' /> + {/* === SECTION TABS: DESKRIPSI & SPESIFIKASI === */} <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> + <Tabs variant="unstyled"> + <TabList borderBottom="1px solid" borderColor="gray.200"> + <Tab + _selected={{ color: 'red.600', borderColor: 'red.600', borderBottomWidth: '3px', fontWeight: 'bold', marginBottom: '-1.5px' }} + color="gray.500" fontWeight="medium" fontSize="sm" px={4} py={3} + > + Deskripsi + </Tab> + <Tab + _selected={{ color: 'red.600', borderColor: 'red.600', borderBottomWidth: '3px', fontWeight: 'bold', marginBottom: '-1.5px' }} + color="gray.500" fontWeight="medium" fontSize="sm" px={4} py={3} + > + Spesifikasi + </Tab> + {/* <Tab + _selected={{ color: 'red.600', borderColor: 'red.600', borderBottomWidth: '3px', fontWeight: 'bold', marginBottom: '-1.5px' }} + color="gray.500" fontWeight="medium" fontSize="sm" px={4} py={3} + > + Detail Lainnya + </Tab> */} + </TabList> + + <TabPanels> + {/* DESKRIPSI */} + <TabPanel px={0} py={6}> + <div className='overflow-x-auto text-sm text-gray-700'> + {loadingSpecs ? ( + <Stack spacing={4}> + <Skeleton height='20px' width="100%" /> + <Skeleton height='20px' width="90%" /> + <Skeleton height='20px' width="95%" /> + <Skeleton height='20px' width="70%" /> + </Stack> + ) : ( + <Box + className={style['description']} + sx={{ + 'ul, ol': { + marginTop: '0.5em !important', + marginBottom: '1em !important', + marginLeft: '0 !important', + listStylePosition: 'outside !important', + paddingLeft: '1.5em !important' + }, + 'ul': { listStyleType: 'disc !important' }, + 'ol': { listStyleType: 'decimal !important' }, + 'li': { + marginBottom: '0.4em !important', + paddingLeft: '0.3em !important', + lineHeight: '1.6 !important' + } + }} + dangerouslySetInnerHTML={{ __html: cleanDescription }} + /> + )} + + </div> + </TabPanel> + + {/* SPESIFIKASI */} + <TabPanel px={0} py={2}> + <Box + border="1px solid" + borderColor="gray.200" + borderRadius="sm" + overflowX="auto" + overflowY="auto" + maxHeight="500px" + css={isMobile ? cssScrollbarMobile : cssScrollbarDesktop} + > + {loadingSpecs ? ( + <Center py={6}><Spinner color='red.500' /></Center> + ) : specsMatrix.length > 0 ? ( + (() => { + const variantCount = sortedVariants.length; + const isSingleVariant = variantCount === 1; + + // === LOGIC 1: SINGLE VARIANT (VERTICAL TABLE) === + if (isSingleVariant) { + const singleVariantId = sortedVariants[0].id; + // Flatten data untuk list vertical + const rows: any[] = []; + specsMatrix.forEach(row => { + if (row.type === 'group') { + row.children.forEach((child: any) => rows.push(child)); + } else { + rows.push(row); + } + }); + + return ( + <Table variant="simple" size={isMobile ? "sm" : "md"}> + <Tbody> + {rows.map((row, idx) => ( + <Tr key={idx} bg={idx % 2 === 0 ? 'white' : 'gray.50'}> + {/* Kolom Label (Kiri) */} + <Td + width="40%" + fontWeight="bold" + color="gray.600" + borderColor="gray.200" + verticalAlign="top" + py={3} + > + {row.label} + </Td> + {/* Kolom Value (Kanan) */} + <Td + color="gray.800" + borderColor="gray.200" + verticalAlign="top" + py={3} + > + {renderSpecValue(row.values[singleVariantId])} + </Td> + </Tr> + ))} + </Tbody> + </Table> + ); + } + + // === LOGIC 2: MULTIPLE VARIANTS (MATRIX TABLE HORIZONTAL) === + const topHeaders: any[] = []; + const subHeaders: any[] = []; + const flatSpecs: any[] = []; + + specsMatrix.forEach(row => { + if (row.type === 'group') { + topHeaders.push({ + label: row.label, + type: 'group', + colSpan: row.children.length, + rowSpan: 1 + }); + row.children.forEach((child: any) => { + subHeaders.push(child); + flatSpecs.push(child); + }); + } else { + topHeaders.push({ + label: row.label, + type: 'single', + colSpan: 1, + rowSpan: 2 + }); + flatSpecs.push(row); + } + }); + + return ( + <Table variant="simple" size={isMobile ? "sm" : "md"}> + <Thead bg="red.600" position="sticky" top={0} zIndex={3}> + <Tr> + {topHeaders.map((th, idx) => ( + <Th + key={`top-${idx}`} + position={idx === 0 ? "sticky" : "static"} + left={idx === 0 ? 0 : undefined} + zIndex={idx === 0 ? 4 : 3} + boxShadow={idx === 0 ? "2px 0 5px -2px rgba(0,0,0,0.2)" : "none"} + bg="red.600" + colSpan={th.colSpan} + rowSpan={th.rowSpan} + color="white" + textAlign="center" + fontSize={isMobile ? "xs" : "sm"} + textTransform="none" + fontWeight="800" + letterSpacing="wide" + verticalAlign="middle" + borderBottom="none" + px={isMobile ? 2 : 4} + > + {th.label} + </Th> + ))} + </Tr> + <Tr> + {subHeaders.map((sub, idx) => { + const isFirstHeaderGroup = topHeaders[0]?.type === 'group'; + const shouldSticky = idx === 0 && isFirstHeaderGroup; + return ( + <Th + key={`sub-${idx}`} + position={shouldSticky ? "sticky" : "static"} + left={shouldSticky ? 0 : undefined} + zIndex={shouldSticky ? 4 : 1} + boxShadow={shouldSticky ? "2px 0 5px -2px rgba(0,0,0,0.2)" : "none"} + color="white" + textAlign="center" + fontSize="xs" + textTransform="none" + verticalAlign="middle" + whiteSpace="nowrap" + bg="red.600" + pt={1} pb={1} + px={isMobile ? 2 : 4} + > + {sub.label} + </Th> + ); + })} + </Tr> + </Thead> + + <Tbody> + {sortedVariants.map((v, vIdx) => ( + <Tr key={v.id} bg={vIdx % 2 === 0 ? 'white' : 'gray.50'}> + {flatSpecs.map((spec, sIdx) => { + const rawValue = spec.values[v.id] || '-'; + const isFirstCol = sIdx === 0; + return ( + <Td + key={sIdx} + position={isFirstCol ? "sticky" : "static"} + left={isFirstCol ? 0 : undefined} + zIndex={isFirstCol ? 2 : 1} + bg={vIdx % 2 === 0 ? 'white' : 'gray.50'} + boxShadow={isFirstCol ? "2px 0 5px -2px rgba(0,0,0,0.1)" : "none"} + borderColor="gray.200" + textAlign="center" + fontSize={isMobile ? "xs" : "sm"} + verticalAlign="middle" + px={isMobile ? 1 : 2} + py={3} + minW={isMobile ? "100px" : "120px"} + maxW="200px" + whiteSpace="normal" + overflowWrap="break-word" + fontWeight={isFirstCol ? "bold" : "normal"} + > + {renderSpecValue(rawValue)} + </Td> + ); + })} + </Tr> + ))} + </Tbody> + </Table> + ); + })() + ) : ( + <Box p={4} color="gray.500" fontSize="sm"><Text>Spesifikasi teknis belum tersedia.</Text></Box> + )} + </Box> + </TabPanel> + </TabPanels> + </Tabs> </div> </div> </div> {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} />} - isDisabled={!hasPrice} - > - Ask Admin - </Button> - + {/* 4. INTEGRASI: PASSING HANDLER MODAL KE PRICE ACTION */} + <PriceAction + product={product} + onCompare={() => setCompareOpen(true)} + /> + + <div className='flex gap-x-5 items-center justify-center py-4'> + <Button as={Link} href={askAdminUrl} variant='link' target='_blank' colorScheme='gray' leftIcon={<MessageCircleIcon size={18} />} isDisabled={!hasPrice}>Ask Admin</Button> <span>|</span> - - <div className={hasPrice ? '' : 'opacity-40 pointer-events-none'}> - <AddToWishlist productId={product.id} /> - </div> - + <div className={hasPrice ? '' : 'opacity-40 pointer-events-none'}><AddToWishlist productId={product.id} /></div> <span>|</span> - - {canShare && ( - <RWebShare - data={{ - text: 'Check out this product', - title: `${product.name} - Indoteknik.com`, - url: - (process.env.NEXT_PUBLIC_SELF_HOST || '') + - (router?.asPath || '/'), - }} - > - <Button - variant='link' - colorScheme='gray' - leftIcon={<Share2Icon size={18} />} - isDisabled={!hasPrice} - > - Share - </Button> - </RWebShare> - )} + {canShare && (<RWebShare data={{ text: 'Check out this product', title: `${product.name} - Indoteknik.com`, url: (process.env.NEXT_PUBLIC_SELF_HOST || '') + (router?.asPath || '/'), }}><Button variant='link' colorScheme='gray' leftIcon={<Share2Icon size={18} />} isDisabled={!hasPrice}>Share</Button></RWebShare>)} </div> <div className='h-6' /> <div className={style['heading']}>Produk Serupa</div> - <div className='h-4' /> - - <SimilarSide product={product} /> + <SimilarSide product={product} relatedIds={relatedIds} /> </div> )} <div className='md:w-full pt-4 md:py-10 px-4 md:px-0'> <div className={style['heading']}>Kamu Mungkin Juga Suka</div> - <div className='h-6' /> - <LazyLoadComponent> - <SimilarBottom product={product} /> + <SimilarBottom product={product} upsellIds={upsellIds} /> </LazyLoadComponent> </div> - <div className='h-6 md:h-0' /> </div> </> ); }; -export default ProductDetail; +export default ProductDetail;
\ No newline at end of file |
