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/Information.tsx | 393 +++++++---- .../product-detail/components/ProductDetail.tsx | 741 ++++++++++++--------- 2 files changed, 692 insertions(+), 442 deletions(-) (limited to 'src-migrate') diff --git a/src-migrate/modules/product-detail/components/Information.tsx b/src-migrate/modules/product-detail/components/Information.tsx index ce848267..757cd473 100644 --- a/src-migrate/modules/product-detail/components/Information.tsx +++ b/src-migrate/modules/product-detail/components/Information.tsx @@ -12,7 +12,16 @@ import { useEffect, useRef, useState } from 'react'; import axios from 'axios'; import currencyFormat from '@/core/utils/currencyFormat'; -import { InputGroup, InputRightElement, SimpleGrid, Flex, Text, Box, Center, Icon } from '@chakra-ui/react'; +import { + InputGroup, + InputRightElement, + SimpleGrid, + Flex, + Text, + Box, + Center, + Icon, +} from '@chakra-ui/react'; import { ChevronDownIcon } from '@heroicons/react/24/outline'; import ImageNext from 'next/image'; import { formatToShortText } from '~/libs/formatNumber'; @@ -28,7 +37,7 @@ import MobileView from '@/core/components/views/MobileView'; // Pastikan path im import ProductComparisonModal from './ProductComparisonModal'; const Skeleton = dynamic(() => - import('@chakra-ui/react').then((mod) => mod.Skeleton) + import('@chakra-ui/react').then((mod) => mod.Skeleton), ); type Props = { @@ -36,7 +45,8 @@ type Props = { }; const Information = ({ product }: Props) => { - const { selectedVariant, setSelectedVariant, setSla, sla } = useProductDetail(); + const { selectedVariant, setSelectedVariant, setSla, sla } = + useProductDetail(); const [inputValue, setInputValue] = useState(''); const [disableFilter, setDisableFilter] = useState(false); @@ -45,7 +55,7 @@ const Information = ({ product }: Props) => { // source of truth // const variantOptions = product.variants; const [variantOptions, setVariantOptions] = useState( - product?.variants + product?.variants, ); const variantId = selectedVariant?.id; @@ -59,25 +69,25 @@ const Information = ({ product }: Props) => { useEffect(() => { const fetchWarrantyDirectly = async () => { - if (!product?.variants || product.variants.length === 0) return; - - setLoadingWarranty(true); - try { - const skus = product.variants.map((v) => v.id).join(','); - const mainSku = product.variants[0].id; - - const res = await axios.get('/api/magento-product', { - params: { skus, main_sku: mainSku } - }); - - if (res.data && res.data.warranties) { - setWarranties(res.data.warranties); - } - } catch (error) { - console.error("Gagal ambil garansi:", error); - } finally { - setLoadingWarranty(false); - } + if (!product?.variants || product.variants.length === 0) return; + + setLoadingWarranty(true); + try { + const skus = product.variants.map((v) => v.id).join(','); + const mainSku = product.variants[0].id; + + const res = await axios.get('/api/magento-product', { + params: { skus, main_sku: mainSku }, + }); + + if (res.data && res.data.warranties) { + setWarranties(res.data.warranties); + } + } catch (error) { + // console.error("Gagal ambil garansi:", error); + } finally { + setLoadingWarranty(false); + } }; fetchWarrantyDirectly(); @@ -90,7 +100,7 @@ const Information = ({ product }: Props) => { selectedVariant.code + (selectedVariant.attributes?.[0] ? ` - ${selectedVariant.attributes[0]}` - : '') + : ''), ); }, [selectedVariant]); @@ -114,14 +124,14 @@ const Information = ({ product }: Props) => { setDisableFilter(true); let code = vals.replace(/\s-\s.*$/, '').trim(); let variant = product?.variants.find((item) => item.code === code); - + if (variant) { setSelectedVariant(variant); setInputValue( variant?.code + - (variant?.attributes[0] ? ' - ' + variant?.attributes[0] : '') + (variant?.attributes[0] ? ' - ' + variant?.attributes[0] : ''), ); - setVariantOptions(product?.variants); + setVariantOptions(product?.variants); } }; @@ -135,7 +145,7 @@ const Information = ({ product }: Props) => { fontSize: '13px', borderBottom: '1px dashed #e2e8f0', padding: '8px 0', - marginBottom: '0px' + marginBottom: '0px', }; return ( @@ -173,96 +183,126 @@ const Information = ({ product }: Props) => { {variantOptions .sort((a: any, b: any) => { - return a.code.localeCompare(b.code, undefined, { numeric: true, sensitivity: 'base' }); + return a.code.localeCompare(b.code, undefined, { + numeric: true, + sensitivity: 'base', + }); }) .map((option, cid) => ( - -
-
- {option.code} - {option.attributes?.[0] ? ` - ${option.attributes[0]}` : ''} -
-
- {option?.price?.discount_percentage > 0 && ( - <> -
- {Math.floor(option.price.discount_percentage)}% -
-
- {currencyFormat(option.price.price)} -
- - )} -
- {currencyFormat(option.price.price_discount)} + +
+
+ {option.code} + {option.attributes?.[0] + ? ` - ${option.attributes[0]}` + : ''} +
+
+ {option?.price?.discount_percentage > 0 && ( + <> +
+ {Math.floor(option.price.discount_percentage)}% +
+
+ {currencyFormat(option.price.price)} +
+ + )} +
+ {currencyFormat(option.price.price_discount)} +
-
- - ))} + + ))} {/* === TOMBOL BANDINGKAN PRODUK (HANYA MOBILE) === */} -
setIsCompareOpen(true)} - > -
-
- -
-
- Bandingkan Produk - Coba bandingkan dengan produk lainnya -
-
-
- Baru - -
+
setIsCompareOpen(true)} + > +
+
+ +
+
+ + Bandingkan Produk + + + Coba bandingkan dengan produk lainnya + +
+
+
+ + Baru + +
+
{/* Render Modal (Logic open/close ada di dalam component) */} {isCompareOpen && ( - setIsCompareOpen(false)} - mainProduct={product} - selectedVariant={selectedVariant} - /> + setIsCompareOpen(false)} + mainProduct={product} + selectedVariant={selectedVariant} + /> )}
{/* ITEM CODE */}
-
Item Code
+
+ Item Code +
{selectedVariant?.code}
{/* MANUFACTURE */}
-
Manufacture
+
+ Manufacture +
{!!product.manufacture.name ? ( {product?.manufacture.logo ? ( @@ -287,81 +327,144 @@ const Information = ({ product }: Props) => { {/* BERAT BARANG */}
-
Berat Barang
+
+ Berat Barang +
{selectedVariant?.weight > 0 ? `${selectedVariant.weight} Kg` : '-'}
{/* TERJUAL */} -
-
Terjual
+
+
+ Terjual +
{product.qty_sold > 0 ? formatToShortText(product.qty_sold) : '-'}
{/* === DETAIL INFORMASI PRODUK === */} -
-

+
+

Detail Informasi Produk

- + - - Distributor Resmi - - Distributor Resmi - Jaminan Produk Asli - - - - - Estimasi Penyiapan - - Estimasi Penyiapan - {isLoading ? ( -
- ) : ( - - {sla?.sla_date || '-'} - - )} -
-
- - - Garansi Produk - - Garansi Produk - {loadingWarranty ? ( -
- ) : ( - - {selectedVariant && warranties[selectedVariant.id] ? warranties[selectedVariant.id] : '-'} - - )} -
-
+ + Distributor Resmi + + + Distributor Resmi + + + Jaminan Produk Asli + + + + + + Estimasi Penyiapan + + + Estimasi Penyiapan + + {isLoading ? ( +
+ +
+ ) : ( + + {sla?.sla_date || '-'} + + )} +
+
+ + + Garansi Produk + + + Garansi Produk + + {loadingWarranty ? ( +
+ +
+ ) : ( + + {selectedVariant && warranties[selectedVariant.id] + ? warranties[selectedVariant.id] + : '-'} + + )} +
+

); }; -export default Information; \ No newline at end of file +export default Information; 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)}