From 190bd5c5f66fc319d91d7ad3979ef8ff125dbd52 Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Mon, 22 Dec 2025 09:22:14 +0700 Subject: (andri) fix view --- .../components/ProductComparisonModal.tsx | 233 +++++++++++++-------- 1 file changed, 147 insertions(+), 86 deletions(-) diff --git a/src-migrate/modules/product-detail/components/ProductComparisonModal.tsx b/src-migrate/modules/product-detail/components/ProductComparisonModal.tsx index 43f43ac4..1190e95d 100644 --- a/src-migrate/modules/product-detail/components/ProductComparisonModal.tsx +++ b/src-migrate/modules/product-detail/components/ProductComparisonModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; // Tambah useRef import { Modal, ModalOverlay, @@ -25,10 +25,11 @@ import { List, ListItem, useToast, - Select + Select, + useOutsideClick // Tambah import ini } from '@chakra-ui/react'; -import { Search, ShoppingCart, Trash2 } from 'lucide-react'; +import { Search, Trash2 } from 'lucide-react'; // --- HELPER FORMATTING --- const formatPrice = (price: number) => { @@ -65,6 +66,20 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant const [searchResults, setSearchResults] = useState([]); const [isSearching, setIsSearching] = useState(false); + // --- REF & OUTSIDE CLICK --- + const searchWrapperRef = useRef(null); // Ref untuk mendeteksi klik + + useOutsideClick({ + ref: searchWrapperRef, + handler: () => { + // Jika user klik di luar area search yang aktif, tutup search + if (activeSearchSlot !== null) { + setActiveSearchSlot(null); + setSearchResults([]); // Opsional: bersihkan hasil juga + } + }, + }); + // =========================================================================== // 1. LOGIC UTAMA: ISI SLOT 1 // =========================================================================== @@ -80,7 +95,6 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant } const targetId = activeItem.id; - // Ambil code dari properti yang tersedia const displayCode = activeItem.default_code || activeItem.code || activeItem.sku || mainProduct.default_code || mainProduct.code; const variantOptions = mainProduct.variants?.map((v: any) => ({ @@ -103,8 +117,8 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant const productSlot1 = { id: targetId, - sku: targetId, // ID untuk API & Key - realCode: displayCode, // String untuk Tampilan + sku: targetId, + realCode: displayCode, name: mainProduct.name, price: activeItem.price?.price || activeItem.price || mainProduct.lowest_price?.price || 0, image: activeItem.image || mainProduct.image, @@ -131,7 +145,6 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant const fetchSpecs = async () => { setIsLoadingMatrix(true); try { - // Kirim ID ke API const allSkus = validProducts.map(p => p.sku).join(','); const mainSku = validProducts[0]?.sku; @@ -153,34 +166,26 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant }, [products, isOpen]); // =========================================================================== - // 3. SEARCH LOGIC (MATCHED WITH API JSON) + // 3. SEARCH LOGIC // =========================================================================== useEffect(() => { - // Gunakan timeout untuk debounce (menunggu user selesai ketik) const delayDebounceFn = setTimeout(async () => { - - // LOGIKA BARU: - // Jika ada text tapi kurang dari 3 huruf -> Jangan search (hemat API) - // Jika text KOSONG (0) -> Lanjut search (ini untuk fitur 'klik muncul barang') if (searchQuery.length > 0 && searchQuery.length < 3) { setSearchResults([]); return; } - // Jangan trigger search kalau slot belum dipilih (mencegah fetch saat modal baru buka) if (activeSearchSlot === null) return; setIsSearching(true); try { const attrSetId = selectedVariant?.attribute_set_id || mainProduct?.attribute_set_id; - - // Tentukan Query: Kalau kosong pakai bintang (*), kalau ada isi pakai isinya const queryParam = searchQuery === '' ? '*' : searchQuery; const params = new URLSearchParams({ source: 'compare', - q: queryParam, // <-- Pakai logika wildcard - limit: '20', // <-- Ubah limit jadi 20 + q: queryParam, + limit: '20', fq: attrSetId ? `attribute_set_id_i:${attrSetId}` : '' }); @@ -228,16 +233,12 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant const handleAddProduct = (searchItem: any, slotIndex: number) => { const newProducts = [...products]; - // [FIX] MAPPING DARI JSON API ANDA (CAMELCASE) - // JSON: { id: 88019, code: "RX-SP0006", displayName: "...", lowestPrice: { price: ... } } - const idToAdd = searchItem.id; - const codeToAdd = searchItem.code; // Langsung 'code', bukan 'default_code_s' + const codeToAdd = searchItem.code; const nameToAdd = searchItem.displayName || searchItem.name; const imageToAdd = searchItem.image; const priceToAdd = searchItem.lowestPrice?.price || 0; - // Cek Duplikat if (newProducts.find(p => p && String(p.id) === String(idToAdd))) { toast({ title: "Produk sudah ada", status: "warning", position: "top" }); return; @@ -245,8 +246,8 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant newProducts[slotIndex] = { id: idToAdd, - sku: idToAdd, // ID untuk API - realCode: codeToAdd, // Code String untuk Tampilan + sku: idToAdd, + realCode: codeToAdd, name: nameToAdd, price: priceToAdd, image: imageToAdd, @@ -284,7 +285,7 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant - Detail Spesifikasi Produk yang kamu pilih + Detail Spesifikasi Produk yang kamu pilih @@ -331,95 +332,155 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant - } variant="outline" colorScheme="red" size="sm" /> + } + variant="outline" + colorScheme="red" + size="sm" + /> ) : ( - - - { setActiveSearchSlot(index); setSearchQuery(''); }} - onChange={(e) => setSearchQuery(e.target.value)} - /> - - - {/* --- HASIL SEARCH (MAPPING FIX) --- */} - {activeSearchSlot === index && ( - - - {/* Tampilkan Loading jika sedang searching */} - {isSearching ? ( - - ) : searchResults.length > 0 ? ( - - {searchResults.map((res) => ( - handleAddProduct(res, index)} - > - - { (e.target as HTMLImageElement).src = '/images/no-image-compare.svg'; }} /> - - {res.displayName || res.name} - - {formatPrice(res.lowestPrice?.price || 0)} - - - - - ))} - - ) : ( - // Logic tambahan: Jika input kosong tapi hasil 0 (biasanya belum kelar loading awal), jangan tampilkan "Tidak Ditemukan" - // Tapi kalau sudah ngetik dan hasil 0, baru tampilkan "Tidak Ditemukan" - - {searchQuery === '' ? 'Menampilkan rekomendasi...' : 'Tidak ditemukan.'} - - )} - - )} - - - Tambah produk
untuk membandingkan
+ {/* WRAPPER SEARCH DENGAN REF */} + {/* Hanya berikan ref jika ini adalah slot yang sedang aktif dicari */} + + + + { setActiveSearchSlot(index); setSearchQuery(''); }} + onChange={(e) => setSearchQuery(e.target.value)} + /> + + + {/* HASIL SEARCH */} + {activeSearchSlot === index && ( + + {isSearching ? ( + + ) : searchResults.length > 0 ? ( + + {searchResults.map((res) => ( + handleAddProduct(res, index)} + > + + { (e.target as HTMLImageElement).src = '/images/no-image-compare.svg'; }} /> + + {res.displayName || res.name} + + {formatPrice(res.lowestPrice?.price || 0)} + + + + + ))} + + ) : ( + + {searchQuery === '' ? 'Menampilkan rekomendasi...' : 'Tidak ditemukan.'} + + )} + + )} + + + {/* SLOT KOSONG */} + + Empty Slot + + Produk Belum Ditambahkan +
)} ))} - - Spesifikasi Teknis + {/* --- HEADER SPESIFIKASI --- */} + + + + Spesifikasi Teknis + {isLoadingMatrix && specsMatrix.length > 0 && ( + + + Updating... + + )} + + - {isLoadingMatrix ? ( - Memuat data... + {/* --- MATRIX SPEK --- */} + {isLoadingMatrix && specsMatrix.length === 0 ? ( + + + Memuat data... + ) : specsMatrix.length > 0 ? ( specsMatrix.map((row, rowIndex) => ( - + {row.label} + {products.map((product, colIndex) => { const val = product ? (row.values[String(product.sku)] || '-') : ''; return ( - - {renderSpecValue(val)} + + {isLoadingMatrix && product && !row.values[String(product.sku)] ? ( + + ) : ( + {renderSpecValue(val)} + )} ); })} )) ) : ( - Data spesifikasi belum tersedia untuk produk ini. + + Data spesifikasi belum tersedia untuk produk ini. + )} -- cgit v1.2.3