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'; 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); // 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(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 (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; el.scrollTo({ left: i * el.clientWidth, behavior: 'smooth' }); setCurrentIdx(i); setMainImage(allImages[i] || ''); }; 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 Detail Lainnya {/* DESKRIPSI */}
{/* SPESIFIKASI */} {loadingSpecs ? (
) : specsMatrix.length > 0 ? ( (() => { 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) => ( ))} {product.variants.map((v, vIdx) => ( {/* 1. KOLOM JUDUL VARIANT (Sticky Kiri) */} {/* 2. KOLOM DATA SPESIFIKASI */} {flatSpecs.map((spec, sIdx) => { const rawValue = spec.values[v.id] || '-'; return ( ); })} ))}
Variant {th.label}
{sub.label}
{v.attributes && v.attributes.length > 0 ? v.attributes.join(' - ') : v.code} {renderSpecValue(rawValue)}
); })() ) : ( Spesifikasi teknis belum tersedia. )}
{/* DETAIL LAINNYA */}

Informasi tambahan belum tersedia.

{/* ... (Bagian Sidebar & Bottom SAMA) ... */} {isDesktop && (
{/* ... Buttons ... */}
Produk Serupa
)}
Kamu Mungkin Juga Suka
); }; export default ProductDetail;