From f56cc888934d4b4ef962e967d40533ab5ded2414 Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Mon, 19 Jan 2026 09:39:25 +0700 Subject: (andri) fix view mobile compare --- public/images/keranjang-compare.svg | 3 - public/images/keranjang.svg | 3 + .../product-detail/components/Information.tsx | 95 ++-- .../components/ProductComparisonModal.tsx | 527 ++++++++++++--------- .../product-detail/components/ProductDetail.tsx | 4 +- 5 files changed, 360 insertions(+), 272 deletions(-) delete mode 100644 public/images/keranjang-compare.svg create mode 100644 public/images/keranjang.svg diff --git a/public/images/keranjang-compare.svg b/public/images/keranjang-compare.svg deleted file mode 100644 index 6504e420..00000000 --- a/public/images/keranjang-compare.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/public/images/keranjang.svg b/public/images/keranjang.svg new file mode 100644 index 00000000..6504e420 --- /dev/null +++ b/public/images/keranjang.svg @@ -0,0 +1,3 @@ + + + diff --git a/src-migrate/modules/product-detail/components/Information.tsx b/src-migrate/modules/product-detail/components/Information.tsx index 6e2c930e..f3abe0c7 100644 --- a/src-migrate/modules/product-detail/components/Information.tsx +++ b/src-migrate/modules/product-detail/components/Information.tsx @@ -12,15 +12,21 @@ import { useEffect, useRef, useState } from 'react'; import axios from 'axios'; import currencyFormat from '@/core/utils/currencyFormat'; -import { InputGroup, InputRightElement, SimpleGrid, Flex, Text, Box, Center } 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) ); @@ -30,8 +36,7 @@ type Props = { }; const Information = ({ product }: Props) => { - const { selectedVariant, setSelectedVariant, setSla, sla } = - useProductDetail(); + const { selectedVariant, setSelectedVariant, setSla, sla } = useProductDetail(); const [inputValue, setInputValue] = useState( selectedVariant?.code + ' - ' + selectedVariant?.attributes[0] @@ -48,6 +53,9 @@ const Information = ({ product }: Props) => { const [warranties, setWarranties] = useState>({}); 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; @@ -166,9 +174,7 @@ const Information = ({ product }: Props) => { } _selected={ option.id === selectedVariant?.id - ? { - bg: 'gray.300', - } + ? { bg: 'gray.300' } : undefined } textTransform='capitalize' @@ -183,13 +189,7 @@ const Information = ({ product }: Props) => { ? ' - ' + option?.attributes[0] : '')} -
+
{option?.price?.discount_percentage > 0 && ( <>
@@ -209,6 +209,38 @@ const Information = ({ product }: Props) => { ))} + + {/* === TOMBOL BANDINGKAN PRODUK (HANYA MOBILE) === */} + +
setIsCompareOpen(true)} + > +
+
+ +
+
+ Bandingkan Produk + Coba bandingkan dengan produk lainnya +
+
+
+ Baru + +
+
+
+ + {/* Render Modal (Logic open/close ada di dalam component) */} + {isCompareOpen && ( + setIsCompareOpen(false)} + mainProduct={product} + selectedVariant={selectedVariant} + /> + )}
{/* ITEM CODE */} @@ -230,7 +262,7 @@ const Information = ({ product }: Props) => { )} > {product?.manufacture.logo ? ( - {
- {/* === DETAIL INFORMASI PRODUK (Horizontal Minimalis) === */} + {/* === DETAIL INFORMASI PRODUK === */}

Detail Informasi Produk

- {/* Mobile: 3 Kolom, Spacing Kecil. Desktop: 3 Kolom, Spacing Besar */} - - {/* 1. Distributor Resmi */} - Distributor Resmi + Distributor Resmi Distributor Resmi Jaminan Produk Asli - {/* 2. Estimasi Penyiapan */} - Estimasi Penyiapan + Estimasi Penyiapan Estimasi Penyiapan {isLoading ? ( @@ -316,35 +336,26 @@ const Information = ({ product }: Props) => { - {/* 3. Garansi Produk */} - Garansi Produk + Garansi Produk Garansi Produk - {loadingWarranty ? ( -
+
) : ( - {selectedVariant && warranties[selectedVariant.id] - ? warranties[selectedVariant.id] - : '-'} + {selectedVariant && warranties[selectedVariant.id] ? warranties[selectedVariant.id] : '-'} )}
- ); }; diff --git a/src-migrate/modules/product-detail/components/ProductComparisonModal.tsx b/src-migrate/modules/product-detail/components/ProductComparisonModal.tsx index 00eaebe5..b26be520 100644 --- a/src-migrate/modules/product-detail/components/ProductComparisonModal.tsx +++ b/src-migrate/modules/product-detail/components/ProductComparisonModal.tsx @@ -6,6 +6,12 @@ import { ModalHeader, ModalBody, ModalCloseButton, + Drawer, // Tambahan untuk Mobile + DrawerOverlay, + DrawerContent, + DrawerHeader, + DrawerBody, + DrawerCloseButton, Button, Text, Box, @@ -26,7 +32,9 @@ import { List, ListItem, useToast, - useOutsideClick + useOutsideClick, + useBreakpointValue, // Tambahan untuk Mobile + Divider // Tambahan untuk Mobile } from '@chakra-ui/react'; import { @@ -36,7 +44,7 @@ import { AutoCompleteList, } from '@choc-ui/chakra-autocomplete'; -import { Search, Trash2, ChevronDown } from 'lucide-react'; +import { Search, Trash2, ChevronDown, X } from 'lucide-react'; // --- HELPER FORMATTING --- const formatPrice = (price: number) => { @@ -82,6 +90,8 @@ type Props = { const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant }: Props) => { const toast = useToast(); + // Deteksi Mobile + const isMobile = useBreakpointValue({ base: true, md: false }); const [products, setProducts] = useState<(any | null)[]>([null, null, null, null]); const [specsMatrix, setSpecsMatrix] = useState([]); @@ -150,7 +160,7 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant const tempActiveVar = { code: displayCode, - name: displayName, + name: displayName, displayName: displayName, attributes: activeItem.attributes || [] }; @@ -273,7 +283,6 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant const currentProduct = products[slotIndex]; if (!currentProduct || !currentProduct.variants) return; - // Cari varian yang labelnya cocok dengan string input const selectedVar = currentProduct.variants.find((v: any) => { return getVariantLabel(v) === selectedValueString; }); @@ -334,11 +343,10 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant } } - // Siapkan object sementara untuk generate Label (Memanfaatkan Extract Attribute) const tempVar = { code: codeToAdd, name: nameToAdd, - displayName: searchItem.displayName || nameToAdd, // Pastikan ada displayName untuk diparsing + displayName: searchItem.displayName || nameToAdd, attributes: searchItem.attributes || [] }; const initialLabel = getVariantLabel(tempVar); @@ -354,7 +362,7 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant id: idToAdd, code: codeToAdd, name: nameToAdd, - displayName: searchItem.displayName || nameToAdd, // Simpan untuk varian sendiri + displayName: searchItem.displayName || nameToAdd, price: priceToAdd, image: imageToAdd, attributes: searchItem.attributes || [] @@ -391,10 +399,10 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant id: s.variantId || s.productIdI || s.id, code: s.defaultCode || s.default_code || s.code, name: s.displayName || s.name || s.nameS, - displayName: s.displayName, // Wajib disimpan agar extractAttribute bisa bekerja + displayName: s.displayName, price: s.lowestPrice?.price || s.priceTier1V2F || 0, image: s.image || s.imageS, - attributes: [] // Biarkan kosong, nanti dihandle extractAttribute via displayName + attributes: [] })); allVariants.sort((a: any, b: any) => @@ -426,6 +434,290 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant if (newProducts.every(p => p === null)) setSpecsMatrix([]); }; + // --- RENDER SLOT ITEM --- + const renderProductSlot = (product: any, index: number) => { + if (product) { + return ( + + {index !== 0 && ( + } + size="xs" position="absolute" top={-2} right={-2} + colorScheme="red" onClick={() => handleRemoveProduct(index)} zIndex={2} + /> + )} + + {product.name} { (e.target as HTMLImageElement).src = '/images/no-image-compare.svg'; }} + /> + + + + {product.price > 0 ? formatPrice(product.price) : 'Hubungi Admin'} + + + {product.name} + + + + + handleVariantChange(index, val)} + value={product.inputValue} + > + + handleInputChange(index, e.target.value)} + onFocus={() => setDisableVariantFilter(true)} + /> + + + + + + + {product.variants && product.variants.map((v: any, vIdx: number) => { + const attributeText = extractAttribute(v); + const label = `${v.code} - ${attributeText}`; + return ( + + + + {v.code} + + {attributeText} + + + + {formatPrice(v.price)} + + + + ); + })} + + + + + + } + variant="outline" + colorScheme="red" + size="sm" + /> + + + + ); + } + + return ( + + + + + { setActiveSearchSlot(index); setSearchQuery(''); }} + onChange={(e) => setSearchQuery(e.target.value)} + /> + {activeSearchSlot === index && searchQuery && ( + { setSearchQuery(''); setActiveSearchSlot(null); }}> + + + )} + + + {activeSearchSlot === index && ( + + {!selectedVariant?.attribute_set_id && !mainProduct?.attribute_set_id ? ( + + Perbandingan Tidak Tersedia + Produk utama tidak memiliki data kategori yang valid untuk dibandingkan. + + ) : ( + <> + {isSearching ? ( + + ) : searchResults.length > 0 ? ( + + {searchResults.map((res) => ( + handleAddProduct(res, index)} + > + + { (e.target as HTMLImageElement).src = '/images/no-image-compare.svg'; }} + flexShrink={0} + mt={1} + /> + + + {res.displayName || res.name} + + + {formatPrice(res.lowestPrice?.price || 0)} + + + + + ))} + + ) : ( + + {searchQuery === '' ? 'Menampilkan rekomendasi...' : 'Produk tidak ditemukan.'} + + )} + + )} + + )} + + + + Empty Slot + + Produk Belum Ditambahkan + + + + ); + }; + + // --- RENDER MOBILE CONTENT --- + const renderMobileContent = () => { + const mobileProducts = products.slice(0, 2); + + return ( + + {/* Sticky Header */} + + + {mobileProducts.map((p, i) => ( + + {renderProductSlot(p, i)} + + ))} + + + + + Spesifikasi Teknis + + {/* Loader Header Hapus saja, kita pindah ke per-item */} + + + + {/* Specs List with Loader per Line */} + + {specsMatrix.length > 0 ? ( + }> + {specsMatrix.map((row, rIdx) => ( + + + {mobileProducts.map((p, cIdx) => { + const val = p ? (row.values[String(p.sku)] || '-') : '-'; + // Logic Loader Per Item + const isItemLoading = isLoadingMatrix && p && !row.values[String(p.sku)]; + + return ( + + {/* VALUE (SPINNER JIKA LOADING) */} + {isItemLoading ? ( + + ) : ( + + {renderSpecValue(val)} + + )} + + {/* LABEL */} + + {row.label} + + + ) + })} + + + ))} + + ) : ( + + {isLoadingMatrix ? Memuat data... : "Data spesifikasi tidak tersedia"} + + )} + + + ); + }; + + // --- MAIN RENDER --- + + // Tampilan Mobile (Drawer 75%) + if (isMobile) { + return ( + + + + + Bandingkan Produk + + {renderMobileContent()} + + + + ); + } + + // Tampilan Desktop (Modal 6XL) return ( @@ -448,222 +740,7 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant {products.map((product, index) => ( - {product ? ( - - {index !== 0 && ( - } - size="xs" position="absolute" top={-2} right={-2} - colorScheme="red" onClick={() => handleRemoveProduct(index)} zIndex={2} - /> - )} - - {product.name} { (e.target as HTMLImageElement).src = '/images/no-image-compare.svg'; }} - /> - - - - {product.price > 0 ? formatPrice(product.price) : 'Hubungi Admin'} - - - {product.name} - - - - {/* --- AUTOCOMPLETE DROPDOWN (FINAL FIX) --- */} - - handleVariantChange(index, val)} - value={product.inputValue} - > - - handleInputChange(index, e.target.value)} - onFocus={() => setDisableVariantFilter(true)} - /> - - - - - - {/* Dropdown List dengan Style Information.tsx */} - - {product.variants && product.variants.map((v: any, vIdx: number) => { - - // Gunakan Helper extractAttribute untuk mendapatkan teks spesifikasi - // Ini akan bekerja baik untuk Slot 1 (pakai attributes) - // maupun Slot Search (parsing displayName) - const attributeText = extractAttribute(v); - const label = `${v.code} - ${attributeText}`; - - return ( - - - {/* KIRI: KODE & ATRIBUT */} - - - {v.code} - - - {attributeText} - - - - {/* KANAN: HARGA */} - - {formatPrice(v.price)} - - - - ); - })} - - - - - - } - variant="outline" - colorScheme="red" - size="sm" - /> - - - - ) : ( - - - - - { setActiveSearchSlot(index); setSearchQuery(''); }} - onChange={(e) => setSearchQuery(e.target.value)} - /> - - - {activeSearchSlot === index && ( - - {!selectedVariant?.attribute_set_id && !mainProduct?.attribute_set_id ? ( - - Perbandingan Tidak Tersedia - Produk utama tidak memiliki data kategori yang valid untuk dibandingkan. - - ) : ( - <> - {isSearching ? ( - - ) : searchResults.length > 0 ? ( - - {searchResults.map((res) => ( - handleAddProduct(res, index)} - > - - { (e.target as HTMLImageElement).src = '/images/no-image-compare.svg'; }} - flexShrink={0} - mt={1} - /> - - - {res.displayName || res.name} - - - {formatPrice(res.lowestPrice?.price || 0)} - - - - - ))} - - ) : ( - - {searchQuery === '' ? 'Menampilkan rekomendasi...' : 'Produk tidak ditemukan.'} - - )} - - )} - - )} - - - - Empty Slot - - Produk Belum Ditambahkan - - - - )} + {renderProductSlot(product, index)} ))} diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index 54a0fb52..d63eb365 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -565,12 +565,12 @@ const ProductDetail = ({ product }: Props) => { > Spesifikasi - Detail Lainnya - + */} -- cgit v1.2.3