From 190bd5c5f66fc319d91d7ad3979ef8ff125dbd52 Mon Sep 17 00:00:00 2001
From: FIN-IT_AndriFP <andrifebriyadiputra@gmail.com>
Date: Mon, 22 Dec 2025 09:22:14 +0700
Subject: (andri) fix view

---
 .../components/ProductComparisonModal.tsx          | 233 +++++++++++++--------
 1 file changed, 147 insertions(+), 86 deletions(-)

(limited to 'src-migrate/modules/product-detail/components/ProductComparisonModal.tsx')

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<any[]>([]);
   const [isSearching, setIsSearching] = useState(false);
 
+  // --- REF & OUTSIDE CLICK ---
+  const searchWrapperRef = useRef<HTMLDivElement>(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
             </Badge>
           </HStack>
           <Text fontSize="sm" color="gray.500" fontWeight="normal" mt={1}>
-             Detail Spesifikasi Produk yang kamu pilih
+              Detail Spesifikasi Produk yang kamu pilih
           </Text>
         </ModalHeader>
         <ModalCloseButton />
@@ -331,95 +332,155 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant
                     </Select>
 
                     <HStack spacing={2}>
-                        <IconButton aria-label="Cart" icon={<Icon as={ShoppingCart} />} variant="outline" colorScheme="red" size="sm" />
+                        <IconButton 
+                            aria-label="Cart" 
+                            icon={<Image src="/images/keranjang-compare.svg" w="15px" h="15px" objectFit="contain" />} 
+                            variant="outline" 
+                            colorScheme="red" 
+                            size="sm" 
+                        />
                         <Button as="a" href={`/product/${product.id}`} target="_blank" colorScheme="red" size="sm" flex={1} fontSize="xs">
-                            Lihat Detail
+                            Beli Sekarang
                         </Button>
                     </HStack>
                   </VStack>
                 ) : (
                   <VStack align="stretch" spacing={3} h="100%" position="relative">
-                     <InputGroup size="sm">
-                      <InputLeftElement pointerEvents="none"><Icon as={Search} color="gray.300" /></InputLeftElement>
-                      <Input 
-                        placeholder="Cari Produk..." borderRadius="md" 
-                        value={activeSearchSlot === index ? searchQuery : ''}
-                        onFocus={() => { setActiveSearchSlot(index); setSearchQuery(''); }}
-                        onChange={(e) => setSearchQuery(e.target.value)}
-                      />
-                    </InputGroup>
-
-                    {/* --- HASIL SEARCH (MAPPING FIX) --- */}
-                    {activeSearchSlot === index && (
-                        <Box position="absolute" top="35px" left={0} right={0} bg="white" boxShadow="lg" zIndex={10} borderRadius="md" border="1px solid" borderColor="gray.200" maxH="250px" overflowY="auto">
-                            
-                            {/* Tampilkan Loading jika sedang searching */}
-                            {isSearching ? (
-                                <Box p={4} textAlign="center"><Spinner size="sm" color="red.500"/></Box>
-                            ) : searchResults.length > 0 ? (
-                                <List spacing={0}>
-                                    {searchResults.map((res) => (
-                                        <ListItem 
-                                            key={res.id} 
-                                            p={2} 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">
-                                                        {formatPrice(res.lowestPrice?.price || 0)}
-                                                    </Text>
-                                                </Box>
-                                            </Flex>
-                                        </ListItem>
-                                    ))}
-                                </List>
-                            ) : (
-                                // 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"
-                                <Box p={3} fontSize="xs" color="gray.500" textAlign="center">
-                                    {searchQuery === '' ? 'Menampilkan rekomendasi...' : 'Tidak ditemukan.'}
-                                </Box>
-                            )}
-                        </Box>
-                    )}
                     
-                    <Flex direction="column" align="center" justify="center" flex={1} border="2px dashed" borderColor="gray.200" borderRadius="md" bg="gray.50" color="gray.400">
-                        <Icon as={Search} w={8} h={8} opacity={0.3} mb={2} />
-                        <Text fontSize="xs" textAlign="center">Tambah produk<br/>untuk membandingkan</Text>
+                    {/* WRAPPER SEARCH DENGAN REF */}
+                    {/* Hanya berikan ref jika ini adalah slot yang sedang aktif dicari */}
+                    <Box position="relative" w="100%" ref={activeSearchSlot === index ? searchWrapperRef : null}>
+                        <InputGroup size="sm">
+                            <InputLeftElement pointerEvents="none"><Icon as={Search} color="gray.300" /></InputLeftElement>
+                            <Input 
+                                placeholder="Cari Produk..." borderRadius="md" 
+                                value={activeSearchSlot === index ? searchQuery : ''}
+                                onFocus={() => { setActiveSearchSlot(index); setSearchQuery(''); }}
+                                onChange={(e) => setSearchQuery(e.target.value)}
+                            />
+                        </InputGroup>
+
+                        {/* HASIL SEARCH */}
+                        {activeSearchSlot === index && (
+                            <Box position="absolute" top="35px" left={0} right={0} bg="white" boxShadow="lg" zIndex={10} borderRadius="md" border="1px solid" borderColor="gray.200" maxH="250px" overflowY="auto">
+                                {isSearching ? (
+                                    <Box p={4} textAlign="center"><Spinner size="sm" color="red.500"/></Box>
+                                ) : searchResults.length > 0 ? (
+                                    <List spacing={0}>
+                                        {searchResults.map((res) => (
+                                            <ListItem 
+                                                key={res.id} 
+                                                p={2} 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">
+                                                            {formatPrice(res.lowestPrice?.price || 0)}
+                                                        </Text>
+                                                    </Box>
+                                                </Flex>
+                                            </ListItem>
+                                        ))}
+                                    </List>
+                                ) : (
+                                    <Box p={3} fontSize="xs" color="gray.500" textAlign="center">
+                                        {searchQuery === '' ? 'Menampilkan rekomendasi...' : 'Tidak ditemukan.'}
+                                    </Box>
+                                )}
+                            </Box>
+                        )}
+                    </Box>
+
+                    {/* SLOT KOSONG */}
+                    <Flex 
+                        direction="column" 
+                        align="center" 
+                        justify="center" 
+                        flex={1} 
+                        bg="gray.50" 
+                        borderRadius="md" 
+                    >
+                        <Image 
+                            src="/images/no-image-compare.svg" 
+                            alt="Empty Slot" 
+                            boxSize="100px" 
+                            mb={2} 
+                            opacity={0.6}
+                        />
+                        <Text fontSize="xs" color="gray.500" textAlign="center">
+                            Produk Belum Ditambahkan
+                        </Text>
                     </Flex>
                   </VStack>
                 )}
               </GridItem>
             ))}
 
