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 komponen Chakra UI import { Button, Tabs, TabList, TabPanels, Tab, TabPanel, Table, Tbody, Tr, Td, Th, Thead, Box, Spinner, Center, Text } from '@chakra-ui/react'; // Import Icons import { AlertTriangle, MessageCircleIcon, Share2Icon, ExternalLink } from 'lucide-react'; import { LazyLoadComponent } from 'react-lazy-load-image-component'; import useDevice from '@/core/hooks/useDevice'; import { getAuth } from '~/libs/auth'; import { whatsappUrl } from '~/libs/whatsappUrl'; import ProductPromoSection from '~/modules/product-promo/components/Section'; import { IProductDetail } from '~/types/product'; import { useProductDetail } from '../stores/useProductDetail'; import AddToWishlist from './AddToWishlist'; import Breadcrumb from './Breadcrumb'; import ProductImage from './Image'; import Information from './Information'; import PriceAction from './PriceAction'; 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'; type Props = { product: IProductDetail; }; const RWebShare = dynamic( () => import('react-web-share').then((m) => m.RWebShare), { ssr: false } ); const SELF_HOST = process.env.NEXT_PUBLIC_SELF_HOST; const ProductDetail = ({ product }: Props) => { const { isDesktop, isMobile } = useDevice(); const router = useRouter(); const [auth, setAuth] = useState(null); console.log('Render ProductDetail for product ID:', product); // State Data dari Magento const [specsMatrix, setSpecsMatrix] = useState([]); const [upsellIds, setUpsellIds] = useState([]); const [relatedIds, setRelatedIds] = useState([]); const [loadingSpecs, setLoadingSpecs] = useState(false); // 2. STATE MODAL COMPARE (Baru) const [isCompareOpen, setCompareOpen] = useState(false); useEffect(() => { try { setAuth(getAuth() ?? null); } catch {} }, []); const canShare = typeof navigator !== 'undefined' && typeof (navigator as any).share === 'function'; const { setAskAdminUrl, askAdminUrl, activeVariantId, setIsApproval, isApproval, selectedVariant, setSelectedVariant, } = useProductDetail(); useEffect(() => { gtagProductDetail(product); }, [product]); useEffect(() => { const createdAskUrl = whatsappUrl({ template: 'product', payload: { manufacture: product.manufacture.name, productName: product.name, url: process.env.NEXT_PUBLIC_SELF_HOST + router.asPath, }, fallbackUrl: router.asPath, }); setAskAdminUrl(createdAskUrl); }, [router.asPath, product.manufacture.name, product.name, setAskAdminUrl]); // ========================================================================= // 1. LOGIC INISIALISASI VARIANT // ========================================================================= useEffect(() => { if (typeof auth === 'object') { setIsApproval(auth?.feature?.soApproval); } const variantInit = product?.variants?.find((variant) => variant.is_in_bu) || product?.variants?.[0]; 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 processed = processMatrixData(data.specsMatrix); setSpecsMatrix(processed); } else { setSpecsMatrix([]); } // 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 (Separator ':') // ========================================================================= const processMatrixData = (rawMatrix: any[]) => { const groups: any = {}; const result: any[] = []; rawMatrix.forEach(item => { // Cek Label: "Group Name : Sub Label" 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 // Override label jadi pendek }); } 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(); // URL Link const isUrl = !strVal.includes(' ') && ( strVal.startsWith('http') || strVal.startsWith('www.') ); if (isUrl) { const href = strVal.startsWith('http') ? strVal : `https://${strVal}`; return ( Link ); } // HTML if (strVal.includes('<') && strVal.includes('>')) { return (
); } // Teks Biasa return strVal; }; const allImages = (() => { const arr: string[] = []; if (product?.image) arr.push(product.image); if ( Array.isArray(product?.image_carousel) && product.image_carousel.length ) { 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] || ''); const hasPrice = Number(product?.lowest_price?.price) > 0; useEffect(() => { if (!allImages.includes(mainImage)) { setMainImage(allImages[0] || ''); } }, [allImages]); 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; const elRef = sliderRef.current; elRef.scrollTo({ left: i * elRef.clientWidth, behavior: 'smooth' }); setCurrentIdx(i); setMainImage(allImages[i] || ''); }; return ( <> {/* 3. MODAL POPUP DIRENDER DISINI */} {/* Render di luar layout utama agar tidak tertutup elemen lain */} setCompareOpen(false)} />
{isDesktop && !hasPrice && (
Produk tidak tersedia
)}
{isMobile && !hasPrice && (
Produk tidak tersedia
)}
{/* ===== Kolom kiri: gambar ===== */}
{/* ... Image Slider ... */} {isMobile ? (
{allImages.length > 0 ? ( allImages.map((img, i) => (
{`Gambar { (e.target as HTMLImageElement).src = '/images/noimage.jpeg'; }} />
)) ) : (
Gambar produk
)}
{allImages.length > 1 && (
{allImages.map((_, i) => (
)}
) : ( <> {allImages.length > 0 && (
{allImages.map((img, index) => (
setMainImage(img)}> {`Thumbnail { (e.target as HTMLImageElement).src = '/images/noimage.jpeg'; }} />
))}
)} )}
{/* ===== Kolom kanan: info ===== */} {isDesktop && (
{!hasPrice && (

Maaf untuk saat ini Produk yang anda cari tidak tersedia

)}

{product.name}

)} {isMobile && (
{!hasPrice && (

Maaf untuk saat ini Produk yang anda cari tidak tersedia

)}

{product.name}

)}
{isMobile && (
)}
{!!activeVariantId && !isApproval && ( )}
{/* === SECTION TABS: DESKRIPSI & SPESIFIKASI === */}
Deskripsi Spesifikasi Detail Lainnya {/* DESKRIPSI */}

' ? '

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

' : product.description, }} />
{/* SPESIFIKASI (MATRIKS & SPLIT PIPA) */} {loadingSpecs ? (
) : specsMatrix.length > 0 ? ( // Cek Single vs Multi Variant (() => { const isSingleVariant = product.variants.length === 1; const globalAlign = isSingleVariant ? "left" : "center"; return ( {product.variants.map(v => ( ))} {specsMatrix.map((row, i) => { // CASE 1: GROUPING (Label punya ' : ') if (row.type === 'group') { return ( {/* Header Group Kiri */} {/* Content Group Kanan */} {product.variants.map(v => ( ))} ); } // CASE 2: SINGLE ITEM return ( {product.variants.map(v => { const rawValue = row.values[v.id] || '-'; return ( ); })} ); })}
Spesifikasi {isSingleVariant ? 'Detail' : (v.attributes && v.attributes.length > 0 ? v.attributes.join(' - ') : v.code)}
{row.label}
{row.children.map((child: any, idx: number) => { const rawVal = child.values[v.id]; if (!rawVal || rawVal === '-') return null; return (
{child.label}: {renderSpecValue(rawVal)}
); })}
{row.label} {renderSpecValue(rawValue)}
); })() ) : ( Spesifikasi teknis belum tersedia. )}
{/* DETAIL LAINNYA */}

Informasi tambahan belum tersedia.

{isDesktop && (
{/* 4. INTEGRASI: PASSING HANDLER MODAL KE PRICE ACTION */} setCompareOpen(true)} />
|
| {canShare && ()}
Produk Serupa
)}
Kamu Mungkin Juga Suka
); }; export default ProductDetail;