diff options
| author | FIN-IT_AndriFP <andrifebriyadiputra@gmail.com> | 2025-11-28 09:36:15 +0700 |
|---|---|---|
| committer | FIN-IT_AndriFP <andrifebriyadiputra@gmail.com> | 2025-11-28 09:36:15 +0700 |
| commit | a5e695f82e03577cc85c4a1dded9f6021f0235fc (patch) | |
| tree | 854eb7e74f4beb94bf494979ad0cb31870ce06db | |
| parent | 629133beeb7f5668b63db61e415cf844d513cde7 (diff) | |
(andri) try get detail product from magento
| -rw-r--r-- | src-migrate/modules/product-detail/components/ProductDetail.tsx | 114 | ||||
| -rw-r--r-- | src/pages/api/magento-product.ts | 114 |
2 files changed, 216 insertions, 12 deletions
diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index 1bacd2e2..b0950194 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -4,7 +4,7 @@ import Link from 'next/link'; import { useRouter } from 'next/router'; import { useEffect, useRef, useState, UIEvent } from 'react'; -// Import komponen Chakra UI yang dibutuhkan +// Import komponen Chakra UI import { Button, Tabs, @@ -16,7 +16,10 @@ import { Tbody, Tr, Td, - Box + Box, + Spinner, + Center, + Text } from '@chakra-ui/react'; import { @@ -58,6 +61,12 @@ const ProductDetail = ({ product }: Props) => { const { isDesktop, isMobile } = useDevice(); const router = useRouter(); const [auth, setAuth] = useState<any>(null); + + // State untuk Spesifikasi dari Magento + const [specs, setSpecs] = useState<{ label: string; value: string }[]>([]); + const [loadingSpecs, setLoadingSpecs] = useState(false); + const [errorSpecs, setErrorSpecs] = useState(false); + useEffect(() => { try { setAuth(getAuth() ?? null); @@ -74,6 +83,7 @@ const ProductDetail = ({ product }: Props) => { activeVariantId, setIsApproval, isApproval, + selectedVariant, setSelectedVariant, } = useProductDetail(); @@ -99,12 +109,65 @@ const ProductDetail = ({ product }: Props) => { if (typeof auth === 'object') { setIsApproval(auth?.feature?.soApproval); } - const selectedVariant = + const variantInit = product?.variants?.find((variant) => variant.is_in_bu) || product?.variants?.[0]; - setSelectedVariant(selectedVariant); + setSelectedVariant(variantInit); }, []); + // ========================================================================= + // LOGIC FETCH: MENGGUNAKAN ID VARIANT + // ========================================================================= + useEffect(() => { + const fetchMagentoSpecs = async () => { + // MENGGUNAKAN ID VARIANT SESUAI REQUEST + const skuToFetch = selectedVariant?.id; + + if (!skuToFetch) return; + + setLoadingSpecs(true); + setErrorSpecs(false); + + try { + console.log("Fetching Magento specs via Proxy for Variant ID:", skuToFetch); + + // Pastikan dikonversi ke string + const endpoint = `/api/magento-product?sku=${encodeURIComponent(String(skuToFetch))}`; + + const response = await fetch(endpoint, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + } + }); + + if (!response.ok) { + console.warn(`Spec API status: ${response.status}`); + setSpecs([]); + return; + } + + const data = await response.json(); + + if (data.specs && Array.isArray(data.specs)) { + setSpecs(data.specs); + } else { + setSpecs([]); + } + + } catch (error) { + console.error("Gagal mengambil data spesifikasi:", error); + setErrorSpecs(true); + setSpecs([]); + } finally { + setLoadingSpecs(false); + } + }; + + fetchMagentoSpecs(); + }, [selectedVariant]); + + const allImages = (() => { const arr: string[] = []; if (product?.image) arr.push(product.image); @@ -154,9 +217,6 @@ const ProductDetail = ({ product }: Props) => { setMainImage(allImages[i] || ''); }; - console.log('detail product render'); - console.log('product: ', product); - return ( <> <div className='relative'> @@ -192,7 +252,7 @@ const ProductDetail = ({ product }: Props) => { <div className='md:flex md:flex-wrap'> {/* ===== Kolom kiri: gambar ===== */} <div className='md:w-4/12'> - {/* === MOBILE: Slider swipeable, tanpa thumbnail carousel === */} + {/* === MOBILE: Slider swipeable === */} {isMobile ? ( <div className='relative'> <div @@ -233,6 +293,7 @@ const ProductDetail = ({ product }: Props) => { )} </div> + {/* Dots indicator */} {allImages.length > 1 && ( <div className='absolute bottom-2 left-0 right-0 flex justify-center gap-2'> {allImages.map((_, i) => ( @@ -250,7 +311,7 @@ const ProductDetail = ({ product }: Props) => { </div> ) : ( <> - {/* === DESKTOP: Tetap seperti sebelumnya === */} + {/* === DESKTOP === */} <ProductImage product={{ ...product, image: mainImage }} /> {allImages.length > 0 && ( <div className='mt-4 overflow-x-auto'> @@ -414,9 +475,38 @@ const ProductDetail = ({ product }: Props) => { </div> </TabPanel> - {/* PANEL 2: SPESIFIKASI (Sesuai Gambar) */} - <TabPanel px={0} py={6}> - <p className="text-gray-500 text-sm">Informasi tambahan belum tersedia.</p> + {/* PANEL 2: SPESIFIKASI DARI MAGENTO */} + <TabPanel px={0} py={2}> + <Box border="1px solid" borderColor="gray.200" borderRadius="sm"> + {loadingSpecs ? ( + <Center py={6}> + <Spinner color='red.500' /> + </Center> + ) : errorSpecs ? ( + <Box p={4} color="red.500" fontSize="sm"> + Gagal memuat data spesifikasi. Silakan coba lagi nanti. + </Box> + ) : specs.length > 0 ? ( + <Table variant="simple" size="md"> + <Tbody> + {specs.map((item, index) => ( + <Tr key={index}> + <Td fontWeight="bold" width="30%" verticalAlign="top" borderColor="gray.200"> + {item.label} + </Td> + <Td verticalAlign="top" borderColor="gray.200"> + {item.value} + </Td> + </Tr> + ))} + </Tbody> + </Table> + ) : ( + <Box p={4} color="gray.500" fontSize="sm"> + <Text>Spesifikasi teknis belum tersedia untuk varian ID: <b>{selectedVariant?.id}</b></Text> + </Box> + )} + </Box> </TabPanel> {/* PANEL 3: DETAIL LAINNYA */} diff --git a/src/pages/api/magento-product.ts b/src/pages/api/magento-product.ts new file mode 100644 index 00000000..c906079e --- /dev/null +++ b/src/pages/api/magento-product.ts @@ -0,0 +1,114 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const { sku } = req.query; + + if (!sku) { + return res.status(400).json({ error: 'SKU is required' }); + } + + // Token Magento + const token = 'vxrtcjvztv1icgjzsui45de9kmwlz0lf'; + const baseUrl = 'https://pimdev.1211.my.id/rest/V1/products'; + + try { + // 1. Pastikan SKU menjadi string dan hapus spasi kiri/kanan + const cleanSku = String(sku).trim(); + + // 2. Encode SKU + const encodedSku = encodeURIComponent(cleanSku); + + // 3. Bentuk URL Final + const finalUrl = `${baseUrl}/${encodedSku}`; + + // --- DEBUGGING LOG --- + console.log('Fetching URL:', finalUrl); + + // Request ke Product Endpoint + const response = await fetch(finalUrl, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + }); + + if (!response.ok) { + console.error(`Magento Error: ${response.status} ${response.statusText}`); + return res.status(response.status).json({ + error: 'Failed to fetch from Magento', + magentoStatus: response.status, + checkedUrl: finalUrl + }); + } + + const data = await response.json(); + + // ===================================================================== + // TAMBAHAN: FETCH LABEL ATRIBUT (z_*) + // ===================================================================== + + let specsWithLabels: any[] = []; + + // Cek apakah ada custom_attributes + if (data.custom_attributes) { + // Filter atribut yang kodenya dimulai dengan 'z' + const zAttributes = data.custom_attributes.filter((attr: any) => + attr.attribute_code.startsWith('z') + ); + + // Fetch detail label untuk setiap atribut secara paralel + specsWithLabels = await Promise.all( + zAttributes.map(async (attr: any) => { + try { + // Endpoint untuk ambil detail atribut (Label): /V1/products/attributes/{attributeCode} + const attrUrl = `${baseUrl}/attributes/${attr.attribute_code}`; + + const attrRes = await fetch(attrUrl, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + } + }); + + if (attrRes.ok) { + const attrData = await attrRes.json(); + // AMBIL NILAI 'default_frontend_label' + return { + label: attrData.default_frontend_label || attr.attribute_code, + value: attr.value + }; + } + } catch (err) { + console.error(`Failed to fetch label for ${attr.attribute_code}`); + } + + // Fallback: Jika gagal ambil label, format manual dari kode + const fallbackLabel = attr.attribute_code + .substring(1).replace(/_/g, ' ').trim(); // z_size_ml -> size ml + + return { + label: fallbackLabel, + value: attr.value + }; + }) + ); + } + + // Gabungkan data asli dengan data specs yang sudah ada labelnya + const responseData = { + ...data, + specs: specsWithLabels // Frontend tinggal pakai field ini + }; + + res.status(200).json(responseData); + + } catch (error) { + console.error('Proxy Server Error:', error); + res.status(500).json({ error: 'Internal Server Error' }); + } +}
\ No newline at end of file |