-            <GridItem colSpan={5} py={6}>
-               <Box borderBottom="2px solid" borderColor="gray.100" pb={2}><Text fontSize="lg" fontWeight="bold">Spesifikasi Teknis</Text></Box>
+            {/* --- HEADER SPESIFIKASI --- */}
+            <GridItem colSpan={5} py={6} display="flex" alignItems="center" justifyContent="space-between">
+               <Box borderBottom="2px solid" borderColor="gray.100" pb={2} width="100%">
+                  <HStack>
+                    <Text fontSize="lg" fontWeight="bold">Spesifikasi Teknis</Text>
+                    {isLoadingMatrix && specsMatrix.length > 0 && (
+                        <HStack spacing={2}>
+                            <Spinner size="xs" color="red.500" />
+                            <Text fontSize="xs" color="gray.500">Updating...</Text>
+                        </HStack>
+                    )}
+                  </HStack>
+               </Box>
             </GridItem>
 
-            {isLoadingMatrix ? (
-                 <GridItem colSpan={5} textAlign="center" py={10}><Spinner color="red.500" thickness="4px" size="xl" /><Text mt={2} color="gray.500">Memuat data...</Text></GridItem>
+            {/* --- MATRIX SPEK --- */}
+            {isLoadingMatrix && specsMatrix.length === 0 ? (
+                 <GridItem colSpan={5} textAlign="center" py={10}>
+                    <Spinner color="red.500" thickness="4px" size="xl" />
+                    <Text mt={2} color="gray.500">Memuat data...</Text>
+                 </GridItem>
             ) : specsMatrix.length > 0 ? (
                 specsMatrix.map((row, rowIndex) => (
                   <React.Fragment key={row.code || rowIndex}>
-                    <GridItem py={3} px={2} borderBottom="1px solid" borderColor="gray.100" bg={rowIndex % 2 !== 0 ? "white" : "gray.50"} display="flex" alignItems="center">
+                    <GridItem 
+                        py={3} px={2} 
+                        borderBottom="1px solid" borderColor="gray.100" 
+                        bg={rowIndex % 2 !== 0 ? "white" : "gray.50"} 
+                        display="flex" alignItems="center"
+                        opacity={isLoadingMatrix ? 0.6 : 1}
+                        transition="opacity 0.2s"
+                    >
                       <Text fontWeight="bold" fontSize="sm" color="gray.700">{row.label}</Text>
                     </GridItem>
+
                     {products.map((product, colIndex) => {
                         const val = product ? (row.values[String(product.sku)] || '-') : '';
                         return (
-                          <GridItem key={`${row.code}-${colIndex}`} py={3} px={2} borderBottom="1px solid" borderColor="gray.100" bg={rowIndex % 2 !== 0 ? "white" : "gray.50"} display="flex" alignItems="center" justifyContent="center" textAlign="center">
-                            <Text fontSize="sm" color="gray.600">{renderSpecValue(val)}</Text>
+                          <GridItem 
+                            key={`${row.code}-${colIndex}`} 
+                            py={3} px={2} 
+                            borderBottom="1px solid" borderColor="gray.100" 
+                            bg={rowIndex % 2 !== 0 ? "white" : "gray.50"} 
+                            display="flex" alignItems="center" justifyContent="center" textAlign="center"
+                            opacity={isLoadingMatrix ? 0.6 : 1}
+                            transition="opacity 0.2s"
+                          >
+                             {isLoadingMatrix && product && !row.values[String(product.sku)] ? (
+                                <Spinner size="xs" color="gray.400"/> 
+                             ) : (
+                                <Text fontSize="sm" color="gray.600">{renderSpecValue(val)}</Text>
+                             )}
                           </GridItem>
                         );
                     })}
                   </React.Fragment>
                 ))
             ) : (
-                <GridItem colSpan={5} py={10} textAlign="center" color="gray.500" bg="gray.50"><Text>Data spesifikasi belum tersedia untuk produk ini.</Text></GridItem>
+                <GridItem colSpan={5} py={10} textAlign="center" color="gray.500" bg="gray.50">
+                    <Text>Data spesifikasi belum tersedia untuk produk ini.</Text>
+                </GridItem>
             )}
           </Grid>
         </ModalBody>
-- 
cgit v1.2.3

