diff options
| author | FIN-IT_AndriFP <andrifebriyadiputra@gmail.com> | 2025-12-01 11:31:30 +0700 |
|---|---|---|
| committer | FIN-IT_AndriFP <andrifebriyadiputra@gmail.com> | 2025-12-01 11:31:30 +0700 |
| commit | 825d86bb50f48f9a21d740d474c0dddee858dffb (patch) | |
| tree | 023c9412c4c451aee45e54fe1d987e6220a8a6c5 /src-migrate/modules | |
| parent | a5e695f82e03577cc85c4a1dded9f6021f0235fc (diff) | |
(andri) show upsells product from magento in similarbottom product
Diffstat (limited to 'src-migrate/modules')
| -rw-r--r-- | src-migrate/modules/product-detail/components/ProductDetail.tsx | 183 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/components/SimilarBottom.tsx | 55 |
2 files changed, 122 insertions, 116 deletions
diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index b0950194..5f930117 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -22,11 +22,13 @@ import { Text } from '@chakra-ui/react'; +// Import Icons import { AlertTriangle, MessageCircleIcon, Share2Icon, } from 'lucide-react'; + import { LazyLoadComponent } from 'react-lazy-load-image-component'; import useDevice from '@/core/hooks/useDevice'; @@ -62,8 +64,9 @@ const ProductDetail = ({ product }: Props) => { const router = useRouter(); const [auth, setAuth] = useState<any>(null); - // State untuk Spesifikasi dari Magento - const [specs, setSpecs] = useState<{ label: string; value: string }[]>([]); + // State Data dari Magento + const [specs, setSpecs] = useState<{ code: string; label: string; value: string }[]>([]); + const [upsellIds, setUpsellIds] = useState<number[]>([]); const [loadingSpecs, setLoadingSpecs] = useState(false); const [errorSpecs, setErrorSpecs] = useState(false); @@ -105,6 +108,9 @@ const ProductDetail = ({ product }: Props) => { setAskAdminUrl(createdAskUrl); }, [router.asPath, product.manufacture.name, product.name, setAskAdminUrl]); + // ========================================================================= + // LOGIC INISIALISASI VARIANT (HANDLE NAVIGASI) + // ========================================================================= useEffect(() => { if (typeof auth === 'object') { setIsApproval(auth?.feature?.soApproval); @@ -112,60 +118,95 @@ const ProductDetail = ({ product }: Props) => { const variantInit = product?.variants?.find((variant) => variant.is_in_bu) || product?.variants?.[0]; + setSelectedVariant(variantInit); - }, []); + + // Reset data Magento saat produk berubah + setSpecs([]); + setUpsellIds([]); + + }, [product, auth]); // ========================================================================= - // LOGIC FETCH: MENGGUNAKAN ID VARIANT + // LOGIC FETCH: SPECS & UPSELLS // ========================================================================= useEffect(() => { - const fetchMagentoSpecs = async () => { - // MENGGUNAKAN ID VARIANT SESUAI REQUEST - const skuToFetch = selectedVariant?.id; + const fetchMagentoData = async () => { + // Gunakan ID Variant (SKU Odoo) + const idToFetch = selectedVariant?.id; - if (!skuToFetch) return; + if (!idToFetch) return; setLoadingSpecs(true); setErrorSpecs(false); try { - console.log("Fetching Magento specs via Proxy for Variant ID:", skuToFetch); + console.log("Fetching Magento data via Proxy for ID:", idToFetch); - // Pastikan dikonversi ke string - const endpoint = `/api/magento-product?sku=${encodeURIComponent(String(skuToFetch))}`; + const endpoint = `/api/magento-product?sku=${encodeURIComponent(String(idToFetch))}`; const response = await fetch(endpoint, { method: 'GET', - headers: { - 'Content-Type': 'application/json', - } + headers: { 'Content-Type': 'application/json' } }); if (!response.ok) { - console.warn(`Spec API status: ${response.status}`); + console.warn(`Magento API status: ${response.status}`); setSpecs([]); + setUpsellIds([]); return; } const data = await response.json(); + // 1. Simpan Data Spesifikasi if (data.specs && Array.isArray(data.specs)) { setSpecs(data.specs); } else { setSpecs([]); } + // 2. Simpan Data Up-Sells (ID) + if (data.upsell_ids && Array.isArray(data.upsell_ids)) { + setUpsellIds(data.upsell_ids); + } else { + setUpsellIds([]); + } + } catch (error) { - console.error("Gagal mengambil data spesifikasi:", error); + console.error("Gagal mengambil data Magento:", error); setErrorSpecs(true); setSpecs([]); + setUpsellIds([]); } finally { setLoadingSpecs(false); } }; - fetchMagentoSpecs(); - }, [selectedVariant]); + fetchMagentoData(); + }, [selectedVariant, product.id]); + + + // ========================================================================= + // HELPER: RENDER SPEC VALUE (SIMPLE TEXT/HTML ONLY) + // ========================================================================= + const renderSpecValue = (item: { code: string; label: string; value: string }) => { + const val = item.value; + if (!val) return '-'; + + // Cek apakah mengandung tag HTML sederhana (<p>, <a>, <ul>, dll) + if (val.includes('<') && val.includes('>')) { + return ( + <div + className="prose prose-sm text-gray-700" + dangerouslySetInnerHTML={{ __html: val }} + /> + ); + } + + // Default: Teks Biasa + return val; + }; const allImages = (() => { @@ -404,63 +445,32 @@ const ProductDetail = ({ product }: Props) => { <div className='h-0 md:h-6' /> - {/* === SECTION TABS: DESKRIPSI & SPESIFIKASI === */} + {/* === SECTION TABS === */} <div className={style['section-card']}> <Tabs variant="unstyled"> - {/* Header Tabs */} <TabList borderBottom="1px solid" borderColor="gray.200"> <Tab - _selected={{ - color: 'red.600', - borderColor: 'red.600', - borderBottomWidth: '3px', - fontWeight: 'bold', - marginBottom: '-1.5px' - }} - color="gray.500" - fontWeight="medium" - fontSize="sm" - px={4} - py={3} + _selected={{ color: 'red.600', borderColor: 'red.600', borderBottomWidth: '3px', fontWeight: 'bold', marginBottom: '-1.5px' }} + color="gray.500" fontWeight="medium" fontSize="sm" px={4} py={3} > Deskripsi </Tab> <Tab - _selected={{ - color: 'red.600', - borderColor: 'red.600', - borderBottomWidth: '3px', - fontWeight: 'bold', - marginBottom: '-1.5px' - }} - color="gray.500" - fontWeight="medium" - fontSize="sm" - px={4} - py={3} + _selected={{ color: 'red.600', borderColor: 'red.600', borderBottomWidth: '3px', fontWeight: 'bold', marginBottom: '-1.5px' }} + color="gray.500" fontWeight="medium" fontSize="sm" px={4} py={3} > Spesifikasi </Tab> <Tab - _selected={{ - color: 'red.600', - borderColor: 'red.600', - borderBottomWidth: '3px', - fontWeight: 'bold', - marginBottom: '-1.5px' - }} - color="gray.500" - fontWeight="medium" - fontSize="sm" - px={4} - py={3} + _selected={{ color: 'red.600', borderColor: 'red.600', borderBottomWidth: '3px', fontWeight: 'bold', marginBottom: '-1.5px' }} + color="gray.500" fontWeight="medium" fontSize="sm" px={4} py={3} > Detail Lainnya </Tab> </TabList> <TabPanels> - {/* PANEL 1: DESKRIPSI */} + {/* DESKRIPSI */} <TabPanel px={0} py={6}> <div className='overflow-x-auto text-sm text-gray-700'> <div @@ -468,24 +478,18 @@ const ProductDetail = ({ product }: Props) => { dangerouslySetInnerHTML={{ __html: !product.description || product.description === '<p><br></p>' - ? '<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>' + ? '<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>' : product.description, }} /> </div> </TabPanel> - {/* PANEL 2: SPESIFIKASI DARI MAGENTO */} + {/* SPESIFIKASI */} <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> + <Center py={6}><Spinner color='red.500' /></Center> ) : specs.length > 0 ? ( <Table variant="simple" size="md"> <Tbody> @@ -495,7 +499,7 @@ const ProductDetail = ({ product }: Props) => { {item.label} </Td> <Td verticalAlign="top" borderColor="gray.200"> - {item.value} + {renderSpecValue(item)} </Td> </Tr> ))} @@ -503,13 +507,13 @@ const ProductDetail = ({ product }: Props) => { </Table> ) : ( <Box p={4} color="gray.500" fontSize="sm"> - <Text>Spesifikasi teknis belum tersedia untuk varian ID: <b>{selectedVariant?.id}</b></Text> + <Text>Spesifikasi teknis belum tersedia.</Text> </Box> )} </Box> </TabPanel> - {/* PANEL 3: DETAIL LAINNYA */} + {/* DETAIL LAINNYA */} <TabPanel px={0} py={6}> <p className="text-gray-500 text-sm">Informasi tambahan belum tersedia.</p> </TabPanel> @@ -524,64 +528,31 @@ const ProductDetail = ({ product }: Props) => { <div className='md:w-3/12'> <PriceAction product={product} /> <div className='flex gap-x-5 items-center justify-center'> - <Button - as={Link} - href={askAdminUrl} - variant='link' - target='_blank' - colorScheme='gray' - leftIcon={<MessageCircleIcon size={18} />} - isDisabled={!hasPrice} - > - Ask Admin - </Button> - + <Button as={Link} href={askAdminUrl} variant='link' target='_blank' colorScheme='gray' leftIcon={<MessageCircleIcon size={18} />} isDisabled={!hasPrice}>Ask Admin</Button> <span>|</span> - <div className={hasPrice ? '' : 'opacity-40 pointer-events-none'}> <AddToWishlist productId={product.id} /> </div> - <span>|</span> - {canShare && ( - <RWebShare - data={{ - text: 'Check out this product', - title: `${product.name} - Indoteknik.com`, - url: - (process.env.NEXT_PUBLIC_SELF_HOST || '') + - (router?.asPath || '/'), - }} - > - <Button - variant='link' - colorScheme='gray' - leftIcon={<Share2Icon size={18} />} - isDisabled={!hasPrice} - > - Share - </Button> + <RWebShare data={{ text: 'Check out this product', title: `${product.name} - Indoteknik.com`, url: (process.env.NEXT_PUBLIC_SELF_HOST || '') + (router?.asPath || '/'), }}> + <Button variant='link' colorScheme='gray' leftIcon={<Share2Icon size={18} />} isDisabled={!hasPrice}>Share</Button> </RWebShare> )} </div> <div className='h-6' /> <div className={style['heading']}>Produk Serupa</div> - <div className='h-4' /> - <SimilarSide product={product} /> </div> )} <div className='md:w-full pt-4 md:py-10 px-4 md:px-0'> <div className={style['heading']}>Kamu Mungkin Juga Suka</div> - <div className='h-6' /> - <LazyLoadComponent> - <SimilarBottom product={product} /> + <SimilarBottom product={product} upsellIds={upsellIds} /> </LazyLoadComponent> </div> diff --git a/src-migrate/modules/product-detail/components/SimilarBottom.tsx b/src-migrate/modules/product-detail/components/SimilarBottom.tsx index 40d4dd82..d3957f4b 100644 --- a/src-migrate/modules/product-detail/components/SimilarBottom.tsx +++ b/src-migrate/modules/product-detail/components/SimilarBottom.tsx @@ -1,23 +1,58 @@ import { Skeleton } from '@chakra-ui/react' -import useProductSimilar from '~/modules/product-similar/hooks/useProductSimilar' +import { useQuery } from 'react-query' import ProductSlider from '~/modules/product-slider' +import { getProductSimilar, getProductsByIds } from '~/services/product' import { IProductDetail } from '~/types/product' type Props = { - product: IProductDetail + product: IProductDetail; + upsellIds?: number[]; } -const SimilarBottom = ({ product }: Props) => { - const productSimilar = useProductSimilar({ - name: product.name, - except: { productId: product.id } - }) +const SimilarBottom = ({ product, upsellIds = [] }: Props) => { + + const hasUpsell = upsellIds.length > 0; - const products = productSimilar.data?.products || [] + // Query 1: Upsell + const upsellQuery = useQuery({ + queryKey: ['product-upsell', upsellIds], + queryFn: () => getProductsByIds({ ids: upsellIds }), + enabled: hasUpsell, + staleTime: 1000 * 60 * 5, + }); + + // Query 2: Similar Biasa + const similarQuery = useQuery({ + queryKey: ['product-similar', product.name], + queryFn: () => getProductSimilar({ + name: product.name, + except: { productId: product.id } + }), + enabled: !hasUpsell, + staleTime: 1000 * 60 * 5, + }); + + let products = []; + let isLoading = false; + + // ========================================== + // PERBAIKAN DI SINI + // ========================================== + if (hasUpsell) { + // Salah: products = upsellQuery.data || []; + // Benar: Ambil properti .products di dalamnya + products = (upsellQuery.data as any)?.products || []; + isLoading = upsellQuery.isLoading; + } else { + products = similarQuery.data?.products || []; + isLoading = similarQuery.isLoading; + } + + if (!isLoading && products.length === 0) return null; return ( <Skeleton - isLoaded={!productSimilar.isLoading} + isLoaded={!isLoading} rounded='lg' className='h-[350px]' > @@ -26,4 +61,4 @@ const SimilarBottom = ({ product }: Props) => { ); } -export default SimilarBottom
\ No newline at end of file +export default SimilarBottom;
\ No newline at end of file |
