diff options
| author | IT Fixcomart <it@fixcomart.co.id> | 2026-01-31 16:22:14 +0000 |
|---|---|---|
| committer | IT Fixcomart <it@fixcomart.co.id> | 2026-01-31 16:22:14 +0000 |
| commit | 8c6f1b3bf6eac52041337b33e746888933e1e34a (patch) | |
| tree | 0787627b48ef1b1db8e6d76066eb3400afb71123 /src-migrate/modules/product-detail/components/Information.tsx | |
| parent | ec7ab4c654fc5b29b277d42ad84986f4c1220134 (diff) | |
| parent | 99aa3500fc5bbb3bb24d73461639e6fc88042a85 (diff) | |
Merged in magento-v2.1 (pull request #472)
Magento v2.1
Diffstat (limited to 'src-migrate/modules/product-detail/components/Information.tsx')
| -rw-r--r-- | src-migrate/modules/product-detail/components/Information.tsx | 229 |
1 files changed, 182 insertions, 47 deletions
diff --git a/src-migrate/modules/product-detail/components/Information.tsx b/src-migrate/modules/product-detail/components/Information.tsx index 813b6bf5..ce848267 100644 --- a/src-migrate/modules/product-detail/components/Information.tsx +++ b/src-migrate/modules/product-detail/components/Information.tsx @@ -9,17 +9,24 @@ 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'; import currencyFormat from '@/core/utils/currencyFormat'; -import { InputGroup, InputRightElement } 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 Image from 'next/image'; +import ImageNext from 'next/image'; import { formatToShortText } from '~/libs/formatNumber'; import { createSlug } from '~/libs/slug'; import { IProductDetail } from '~/types/product'; import { useProductDetail } from '../stores/useProductDetail'; import useVariant from '../hook/useVariant'; +// Import View Components +import MobileView from '@/core/components/views/MobileView'; // Pastikan path import benar + +// Import Modal Compare +import ProductComparisonModal from './ProductComparisonModal'; + const Skeleton = dynamic(() => import('@chakra-ui/react').then((mod) => mod.Skeleton) ); @@ -29,22 +36,53 @@ type Props = { }; const Information = ({ product }: Props) => { - const { selectedVariant, setSelectedVariant, setSla, sla } = - useProductDetail(); + const { selectedVariant, setSelectedVariant, setSla, sla } = useProductDetail(); const [inputValue, setInputValue] = useState<string>(''); const [disableFilter, setDisableFilter] = useState<boolean>(false); const inputRef = useRef<HTMLInputElement>(null); // source of truth - const variantOptions = product.variants; + // const variantOptions = product.variants; + const [variantOptions, setVariantOptions] = useState<any[]>( + product?.variants + ); const variantId = selectedVariant?.id; const { slaVariant, isLoading } = useVariant({ variantId }); - /* ====================== - * Sync input text - * ====================== */ + const [warranties, setWarranties] = useState<Record<string, string>>({}); + const [loadingWarranty, setLoadingWarranty] = useState(false); + + // State untuk Modal Compare + const [isCompareOpen, setIsCompareOpen] = useState(false); + + 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); + } + }; + + fetchWarrantyDirectly(); + }, [product]); + useEffect(() => { if (!selectedVariant) return; @@ -72,14 +110,19 @@ const Information = ({ product }: Props) => { /* ====================== * Handlers * ====================== */ - const handleOnChange = (value: string) => { + const handleOnChange = (vals: any) => { setDisableFilter(true); - - const variant = variantOptions.find((item) => String(item.id) === value); - - if (!variant) return; - - setSelectedVariant(variant); + 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] : '') + ); + setVariantOptions(product?.variants); + } }; const handleOnKeyUp = (e: any) => { @@ -87,6 +130,14 @@ const Information = ({ product }: Props) => { setInputValue(e.target.value); }; + const rowStyle = { + backgroundColor: '#ffffff', + fontSize: '13px', + borderBottom: '1px dashed #e2e8f0', + padding: '8px 0', + marginBottom: '0px' + }; + return ( <div className={style['wrapper']}> {/* ===== Variant Selector ===== */} @@ -120,10 +171,17 @@ const Information = ({ product }: Props) => { </InputGroup> <AutoCompleteList> - {variantOptions.map((option) => ( + {variantOptions + .sort((a: any, b: any) => { + return a.code.localeCompare(b.code, undefined, { numeric: true, sensitivity: 'base' }); + }) + .map((option, cid) => ( <AutoCompleteItem key={option.id} - value={String(option.id)} + // value={String(option.id)} + value={option.code + + (option.attributes?.[0] ? ` - ${option.attributes[0]}` : '') + } _selected={ option.id === selectedVariant?.id ? { bg: 'gray.300' } @@ -135,15 +193,8 @@ const Information = ({ product }: Props) => { {option.code} {option.attributes?.[0] ? ` - ${option.attributes[0]}` : ''} </div> - - <div - className={ - option.price?.discount_percentage - ? 'flex gap-x-4 items-center' - : '' - } - > - {option.price?.discount_percentage > 0 && ( + <div className={option?.price?.discount_percentage ? 'flex gap-x-4 items-center justify-between' : ''}> + {option?.price?.discount_percentage > 0 && ( <> <div className='badge-solid-red text-xs'> {Math.floor(option.price.discount_percentage)}% @@ -162,16 +213,49 @@ const Information = ({ product }: Props) => { ))} </AutoCompleteList> </AutoComplete> + + {/* === TOMBOL BANDINGKAN PRODUK (HANYA MOBILE) === */} + <MobileView> + <div + className="w-full flex items-center justify-between py-3 px-1 mt-3 bg-white border-t border-b border-black-100 cursor-pointer hover:bg-gray-50 transition-colors group" + onClick={() => setIsCompareOpen(true)} + > + <div className="flex items-center gap-3"> + <div className="bg-red-50 p-2 rounded-full group-hover:bg-red-100 transition-colors"> + <ImageNext src="/images/logo-bandingkan.svg" width={15} height={15} alt="bandingkan" /> + </div> + <div className="flex flex-col"> + <span className="text-sm font-bold text-gray-800">Bandingkan Produk</span> + <span className="text-xs text-gray-500">Coba bandingkan dengan produk lainnya</span> + </div> + </div> + <div className="flex items-center gap-2"> + <span className="bg-red-600 text-white text-[10px] font-bold px-2 py-0.5 rounded-full">Baru</span> + <Icon as={ChevronDownIcon} className="w-4 h-4 text-gray-400 transform -rotate-90" /> + </div> + </div> + </MobileView> + + {/* Render Modal (Logic open/close ada di dalam component) */} + {isCompareOpen && ( + <ProductComparisonModal + isOpen={isCompareOpen} + onClose={() => setIsCompareOpen(false)} + mainProduct={product} + selectedVariant={selectedVariant} + /> + )} </div> - {/* ===== Info Rows ===== */} - <div className={style['row']}> - <div className={style['label']}>Item Code</div> + {/* ITEM CODE */} + <div className={style['row']} style={rowStyle}> + <div className={style['label']} style={{ color: '#6b7280' }}>Item Code</div> <div className={style['value']}>{selectedVariant?.code}</div> </div> - <div className={style['row']}> - <div className={style['label']}>Manufacture</div> + {/* MANUFACTURE */} + <div className={style['row']} style={rowStyle}> + <div className={style['label']} style={{ color: '#6b7280' }}>Manufacture</div> <div className={style['value']}> {!!product.manufacture.name ? ( <Link @@ -181,8 +265,8 @@ const Information = ({ product }: Props) => { product.manufacture.id.toString() )} > - {product.manufacture.logo ? ( - <Image + {product?.manufacture.logo ? ( + <ImageNext height={50} width={100} src={product.manufacture.logo} @@ -201,32 +285,83 @@ const Information = ({ product }: Props) => { </div> </div> - <div className={style['row']}> - <div className={style['label']}>Berat Barang</div> + {/* BERAT BARANG */} + <div className={style['row']} style={rowStyle}> + <div className={style['label']} style={{ color: '#6b7280' }}>Berat Barang</div> <div className={style['value']}> {selectedVariant?.weight > 0 ? `${selectedVariant.weight} Kg` : '-'} </div> </div> - <div className={style['row']}> - <div className={style['label']}>Terjual</div> + {/* TERJUAL */} + <div className={style['row']} style={{ ...rowStyle, borderBottom: 'none' }}> + <div className={style['label']} style={{ color: '#6b7280' }}>Terjual</div> <div className={style['value']}> {product.qty_sold > 0 ? formatToShortText(product.qty_sold) : '-'} </div> </div> - <div className={style['row']}> - <div className={style['label']}>Persiapan Barang</div> - {isLoading ? ( - <div className={style['value']}> - <Skeleton height={5} width={100} /> - </div> - ) : ( - <div className={style['value']}>{sla?.sla_date}</div> - )} + {/* === DETAIL INFORMASI PRODUK === */} + <div className="mt-6 border-t pt-4"> + <h2 className="hidden md:block font-bold text-gray-800 text-sm mb-4"> + Detail Informasi Produk + </h2> + + <SimpleGrid columns={{ base: 3, md: 3 }} spacing={{ base: 2, md: 10 }}> + <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-8 h-8 md:w-10 md:h-10 shrink-0" /> + <Box> + <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> + + <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-8 h-8 md:w-9 md:h-9 shrink-0" /> + <Box> + <Text fontSize={{ base: "10px", md: "11px" }} color="gray.500" lineHeight="short" mb="1px">Estimasi Penyiapan</Text> + {isLoading ? ( + <Center><Skeleton height="10px" width="50px" mt="2px" /></Center> + ) : ( + <Text fontSize={{ base: "10px", md: "12px" }} fontWeight="bold" color="gray.800" lineHeight="1.2"> + {sla?.sla_date || '-'} + </Text> + )} + </Box> + </Flex> + + <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-8 h-8 md:w-10 md:h-10 shrink-0" /> + <Box> + <Text fontSize={{ base: "10px", md: "11px" }} color="gray.500" lineHeight="short" mb="1px">Garansi Produk</Text> + {loadingWarranty ? ( + <Center><Skeleton height="10px" width="50px" mt="2px" /></Center> + ) : ( + <Text fontSize={{ base: "10px", md: "12px" }} fontWeight="bold" color="gray.800" lineHeight="1.2"> + {selectedVariant && warranties[selectedVariant.id] ? warranties[selectedVariant.id] : '-'} + </Text> + )} + </Box> + </Flex> + </SimpleGrid> </div> </div> ); }; -export default Information; +export default Information;
\ No newline at end of file |
