diff options
| author | FIN-IT_AndriFP <andrifebriyadiputra@gmail.com> | 2026-01-14 09:24:22 +0700 |
|---|---|---|
| committer | FIN-IT_AndriFP <andrifebriyadiputra@gmail.com> | 2026-01-14 09:24:22 +0700 |
| commit | a891c880bb7dc8b2ff141ea015490283ea6278fd (patch) | |
| tree | bf8afb0201941ef761a0bf2ebffec59219d44e84 /src-migrate | |
| parent | 13a6ccd99722464342942eeb7fbc73c2f0f27542 (diff) | |
| parent | 2843980c8bfe25d67bd70e176c546b5e2fe66975 (diff) | |
(andri) fix merge
Diffstat (limited to 'src-migrate')
| -rw-r--r-- | src-migrate/modules/product-detail/components/Information.tsx | 92 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/components/ProductDetail.tsx | 178 |
2 files changed, 166 insertions, 104 deletions
diff --git a/src-migrate/modules/product-detail/components/Information.tsx b/src-migrate/modules/product-detail/components/Information.tsx index c565682f..6e2c930e 100644 --- a/src-migrate/modules/product-detail/components/Information.tsx +++ b/src-migrate/modules/product-detail/components/Information.tsx @@ -9,10 +9,10 @@ import style from '../styles/information.module.css'; import dynamic from 'next/dynamic'; import Link from 'next/link'; import { useEffect, useRef, useState } from 'react'; -import axios from 'axios'; // <--- 1. TAMBAHAN IMPORT AXIOS +import axios from 'axios'; import currencyFormat from '@/core/utils/currencyFormat'; -import { InputGroup, InputRightElement, SimpleGrid, Flex, Text, Box } from '@chakra-ui/react'; +import { InputGroup, InputRightElement, SimpleGrid, Flex, Text, Box, Center } from '@chakra-ui/react'; import { ChevronDownIcon } from '@heroicons/react/24/outline'; import Image from 'next/image'; import { formatToShortText } from '~/libs/formatNumber'; @@ -54,23 +54,16 @@ const Information = ({ product }: Props) => { setLoadingWarranty(true); try { - // Ambil semua SKU untuk dikirim ke API const skus = product.variants.map((v) => v.id).join(','); const mainSku = product.variants[0].id; - console.log("Fetching warranties for SKUs:", skus); - console.log("Main SKU:", mainSku); - // Panggil API magento-product const res = await axios.get('/api/magento-product', { params: { skus, main_sku: mainSku } }); - // Simpan hasil ke state lokal if (res.data && res.data.warranties) { setWarranties(res.data.warranties); } - console.log("Warranties API Response:", res); - console.log("Warranties fetched:", res.data.warranties); } catch (error) { console.error("Gagal ambil garansi:", error); } finally { @@ -80,8 +73,6 @@ const Information = ({ product }: Props) => { fetchWarrantyDirectly(); }, [product]); - // ====================================================== - useEffect(() => { if (selectedVariant) { @@ -103,27 +94,26 @@ const Information = ({ product }: Props) => { } }, [slaVariant, isLoading, setSla]); -const handleOnChange = (vals: any) => { - setDisableFilter(true); - let code = vals.replace(/\s-\s.*$/, '').trim(); - let variant = product?.variants.find((item) => item.code === code); - + const handleOnChange = (vals: any) => { + 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] : '') - ); + setSelectedVariant(variant); + setInputValue( + variant?.code + + (variant?.attributes[0] ? ' - ' + variant?.attributes[0] : '') + ); setVariantOptions(product?.variants); - } - }; + } + }; const handleOnKeyUp = (e: any) => { setDisableFilter(false); setInputValue(e.target.value); }; - // STYLE CUSTOM UNTUK BARIS (Item Code, dll) const rowStyle = { backgroundColor: '#ffffff', fontSize: '13px', @@ -275,37 +265,51 @@ const handleOnChange = (vals: any) => { </div> </div> - {/* === DETAIL INFORMASI PRODUK (Updated Layout) === */} + {/* === DETAIL INFORMASI PRODUK (Horizontal Minimalis) === */} <div className="mt-6 border-t pt-4"> - <h2 className="font-bold text-gray-800 text-sm mb-4">Detail Informasi Produk</h2> + <h2 className="hidden md:block font-bold text-gray-800 text-sm mb-4"> + Detail Informasi Produk + </h2> - <SimpleGrid columns={{ base: 1, md: 3 }} spacing={10}> + {/* Mobile: 3 Kolom, Spacing Kecil. Desktop: 3 Kolom, Spacing Besar */} + <SimpleGrid columns={{ base: 3, md: 3 }} spacing={{ base: 2, md: 10 }}> + {/* 1. Distributor Resmi */} - <Flex align="center" className="gap-3"> + <Flex + direction={{ base: 'column', md: 'row' }} + align="center" + textAlign={{ base: 'center', md: 'left' }} + gap={{ base: 2, md: 3 }} + > <img src="/images/produk_asli.svg" alt="Distributor Resmi" - className="w-10 h-10 shrink-0" + className="w-8 h-8 md:w-10 md:h-10 shrink-0" /> <Box> - <Text fontSize="11px" color="gray.500" lineHeight="short" mb="1px">Distributor Resmi</Text> - <Text fontSize="12px" fontWeight="bold" color="gray.800" lineHeight="short">Jaminan Produk Asli</Text> + <Text fontSize={{ base: "10px", md: "11px" }} color="gray.500" lineHeight="short" mb="1px">Distributor Resmi</Text> + <Text fontSize={{ base: "10px", md: "12px" }} fontWeight="bold" color="gray.800" lineHeight="1.2">Jaminan Produk Asli</Text> </Box> </Flex> {/* 2. Estimasi Penyiapan */} - <Flex align="center" className="gap-3"> + <Flex + direction={{ base: 'column', md: 'row' }} + align="center" + textAlign={{ base: 'center', md: 'left' }} + gap={{ base: 2, md: 3 }} + > <img src="/images/estimasi.svg" alt="Estimasi Penyiapan" - className="w-9 h-9 shrink-0" + className="w-8 h-8 md:w-9 md:h-9 shrink-0" /> <Box> - <Text fontSize="11px" color="gray.500" lineHeight="short" mb="1px">Estimasi Penyiapan</Text> + <Text fontSize={{ base: "10px", md: "11px" }} color="gray.500" lineHeight="short" mb="1px">Estimasi Penyiapan</Text> {isLoading ? ( - <Skeleton height="12px" width="60px" mt="2px" /> + <Center><Skeleton height="10px" width="50px" mt="2px" /></Center> ) : ( - <Text fontSize="12px" fontWeight="bold" color="gray.800" lineHeight="short"> + <Text fontSize={{ base: "10px", md: "12px" }} fontWeight="bold" color="gray.800" lineHeight="1.2"> {sla?.sla_date || '3 - 7 Hari'} </Text> )} @@ -313,20 +317,24 @@ const handleOnChange = (vals: any) => { </Flex> {/* 3. Garansi Produk */} - <Flex align="center" className="gap-3"> + <Flex + direction={{ base: 'column', md: 'row' }} + align="center" + textAlign={{ base: 'center', md: 'left' }} + gap={{ base: 2, md: 3 }} + > <img src="/images/garansi.svg" alt="Garansi Produk" - className="w-10 h-10 shrink-0" + className="w-8 h-8 md:w-10 md:h-10 shrink-0" /> <Box> - <Text fontSize="11px" color="gray.500" lineHeight="short" mb="1px">Garansi Produk</Text> + <Text fontSize={{ base: "10px", md: "11px" }} color="gray.500" lineHeight="short" mb="1px">Garansi Produk</Text> - {/* Menggunakan Loading State & Data dari 'warranties' */} {loadingWarranty ? ( - <Skeleton height="12px" width="80px" mt="2px" /> + <Center><Skeleton height="10px" width="50px" mt="2px" /></Center> ) : ( - <Text fontSize="12px" fontWeight="bold" color="gray.800" lineHeight="short"> + <Text fontSize={{ base: "10px", md: "12px" }} fontWeight="bold" color="gray.800" lineHeight="1.2"> {selectedVariant && warranties[selectedVariant.id] ? warranties[selectedVariant.id] : '-'} diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index dcd97ef1..54a0fb52 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -2,7 +2,6 @@ import style from '../styles/product-detail.module.css'; import Link from 'next/link'; import { useRouter } from 'next/router'; -// Import useMemo import { useEffect, useRef, useState, UIEvent, useMemo } from 'react'; // Import komponen Chakra UI @@ -64,6 +63,41 @@ const RWebShare = dynamic( { ssr: false } ); +// 1. STYLE DESKTOP (Tebal, Jelas, dengan Border/Padding) +const cssScrollbarDesktop = { + '&::-webkit-scrollbar': { + width: '10px', + height: '10px', + }, + '&::-webkit-scrollbar-track': { + background: '#f1f1f1', + borderRadius: '4px', + }, + '&::-webkit-scrollbar-thumb': { + backgroundColor: '#9ca3af', // Gray-400 + borderRadius: '6px', + border: '2px solid #f1f1f1', // Efek padding + }, + '&::-webkit-scrollbar-thumb:hover': { + backgroundColor: '#6b7280', + }, +}; + +// 2. STYLE MOBILE (Tipis, Minimalis, Tanpa Border) +const cssScrollbarMobile = { + '&::-webkit-scrollbar': { + width: '3px', // Sangat tipis vertikal + height: '3px', // Sangat tipis horizontal + }, + '&::-webkit-scrollbar-track': { + background: 'transparent', + }, + '&::-webkit-scrollbar-thumb': { + backgroundColor: '#cbd5e1', // Gray-300 + borderRadius: '3px', + }, +}; + const SELF_HOST = process.env.NEXT_PUBLIC_SELF_HOST; const ProductDetail = ({ product }: Props) => { @@ -178,13 +212,14 @@ const ProductDetail = ({ product }: Props) => { // 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(filteredMatrix); + // 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([]); } @@ -213,14 +248,13 @@ const ProductDetail = ({ product }: Props) => { }, [product.id]); // ========================================================================= - // HELPER 1: GROUPING DATA BY LABEL (Separator ':') + // HELPER 1: GROUPING DATA BY LABEL // ========================================================================= 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(); @@ -237,7 +271,7 @@ const ProductDetail = ({ product }: Props) => { groups[groupName].children.push({ ...item, - label: childLabel // Override label jadi pendek + label: childLabel }); } else { @@ -256,7 +290,6 @@ const ProductDetail = ({ product }: Props) => { if (!val) return '-'; const strVal = String(val).trim(); - // URL Link const isUrl = !strVal.includes(' ') && ( strVal.startsWith('http') || strVal.startsWith('www.') @@ -275,7 +308,6 @@ const ProductDetail = ({ product }: Props) => { ); } - // HTML if (strVal.includes('<') && strVal.includes('>')) { return ( <div @@ -285,7 +317,6 @@ const ProductDetail = ({ product }: Props) => { ); } - // Teks Biasa return strVal; }; @@ -496,19 +527,19 @@ const ProductDetail = ({ product }: Props) => { <h1 className={style['title']}>{product.name}</h1> <div className='h-3 md:h-0' /> <Information product={product} /> - <div className='h-6' /> + <div className='h-2' /> </div> )} </div> <div className='h-full'> {isMobile && ( - <div className='px-4 pt-6'> + <div className='px-4 pt-2'> <PriceAction product={product} /> </div> )} - <div className='h-4 md:h-10' /> + <div className='h-2 md:h-10' /> {!!activeVariantId && !isApproval && ( <ProductPromoSection product={product} @@ -559,29 +590,61 @@ const ProductDetail = ({ product }: Props) => { overflowX="auto" overflowY="auto" maxHeight="500px" - css={{ - '&::-webkit-scrollbar': { - width: '12px', - height: '12px', - }, - '&::-webkit-scrollbar-track': { - background: '#f1f1f1', - }, - '&::-webkit-scrollbar-thumb': { - backgroundColor: '#a0aec0', - borderRadius: '8px', - border: '4px solid transparent', - backgroundClip: 'content-box', - }, - '&::-webkit-scrollbar-thumb:hover': { - backgroundColor: '#718096', - }, - }} + css={isMobile ? cssScrollbarMobile : cssScrollbarDesktop} > {loadingSpecs ? ( <Center py={6}><Spinner color='red.500' /></Center> ) : specsMatrix.length > 0 ? ( (() => { + const variantCount = sortedVariants.length; + const isSingleVariant = variantCount === 1; + + // === 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 ( + <Table variant="simple" size={isMobile ? "sm" : "md"}> + <Tbody> + {rows.map((row, idx) => ( + <Tr key={idx} bg={idx % 2 === 0 ? 'white' : 'gray.50'}> + {/* Kolom Label (Kiri) */} + <Td + width="40%" + fontWeight="bold" + color="gray.600" + borderColor="gray.200" + verticalAlign="top" + py={3} + > + {row.label} + </Td> + {/* Kolom Value (Kanan) */} + <Td + color="gray.800" + borderColor="gray.200" + verticalAlign="top" + py={3} + > + {renderSpecValue(row.values[singleVariantId])} + </Td> + </Tr> + ))} + </Tbody> + </Table> + ); + } + + // === LOGIC 2: MULTIPLE VARIANTS (MATRIX TABLE HORIZONTAL) === const topHeaders: any[] = []; const subHeaders: any[] = []; const flatSpecs: any[] = []; @@ -610,48 +673,44 @@ const ProductDetail = ({ product }: Props) => { }); return ( - <Table variant="simple" size="md"> - <Thead bg="red.600" position="sticky" top={0} zIndex={20}> - {/* Baris 1: Header Utama */} + <Table variant="simple" size={isMobile ? "sm" : "md"}> + <Thead bg="red.600" position="sticky" top={0} zIndex={3}> <Tr> {topHeaders.map((th, idx) => ( <Th key={`top-${idx}`} position={idx === 0 ? "sticky" : "static"} left={idx === 0 ? 0 : undefined} - zIndex={idx === 0 ? 22 : 20} + zIndex={idx === 0 ? 4 : 3} boxShadow={idx === 0 ? "2px 0 5px -2px rgba(0,0,0,0.2)" : "none"} - bg="red.600" colSpan={th.colSpan} rowSpan={th.rowSpan} color="white" textAlign="center" - fontSize="sm" + fontSize={isMobile ? "xs" : "sm"} textTransform="none" fontWeight="800" letterSpacing="wide" verticalAlign="middle" borderBottom="none" + px={isMobile ? 2 : 4} > {th.label} </Th> ))} </Tr> - - {/* Baris 2: Sub Header */} <Tr> - {subHeaders.map((sub, idx) => { - const isFirstHeaderGroup = topHeaders[0]?.type === 'group'; - const shouldSticky = idx === 0 && isFirstHeaderGroup; - return ( + {subHeaders.map((sub, idx) => { + const isFirstHeaderGroup = topHeaders[0]?.type === 'group'; + const shouldSticky = idx === 0 && isFirstHeaderGroup; + return ( <Th key={`sub-${idx}`} position={shouldSticky ? "sticky" : "static"} left={shouldSticky ? 0 : undefined} - zIndex={shouldSticky ? 21 : 1} + zIndex={shouldSticky ? 4 : 1} boxShadow={shouldSticky ? "2px 0 5px -2px rgba(0,0,0,0.2)" : "none"} - color="white" textAlign="center" fontSize="xs" @@ -660,6 +719,7 @@ const ProductDetail = ({ product }: Props) => { whiteSpace="nowrap" bg="red.600" pt={1} pb={1} + px={isMobile ? 2 : 4} > {sub.label} </Th> @@ -667,35 +727,32 @@ const ProductDetail = ({ product }: Props) => { })} </Tr> </Thead> - + <Tbody> {sortedVariants.map((v, vIdx) => ( <Tr key={v.id} bg={vIdx % 2 === 0 ? 'white' : 'gray.50'}> {flatSpecs.map((spec, sIdx) => { const rawValue = spec.values[v.id] || '-'; - const isFirstCol = sIdx === 0; + const isFirstCol = sIdx === 0; return ( <Td key={sIdx} - // === LOGIC STICKY DATA PERTAMA === position={isFirstCol ? "sticky" : "static"} left={isFirstCol ? 0 : undefined} - zIndex={isFirstCol ? 10 : 1} + zIndex={isFirstCol ? 2 : 1} bg={vIdx % 2 === 0 ? 'white' : 'gray.50'} boxShadow={isFirstCol ? "2px 0 5px -2px rgba(0,0,0,0.1)" : "none"} - // ================================= - borderColor="gray.200" textAlign="center" - fontSize="sm" + fontSize={isMobile ? "xs" : "sm"} verticalAlign="middle" - px={2} + px={isMobile ? 1 : 2} py={3} - minW="100px" - maxW="150px" + minW={isMobile ? "100px" : "120px"} + maxW="200px" whiteSpace="normal" overflowWrap="break-word" - fontWeight={isFirstCol ? "bold" : "normal"} + fontWeight={isFirstCol ? "bold" : "normal"} > {renderSpecValue(rawValue)} </Td> @@ -712,9 +769,6 @@ const ProductDetail = ({ product }: Props) => { )} </Box> </TabPanel> - - {/* DETAIL LAINNYA */} - <TabPanel px={0} py={6}><p className="text-gray-500 text-sm">Informasi tambahan belum tersedia.</p></TabPanel> </TabPanels> </Tabs> </div> |
