import style from '../styles/product-detail.module.css'; import Link from 'next/link'; import { useRouter } from 'next/router'; 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 } 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'; import { gtagProductDetail } from '@/core/utils/googleTag'; type Props = { product: IProductDetail; }; const RWebShare = dynamic( () => import('react-web-share').then((m) => m.RWebShare), { 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(null); // State Data dari Magento const [specsMatrix, setSpecsMatrix] = useState([]); const [upsellIds, setUpsellIds] = useState([]); const [relatedIds, setRelatedIds] = useState([]); const [descriptionMap, setDescriptionMap] = useState>({}); const [loadingSpecs, setLoadingSpecs] = 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 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 ( Link ); } if (strVal.includes('<') && strVal.includes('>')) { return (
); } 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; 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 === '


' ? 'Deskripsi produk tidak tersedia.' : finalDescription; return ( <>
{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 {/* DESKRIPSI */}
{/* SPESIFIKASI */} {loadingSpecs ? (
) : 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 ( {rows.map((row, idx) => ( {/* Kolom Label (Kiri) */} {/* Kolom Value (Kanan) */} ))}
{row.label} {renderSpecValue(row.values[singleVariantId])}
); } // === 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 ( {topHeaders.map((th, idx) => ( ))} {subHeaders.map((sub, idx) => { const isFirstHeaderGroup = topHeaders[0]?.type === 'group'; const shouldSticky = idx === 0 && isFirstHeaderGroup; return ( ); })} {sortedVariants.map((v, vIdx) => ( {flatSpecs.map((spec, sIdx) => { const rawValue = spec.values[v.id] || '-'; const isFirstCol = sIdx === 0; return ( ); })} ))}
{th.label}
{sub.label}
{renderSpecValue(rawValue)}
); })() ) : ( Spesifikasi teknis belum tersedia. )}
{/* ... (Bagian Sidebar & Bottom SAMA) ... */} {isDesktop && (
{/* ... Buttons ... */}
Produk Serupa
)}
Kamu Mungkin Juga Suka
); }; export default ProductDetail;