diff options
| author | FIN-IT_AndriFP <andrifebriyadiputra@gmail.com> | 2025-12-08 14:55:49 +0700 |
|---|---|---|
| committer | FIN-IT_AndriFP <andrifebriyadiputra@gmail.com> | 2025-12-08 14:55:49 +0700 |
| commit | d2fb20c30fa3440369807c05d01897eb19483f4c (patch) | |
| tree | 5c65f04fc8c73135411518e1161ad23fe447e2db | |
| parent | 484107049889fc9054cd1bd786929cde4acec1b9 (diff) | |
(andri) modal compare without data
4 files changed, 196 insertions, 76 deletions
diff --git a/src-migrate/modules/product-detail/components/AddToQuotation.tsx b/src-migrate/modules/product-detail/components/AddToQuotation.tsx index 3e811330..0ea88830 100644 --- a/src-migrate/modules/product-detail/components/AddToQuotation.tsx +++ b/src-migrate/modules/product-detail/components/AddToQuotation.tsx @@ -1,7 +1,8 @@ import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; import style from '../styles/price-action.module.css'; -import { Button, Link, useToast } from '@chakra-ui/react'; -import { ArrowDownTrayIcon } from '@heroicons/react/24/outline'; +import { Button, Link, useToast, Icon } from '@chakra-ui/react'; +// Pastikan icon ini ada (atau ganti dengan text sementara jika error) +import { ScaleIcon } from '@heroicons/react/24/outline'; import product from 'next-seo/lib/jsonld/product'; import { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; @@ -17,12 +18,15 @@ import { createSlug } from '~/libs/slug'; import formatCurrency from '~/libs/formatCurrency'; import { useProductDetail } from '../stores/useProductDetail'; import useDevice from '@/core/hooks/useDevice'; +import DesktopView from '@/core/components/views/DesktopView'; +import MobileView from '@/core/components/views/MobileView'; type Props = { variantId: number | null; quantity?: number; source?: 'buy' | 'add_to_cart'; products: IProductDetail; + onCompare?: () => void; // <--- 1. Tambah Props }; type Status = 'idle' | 'loading' | 'success'; @@ -32,6 +36,7 @@ const AddToQuotation = ({ quantity = 1, source = 'add_to_cart', products, + onCompare // <--- 2. Tangkap Props }: Props) => { const auth = getAuth(); const router = useRouter(); @@ -106,37 +111,59 @@ const AddToQuotation = ({ }, 3000); }, [status]); - const btnConfig = { - add_to_cart: { - colorScheme: 'red', - variant: 'outline', - text: 'Keranjang', - }, - buy: { - colorScheme: 'red', - variant: 'solid', - text: 'Beli', - }, - }; - return ( - <div className='w-full'> - <Button - onClick={handleButton} - color={'red'} - colorScheme='white' - className='w-full border-2 p-2 gap-1 hover:bg-slate-100 flex items-center' - isDisabled={!hasPrice} - > - <ImageNext - src={isDesktop ? '/images/doc_red.svg' : '/images/doc.svg'} - alt='penawaran instan' - className='' - width={25} - height={25} - /> - {isDesktop ? 'Penawaran Harga Instan' : ''} - </Button> + <div className='w-full flex flex-col gap-3'> {/* Ganti div biasa jadi Flex Column gap 3 */} + + {/* 3. TAMPILAN DESKTOP: GRID 2 KOLOM (Bandingkan & Penawaran) */} + <DesktopView> + <div className="grid grid-cols-2 gap-3 w-full"> + {/* Tombol Kiri: Bandingkan */} + <Button + onClick={onCompare} // <--- Jalankan Modal Compare + variant="outline" + colorScheme="gray" + className="w-full border border-gray-300 p-2 gap-2 flex items-center justify-center text-gray-600 hover:text-red-600 hover:border-red-600 transition-all font-normal text-sm" + > + <Icon as={ScaleIcon} w={5} h={5} /> + Bandingkan + </Button> + + {/* Tombol Kanan: Penawaran (Link WA) */} + <Button + as={Link} + href={askAdminUrl} + target='_blank' + variant="outline" + colorScheme="gray" + className="w-full border border-gray-300 p-2 gap-2 flex items-center justify-center text-gray-600 hover:text-red-600 hover:border-red-600 transition-all font-normal text-sm" + _hover={{ textDecoration: 'none' }} + onClick={handleButton} // Opsional: Jalankan handleButton jika ingin fungsi tombol ini tetap jalan + > + <ImageNext src="/images/doc_red.svg" width={20} height={20} alt="penawaran" /> + Penawaran + </Button> + </div> + </DesktopView> + + {/* TAMPILAN MOBILE (Tetap seperti semula - hanya icon Penawaran) */} + <MobileView> + <Button + onClick={handleButton} + color={'red'} + colorScheme='white' + className='w-full border-2 p-2 gap-1 hover:bg-slate-100 flex items-center' + isDisabled={!hasPrice} + > + <ImageNext + src='/images/doc.svg' + alt='penawaran instan' + className='' + width={25} + height={25} + /> + </Button> + </MobileView> + <BottomPopup className='!container' title='Berhasil Ditambahkan' @@ -243,4 +270,4 @@ const AddToQuotation = ({ ); }; -export default AddToQuotation; +export default AddToQuotation;
\ No newline at end of file diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx index d73ab5f6..ee8009ef 100644 --- a/src-migrate/modules/product-detail/components/PriceAction.tsx +++ b/src-migrate/modules/product-detail/components/PriceAction.tsx @@ -1,5 +1,4 @@ import style from '../styles/price-action.module.css'; - import Image from 'next/image'; import Link from 'next/link'; import { useEffect, useState } from 'react'; @@ -15,14 +14,17 @@ import { Button, Skeleton } from '@chakra-ui/react'; import DesktopView from '@/core/components/views/DesktopView'; import MobileView from '@/core/components/views/MobileView'; +// 1. Tambahkan onCompare (Optional) di sini type Props = { product: IProductDetail; + onCompare?: () => void; }; const PPN: number = process.env.NEXT_PUBLIC_PPN ? parseFloat(process.env.NEXT_PUBLIC_PPN) : 0; -const PriceAction = ({ product }: Props) => { + +const PriceAction = ({ product, onCompare }: Props) => { const { activePrice, setActive, @@ -146,19 +148,6 @@ const PriceAction = ({ product }: Props) => { </> )} - {/* {!!activePrice && activePrice.price === 0 && ( - <span> - Hubungi kami untuk dapatkan harga terbaik,{' '} - <Link - href={askAdminUrl} - target='_blank' - className={style['contact-us']} - > - klik disini - </Link> - </span> - )} */} - <DesktopView> <div className='h-4' /> <div className='flex gap-x-5 items-center'> @@ -227,9 +216,6 @@ const PriceAction = ({ product }: Props) => { )} </div> </div> - {/* <span className='text-[12px] text-red-500 italic'> - * {qtyPickUp} barang bisa di pickup - </span> */} </DesktopView> {/* ===== MOBILE: grid kiri-kanan, kanan hanya qty ===== */} @@ -263,12 +249,6 @@ const PriceAction = ({ product }: Props) => { </Link> )} </div> - - {/* {qtyPickUp > 0 && ( - <div className='text-[12px] mt-1 text-red-500 italic'> - * {qtyPickUp} barang bisa di pickup - </div> - )} */} </div> {/* Kanan: hanya qty, rata kanan */} @@ -295,9 +275,9 @@ const PriceAction = ({ product }: Props) => { value={quantityInput} onChange={(e) => setQuantityInput(e.target.value)} className='h-11 md:h-12 w-16 md:w-20 text-center text-lg md:text-xl outline-none border-x - [appearance:textfield] - [&::-webkit-outer-spin-button]:appearance-none - [&::-webkit-inner-spin-button]:appearance-none' + [appearance:textfield] + [&::-webkit-outer-spin-button]:appearance-none + [&::-webkit-inner-spin-button]:appearance-none' disabled={!hasPrice} /> @@ -335,11 +315,13 @@ const PriceAction = ({ product }: Props) => { )} </div> <div className='mt-4'> + {/* 2. TERUSKAN onCompare KE SINI */} <AddToQuotation source='buy' products={product} variantId={activeVariantId} quantity={Number(quantityInput)} + onCompare={onCompare} /> </div> </DesktopView> @@ -376,4 +358,4 @@ const PriceAction = ({ product }: Props) => { ); }; -export default PriceAction; +export default PriceAction;
\ No newline at end of file diff --git a/src-migrate/modules/product-detail/components/ProductComparisonModal.tsx b/src-migrate/modules/product-detail/components/ProductComparisonModal.tsx new file mode 100644 index 00000000..aa4388a6 --- /dev/null +++ b/src-migrate/modules/product-detail/components/ProductComparisonModal.tsx @@ -0,0 +1,46 @@ +import { + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalBody, + ModalCloseButton, + Button, + ModalFooter, + Center, + Text, + Box +} from '@chakra-ui/react'; + +type Props = { + isOpen: boolean; + onClose: () => void; +}; + +const ProductComparisonModal = ({ isOpen, onClose }: Props) => { + return ( + <Modal isOpen={isOpen} onClose={onClose} size="xl" isCentered> + <ModalOverlay /> + <ModalContent> + <ModalHeader borderBottom="1px solid #eee">Perbandingan Produk</ModalHeader> + <ModalCloseButton /> + + <ModalBody py={10}> + <Center flexDirection="column"> + <Box fontSize="4xl" mb={4}>Test</Box> + <Text fontWeight="bold" fontSize="lg">Modal Berhasil Terbuka!</Text> + <Text fontSize="sm" color="gray.500" mt={2}> + Fitur komparasi produk akan muncul di sini nanti. + </Text> + </Center> + </ModalBody> + + <ModalFooter borderTop="1px solid #eee"> + <Button colorScheme="red" onClick={onClose}>Tutup</Button> + </ModalFooter> + </ModalContent> + </Modal> + ); +}; + +export default ProductComparisonModal;
\ No newline at end of file diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index fab5ecf3..f06f958a 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -49,6 +49,9 @@ import SimilarBottom from './SimilarBottom'; import SimilarSide from './SimilarSide'; import dynamic from 'next/dynamic'; +// 1. IMPORT MODAL (Baru) +import ProductComparisonModal from './ProductComparisonModal'; + import { gtagProductDetail } from '@/core/utils/googleTag'; type Props = { @@ -74,6 +77,9 @@ const ProductDetail = ({ product }: Props) => { const [loadingSpecs, setLoadingSpecs] = useState(false); + // 2. STATE MODAL COMPARE (Baru) + const [isCompareOpen, setCompareOpen] = useState(false); + useEffect(() => { try { setAuth(getAuth() ?? null); @@ -322,6 +328,13 @@ const ProductDetail = ({ product }: Props) => { return ( <> + {/* 3. MODAL POPUP DIRENDER DISINI */} + {/* Render di luar layout utama agar tidak tertutup elemen lain */} + <ProductComparisonModal + isOpen={isCompareOpen} + onClose={() => setCompareOpen(false)} + /> + <div className='relative'> {isDesktop && !hasPrice && ( <div className='absolute inset-0 z-[20] flex items-center justify-center pointer-events-none select-none'> @@ -406,9 +419,9 @@ const ProductDetail = ({ product }: Props) => { size={18} className='text-red-600 shrink-0 mx-2' /> - <div className='text-red-600 font-normal text-h-sm p-2'> + <h1 className='text-red-600 font-normal text-h-sm'> Maaf untuk saat ini Produk yang anda cari tidak tersedia - </div> + </h1> </div> )} <div className='h-6 md:h-0' /> @@ -426,9 +439,9 @@ const ProductDetail = ({ product }: Props) => { size={18} className='text-red-600 shrink-0 mx-2' /> - <div className='text-red-600 font-normal text-h-sm p-2'> + <h1 className='text-red-600 font-normal text-h-sm'> Maaf untuk saat ini Produk yang anda cari tidak tersedia - </div> + </h1> </div> )} <h1 className={style['title']}>{product.name}</h1> @@ -448,7 +461,10 @@ const ProductDetail = ({ product }: Props) => { <div className='h-4 md:h-10' /> {!!activeVariantId && !isApproval && ( - <ProductPromoSection product={product} productId={activeVariantId} /> + <ProductPromoSection + product={product} + productId={activeVariantId} + /> )} <div className='h-0 md:h-6' /> @@ -457,25 +473,49 @@ const ProductDetail = ({ product }: Props) => { <div className={style['section-card']}> <Tabs variant="unstyled"> <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}>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}>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}>Detail Lainnya</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} + > + 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} + > + 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} + > + Detail Lainnya + </Tab> </TabList> <TabPanels> {/* DESKRIPSI */} <TabPanel px={0} py={6}> <div className='overflow-x-auto text-sm text-gray-700'> - <div className={style['description']} dangerouslySetInnerHTML={{ __html: !product.description || product.description === '<p><br></p>' ? '<p>Lorem ipsum dolor sit amet.</p>' : product.description, }} /> + <div + className={style['description']} + dangerouslySetInnerHTML={{ + __html: + !product.description || product.description === '<p><br></p>' + ? '<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>' + : product.description, + }} + /> </div> </TabPanel> - {/* SPESIFIKASI (LOGIKA GROUPING + RATA TENGAH) */} + {/* SPESIFIKASI (MATRIKS & SPLIT PIPA) */} <TabPanel px={0} py={2}> <Box border="1px solid" borderColor="gray.200" borderRadius="sm" overflowX="auto"> {loadingSpecs ? ( <Center py={6}><Spinner color='red.500' /></Center> ) : specsMatrix.length > 0 ? ( + // Cek Single vs Multi Variant (() => { const isSingleVariant = product.variants.length === 1; const globalAlign = isSingleVariant ? "left" : "center"; @@ -484,11 +524,27 @@ const ProductDetail = ({ product }: Props) => { <Table variant="simple" size="md"> <Thead bg="red.600"> <Tr> - <Th width={isSingleVariant ? "30%" : "20%"} borderColor="whiteAlpha.300" color="white" fontSize="sm" textTransform="none" verticalAlign="middle"> + <Th + width={isSingleVariant ? "30%" : "20%"} + borderColor="whiteAlpha.300" + color="white" + fontSize="sm" + textTransform="none" + verticalAlign="middle" + > Spesifikasi </Th> + {product.variants.map(v => ( - <Th key={v.id} borderColor="whiteAlpha.300" color="white" textAlign={globalAlign} fontSize="sm" textTransform="none" verticalAlign="middle"> + <Th + key={v.id} + borderColor="whiteAlpha.300" + color="white" + textAlign={globalAlign} + fontSize="sm" + textTransform="none" + verticalAlign="middle" + > {isSingleVariant ? 'Detail' : (v.attributes && v.attributes.length > 0 ? v.attributes.join(' - ') : v.code)} </Th> ))} @@ -563,13 +619,22 @@ const ProductDetail = ({ product }: Props) => { </div> </div> - {/* ... (Bagian Sidebar & Bottom SAMA) ... */} {isDesktop && ( <div className='md:w-3/12'> - <PriceAction product={product} /> + {/* 4. INTEGRASI: PASSING HANDLER MODAL KE PRICE ACTION */} + <PriceAction + product={product} + onCompare={() => setCompareOpen(true)} + /> + <div className='flex gap-x-5 items-center justify-center py-4'> - {/* ... Buttons ... */} + <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>)} </div> + <div className='h-6' /> <div className={style['heading']}>Produk Serupa</div> <div className='h-4' /> |
