From 43b2ce4d59c153655eb9b7a2190b83050fd48855 Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Wed, 3 Dec 2025 17:06:01 +0700 Subject: (andri) table spek untuk product dengan variant lebih dari 1 --- .../product-detail/components/ProductDetail.tsx | 385 +++++++++++---------- 1 file changed, 193 insertions(+), 192 deletions(-) (limited to 'src-migrate') diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index cfe73628..3beb75b4 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -16,6 +16,8 @@ import { Tbody, Tr, Td, + Th, + Thead, Box, Spinner, Center, @@ -27,6 +29,7 @@ import { AlertTriangle, MessageCircleIcon, Share2Icon, + ExternalLink } from 'lucide-react'; import { LazyLoadComponent } from 'react-lazy-load-image-component'; @@ -65,12 +68,11 @@ const ProductDetail = ({ product }: Props) => { const [auth, setAuth] = useState(null); // State Data dari Magento - const [specs, setSpecs] = useState<{ code: string; label: string; value: string }[]>([]); + const [specsMatrix, setSpecsMatrix] = useState([]); const [upsellIds, setUpsellIds] = useState([]); const [relatedIds, setRelatedIds] = useState([]); const [loadingSpecs, setLoadingSpecs] = useState(false); - const [errorSpecs, setErrorSpecs] = useState(false); useEffect(() => { try { @@ -111,7 +113,7 @@ const ProductDetail = ({ product }: Props) => { }, [router.asPath, product.manufacture.name, product.name, setAskAdminUrl]); // ========================================================================= - // LOGIC INISIALISASI VARIANT (RESET SAAT NAVIGASI) + // 1. LOGIC INISIALISASI VARIANT // ========================================================================= useEffect(() => { if (typeof auth === 'object') { @@ -123,30 +125,32 @@ const ProductDetail = ({ product }: Props) => { setSelectedVariant(variantInit); - // Reset data Magento - setSpecs([]); + setSpecsMatrix([]); setUpsellIds([]); setRelatedIds([]); }, [product, auth]); // ========================================================================= - // LOGIC FETCH: SPECS, UPSELLS, RELATED + // 2. LOGIC FETCH DATA // ========================================================================= useEffect(() => { const fetchMagentoData = async () => { - // Validasi kepemilikan varian - if (!selectedVariant?.id) return; - const isVariantOwner = product.variants.some(v => Number(v.id) === Number(selectedVariant.id)); - if (!isVariantOwner) return; + const allVariantIds = product.variants.map(v => v.id); + + if (allVariantIds.length === 0) return; - const idToFetch = selectedVariant.id; + const mainId = allVariantIds[0]; setLoadingSpecs(true); - setErrorSpecs(false); try { - const endpoint = `/api/magento-product?sku=${encodeURIComponent(String(idToFetch))}`; + 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', @@ -154,7 +158,7 @@ const ProductDetail = ({ product }: Props) => { }); if (!response.ok) { - setSpecs([]); + setSpecsMatrix([]); setUpsellIds([]); setRelatedIds([]); return; @@ -162,67 +166,108 @@ const ProductDetail = ({ product }: Props) => { const data = await response.json(); - // Double Check - if (Number(idToFetch) !== Number(selectedVariant.id)) return; - - // 1. Specs - if (data.specs && Array.isArray(data.specs)) { - setSpecs(data.specs); + // 1. Specs Matrix (Processed Grouping) + if (data.specsMatrix && Array.isArray(data.specsMatrix)) { + const processed = processMatrixData(data.specsMatrix); + setSpecsMatrix(processed); } else { - setSpecs([]); + setSpecsMatrix([]); } - // 2. Upsell - if (data.upsell_ids && Array.isArray(data.upsell_ids)) { - setUpsellIds(data.upsell_ids); - } else { - setUpsellIds([]); - } + // 2. Upsell & Related + if (data.upsell_ids && Array.isArray(data.upsell_ids)) setUpsellIds(data.upsell_ids); + else setUpsellIds([]); - // 3. Related - if (data.related_ids && Array.isArray(data.related_ids)) { - setRelatedIds(data.related_ids); - } else { - setRelatedIds([]); - } + if (data.related_ids && Array.isArray(data.related_ids)) setRelatedIds(data.related_ids); + else setRelatedIds([]); } catch (error) { console.error("Gagal mengambil data Magento:", error); - setErrorSpecs(true); - setSpecs([]); - setUpsellIds([]); - setRelatedIds([]); + setSpecsMatrix([]); } finally { setLoadingSpecs(false); } }; fetchMagentoData(); - }, [selectedVariant, product]); + }, [product.id]); // ========================================================================= - // HELPER: RENDER SPEC VALUE (SIMPLE - NO LINK DETECT) + // HELPER 1: GROUPING DATA BY LABEL (Separator ':') // ========================================================================= - const renderSpecValue = (item: { code: string; label: string; value: string }) => { - const val = item.value; + 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 cleanVal = val.trim(); + 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 + + ); + } - // 1. JIKA HTML (Legacy Data) - // Deteksi tag HTML sederhana - if (cleanVal.includes('<') && cleanVal.includes('>')) { + // HTML + if (strVal.includes('<') && strVal.includes('>')) { return (
); } - // 2. TEKS BIASA - return cleanVal; + // Teks Biasa + return strVal; }; @@ -310,90 +355,39 @@ const ProductDetail = ({ product }: Props) => {
{/* ===== Kolom kiri: gambar ===== */}
- {/* === MOBILE: Slider swipeable === */} + {/* ... 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
)}
- - {/* Dots indicator */} {allImages.length > 1 && (
{allImages.map((_, i) => ( -
)}
) : ( <> - {/* === DESKTOP === */} {allImages.length > 0 && (
{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'; }} />
))}
@@ -408,13 +402,8 @@ const ProductDetail = ({ product }: Props) => {
{!hasPrice && (
- -

- Maaf untuk saat ini Produk yang anda cari tidak tersedia -

+ +

Maaf untuk saat ini Produk yang anda cari tidak tersedia

)}
@@ -428,13 +417,8 @@ const ProductDetail = ({ product }: Props) => {
{!hasPrice && (
- -

- Maaf untuk saat ini Produk yang anda cari tidak tersedia -

+ +

Maaf untuk saat ini Produk yang anda cari tidak tersedia

)}

{product.name}

@@ -454,110 +438,128 @@ const ProductDetail = ({ product }: Props) => {
{!!activeVariantId && !isApproval && ( - + )}
- {/* === SECTION TABS === */} + {/* === SECTION TABS: DESKRIPSI & SPESIFIKASI === */}
- - Deskripsi - - - Spesifikasi - - - Detail Lainnya - + Deskripsi + Spesifikasi + Detail Lainnya {/* DESKRIPSI */}
-

' - ? '

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

' - : product.description, - }} - /> +

' ? '

Lorem ipsum dolor sit amet.

' : product.description, }} />
- {/* SPESIFIKASI */} + {/* SPESIFIKASI (LOGIKA GROUPING + RATA TENGAH) */} - + {loadingSpecs ? (
- ) : specs.length > 0 ? ( - - - {specs.map((item, index) => ( - - - - - ))} - -
- {item.label} - - {renderSpecValue(item)} -
+ ) : specsMatrix.length > 0 ? ( + (() => { + 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. - + Spesifikasi teknis belum tersedia. )}
{/* DETAIL LAINNYA */} - -

Informasi tambahan belum tersedia.

-
- +

Informasi tambahan belum tersedia.

+ {/* ... (Bagian Sidebar & Bottom SAMA) ... */} {isDesktop && (
-
- - | -
- -
- | - {canShare && ( - - - - )} +
+ {/* ... Buttons ... */}
-
Produk Serupa
@@ -572,7 +574,6 @@ const ProductDetail = ({ product }: Props) => {
-
-- cgit v1.2.3