diff options
| author | FIN-IT_AndriFP <andrifebriyadiputra@gmail.com> | 2026-01-14 16:15:34 +0700 |
|---|---|---|
| committer | FIN-IT_AndriFP <andrifebriyadiputra@gmail.com> | 2026-01-14 16:15:34 +0700 |
| commit | 26ef51057f90c8f297dfaa90bf54fb46f83d74b0 (patch) | |
| tree | 964b34d19fb62df5f5d2a1ec02835b17994cbd44 | |
| parent | a7572147d987175ce1f52d32c9dbededdba985ea (diff) | |
(andri) fix comparable search
| -rw-r--r-- | src-migrate/modules/product-detail/components/ProductComparisonModal.tsx | 145 |
1 files changed, 126 insertions, 19 deletions
diff --git a/src-migrate/modules/product-detail/components/ProductComparisonModal.tsx b/src-migrate/modules/product-detail/components/ProductComparisonModal.tsx index bc4c5415..4f1fcd82 100644 --- a/src-migrate/modules/product-detail/components/ProductComparisonModal.tsx +++ b/src-migrate/modules/product-detail/components/ProductComparisonModal.tsx @@ -100,7 +100,7 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant const variantOptions = mainProduct.variants?.map((v: any) => ({ id: v.id, code: v.default_code || v.code || v.sku, - name: v.name, + name: v.name || v.displayName || v.display_name, price: v.price?.price || v.price || 0, image: v.image })) || []; @@ -115,11 +115,13 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant }); } + const displayName = activeItem.name || activeItem.displayName || mainProduct.name; + const productSlot1 = { id: targetId, sku: targetId, realCode: displayCode, - name: mainProduct.name, + name: displayName, price: activeItem.price?.price || activeItem.price || mainProduct.lowest_price?.price || 0, image: activeItem.image || mainProduct.image, variants: variantOptions @@ -230,6 +232,7 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant ...currentProduct, id: selectedVar.id, sku: selectedVar.id, + name: selectedVar.name, realCode: selectedVar.code, price: selectedVar.price, image: selectedVar.image @@ -238,21 +241,55 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant } }; - const handleAddProduct = (searchItem: any, slotIndex: number) => { - const newProducts = [...products]; - - const idToAdd = searchItem.id; - const codeToAdd = searchItem.code; - const nameToAdd = searchItem.displayName || searchItem.name; - const imageToAdd = searchItem.image; - const priceToAdd = searchItem.lowestPrice?.price || 0; - if (newProducts.find(p => p && String(p.id) === String(idToAdd))) { + + const handleAddProduct = async (searchItem: any, slotIndex: number) => { + + if (products.find(p => p && String(p.id) === String(searchItem.id))) { toast({ title: "Produk sudah ada", status: "warning", position: "top" }); return; } - newProducts[slotIndex] = { + const idToAdd = searchItem.id; + const codeToAdd = searchItem.defaultCode || searchItem.default_code || searchItem.code; + const nameToAdd = searchItem.displayName || searchItem.name; + const imageToAdd = searchItem.image || searchItem.imageS || searchItem.image_s; + const priceToAdd = searchItem.lowestPrice?.price || searchItem.priceTier1V2F || searchItem.price || 0; + + let parentId = searchItem.templateId || + searchItem.templateIdI || + searchItem.template_id_i || + searchItem.template_id; + + if (!parentId) { + try { + const checkParams = new URLSearchParams({ + source: 'upsell', + q: '*:*', + fq: `id:${idToAdd}` + }); + + const checkRes = await fetch(`/api/shop/search?${checkParams.toString()}`); + if (checkRes.ok) { + const checkData = await checkRes.json(); + const freshItem = checkData.response?.products?.[0]; + + if (freshItem) { + const serverReturnedId = freshItem.id; + if (String(serverReturnedId) !== String(idToAdd)) { + parentId = serverReturnedId; + } else { + parentId = freshItem.templateId || freshItem.templateIdI || idToAdd; + } + } + } + } catch (e) { + console.error("Gagal validasi parent:", e); + parentId = idToAdd; + } + } + + const newProductEntry = { id: idToAdd, sku: idToAdd, realCode: codeToAdd, @@ -268,10 +305,59 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant }] }; - setProducts(newProducts); + setProducts((prev) => { + const newSlots = [...prev]; + newSlots[slotIndex] = newProductEntry; + return newSlots; + }); + setActiveSearchSlot(null); setSearchQuery(''); setSearchResults([]); + + if (parentId) { + try { + const params = new URLSearchParams({ + source: 'upsell', + limit: '100', + fq: `template_id_i:${parentId}` + }); + + const res = await fetch(`/api/shop/search?${params.toString()}`); + + if (res.ok) { + const data = await res.json(); + const siblings = data.response?.products || []; + + if (siblings.length > 0) { + const allVariants = siblings.map((s: any) => ({ + id: s.variantId || s.productIdI || s.id, + code: s.defaultCode || s.default_code || s.code, + name: s.displayName || s.name || s.nameS, + price: s.lowestPrice?.price || s.priceTier1V2F || 0, + image: s.image || s.imageS + })); + + allVariants.sort((a: any, b: any) => + String(a.code).localeCompare(String(b.code)) + ); + + setProducts((prev) => { + const updated = [...prev]; + if (updated[slotIndex] && String(updated[slotIndex].id) === String(idToAdd)) { + updated[slotIndex] = { + ...updated[slotIndex], + variants: allVariants + }; + } + return updated; + }); + } + } + } catch (error) { + console.error("Gagal fetch variant lain:", error); + } + } }; const handleRemoveProduct = (index: number) => { @@ -387,15 +473,36 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant {searchResults.map((res) => ( <ListItem key={res.id} - p={2} borderBottom="1px solid #f0f0f0" + p={3} + borderBottom="1px solid #f0f0f0" _hover={{ bg: 'red.50', cursor: 'pointer' }} onClick={() => handleAddProduct(res, index)} > - <Flex align="center" gap={2}> - <Image src={res.image || '/images/no-image-compare.svg'} boxSize="30px" objectFit="contain" onError={(e) => { (e.target as HTMLImageElement).src = '/images/no-image-compare.svg'; }} /> - <Box> - <Text fontSize="xs" fontWeight="bold" noOfLines={1}>{res.displayName || res.name}</Text> - <Text fontSize="xs" color="red.500"> + <Flex align="flex-start" gap={3}> {/* Align top biar rapi kalau teks panjang */} + {/* GAMBAR */} + <Image + src={res.image || '/images/no-image-compare.svg'} + boxSize="40px" // Gambar sedikit dibesarkan + objectFit="contain" + onError={(e) => { (e.target as HTMLImageElement).src = '/images/no-image-compare.svg'; }} + flexShrink={0} // Mencegah gambar gepeng + mt={1} + /> + + {/* TEXT CONTAINER */} + <Box flex={1} w="0"> {/* w="0" adalah trik CSS agar text-overflow bekerja di dalam Flex */} + <Text + fontSize="xs" + fontWeight="bold" + noOfLines={2} // [SOLUSI UTAMA] Ijinkan maksimal 2 baris + lineHeight="shorter" // Spasi antar baris dirapatkan + whiteSpace="normal" // Pastikan text wrapping aktif + mb={1} + title={res.displayName || res.name} // Menampilkan tooltip native saat hover + > + {res.displayName || res.name} + </Text> + <Text fontSize="xs" color="red.500" fontWeight="bold"> {formatPrice(res.lowestPrice?.price || 0)} </Text> </Box> |
