From dc14a5fd2c97d50b19b043a44d61786d98fda48b Mon Sep 17 00:00:00 2001 From: Mqdd Date: Sat, 31 Jan 2026 23:48:37 +0700 Subject: remove console log --- .../product-detail/components/ProductDetail.tsx | 741 ++++++++++++--------- 1 file changed, 444 insertions(+), 297 deletions(-) (limited to 'src-migrate/modules/product-detail/components/ProductDetail.tsx') diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index 35726437..de205c41 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -5,24 +5,24 @@ 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, +import { + Button, + Tabs, + TabList, + TabPanels, + Tab, + TabPanel, + Table, + Tbody, + Tr, Td, - Th, + Th, Thead, Box, Spinner, Center, Text, - Stack + Stack, } from '@chakra-ui/react'; // Import Icons @@ -30,7 +30,7 @@ import { AlertTriangle, MessageCircleIcon, Share2Icon, - ExternalLink + ExternalLink, } from 'lucide-react'; import { LazyLoadComponent } from 'react-lazy-load-image-component'; @@ -62,7 +62,7 @@ type Props = { const RWebShare = dynamic( () => import('react-web-share').then((m) => m.RWebShare), - { ssr: false } + { ssr: false }, ); // 1. STYLE DESKTOP (Tebal, Jelas, dengan Border/Padding) @@ -88,8 +88,8 @@ const cssScrollbarDesktop = { // 2. STYLE MOBILE (Tipis, Minimalis, Tanpa Border) const cssScrollbarMobile = { '&::-webkit-scrollbar': { - width: '3px', // Sangat tipis vertikal - height: '3px', // Sangat tipis horizontal + width: '3px', // Sangat tipis vertikal + height: '3px', // Sangat tipis horizontal }, '&::-webkit-scrollbar-track': { background: 'transparent', @@ -107,14 +107,16 @@ const ProductDetail = ({ product }: Props) => { const router = useRouter(); const [auth, setAuth] = useState(null); - console.log('Render ProductDetail for product ID:', product); - + // console.log('Render ProductDetail for product ID:', product); + // State Data dari Magento - const [specsMatrix, setSpecsMatrix] = useState([]); + const [specsMatrix, setSpecsMatrix] = useState([]); const [upsellIds, setUpsellIds] = useState([]); - const [relatedIds, setRelatedIds] = useState([]); - const [descriptionMap, setDescriptionMap] = useState>({}); - + const [relatedIds, setRelatedIds] = useState([]); + const [descriptionMap, setDescriptionMap] = useState>( + {}, + ); + const [loadingSpecs, setLoadingSpecs] = useState(false); // 2. STATE MODAL COMPARE (Baru) @@ -136,7 +138,7 @@ const ProductDetail = ({ product }: Props) => { activeVariantId, setIsApproval, isApproval, - selectedVariant, + selectedVariant, setSelectedVariant, } = useProductDetail(); @@ -178,75 +180,75 @@ const ProductDetail = ({ product }: Props) => { const variantInit = product?.variants?.find((variant) => variant.is_in_bu) || product?.variants?.[0]; - + setSelectedVariant(variantInit); setSpecsMatrix([]); setUpsellIds([]); setRelatedIds([]); - - }, [product, auth]); + }, [product, auth]); // 2. LOGIC FETCH DATA useEffect(() => { const fetchMagentoData = async () => { - const allVariantIds = product.variants.map(v => v.id); - + 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) + 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' } + method: 'GET', + headers: { 'Content-Type': 'application/json' }, }); if (!response.ok) { - setSpecsMatrix([]); - setUpsellIds([]); - setRelatedIds([]); - return; + 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); + // 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([]); + setSpecsMatrix([]); } - if (data.descriptions){ + if (data.descriptions) { setDescriptionMap(data.descriptions); } // 2. Upsell & Related - if (data.upsell_ids && Array.isArray(data.upsell_ids)) setUpsellIds(data.upsell_ids); + 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); + if (data.related_ids && Array.isArray(data.related_ids)) + setRelatedIds(data.related_ids); else setRelatedIds([]); - } catch (error) { - console.error("Gagal mengambil data Magento:", error); + console.error('Gagal mengambil data Magento:', error); setSpecsMatrix([]); } finally { setLoadingSpecs(false); @@ -254,60 +256,56 @@ const ProductDetail = ({ product }: Props) => { }; fetchMagentoData(); - - }, [product.id]); + }, [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' }); - } - }); + 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]); + } - return result; - }; + 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.') - ); + const isUrl = + !strVal.includes(' ') && + (strVal.startsWith('http') || strVal.startsWith('www.')); if (isUrl) { const href = strVal.startsWith('http') ? strVal : `https://${strVal}`; return ( - Link @@ -315,39 +313,38 @@ const ProductDetail = ({ product }: Props) => { } if (strVal.includes('<') && strVal.includes('>')) { - return ( - - ); + return ( + + ); } return strVal; }; - const allImages = (() => { const arr: string[] = []; if (product?.image) arr.push(product.image); @@ -389,8 +386,8 @@ 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] || ''); @@ -398,15 +395,17 @@ const ProductDetail = ({ product }: Props) => { 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 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+)?)/); @@ -420,23 +419,31 @@ const ProductDetail = ({ product }: Props) => { return numA - numB; } - return String(labelA).localeCompare(String(labelB), undefined, { - numeric: true, - sensitivity: 'base' + 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; + 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 ( <> {/* 3. MODAL POPUP DIRENDER DISINI */} {/* Render di luar layout utama agar tidak tertutup elemen lain */} - setCompareOpen(false)} mainProduct={product} selectedVariant={selectedVariant} @@ -478,23 +485,52 @@ const ProductDetail = ({ product }: Props) => { {/* ... Image Slider ... */} {isMobile ? (
-
+
{allImages.length > 0 ? ( allImages.map((img, i) => ( -
- {`Gambar { (e.target as HTMLImageElement).src = '/images/noimage.jpeg'; }} /> +
+ {`Gambar { + (e.target as HTMLImageElement).src = + '/images/noimage.jpeg'; + }} + />
)) ) : (
- Gambar produk + Gambar produk
)}
{allImages.length > 1 && (
{allImages.map((_, i) => ( -
)} @@ -506,8 +542,21 @@ const ProductDetail = ({ product }: Props) => {
{allImages.map((img, index) => ( -
setMainImage(img)}> - {`Thumbnail { (e.target as HTMLImageElement).src = '/images/noimage.jpeg'; }} /> +
setMainImage(img)} + > + {`Thumbnail { + (e.target as HTMLImageElement).src = + '/images/noimage.jpeg'; + }} + />
))}
@@ -578,17 +627,37 @@ const ProductDetail = ({ product }: Props) => { {/* === SECTION TABS: DESKRIPSI & SPESIFIKASI === */}
- - - + + Deskripsi - Spesifikasi @@ -603,53 +672,54 @@ const ProductDetail = ({ product }: Props) => { {/* DESKRIPSI */} -
+
{loadingSpecs ? ( - - - - + + + + ) : ( - )} -
{/* SPESIFIKASI */} - {loadingSpecs ? ( -
+
+ +
) : specsMatrix.length > 0 ? ( (() => { const variantCount = sortedVariants.length; @@ -657,47 +727,57 @@ const ProductDetail = ({ product }: Props) => { // === 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])} -
- ); + 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) === @@ -705,51 +785,63 @@ const ProductDetail = ({ product }: Props) => { const subHeaders: any[] = []; const flatSpecs: any[] = []; - specsMatrix.forEach(row => { + 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); - }); + 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); + 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 ( - @@ -821,7 +935,9 @@ const ProductDetail = ({ product }: Props) => { ); })() ) : ( - Spesifikasi teknis belum tersedia. + + Spesifikasi teknis belum tersedia. + )} @@ -834,17 +950,48 @@ const ProductDetail = ({ product }: Props) => { {isDesktop && (
{/* 4. INTEGRASI: PASSING HANDLER MODAL KE PRICE ACTION */} - setCompareOpen(true)} + setCompareOpen(true)} /> - +
- + | -
+
+ +
| - {canShare && ()} + {canShare && ( + + + + )}
@@ -867,4 +1014,4 @@ const ProductDetail = ({ product }: Props) => { ); }; -export default ProductDetail; \ No newline at end of file +export default ProductDetail; -- cgit v1.2.3
{th.label} @@ -758,23 +850,32 @@ const ProductDetail = ({ product }: Props) => {
{sub.label} @@ -786,29 +887,42 @@ const ProductDetail = ({ product }: Props) => {
{renderSpecValue(rawValue)}